diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index c0307253a7208..f18b29ac3737a 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -433,6 +433,9 @@ Set the default environment for the APM app. When left empty, data from all envi [[observability-apm-enable-profiling]]`observability:apmEnableProfilingIntegration`:: Enable the Universal Profiling integration in APM. +[[observability-apm-enable-table-search-bar]]`observability:apmEnableTableSearchBar`:: +beta:[] Enables faster searching in APM tables by adding a handy search bar with live filtering. Available for the following tables: Services, Transactions, and Errors. + [[observability-enable-aws-lambda-metrics]]`observability:enableAwsLambdaMetrics`:: preview:[] Display Amazon Lambda metrics in the service metrics tab. diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/random_names.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/random_names.ts new file mode 100644 index 0000000000000..cb03428a8d032 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/random_names.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const randomGreekishNames = [ + 'Adonis', + 'Agamemnon', + 'Ajax', + 'Alexander', + 'Aphrodite', + 'Apollo', + 'Ares', + 'Aristophanes', + 'Artemis', + 'Athena', + 'Atlas', + 'Boreas', + 'Calliope', + 'Cassandra', + 'Clio', + 'Daphne', + 'Demeter', + 'Dionysius', + 'Dionysus', + 'Eileithyia', + 'Electra', + 'Eos', + 'Erato', + 'Erebos', + 'Eros', + 'Euterpe', + 'Gaia', + 'Hades', + 'Hecate', + 'Helios', + 'Hephaestus', + 'Hera', + 'Heracles (Hercules)', + 'Hercules', + 'Hermes', + 'Hestia', + 'Hypatia', + 'Hypnos', + 'Iphigenia', + 'Iris', + 'Kratos', + 'Leonidas', + 'Medusa', + 'Mnemosyne', + 'Morpheus', + 'Narcissus', + 'Nemesis', + 'Nike', + 'Notus', + 'Nyx', + 'Oceanus', + 'Odysseus', + 'Orpheus', + 'Pan', + 'Pandora', + 'Penelope', + 'Pericles', + 'Persephone', + 'Perseus', + 'Phoebe', + 'Polyphemus', + 'Pontus', + 'Poseidon', + 'Priam', + 'Prometheus', + 'Rhea', + 'Sappho', + 'Selene', + 'Terpsichore', + 'Tethys', + 'Thalia', + 'Thanatos', + 'Theia', + 'Theseus', + 'Thetis', + 'Triton', + 'Tyche', + 'Uranus', + 'Zephyrus', + 'Zeus', + 'Augustus', + 'Bacchus', + 'Brutus', + 'Caesar', + 'Caligula', + 'Caracalla', + 'Cassius', + 'Cato', + 'Cicero', + 'Cleopatra', + 'Commodus', + 'Constantine', + 'Diana', + 'Domitian', + 'Hadrian', + 'Horace', + 'Julius', + 'Juno', + 'Jupiter', + 'Livy', + 'Marcus Aurelius', + 'Mars', + 'Mercury', + 'Minerva', + 'Neptune', + 'Nero', + 'Octavian', + 'Ovid', + 'Pliny', + 'Pluto', + 'Pompey', + 'Remus', + 'Romulus', + 'Saturn', + 'Scipio', + 'Seneca', + 'Spartacus', + 'Theodosius', + 'Tiberius', + 'Titus', + 'Trajan', + 'Venus', + 'Vespasian', + 'Vesta', + 'Virgil', +]; + +export function getRandomNameForIndex(index: number) { + return randomGreekishNames[index % randomGreekishNames.length]; +} diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_errors.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_errors.ts new file mode 100644 index 0000000000000..7948688610f56 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_errors.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ApmFields, apm } from '@kbn/apm-synthtrace-client'; +import { Scenario } from '../cli/scenario'; +import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; +import { withClient } from '../lib/utils/with_client'; +import { getRandomNameForIndex } from './helpers/random_names'; + +const ENVIRONMENT = getSynthtraceEnvironment(__filename); + +const scenario: Scenario = async (runOptions) => { + const { logger } = runOptions; + + const severities = ['critical', 'error', 'warning', 'info', 'debug', 'trace']; + + return { + generate: ({ range, clients: { apmEsClient } }) => { + const transactionName = 'DELETE /api/orders/{id}'; + + const instance = apm + .service({ name: `synth-node`, environment: ENVIRONMENT, agentName: 'nodejs' }) + .instance('instance'); + + const failedTraceEvents = range + .interval('1m') + .rate(2000) + .generator((timestamp, index) => { + const severity = severities[index % severities.length]; + const errorMessage = `${severity}: ${getRandomNameForIndex(index)} ${index}`; + return instance + .transaction({ transactionName }) + .timestamp(timestamp) + .duration(1000) + .failure() + .errors( + instance.error({ message: errorMessage, type: 'My Type' }).timestamp(timestamp + 50) + ); + }); + + return withClient( + apmEsClient, + logger.perf('generating_apm_events', () => failedTraceEvents) + ); + }, + }; +}; + +export default scenario; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_instances.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_instances.ts new file mode 100644 index 0000000000000..4774839c71727 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_instances.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ApmFields, apm, Instance } from '@kbn/apm-synthtrace-client'; +import { random, times } from 'lodash'; +import { Scenario } from '../cli/scenario'; +import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; +import { withClient } from '../lib/utils/with_client'; +import { getRandomNameForIndex } from './helpers/random_names'; + +const ENVIRONMENT = getSynthtraceEnvironment(__filename); + +const scenario: Scenario = async ({ logger, scenarioOpts = { instances: 2000 } }) => { + const numInstances = scenarioOpts.instances; + const agentVersions = ['2.1.0', '2.0.0', '1.15.0', '1.14.0', '1.13.1']; + const language = 'go'; + const serviceName = 'synth-many-instances'; + const transactionName = 'GET /order/{id}'; + + return { + generate: ({ range, clients: { apmEsClient } }) => { + const instances = times(numInstances).map((index) => { + const agentVersion = agentVersions[index % agentVersions.length]; + const randomName = getRandomNameForIndex(index); + return apm + .service({ + name: serviceName, + environment: ENVIRONMENT, + agentName: language, + }) + .instance(`instance-${randomName}-${index}`) + .defaults({ 'agent.version': agentVersion, 'service.language.name': language }); + }); + + const instanceSpans = (instance: Instance) => { + const hasHighDuration = Math.random() > 0.5; + const throughput = random(1, 10); + + const traces = range.ratePerMinute(throughput).generator((timestamp) => { + const parentDuration = hasHighDuration ? random(1000, 5000) : random(100, 1000); + const generateError = random(1, 4) % 3 === 0; + const span = instance + .transaction({ transactionName }) + .timestamp(timestamp) + .duration(parentDuration); + + return !generateError + ? span.success() + : span + .failure() + .errors( + instance + .error({ message: `No handler for ${transactionName}` }) + .timestamp(timestamp + 50) + ); + }); + + const cpuPct = random(0, 1); + const memoryFree = random(0, 1000); + const metricsets = range + .interval('30s') + .rate(1) + .generator((timestamp) => + instance + .appMetrics({ + 'system.memory.actual.free': memoryFree, + 'system.memory.total': 1000, + 'system.cpu.total.norm.pct': cpuPct, + 'system.process.cpu.total.norm.pct': 0.7, + }) + .timestamp(timestamp) + ); + + return [traces, metricsets]; + }; + + return withClient( + apmEsClient, + logger.perf('generating_apm_events', () => instances.flatMap(instanceSpans)) + ); + }, + }; +}; + +export default scenario; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts index 68a5432f1a29a..1af9f2f9e3b5a 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts @@ -7,17 +7,18 @@ */ import { ApmFields, apm, Instance } from '@kbn/apm-synthtrace-client'; -import { flatten, random } from 'lodash'; +import { flatten, random, times } from 'lodash'; import { Scenario } from '../cli/scenario'; import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; import { withClient } from '../lib/utils/with_client'; +import { getRandomNameForIndex } from './helpers/random_names'; const ENVIRONMENT = getSynthtraceEnvironment(__filename); -const scenario: Scenario = async ({ logger, scenarioOpts = { services: 500 } }) => { +const scenario: Scenario = async ({ logger, scenarioOpts = { services: 2000 } }) => { const numServices = scenarioOpts.services; + const transactionName = 'GET /order/{id}'; const languages = ['go', 'dotnet', 'java', 'python']; - const services = ['web', 'order-processing', 'api-backend', 'proxy']; const agentVersions: Record = { go: ['2.1.0', '2.0.0', '1.15.0', '1.14.0', '1.13.1'], dotnet: ['1.18.0', '1.17.0', '1.16.1', '1.16.0', '1.15.0'], @@ -27,82 +28,53 @@ const scenario: Scenario = async ({ logger, scenarioOpts = { services return { generate: ({ range, clients: { apmEsClient } }) => { - const successfulTimestamps = range.ratePerMinute(180); - const instances = flatten( - [...Array(numServices).keys()].map((index) => { + times(numServices).map((index) => { const language = languages[index % languages.length]; const agentLanguageVersions = agentVersions[language]; + const agentVersion = agentLanguageVersions[index % agentLanguageVersions.length]; const numOfInstances = (index % 3) + 1; - - return [...Array(numOfInstances).keys()].map((instanceIndex) => + return times(numOfInstances).map((instanceIndex) => apm .service({ - name: `${services[index % services.length]}-${language}-${index}`, + name: `${getRandomNameForIndex(index)}-${language}-${index}`, environment: ENVIRONMENT, agentName: language, }) .instance(`instance-${index}-${instanceIndex}`) - .defaults({ - 'agent.version': agentLanguageVersions[index % agentLanguageVersions.length], - 'service.language.name': language, - }) + .defaults({ 'agent.version': agentVersion, 'service.language.name': language }) ); }) ); - const urls = ['GET /order/{id}', 'POST /basket/{id}', 'DELETE /basket', 'GET /products']; + const instanceSpans = (instance: Instance) => { + const hasHighDuration = Math.random() > 0.5; + const throughput = random(1, 10); - const instanceSpans = (instance: Instance, url: string) => { - const successfulTraceEvents = successfulTimestamps.generator((timestamp) => { - const randomHigh = random(1000, 4000); - const randomLow = random(100, randomHigh / 5); - const duration = random(randomLow, randomHigh); - const childDuration = random(randomLow, duration); - const remainderDuration = duration - childDuration; + return range.ratePerMinute(throughput).generator((timestamp) => { + const parentDuration = hasHighDuration ? random(1000, 5000) : random(100, 1000); const generateError = random(1, 4) % 3 === 0; - const generateChildError = random(0, 5) % 2 === 0; const span = instance - .transaction({ transactionName: url }) + .transaction({ transactionName }) .timestamp(timestamp) - .duration(duration) - .children( - instance - .span({ - spanName: 'GET apm-*/_search', - spanType: 'db', - spanSubtype: 'elasticsearch', - }) - .duration(childDuration) - .destination('elasticsearch') - .timestamp(timestamp) - .outcome(generateError && generateChildError ? 'failure' : 'success'), - instance - .span({ spanName: 'custom_operation', spanType: 'custom' }) - .duration(remainderDuration) - .success() - .timestamp(timestamp + childDuration) - ); + .duration(parentDuration); + return !generateError ? span.success() : span .failure() .errors( - instance.error({ message: `No handler for ${url}` }).timestamp(timestamp + 50) + instance + .error({ message: `No handler for ${transactionName}` }) + .timestamp(timestamp + 50) ); }); - - return successfulTraceEvents; }; return withClient( apmEsClient, - logger.perf('generating_apm_events', () => - instances - .flatMap((instance) => urls.map((url) => ({ instance, url }))) - .map(({ instance, url }) => instanceSpans(instance, url)) - ) + logger.perf('generating_apm_events', () => instances.map(instanceSpans)) ); }, }; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_transactions_per_service.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_transactions.ts similarity index 62% rename from packages/kbn-apm-synthtrace/src/scenarios/many_transactions_per_service.ts rename to packages/kbn-apm-synthtrace/src/scenarios/many_transactions.ts index 565ac8ea4f1ab..895d413235512 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/many_transactions_per_service.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_transactions.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ import { ApmFields, apm, Instance } from '@kbn/apm-synthtrace-client'; +import { random, times } from 'lodash'; import { Scenario } from '../cli/scenario'; import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; import { withClient } from '../lib/utils/with_client'; @@ -14,36 +15,48 @@ const ENVIRONMENT = getSynthtraceEnvironment(__filename); const scenario: Scenario = async (runOptions) => { const { logger } = runOptions; - const { numServices = 3 } = runOptions.scenarioOpts || {}; - const numTransactions = 100; + const { numServices = 1 } = runOptions.scenarioOpts || {}; + const numTransactions = 2000; + + const transactionNames = ['GET', 'PUT', 'DELETE', 'UPDATE'].flatMap((method) => + [ + '/users', + '/products', + '/orders', + '/customers', + '/profile', + '/categories', + '/invoices', + '/payments', + '/cart', + '/reviews', + ].map((resource) => `${method} ${resource}`) + ); return { generate: ({ range, clients: { apmEsClient } }) => { - const urls = ['GET /order', 'POST /basket', 'DELETE /basket', 'GET /products']; - - const successfulTimestamps = range.ratePerMinute(180); - const failedTimestamps = range.interval('1m').rate(180); - - const instances = [...Array(numServices).keys()].map((index) => + const instances = times(numServices).map((index) => apm .service({ name: `synth-go-${index}`, environment: ENVIRONMENT, agentName: 'go' }) .instance(`instance-${index}`) ); - const transactionNames = [...Array(numTransactions).keys()].map( - (index) => `${urls[index % urls.length]}/${index}` - ); - const instanceSpans = (instance: Instance, transactionName: string) => { - const successfulTraceEvents = successfulTimestamps.generator((timestamp) => - instance.transaction({ transactionName }).timestamp(timestamp).duration(1000).success() - ); + const successfulTraceEvents = range + .ratePerMinute(random(1, 60)) + .generator((timestamp) => + instance + .transaction({ transactionName }) + .timestamp(timestamp) + .duration(random(100, 10_000)) + .success() + ); - const failedTraceEvents = failedTimestamps.generator((timestamp) => + const failedTraceEvents = range.ratePerMinute(random(1, 60)).generator((timestamp) => instance .transaction({ transactionName }) .timestamp(timestamp) - .duration(1000) + .duration(random(100, 10_000)) .failure() .errors( instance @@ -72,13 +85,11 @@ const scenario: Scenario = async (runOptions) => { return withClient( apmEsClient, logger.perf('generating_apm_events', () => - instances - .flatMap((instance) => - transactionNames.map((transactionName) => ({ instance, transactionName })) - ) - .flatMap(({ instance, transactionName }, index) => - instanceSpans(instance, transactionName) - ) + instances.flatMap((instance) => + times(numTransactions) + .map((index) => `${transactionNames[index % transactionNames.length]}-${index}`) + .flatMap((transactionName) => instanceSpans(instance, transactionName)) + ) ) ); }, diff --git a/packages/kbn-ftr-common-functional-ui-services/services/remote/poll_for_log_entry.ts b/packages/kbn-ftr-common-functional-ui-services/services/remote/poll_for_log_entry.ts index 49de29381e87f..133b3da2785f2 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/remote/poll_for_log_entry.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/remote/poll_for_log_entry.ts @@ -8,87 +8,63 @@ import { WebDriver, logging } from 'selenium-webdriver'; import * as Rx from 'rxjs'; -import { mergeMap, catchError, mergeMapTo, delay, first } from 'rxjs/operators'; +import { mergeMap, catchError, delay, repeat } from 'rxjs/operators'; +import { NoSuchSessionError, NoSuchWindowError } from 'selenium-webdriver/lib/error'; + +export const FINAL_LOG_ENTRY_PREFIX = 'WEBDRIVER SESSION IS STOPPED'; /** * Create an observable that emits log entries representing the calls to log messages * available for a specific logger. */ -export function pollForLogEntry$( - driver: WebDriver, - type: string, - ms: number, - stop$: Rx.Observable -) { +export function pollForLogEntry$(driver: WebDriver, type: string, ms: number) { const logCtrl = driver.manage().logs(); - const poll$ = new Rx.BehaviorSubject(undefined); - const FINAL_MSG = '@@final@@'; + // setup log polling + return Rx.defer(async () => await logCtrl.get(type)).pipe( + // filter and flatten list of entries + mergeMap((entries) => + entries.filter((entry) => { + // ignore react devtools + if (entry.message.includes('Download the React DevTools')) { + return false; + } - return new Rx.Observable((subscriber) => { - subscriber.add( - stop$.pipe(first()).subscribe(() => { - driver - .executeScript( - ` - if (window.flushCoverageToLog) { - window.flushCoverageToLog(); - } + // down-level inline script errors + if (entry.message.includes('Refused to execute inline script')) { + entry.level = logging.getLevel('INFO'); + } - console.log(${JSON.stringify(FINAL_MSG)}) - ` - ) - .catch((error) => subscriber.error(error)); + return true; }) - ); - - subscriber.add( - poll$ - .pipe( - delay(ms), - - mergeMap(async () => await logCtrl.get(type)), - - // filter and flatten list of entries - mergeMap((entries) => { - const filtered = entries.filter((entry) => { - if (entry.message.includes(FINAL_MSG)) { - poll$.complete(); - return false; - } + ), - // ignore react devtools - if (entry.message.includes('Download the React DevTools')) { - return false; - } + // resubscribe when parent completes with delay by `ms` milliseconds + repeat(), + delay(ms), - // down-level inline script errors - if (entry.message.includes('Refused to execute inline script')) { - entry.level = logging.getLevel('INFO'); - } + catchError((error, resubscribe) => { + if ( + error instanceof NoSuchSessionError || // session is invalid + error instanceof NoSuchWindowError || // browser window crashed + error?.message.startsWith('ECONNREFUSED') // webdriver server is not responding, often after one of previous errors + ) { + // WebDriver session is invalid, sending the last log message + return Rx.concat([ + new logging.Entry('SEVERE', `${FINAL_LOG_ENTRY_PREFIX}: ${error.message}`), + ]); + } else { + return Rx.concat( + // log error as a log entry + [new logging.Entry('SEVERE', `ERROR FETCHING BROWSR LOGS: ${error.message}`)], - return true; - }); - - if (!poll$.isStopped) { - // schedule next poll - poll$.next(undefined); - } - - return filtered; - }), - - catchError((error, resubscribe) => { - return Rx.concat( - // log error as a log entry - [new logging.Entry('SEVERE', `ERROR FETCHING BROWSR LOGS: ${error.message}`)], - - // pause 10 seconds then resubscribe - Rx.of(1).pipe(delay(10 * 1000), mergeMapTo(resubscribe)) - ); - }) - ) - .subscribe(subscriber) - ); - }); + // pause 10 seconds then resubscribe + Rx.of(1).pipe( + delay(10 * 1000), + mergeMap(() => resubscribe) + ) + ); + } + }) + ); } diff --git a/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts b/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts index 702f674b3c10d..49a43817efc62 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts @@ -10,11 +10,11 @@ import { resolve } from 'path'; import Fs from 'fs'; import * as Rx from 'rxjs'; -import { mergeMap, map, takeUntil, catchError, ignoreElements } from 'rxjs/operators'; +import { mergeMap, map, catchError, ignoreElements, takeWhile } from 'rxjs/operators'; import { Lifecycle } from '@kbn/test'; import { ToolingLog } from '@kbn/tooling-log'; import chromeDriver from 'chromedriver'; -import { Builder, logging } from 'selenium-webdriver'; +import { Builder, logging, WebDriver } from 'selenium-webdriver'; import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; import edge from 'selenium-webdriver/edge'; @@ -25,7 +25,7 @@ import { getLogger } from 'selenium-webdriver/lib/logging'; import { installDriver } from 'ms-chromium-edge-driver'; import { REPO_ROOT } from '@kbn/repo-info'; -import { pollForLogEntry$ } from './poll_for_log_entry'; +import { FINAL_LOG_ENTRY_PREFIX, pollForLogEntry$ } from './poll_for_log_entry'; import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; @@ -139,6 +139,18 @@ function initChromiumOptions(browserType: Browsers, acceptInsecureCerts: boolean return options; } +function pollForChromiumLogs$(session: WebDriver, logPollingMs: number) { + return pollForLogEntry$(session, logging.Type.BROWSER, logPollingMs).pipe( + takeWhile( + (loggingEntry: logging.Entry) => !loggingEntry.message.startsWith(FINAL_LOG_ENTRY_PREFIX) + ), + map(({ message, level: { name: level } }) => ({ + message: message.replace(/\\n/g, '\n'), + level, + })) + ); +} + let attemptCounter = 0; let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( @@ -175,18 +187,7 @@ async function attemptToCreateCommand( return { session, - consoleLog$: pollForLogEntry$( - session, - logging.Type.BROWSER, - config.logPollingMs, - lifecycle.cleanup.after$ - ).pipe( - takeUntil(lifecycle.cleanup.after$), - map(({ message, level: { name: level } }) => ({ - message: message.replace(/\\n/g, '\n'), - level, - })) - ), + consoleLog$: pollForChromiumLogs$(session, config.logPollingMs), }; } @@ -203,18 +204,7 @@ async function attemptToCreateCommand( .build(); return { session, - consoleLog$: pollForLogEntry$( - session, - logging.Type.BROWSER, - config.logPollingMs, - lifecycle.cleanup.after$ - ).pipe( - takeUntil(lifecycle.cleanup.after$), - map(({ message, level: { name: level } }) => ({ - message: message.replace(/\\n/g, '\n'), - level, - })) - ), + consoleLog$: pollForChromiumLogs$(session, config.logPollingMs), }; } else { throw new Error( diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index c176890334dc1..3268bc5ef05c8 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -133,6 +133,8 @@ export const OBSERVABILITY_APM_ENABLE_CONTINUOUS_ROLLUPS_ID = export const OBSERVABILITY_APM_ENABLE_PROFILING_INTEGRATION_ID = 'observability:apmEnableProfilingIntegration'; +export const OBSERVABILITY_APM_ENABLE_TABLE_SEARCH_BAR = 'observability:apmEnableTableSearchBar'; + // Reporting settings export const XPACK_REPORTING_CUSTOM_PDF_LOGO_ID = 'xpackReporting:customPdfLogo'; diff --git a/packages/kbn-typed-react-router-config/src/use_params.ts b/packages/kbn-typed-react-router-config/src/use_params.ts index 0468eb9566236..2ecb417099df0 100644 --- a/packages/kbn-typed-react-router-config/src/use_params.ts +++ b/packages/kbn-typed-react-router-config/src/use_params.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Location } from 'history'; +import { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useRouter } from './use_router'; @@ -23,9 +23,9 @@ export function useParams(...args: any[]) { args.pop(); } - const paths = args as string[]; - - const getParamsArgs = [...paths, location, optional] as [never, Location, boolean]; - - return router.getParams(...getParamsArgs); + return useMemo(() => { + // @ts-expect-error + return router.getParams(...args, location, optional); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router, ...args, location, optional]); } diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index b2e2c5ec3f748..5e49b09b01d24 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -449,6 +449,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:apmEnableTableSearchBar': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:apmAWSLambdaPriceFactor': { type: 'text', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index e1de9aa7842d5..a1125a6d118b8 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -48,6 +48,7 @@ export interface UsageStats { 'observability:enableInfrastructureHostsView': boolean; 'observability:enableInfrastructureProfilingIntegration': boolean; 'observability:apmAgentExplorerView': boolean; + 'observability:apmEnableTableSearchBar': boolean; 'visualization:heatmap:maxBuckets': number; 'visualization:colorMapping': string; 'visualization:useLegacyTimeAxis': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index e04e83dc46feb..7e5a19d41d3d3 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -9863,6 +9863,12 @@ "description": "Non-default value of setting." } }, + "observability:apmEnableTableSearchBar": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:apmAWSLambdaPriceFactor": { "type": "text", "_meta": { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index b3e36fc8bebb9..4d3186c784447 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -17,7 +17,7 @@ import { import { i18n } from '@kbn/i18n'; import { ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; import { TypeOf } from '@kbn/typed-react-router-config'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { ServiceInventoryFieldName, @@ -358,6 +358,16 @@ export function ServiceList({ ] ); + const handleSort = useCallback( + (itemsToSort, sortField, sortDirection) => + sortFn( + itemsToSort, + sortField as ServiceInventoryFieldName, + sortDirection + ), + [sortFn] + ); + return ( @@ -405,13 +415,7 @@ export function ServiceList({ initialSortField={initialSortField} initialSortDirection={initialSortDirection} initialPageSize={initialPageSize} - sortFn={(itemsToSort, sortField, sortDirection) => - sortFn( - itemsToSort, - sortField as ServiceInventoryFieldName, - sortDirection - ) - } + sortFn={handleSort} /> diff --git a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx index 8926e2155592d..c9d3351ce2ebc 100644 --- a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx @@ -20,6 +20,7 @@ import { apmEnableContinuousRollups, enableAgentExplorerView, apmEnableProfilingIntegration, + apmEnableTableSearchBar, } from '@kbn/observability-plugin/common'; import { isEmpty } from 'lodash'; import React from 'react'; @@ -41,6 +42,7 @@ const apmSettingsKeys = [ apmEnableServiceMetrics, apmEnableContinuousRollups, enableAgentExplorerView, + apmEnableTableSearchBar, apmEnableProfilingIntegration, ]; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx index 39ad6f4945dff..35f559d81f982 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx @@ -10,7 +10,10 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode, useRef, useState, useEffect } from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { useTheme } from '../../../../../../hooks/use_theme'; -import { isRumAgentName } from '../../../../../../../common/agent_name'; +import { + isMobileAgentName, + isRumAgentName, +} from '../../../../../../../common/agent_name'; import { TRACE_ID, TRANSACTION_ID, @@ -335,6 +338,18 @@ function RelatedErrors({ kuery += ` and ${TRANSACTION_ID} : "${item.doc.transaction?.id}"`; } + const mobileHref = apmRouter.link( + `/mobile-services/{serviceName}/errors-and-crashes`, + { + path: { serviceName: item.doc.service.name }, + query: { + ...query, + serviceGroup: '', + kuery, + }, + } + ); + const href = apmRouter.link(`/services/{serviceName}/errors`, { path: { serviceName: item.doc.service.name }, query: { @@ -349,7 +364,7 @@ function RelatedErrors({ // eslint-disable-next-line jsx-a11y/click-events-have-key-events
e.stopPropagation()}> diff --git a/x-pack/plugins/apm/public/hooks/use_breakpoints.ts b/x-pack/plugins/apm/public/hooks/use_breakpoints.ts index 9ec8b20bb472d..5e991cc477762 100644 --- a/x-pack/plugins/apm/public/hooks/use_breakpoints.ts +++ b/x-pack/plugins/apm/public/hooks/use_breakpoints.ts @@ -9,19 +9,20 @@ import { useIsWithinMaxBreakpoint, useIsWithinMinBreakpoint, } from '@elastic/eui'; +import { useMemo } from 'react'; export type Breakpoints = Record; export function useBreakpoints() { - const screenSizes = { - isXSmall: useIsWithinMaxBreakpoint('xs'), - isSmall: useIsWithinMaxBreakpoint('s'), - isMedium: useIsWithinMaxBreakpoint('m'), - isLarge: useIsWithinMaxBreakpoint('l'), - isXl: useIsWithinMaxBreakpoint('xl'), - isXXL: useIsWithinMaxBreakpoint('xxl'), - isXXXL: useIsWithinMinBreakpoint('xxxl'), - }; + const isXSmall = useIsWithinMaxBreakpoint('xs'); + const isSmall = useIsWithinMaxBreakpoint('s'); + const isMedium = useIsWithinMaxBreakpoint('m'); + const isLarge = useIsWithinMaxBreakpoint('l'); + const isXl = useIsWithinMaxBreakpoint('xl'); + const isXXL = useIsWithinMaxBreakpoint('xxl'); + const isXXXL = useIsWithinMinBreakpoint('xxxl'); - return screenSizes; + return useMemo(() => { + return { isXSmall, isSmall, isMedium, isLarge, isXl, isXXL, isXXXL }; + }, [isXSmall, isSmall, isMedium, isLarge, isXl, isXXL, isXXXL]); } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx index 15f4fc928eada..1fa4b2176f1ab 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx @@ -43,6 +43,7 @@ describe('Agent policy advanced options content', () => { const render = ({ isProtected = false, + isManaged = false, policyId = 'agent-policy-1', newAgentPolicy = false, packagePolicy = [createPackagePolicyMock()], @@ -54,6 +55,7 @@ describe('Agent policy advanced options content', () => { ...createAgentPolicyMock(), package_policies: packagePolicy, id: policyId, + is_managed: isManaged, }; } @@ -91,6 +93,16 @@ describe('Agent policy advanced options content', () => { render(); expect(renderResult.queryByTestId('tamperProtectionSwitch')).not.toBeInTheDocument(); }); + it('should be visible if policy is not managed/hosted', () => { + usePlatinumLicense(); + render({ isManaged: false }); + expect(renderResult.queryByTestId('tamperProtectionSwitch')).toBeInTheDocument(); + }); + it('should not be visible if policy is managed/hosted', () => { + usePlatinumLicense(); + render({ isManaged: true }); + expect(renderResult.queryByTestId('tamperProtectionSwitch')).not.toBeInTheDocument(); + }); it('switched to true enables the uninstall command link', async () => { usePlatinumLicense(); render({ isProtected: true }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index 686934377fdf3..0060258fe905f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -293,7 +293,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = }} /> - {agentTamperProtectionEnabled && licenseService.isPlatinum() && ( + {agentTamperProtectionEnabled && licenseService.isPlatinum() && !agentPolicy.is_managed && ( diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts index 96bda0ed31ae8..1ed3290625141 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts @@ -27,16 +27,20 @@ import type { FleetRequestHandlerContext } from '../..'; import type { MockedFleetAppContext } from '../../mocks'; import { createAppContextStartContractMock, xpackMocks } from '../../mocks'; -import { appContextService } from '../../services'; +import { agentPolicyService, appContextService } from '../../services'; import type { GetUninstallTokenRequestSchema, GetUninstallTokensMetadataRequestSchema, } from '../../types/rest_spec/uninstall_token'; +import { createAgentPolicyMock } from '../../../common/mocks'; + import { registerRoutes } from '.'; import { getUninstallTokenHandler, getUninstallTokensMetadataHandler } from './handlers'; +jest.mock('../../services/agent_policy'); + describe('uninstall token handlers', () => { let context: FleetRequestHandlerContext; let response: ReturnType; @@ -74,10 +78,17 @@ describe('uninstall token handlers', () => { unknown, TypeOf >; + const mockAgentPolicyService = agentPolicyService as jest.Mocked; beforeEach(() => { const uninstallTokenService = appContextService.getUninstallTokenService()!; getTokenMetadataMock = uninstallTokenService.getTokenMetadata as jest.Mock; + mockAgentPolicyService.list.mockResolvedValue({ + items: [createAgentPolicyMock()], + total: 1, + page: 1, + perPage: 1, + }); request = httpServerMock.createKibanaRequest(); }); diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts index 50dc1263ddf07..8c0220b4a1d17 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts @@ -8,7 +8,7 @@ import type { TypeOf } from '@kbn/config-schema'; import type { CustomHttpResponseOptions, ResponseError } from '@kbn/core-http-server'; -import { appContextService } from '../../services'; +import { appContextService, agentPolicyService } from '../../services'; import type { FleetRequestHandler } from '../../types'; import type { GetUninstallTokensMetadataRequestSchema, @@ -16,6 +16,7 @@ import type { } from '../../types/rest_spec/uninstall_token'; import { defaultFleetErrorHandler } from '../../errors'; import type { GetUninstallTokenResponse } from '../../../common/types/rest_spec/uninstall_token'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../constants'; const UNINSTALL_TOKEN_SERVICE_UNAVAILABLE_ERROR: CustomHttpResponseOptions = { statusCode: 500, @@ -32,13 +33,24 @@ export const getUninstallTokensMetadataHandler: FleetRequestHandler< } try { + const fleetContext = await context.fleet; + const soClient = fleetContext.internalSoClient; + + const { items: managedPolicies } = await agentPolicyService.list(soClient, { + fields: ['id'], + perPage: SO_SEARCH_LIMIT, + kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.is_managed:true`, + }); + + const managedPolicyIds = managedPolicies.map((policy) => policy.id); + const { page = 1, perPage = 20, policyId } = request.query; const body = await uninstallTokenService.getTokenMetadata( policyId?.trim(), page, perPage, - 'policy-elastic-agent-on-cloud' + managedPolicyIds.length > 0 ? managedPolicyIds : undefined ); return response.ok({ body }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index 3e97594ee959f..ab6d125f39685 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -11,7 +11,11 @@ import { securityMock } from '@kbn/security-plugin/server/mocks'; import { loggerMock } from '@kbn/logging-mocks'; import type { Logger } from '@kbn/core/server'; -import { PackagePolicyRestrictionRelatedError, FleetUnauthorizedError } from '../errors'; +import { + PackagePolicyRestrictionRelatedError, + FleetUnauthorizedError, + HostedAgentPolicyRestrictionRelatedError, +} from '../errors'; import type { AgentPolicy, FullAgentPolicy, @@ -603,6 +607,27 @@ describe('agent policy', () => { expect(calledWith[2]).toHaveProperty('is_managed', true); }); + it('should throw a HostedAgentRestrictionRelated error if user enables "is_protected" for a managed policy', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + soClient.get.mockResolvedValue({ + attributes: { is_managed: true }, + id: 'mocked', + type: 'mocked', + references: [], + }); + + await expect( + agentPolicyService.update(soClient, esClient, 'test-id', { + is_protected: true, + }) + ).rejects.toThrowError( + new HostedAgentPolicyRestrictionRelatedError('Cannot update is_protected') + ); + }); + it('should call audit logger', async () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 330007e23963d..511c3928f81fe 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -77,14 +77,14 @@ export interface UninstallTokenServiceInterface { * @param policyIdFilter a string for partial matching the policyId * @param page * @param perPage - * @param policyIdExcludeFilter + * @param excludePolicyIds * @returns Uninstall Tokens Metadata Response */ getTokenMetadata( policyIdFilter?: string, page?: number, perPage?: number, - policyIdExcludeFilter?: string + excludePolicyIds?: string[] ): Promise; /** @@ -176,14 +176,11 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { policyIdFilter?: string, page = 1, perPage = 20, - policyIdExcludeFilter?: string + excludePolicyIds?: string[] ): Promise { const includeFilter = policyIdFilter ? `.*${policyIdFilter}.*` : undefined; - const tokenObjects = await this.getTokenObjectsByIncludeFilter( - includeFilter, - policyIdExcludeFilter - ); + const tokenObjects = await this.getTokenObjectsByIncludeFilter(includeFilter, excludePolicyIds); const items: UninstallTokenMetadata[] = tokenObjects .slice((page - 1) * perPage, page * perPage) diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 3bd302b262fbc..106eea543760f 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -35,6 +35,7 @@ export { enableInfrastructureProfilingIntegration, enableAwsLambdaMetrics, enableAgentExplorerView, + apmEnableTableSearchBar, apmAWSLambdaPriceFactor, apmAWSLambdaRequestCostPerMillion, apmEnableServiceMetrics, diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index b647fc8361db8..028103f56a207 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -21,6 +21,7 @@ export const enableInfrastructureProfilingIntegration = 'observability:enableInfrastructureProfilingIntegration'; export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics'; export const enableAgentExplorerView = 'observability:apmAgentExplorerView'; +export const apmEnableTableSearchBar = 'observability:apmEnableTableSearchBar'; export const apmAWSLambdaPriceFactor = 'observability:apmAWSLambdaPriceFactor'; export const apmAWSLambdaRequestCostPerMillion = 'observability:apmAWSLambdaRequestCostPerMillion'; export const enableCriticalPath = 'observability:apmEnableCriticalPath'; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 3cf3874ad1f44..9403f67a8d59b 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -40,6 +40,7 @@ export { apmServiceGroupMaxNumberOfServices, enableInfrastructureHostsView, enableAgentExplorerView, + apmEnableTableSearchBar, } from '../common/ui_settings_keys'; export { alertsLocatorID, diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index f52aedfd893be..f58b6dd1f9683 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -20,6 +20,7 @@ import { apmTraceExplorerTab, apmLabsButton, enableAgentExplorerView, + apmEnableTableSearchBar, enableAwsLambdaMetrics, apmAWSLambdaPriceFactor, apmAWSLambdaRequestCostPerMillion, @@ -284,6 +285,23 @@ export const uiSettings: Record = { requiresPageReload: true, type: 'boolean', }, + [apmEnableTableSearchBar]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.apmEnableTableSearchBar', { + defaultMessage: 'Instant table search', + }), + description: i18n.translate('xpack.observability.apmEnableTableSearchBarDescription', { + defaultMessage: + '{betaLabel} Enables faster searching in APM tables by adding a handy search bar with live filtering. Available for the following tables: Services, Transactions and Errors', + values: { + betaLabel: `[${betaLabel}]`, + }, + }), + schema: schema.boolean(), + value: false, + requiresPageReload: false, + type: 'boolean', + }, [apmAWSLambdaPriceFactor]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.apmAWSLambdaPricePerGbSeconds', { diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.ts index 4aadb73283676..c6b8f1baf6974 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.ts @@ -7,6 +7,10 @@ import * as rt from 'io-ts'; -export const deleteTimelinesSchema = rt.type({ +const searchId = rt.partial({ searchIds: rt.array(rt.string) }); + +const baseDeleteTimelinesSchema = rt.type({ savedObjectIds: rt.array(rt.string), }); + +export const deleteTimelinesSchema = rt.intersection([baseDeleteTimelinesSchema, searchId]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml index e6c262f70626e..dba0471992729 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml @@ -33,6 +33,10 @@ paths: type: array items: type: string + searchId: + type: array + items: + type: string responses: 200: description: Indicates the timeline was successfully deleted. diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 2c7910166b196..0402ed1a09923 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -21,7 +21,6 @@ export const allowedExperimentalValues = Object.freeze({ kubernetesEnabled: true, chartEmbeddablesEnabled: true, donutChartEmbeddablesEnabled: false, // Depends on https://github.com/elastic/kibana/issues/136409 item 2 - 6 - alertsPreviewChartEmbeddablesEnabled: false, // Depends on https://github.com/elastic/kibana/issues/136409 item 9 /** * This is used for enabling the end-to-end tests for the security_solution telemetry. * We disable the telemetry since we don't have specific roles or permissions around it and diff --git a/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx b/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx index 6cfc5729b726a..47c1d8b478c2d 100644 --- a/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx @@ -48,7 +48,7 @@ export const useDiscoverInTimelineActions = ( const timeline = useShallowEqualSelector( (state) => getTimeline(state, TimelineId.active) ?? timelineDefaults ); - const { savedSearchId } = timeline; + const { savedSearchId, version } = timeline; // We're using a ref here to prevent a cyclic hook-dependency chain of updateSavedSearch const timelineRef = useRef(timeline); @@ -56,7 +56,7 @@ export const useDiscoverInTimelineActions = ( const queryClient = useQueryClient(); - const { mutateAsync: saveSavedSearch } = useMutation({ + const { mutateAsync: saveSavedSearch, status } = useMutation({ mutationFn: ({ savedSearch, savedSearchOptions, @@ -75,6 +75,7 @@ export const useDiscoverInTimelineActions = ( } queryClient.invalidateQueries({ queryKey: ['savedSearchById', savedSearchId] }); }, + mutationKey: [version], }); const getDefaultDiscoverAppState: () => Promise = useCallback(async () => { @@ -217,7 +218,7 @@ export const useDiscoverInTimelineActions = ( const responseIsEmpty = !response || !response?.id; if (responseIsEmpty) { throw new Error('Response is empty'); - } else if (!savedSearchId && !responseIsEmpty) { + } else if (!savedSearchId && !responseIsEmpty && status !== 'loading') { dispatch( timelineActions.updateSavedSearchId({ id: TimelineId.active, @@ -236,7 +237,7 @@ export const useDiscoverInTimelineActions = ( } } }, - [persistSavedSearch, savedSearchId, dispatch, discoverDataService] + [persistSavedSearch, savedSearchId, dispatch, discoverDataService, status] ); const initializeLocalSavedSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/use_inspect.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/use_inspect.tsx index e1d60be58acc4..23a81288908c1 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/use_inspect.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/use_inspect.tsx @@ -35,11 +35,12 @@ export const useInspect = ({ const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); - const { loading, inspect, selectedInspectIndex, isInspected } = useDeepEqualSelector((state) => - inputId === InputsModelId.global - ? getGlobalQuery(state, queryId) - : getTimelineQuery(state, queryId) - ); + const { loading, inspect, selectedInspectIndex, isInspected, searchSessionId } = + useDeepEqualSelector((state) => + inputId === InputsModelId.global + ? getGlobalQuery(state, queryId) + : getTimelineQuery(state, queryId) + ); const handleClick = useCallback(() => { if (onClick) { @@ -51,9 +52,10 @@ export const useInspect = ({ inputId, isInspected: true, selectedInspectIndex: inspectIndex, + searchSessionId, }) ); - }, [onClick, dispatch, queryId, inputId, inspectIndex]); + }, [onClick, dispatch, queryId, inputId, inspectIndex, searchSessionId]); const handleCloseModal = useCallback(() => { if (onCloseInspect != null) { @@ -65,9 +67,10 @@ export const useInspect = ({ inputId, isInspected: false, selectedInspectIndex: inspectIndex, + searchSessionId, }) ); - }, [onCloseInspect, dispatch, queryId, inputId, inspectIndex]); + }, [onCloseInspect, dispatch, queryId, inputId, inspectIndex, searchSessionId]); let request: string | null = null; let additionalRequests: string[] | null = null; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx index 0320daa2ec338..ac254714adbb7 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx @@ -45,9 +45,9 @@ jest.mock('./utils', () => ({ getCustomChartData: jest.fn().mockReturnValue(true), })); -const mockUseVisualizationResponse = jest.fn(() => [ - { aggregations: [{ buckets: [{ key: '1234' }] }], hits: { total: 999 } }, -]); +const mockUseVisualizationResponse = jest.fn(() => ({ + responses: [{ aggregations: [{ buckets: [{ key: '1234' }] }], hits: { total: 999 } }], +})); jest.mock('../visualization_actions/use_visualization_response', () => ({ useVisualizationResponse: () => mockUseVisualizationResponse(), })); @@ -345,9 +345,9 @@ describe('Matrix Histogram Component', () => { }); test('it should render 0 as subtitle when buckets are empty', () => { - mockUseVisualizationResponse.mockReturnValue([ - { aggregations: [{ buckets: [] }], hits: { total: 999 } }, - ]); + mockUseVisualizationResponse.mockReturnValue({ + responses: [{ aggregations: [{ buckets: [] }], hits: { total: 999 } }], + }); mockUseMatrix.mockReturnValue([ false, { diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 761a3e597cadd..58f1736e13792 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -82,10 +82,14 @@ const HistogramPanel = styled(Panel)<{ height?: number }>` const CHART_HEIGHT = 150; -const visualizationResponseHasData = (response: VisualizationResponse): boolean => - Object.values>(response.aggregations ?? {}).some( - ({ buckets }) => buckets.length > 0 - ); +const visualizationResponseHasData = (response: VisualizationResponse[]): boolean => { + if (response.length === 0) { + return false; + } + return Object.values>( + response[0].aggregations ?? {} + ).some(({ buckets }) => buckets.length > 0); +}; export const MatrixHistogramComponent: React.FC = ({ chartHeight, @@ -209,7 +213,7 @@ export const MatrixHistogramComponent: React.FC = () => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title), [title, selectedStackByOption] ); - const visualizationResponse = useVisualizationResponse({ visualizationId }); + const { responses } = useVisualizationResponse({ visualizationId }); const subtitleWithCounts = useMemo(() => { if (isInitialLoading) { return null; @@ -217,10 +221,10 @@ export const MatrixHistogramComponent: React.FC = if (typeof subtitle === 'function') { if (isChartEmbeddablesEnabled) { - if (!visualizationResponse || !visualizationResponseHasData(visualizationResponse[0])) { + if (!responses || !visualizationResponseHasData(responses)) { return subtitle(0); } - const visualizationCount = visualizationResponse[0].hits.total; + const visualizationCount = responses[0].hits.total; return visualizationCount >= 0 ? subtitle(visualizationCount) : null; } else { return totalCount >= 0 ? subtitle(totalCount) : null; @@ -228,7 +232,7 @@ export const MatrixHistogramComponent: React.FC = } return subtitle; - }, [isChartEmbeddablesEnabled, isInitialLoading, subtitle, totalCount, visualizationResponse]); + }, [isChartEmbeddablesEnabled, isInitialLoading, responses, subtitle, totalCount]); const hideHistogram = useMemo( () => (totalCount <= 0 && hideHistogramIfEmpty ? true : false), diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/use_actions.ts new file mode 100644 index 0000000000000..9f5f46fb67f68 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/use_actions.ts @@ -0,0 +1,62 @@ +/* + * 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. + */ +export const VISUALIZATION_CONTEXT_MENU_TRIGGER = 'VISUALIZATION_CONTEXT_MENU_TRIGGER'; +export const DEFAULT_ACTIONS = [ + 'inspect', + 'addToNewCase', + 'addToExistingCase', + 'saveToLibrary', + 'openInLens', +]; +export const MOCK_ACTIONS = [ + { + id: 'inspect', + getDisplayName: () => 'Inspect', + getIconType: () => 'inspect', + type: 'actionButton', + order: 4, + isCompatible: () => true, + execute: jest.fn(), + }, + { + id: 'addToNewCase', + getDisplayName: () => 'Add to new case', + getIconType: () => 'casesApp', + type: 'actionButton', + order: 3, + isCompatible: () => true, + execute: jest.fn(), + }, + { + id: 'addToExistingCase', + getDisplayName: () => 'Add to existing case', + getIconType: () => 'casesApp', + type: 'actionButton', + order: 2, + isCompatible: () => true, + execute: jest.fn(), + }, + { + id: 'saveToLibrary', + getDisplayName: () => 'Added to library', + getIconType: () => 'save', + type: 'actionButton', + order: 1, + isCompatible: () => true, + execute: jest.fn(), + }, + { + id: 'openInLens', + getDisplayName: () => 'Open in Lens', + getIconType: () => 'visArea', + type: 'actionButton', + order: 0, + isCompatible: () => true, + execute: jest.fn(), + }, +]; +export const useActions = jest.fn().mockReturnValue(MOCK_ACTIONS); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.test.tsx index 924b1158593a7..b3fd18989991c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.test.tsx @@ -5,66 +5,36 @@ * 2.0. */ import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; -import type { Action } from '@kbn/ui-actions-plugin/public'; +import { EuiContextMenu } from '@elastic/eui'; + +import { fireEvent, render, waitFor } from '@testing-library/react'; import { getDnsTopDomainsLensAttributes } from './lens_attributes/network/dns_top_domains'; import { VisualizationActions } from './actions'; -import { - createSecuritySolutionStorageMock, - kibanaObservable, - mockGlobalState, - SUB_PLUGINS_REDUCER, - TestProviders, -} from '../../mock'; -import type { State } from '../../store'; -import { createStore } from '../../store'; -import type { UpdateQueryParams } from '../../store/inputs/helpers'; -import { upsertQuery } from '../../store/inputs/helpers'; -import { cloneDeep } from 'lodash'; -import { useKibana } from '../../lib/kibana/kibana_react'; -import { CASES_FEATURE_ID } from '../../../../common/constants'; -import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; -import { allCasesCapabilities, allCasesPermissions } from '../../../cases_test_utils'; -import { InputsModelId } from '../../store/inputs/constants'; +import { TestProviders } from '../../mock'; + import type { VisualizationActionsProps } from './types'; import * as useLensAttributesModule from './use_lens_attributes'; import { SourcererScopeName } from '../../store/sourcerer/model'; -jest.mock('react-router-dom', () => { - const actual = jest.requireActual('react-router-dom'); +jest.mock('./use_actions'); + +jest.mock('../inspect/use_inspect', () => { return { - ...actual, - useLocation: jest.fn(() => { - return { pathname: 'network' }; - }), + useInspect: jest.fn().mockReturnValue({}), }; }); -jest.mock('../../lib/kibana/kibana_react'); -jest.mock('../../utils/route/use_route_spy', () => { + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); return { - useRouteSpy: jest.fn(() => [{ pageName: 'network', detailName: '', tabName: 'dns' }]), + ...original, + EuiContextMenu: jest.fn().mockReturnValue(
), }; }); describe('VisualizationActions', () => { - const refetch = jest.fn(); - const state: State = mockGlobalState; - const { storage } = createSecuritySolutionStorageMock(); - const newQuery: UpdateQueryParams = { - inputId: InputsModelId.global, - id: 'networkDnsHistogramQuery', - inspect: { - dsl: ['mockDsl'], - response: ['mockResponse'], - }, - loading: false, - refetch, - state: state.inputs, - }; const spyUseLensAttributes = jest.spyOn(useLensAttributesModule, 'useLensAttributes'); - - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const props: VisualizationActionsProps = { getLensAttributes: getDnsTopDomainsLensAttributes, queryId: 'networkDnsHistogramQuery', @@ -76,64 +46,15 @@ describe('VisualizationActions', () => { extraOptions: { dnsIsPtrIncluded: true }, stackByField: 'dns.question.registered_domain', }; - const mockNavigateToPrefilledEditor = jest.fn(); - const mockGetCreateCaseFlyoutOpen = jest.fn(); - const mockGetAllCasesSelectorModalOpen = jest.fn(); + const mockContextMenu = EuiContextMenu as unknown as jest.Mock; beforeEach(() => { jest.clearAllMocks(); - const cases = mockCasesContract(); - cases.helpers.getUICapabilities.mockReturnValue(allCasesPermissions()); - - (useKibana as jest.Mock).mockReturnValue({ - services: { - lens: { - canUseEditor: jest.fn(() => true), - navigateToPrefilledEditor: mockNavigateToPrefilledEditor, - }, - cases: { - ...mockCasesContract(), - hooks: { - useCasesAddToExistingCaseModal: jest - .fn() - .mockReturnValue({ open: mockGetAllCasesSelectorModalOpen }), - useCasesAddToNewCaseFlyout: jest - .fn() - .mockReturnValue({ open: mockGetCreateCaseFlyoutOpen }), - }, - helpers: { canUseCases: jest.fn().mockReturnValue(allCasesPermissions()) }, - }, - application: { - capabilities: { [CASES_FEATURE_ID]: allCasesCapabilities() }, - getUrlForApp: jest.fn(), - navigateToApp: jest.fn(), - }, - notifications: { - toasts: { - addError: jest.fn(), - addSuccess: jest.fn(), - addWarning: jest.fn(), - remove: jest.fn(), - }, - }, - http: jest.fn(), - data: { - search: jest.fn(), - }, - storage: { - set: jest.fn(), - }, - theme: {}, - }, - }); - const myState = cloneDeep(state); - myState.inputs = upsertQuery(newQuery); - store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); }); test('Should generate attributes', () => { render( - + ); @@ -150,161 +71,38 @@ describe('VisualizationActions', () => { ); }); - test('Should render VisualizationActions button', () => { - const { container } = render( - - - - ); - expect( - container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`) - ).toBeInTheDocument(); - }); - - test('Should render Open in Lens button', () => { - const { container } = render( - - - - ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - - expect(screen.getByText('Open in Lens')).toBeInTheDocument(); - expect(screen.getByText('Open in Lens')).not.toBeDisabled(); - }); - - test('Should call NavigateToPrefilledEditor when Open in Lens', () => { - const { container } = render( - - - - ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - - fireEvent.click(screen.getByText('Open in Lens')); - expect(mockNavigateToPrefilledEditor.mock.calls[0][0].timeRange).toEqual(props.timerange); - expect(mockNavigateToPrefilledEditor.mock.calls[0][0].attributes.title).toEqual(''); - expect(mockNavigateToPrefilledEditor.mock.calls[0][0].attributes.references).toEqual([ - { - id: 'security-solution', - name: 'indexpattern-datasource-layer-b1c3efc6-c886-4fba-978f-3b6bb5e7948a', - type: 'index-pattern', - }, - ]); - expect(mockNavigateToPrefilledEditor.mock.calls[0][1].openInNewTab).toEqual(true); - }); - - test('Should render Inspect button', () => { - const { container } = render( - - - - ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - - expect(screen.getByText('Inspect')).toBeInTheDocument(); - expect(screen.getByText('Inspect')).not.toBeDisabled(); - }); - - test('Should render Inspect Modal after clicking the inspect button', () => { - const { baseElement, container } = render( - - - - ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - - expect(screen.getByText('Inspect')).toBeInTheDocument(); - fireEvent.click(screen.getByText('Inspect')); - expect( - baseElement.querySelector('[data-test-subj="modal-inspect-euiModal"]') - ).toBeInTheDocument(); - }); - - test('Should render Add to new case button', () => { - const { container } = render( - - - - ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - - expect(screen.getByText('Add to new case')).toBeInTheDocument(); - expect(screen.getByText('Add to new case')).not.toBeDisabled(); - }); - - test('Should render Add to new case modal after clicking on Add to new case button', () => { - const { container } = render( - - - - ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - fireEvent.click(screen.getByText('Add to new case')); - - expect(mockGetCreateCaseFlyoutOpen).toBeCalled(); - }); - - test('Should render Add to existing case button', () => { - const { container } = render( - + test('Should render VisualizationActions button', async () => { + const { queryByTestId } = render( + ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - expect(screen.getByText('Add to existing case')).toBeInTheDocument(); - expect(screen.getByText('Add to existing case')).not.toBeDisabled(); + await waitFor(() => { + expect(queryByTestId(`stat-networkDnsHistogramQuery`)).toBeInTheDocument(); + }); }); - test('Should render Add to existing case modal after clicking on Add to existing case button', () => { - const { container } = render( - + test('renders context menu', async () => { + const { getByTestId } = render( + ); - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - fireEvent.click(screen.getByText('Add to existing case')); - - expect(mockGetAllCasesSelectorModalOpen).toBeCalled(); - }); - test('Should not render default actions when withDefaultActions = false', () => { - const testProps = { ...props, withDefaultActions: false }; - render( - - - - ); + await waitFor(() => { + expect(getByTestId(`stat-networkDnsHistogramQuery`)).toBeInTheDocument(); + }); - expect( - screen.queryByTestId(`[data-test-subj="stat-networkDnsHistogramQuery"]`) - ).not.toBeInTheDocument(); - expect(screen.queryByText('Inspect')).not.toBeInTheDocument(); - expect(screen.queryByText('Add to new case')).not.toBeInTheDocument(); - expect(screen.queryByText('Add to existing case')).not.toBeInTheDocument(); - expect(screen.queryByText('Open in Lens')).not.toBeInTheDocument(); - }); + fireEvent.click(getByTestId(`stat-networkDnsHistogramQuery`)); - test('Should render extra actions when extraAction is provided', () => { - const testProps = { - ...props, - extraActions: [ - { - getIconType: () => 'reset', - id: 'resetField', - execute: jest.fn(), - getDisplayName: () => 'Reset Field', - } as unknown as Action, - ], - }; - const { container } = render( - - - + expect(getByTestId('viz-actions-menu')).toBeInTheDocument(); + expect(mockContextMenu.mock.calls[0][0].panels[0].items[0].name).toEqual('Inspect'); + expect(mockContextMenu.mock.calls[0][0].panels[0].items[1].name).toEqual('Add to new case'); + expect(mockContextMenu.mock.calls[0][0].panels[0].items[2].name).toEqual( + 'Add to existing case' ); - - fireEvent.click(container.querySelector(`[data-test-subj="stat-networkDnsHistogramQuery"]`)!); - expect(screen.getByText('Reset Field')).toBeInTheDocument(); + expect(mockContextMenu.mock.calls[0][0].panels[1].items[0].name).toEqual('Added to library'); + expect(mockContextMenu.mock.calls[0][0].panels[1].items[1].name).toEqual('Open in Lens'); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx index 5527e0eca44d6..930e510ff07fa 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/actions.tsx @@ -4,34 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import { EuiButtonIcon, EuiContextMenu, EuiPopover } from '@elastic/eui'; +import { buildContextMenuForActions } from '@kbn/ui-actions-plugin/public'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import type { LensEmbeddableInput } from '@kbn/lens-plugin/public'; +import { useAsync } from 'react-use'; import { InputsModelId } from '../../store/inputs/constants'; -import { useKibana } from '../../lib/kibana/kibana_react'; import { ModalInspectQuery } from '../inspect/modal'; import { useInspect } from '../inspect/use_inspect'; import { useLensAttributes } from './use_lens_attributes'; -import { useAddToExistingCase } from './use_add_to_existing_case'; -import { useAddToNewCase } from './use_add_to_new_case'; -import { useSaveToLibrary } from './use_save_to_library'; + import type { VisualizationActionsProps } from './types'; -import { - ADD_TO_EXISTING_CASE, - ADD_TO_NEW_CASE, - INSPECT, - MORE_ACTIONS, - OPEN_IN_LENS, - ADDED_TO_LIBRARY, -} from './translations'; +import { MORE_ACTIONS } from './translations'; import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from './utils'; +import { DEFAULT_ACTIONS, useActions, VISUALIZATION_CONTEXT_MENU_TRIGGER } from './use_actions'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import { useAppToasts } from '../../hooks/use_app_toasts'; const Wrapper = styled.div` &.viz-actions { @@ -62,23 +52,10 @@ const VisualizationActionsComponent: React.FC = ({ title: inspectTitle, scopeId = SourcererScopeName.default, stackByField, - withDefaultActions = true, + withActions = DEFAULT_ACTIONS, }) => { - const { lens } = useKibana().services; - - const { canUseEditor, navigateToPrefilledEditor, SaveModalComponent } = lens; const [isPopoverOpen, setPopover] = useState(false); const [isInspectModalOpen, setIsInspectModalOpen] = useState(false); - const [isSaveModalVisible, setIsSaveModalVisible] = useState(false); - const { addSuccess } = useAppToasts(); - const onSave = useCallback(() => { - setIsSaveModalVisible(false); - addSuccess(ADDED_TO_LIBRARY); - }, [addSuccess]); - const onClose = useCallback(() => { - setIsSaveModalVisible(false); - }, []); - const hasPermission = canUseEditor(); const onButtonClick = useCallback(() => { setPopover(!isPopoverOpen); @@ -100,40 +77,6 @@ const VisualizationActionsComponent: React.FC = ({ const dataTestSubj = `stat-${queryId}`; - const { disabled: isAddToExistingCaseDisabled, onAddToExistingCaseClicked } = - useAddToExistingCase({ - onAddToCaseClicked: closePopover, - lensAttributes: attributes, - timeRange: timerange, - }); - - const { onAddToNewCaseClicked, disabled: isAddToNewCaseDisabled } = useAddToNewCase({ - onClick: closePopover, - timeRange: timerange, - lensAttributes: attributes, - }); - - const onOpenInLens = useCallback(() => { - closePopover(); - if (!timerange || !attributes) { - return; - } - navigateToPrefilledEditor( - { - id: '', - timeRange: timerange, - attributes, - }, - { - openInNewTab: true, - } - ); - }, [attributes, navigateToPrefilledEditor, timerange]); - - const { openSaveVisualizationFlyout, disableVisualizations } = useSaveToLibrary({ - attributes, - }); - const onOpenInspectModal = useCallback(() => { closePopover(); setIsInspectModalOpen(true); @@ -164,91 +107,33 @@ const VisualizationActionsComponent: React.FC = ({ queryId, }); - const items = useMemo(() => { - const context = {} as ActionExecutionContext; - const extraActionsItems = - extraActions?.map((item: Action) => { - return ( - item.execute(context)} - data-test-subj={`viz-actions-${item.id}`} - > - {item.getDisplayName(context)} - - ); - }) ?? []; - return [ - ...(extraActionsItems ? extraActionsItems : []), - ...(withDefaultActions - ? [ - - {INSPECT} - , - - {ADD_TO_NEW_CASE} - , - - {ADD_TO_EXISTING_CASE} - , - ...(hasPermission - ? [ - - {ADDED_TO_LIBRARY} - , - ] - : []), - - {OPEN_IN_LENS} - , - ] - : []), - ]; - }, [ - hasPermission, - disableInspectButton, - disableVisualizations, + const inspectActionProps = useMemo( + () => ({ + handleInspectClick: handleInspectButtonClick, + isInspectButtonDisabled: disableInspectButton, + }), + [disableInspectButton, handleInspectButtonClick] + ); + + const contextMenuActions = useActions({ + attributes, extraActions, - handleInspectButtonClick, - isAddToExistingCaseDisabled, - isAddToNewCaseDisabled, - onAddToExistingCaseClicked, - onAddToNewCaseClicked, - onOpenInLens, - openSaveVisualizationFlyout, - withDefaultActions, - ]); + inspectActionProps, + timeRange: timerange, + withActions, + }); + + const panels = useAsync( + () => + buildContextMenuForActions({ + actions: contextMenuActions.map((action) => ({ + action, + context: {}, + trigger: VISUALIZATION_CONTEXT_MENU_TRIGGER, + })), + }), + [contextMenuActions] + ); const button = useMemo( () => ( @@ -265,7 +150,7 @@ const VisualizationActionsComponent: React.FC = ({ return ( - {items.length > 0 && ( + {panels.value && panels.value.length > 0 && ( = ({ panelClassName="withHoverActions__popover" data-test-subj="viz-actions-popover" > - + )} {isInspectModalOpen && request !== null && response !== null && ( @@ -289,13 +174,6 @@ const VisualizationActionsComponent: React.FC = ({ title={inspectTitle} /> )} - {isSaveModalVisible && hasPermission && ( - - )} ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts index 85b4a11bbc7f9..0e31ae006ddb5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts @@ -41,6 +41,7 @@ describe('getRulePreviewLensAttributes', () => { const { result } = renderHook( () => useLensAttributes({ + extraOptions: { showLegend: false }, getLensAttributes: getRulePreviewLensAttributes, stackByField: 'event.category', }), diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts index 2c4c3ec036034..79d791a15d7e8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts @@ -22,7 +22,7 @@ export const getRulePreviewLensAttributes: GetLensAttributes = ( visualization: { title: 'Empty XY chart', legend: { - isVisible: false, + isVisible: extraOptions?.showLegend, position: 'right', }, valueLabels: 'hide', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.test.tsx index 92f394006d8e9..c87e941b18c9d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.test.tsx @@ -66,7 +66,7 @@ describe('LensEmbeddable', () => { queries: [ { id: 'testId', - inspect: { dsl: [], response: [] }, + inspect: { dsl: [], response: ['{"mockResponse": "mockResponse"}'] }, isInspected: false, loading: false, selectedInspectIndex: 0, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 69d171467f996..4883757132bc0 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -14,19 +14,24 @@ import styled from 'styled-components'; import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import type { RangeFilterParams } from '@kbn/es-query'; import type { ClickTriggerEvent, MultiClickTriggerEvent } from '@kbn/charts-plugin/public'; -import type { EmbeddableComponentProps, XYState } from '@kbn/lens-plugin/public'; +import type { + EmbeddableComponentProps, + TypedLensByValueInput, + XYState, +} from '@kbn/lens-plugin/public'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { useKibana } from '../../lib/kibana'; import { useLensAttributes } from './use_lens_attributes'; import type { LensEmbeddableComponentProps } from './types'; -import { useActions } from './use_actions'; -import { inputsSelectors } from '../../store'; -import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { DEFAULT_ACTIONS, useActions } from './use_actions'; + import { ModalInspectQuery } from '../inspect/modal'; import { InputsModelId } from '../../store/inputs/constants'; -import { getRequestsAndResponses } from './utils'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { VisualizationActions } from './actions'; +import { useEmbeddableInspect } from './use_embeddable_inspect'; +import { useVisualizationResponse } from './use_visualization_response'; +import { useInspect } from '../inspect/use_inspect'; const HOVER_ACTIONS_PADDING = 24; const DISABLED_ACTIONS = [ACTION_CUSTOMIZE_PANEL]; @@ -56,16 +61,6 @@ const LensComponentWrapper = styled.div<{ } `; -const initVisualizationData: { - requests: string[] | undefined; - responses: string[] | undefined; - isLoading: boolean; -} = { - requests: undefined, - responses: undefined, - isLoading: true, -}; - const LensEmbeddableComponent: React.FC = ({ applyGlobalQueriesAndFilters = true, extraActions, @@ -78,10 +73,11 @@ const LensEmbeddableComponent: React.FC = ({ lensAttributes, onLoad, scopeId = SourcererScopeName.default, + enableLegendActions = true, stackByField, timerange, width: wrapperWidth, - withActions = true, + withActions = DEFAULT_ACTIONS, disableOnClickFilter = false, }) => { const style = useMemo( @@ -99,10 +95,7 @@ const LensEmbeddableComponent: React.FC = ({ }, } = useKibana().services; const dispatch = useDispatch(); - const [isShowingModal, setIsShowingModal] = useState(false); - const [visualizationData, setVisualizationData] = useState(initVisualizationData); - const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); - const { searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); + const { searchSessionId } = useVisualizationResponse({ visualizationId: id }); const attributes = useLensAttributes({ applyGlobalQueriesAndFilters, extraOptions, @@ -118,14 +111,39 @@ const LensEmbeddableComponent: React.FC = ({ attributes?.visualizationType !== 'lnsLegacyMetric' && attributes?.visualizationType !== 'lnsPie'; const LensComponent = lens.EmbeddableComponent; + + const overrides: TypedLensByValueInput['overrides'] = useMemo( + () => + enableLegendActions + ? undefined + : { settings: { legendAction: 'ignore', onBrushEnd: 'ignore' } }, + [enableLegendActions] + ); + const { setInspectData } = useEmbeddableInspect(onLoad); + const { responses, loading } = useVisualizationResponse({ visualizationId: id }); + + const { + additionalRequests, + additionalResponses, + handleClick: handleInspectClick, + handleCloseModal, + isButtonDisabled: isInspectButtonDisabled, + isShowingModal, + request, + response, + } = useInspect({ + inputId: inputsModelId, + isDisabled: loading, + multiple: responses != null && responses.length > 1, + queryId: id, + }); + const inspectActionProps = useMemo( () => ({ - onInspectActionClicked: () => { - setIsShowingModal(true); - }, - isDisabled: visualizationData.isLoading, + handleInspectClick, + isInspectButtonDisabled, }), - [visualizationData.isLoading] + [handleInspectClick, isInspectButtonDisabled] ); const actions = useActions({ @@ -136,10 +154,6 @@ const LensEmbeddableComponent: React.FC = ({ withActions, }); - const handleCloseModal = useCallback(() => { - setIsShowingModal(false); - }, []); - const updateDateRange = useCallback( ({ range }) => { const [min, max] = range; @@ -154,65 +168,34 @@ const LensEmbeddableComponent: React.FC = ({ [dispatch, inputsModelId] ); - const requests = useMemo(() => { - const [request, ...additionalRequests] = visualizationData.requests ?? []; - return { request, additionalRequests }; - }, [visualizationData.requests]); - - const responses = useMemo(() => { - const [response, ...additionalResponses] = visualizationData.responses ?? []; - return { response, additionalResponses }; - }, [visualizationData.responses]); - - const onLoadCallback = useCallback( - (isLoading, adapters) => { - if (!adapters) { + const onFilterCallback = useCallback( + (event) => { + if (disableOnClickFilter) { + event.preventDefault(); return; } - const data = getRequestsAndResponses(adapters?.requests?.getRequests()); - setVisualizationData({ - requests: data.requests, - responses: data.responses, - isLoading, - }); - - if (onLoad != null) { - onLoad({ - requests: data.requests, - responses: data.responses, - isLoading, + const callback: EmbeddableComponentProps['onFilter'] = async (e) => { + if (!isClickTriggerEvent(e) || preferredSeriesType !== 'area') { + e.preventDefault(); + return; + } + // Update timerange when clicking on a dot in an area chart + const [{ query }] = await createFiltersFromValueClickAction({ + data: e.data, + negate: e.negate, }); - } + const rangeFilter: RangeFilterParams = query?.range['@timestamp']; + if (rangeFilter?.gte && rangeFilter?.lt) { + updateDateRange({ + range: [rangeFilter.gte, rangeFilter.lt], + }); + } + }; + return callback; }, - [onLoad] + [createFiltersFromValueClickAction, updateDateRange, preferredSeriesType, disableOnClickFilter] ); - const onFilterCallback = useCallback(() => { - const callback: EmbeddableComponentProps['onFilter'] = async (e) => { - if (!isClickTriggerEvent(e) || preferredSeriesType !== 'area' || disableOnClickFilter) { - e.preventDefault(); - return; - } - // Update timerange when clicking on a dot in an area chart - const [{ query }] = await createFiltersFromValueClickAction({ - data: e.data, - negate: e.negate, - }); - const rangeFilter: RangeFilterParams = query?.range['@timestamp']; - if (rangeFilter?.gte && rangeFilter?.lt) { - updateDateRange({ - range: [rangeFilter.gte, rangeFilter.lt], - }); - } - }; - return callback; - }, [ - createFiltersFromValueClickAction, - updateDateRange, - preferredSeriesType, - disableOnClickFilter, - ]); - const adHocDataViews = useMemo( () => attributes?.state?.adHocDataViews != null @@ -230,10 +213,7 @@ const LensEmbeddableComponent: React.FC = ({ return null; } - if ( - !attributes || - (visualizationData?.responses != null && visualizationData?.responses?.length === 0) - ) { + if (!attributes || (responses != null && responses.length === 0)) { return ( @@ -259,7 +239,7 @@ const LensEmbeddableComponent: React.FC = ({ stackByField={stackByField} timerange={timerange} title={inspectTitle} - withDefaultActions={false} + withActions={withActions} /> @@ -275,34 +255,35 @@ const LensEmbeddableComponent: React.FC = ({ $addHoverActionsPadding={addHoverActionsPadding} > )} - {isShowingModal && requests.request != null && responses.response != null && ( + {isShowingModal && request != null && response != null && ( )} diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 6f513e445660e..b09e1fe2cc46c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -35,6 +35,14 @@ export interface UseLensAttributesProps { title?: string; } +export enum VisualizationContextMenuActions { + addToExistingCase = 'addToExistingCase', + addToNewCase = 'addToNewCase', + inspect = 'inspect', + openInLens = 'openInLens', + saveToLibrary = 'saveToLibrary', +} + export interface VisualizationActionsProps { applyGlobalQueriesAndFilters?: boolean; className?: string; @@ -52,7 +60,7 @@ export interface VisualizationActionsProps { stackByField?: string; timerange: { from: string; to: string }; title: React.ReactNode; - withDefaultActions?: boolean; + withActions?: VisualizationContextMenuActions[]; } export interface EmbeddableData { @@ -63,6 +71,14 @@ export interface EmbeddableData { export type OnEmbeddableLoaded = (data: EmbeddableData) => void; +export enum VisualizationContextMenuDefaultActionName { + addToExistingCase = 'addToExistingCase', + addToNewCase = 'addToNewCase', + inspect = 'inspect', + openInLens = 'openInLens', + saveToLibrary = 'saveToLibrary', +} + export interface LensEmbeddableComponentProps { applyGlobalQueriesAndFilters?: boolean; extraActions?: Action[]; @@ -74,11 +90,12 @@ export interface LensEmbeddableComponentProps { inspectTitle?: React.ReactNode; lensAttributes?: LensAttributes; onLoad?: OnEmbeddableLoaded; + enableLegendActions?: boolean; scopeId?: SourcererScopeName; stackByField?: string; timerange: { from: string; to: string }; width?: string | number; - withActions?: boolean; + withActions?: VisualizationContextMenuActions[]; /** * Disable the on click filter for the visualization. */ @@ -125,11 +142,12 @@ export interface Response { export interface ExtraOptions { breakdownField?: string; + dnsIsPtrIncluded?: boolean; filters?: Filter[]; ruleId?: string; + showLegend?: boolean; spaceId?: string; status?: Status; - dnsIsPtrIncluded?: boolean; } export interface VisualizationEmbeddableProps extends LensEmbeddableComponentProps { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.tsx index 273e4d89d1d7a..1582a0b382c75 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { NavigationProvider } from '@kbn/security-solution-navigation'; import { useKibana } from '../../lib/kibana/kibana_react'; import { mockAttributes } from './mocks'; -import { useActions } from './use_actions'; +import { DEFAULT_ACTIONS, useActions } from './use_actions'; import { coreMock } from '@kbn/core/public/mocks'; import { TestProviders } from '../../mock'; @@ -71,15 +71,15 @@ describe(`useActions`, () => { const { result } = renderHook( () => useActions({ - withActions: true, + withActions: DEFAULT_ACTIONS, attributes: mockAttributes, timeRange: { from: '2022-10-26T23:00:00.000Z', to: '2022-11-03T15:16:50.053Z', }, inspectActionProps: { - onInspectActionClicked: jest.fn(), - isDisabled: false, + handleInspectClick: jest.fn(), + isInspectButtonDisabled: false, }, }), { @@ -119,15 +119,15 @@ describe(`useActions`, () => { const { result } = renderHook( () => useActions({ - withActions: true, + withActions: DEFAULT_ACTIONS, attributes: mockAttributes, timeRange: { from: '2022-10-26T23:00:00.000Z', to: '2022-11-03T15:16:50.053Z', }, inspectActionProps: { - onInspectActionClicked: jest.fn(), - isDisabled: false, + handleInspectClick: jest.fn(), + isInspectButtonDisabled: false, }, extraActions: mockExtraAction, }), diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts index 760d9e396584e..8085097838307 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts @@ -5,52 +5,103 @@ * 2.0. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import { useCallback, useMemo } from 'react'; +import type { Action, Trigger } from '@kbn/ui-actions-plugin/public'; + +import { createAction } from '@kbn/ui-actions-plugin/public'; +import type { ActionDefinition } from '@kbn/ui-actions-plugin/public/actions'; import { useKibana } from '../../lib/kibana/kibana_react'; import { useAddToExistingCase } from './use_add_to_existing_case'; import { useAddToNewCase } from './use_add_to_new_case'; import { useSaveToLibrary } from './use_save_to_library'; +import { VisualizationContextMenuActions } from './types'; +import type { LensAttributes } from './types'; import { ADDED_TO_LIBRARY, ADD_TO_EXISTING_CASE, ADD_TO_NEW_CASE, + INSPECT, OPEN_IN_LENS, } from './translations'; -import type { LensAttributes } from './types'; -import { INSPECT } from '../inspect/translations'; -export type ActionTypes = 'addToExistingCase' | 'addToNewCase' | 'openInLens'; +export const DEFAULT_ACTIONS: VisualizationContextMenuActions[] = [ + VisualizationContextMenuActions.inspect, + VisualizationContextMenuActions.addToNewCase, + VisualizationContextMenuActions.addToExistingCase, + VisualizationContextMenuActions.saveToLibrary, + VisualizationContextMenuActions.openInLens, +]; + +export const INSPECT_ACTION: VisualizationContextMenuActions[] = [ + VisualizationContextMenuActions.inspect, +]; + +export const VISUALIZATION_CONTEXT_MENU_TRIGGER: Trigger = { + id: 'VISUALIZATION_CONTEXT_MENU_TRIGGER', +}; + +const ACTION_DEFINITION: Record< + VisualizationContextMenuActions, + Omit +> = { + [VisualizationContextMenuActions.inspect]: { + id: VisualizationContextMenuActions.inspect, + getDisplayName: () => INSPECT, + getIconType: () => 'inspect', + type: 'actionButton', + order: 4, + }, + [VisualizationContextMenuActions.addToNewCase]: { + id: VisualizationContextMenuActions.addToNewCase, + getDisplayName: () => ADD_TO_NEW_CASE, + getIconType: () => 'casesApp', + type: 'actionButton', + order: 3, + }, + [VisualizationContextMenuActions.addToExistingCase]: { + id: VisualizationContextMenuActions.addToExistingCase, + getDisplayName: () => ADD_TO_EXISTING_CASE, + getIconType: () => 'casesApp', + type: 'actionButton', + order: 2, + }, + [VisualizationContextMenuActions.saveToLibrary]: { + id: VisualizationContextMenuActions.saveToLibrary, + getDisplayName: () => ADDED_TO_LIBRARY, + getIconType: () => 'save', + type: 'actionButton', + order: 1, + }, + [VisualizationContextMenuActions.openInLens]: { + id: VisualizationContextMenuActions.openInLens, + getDisplayName: () => OPEN_IN_LENS, + getIconType: () => 'visArea', + type: 'actionButton', + order: 0, + }, +}; export const useActions = ({ attributes, - extraActions, + extraActions = [], inspectActionProps, timeRange, - withActions, + withActions = DEFAULT_ACTIONS, }: { attributes: LensAttributes | null; extraActions?: Action[]; - inspectActionProps?: { onInspectActionClicked: () => void; isDisabled: boolean }; + inspectActionProps: { + handleInspectClick: () => void; + isInspectButtonDisabled: boolean; + }; timeRange: { from: string; to: string }; - withActions?: boolean; + withActions?: VisualizationContextMenuActions[]; }) => { - const { lens } = useKibana().services; - const { navigateToPrefilledEditor } = lens; - const [defaultActions, setDefaultActions] = useState([ - 'inspect', - 'addToNewCase', - 'addToExistingCase', - 'saveToLibrary', - 'openInLens', - ]); - - useEffect(() => { - if (withActions === false) { - setDefaultActions([]); - } - }, [withActions]); + const { services } = useKibana(); + const { + lens: { navigateToPrefilledEditor, canUseEditor }, + } = services; const onOpenInLens = useCallback(() => { if (!timeRange || !attributes) { @@ -80,201 +131,78 @@ export const useActions = ({ }); const { openSaveVisualizationFlyout, disableVisualizations } = useSaveToLibrary({ attributes }); - const actions = useMemo( - () => - defaultActions?.reduce((acc, action) => { - if (action === 'inspect' && inspectActionProps != null) { - return [ - ...acc, - getInspectAction({ - callback: inspectActionProps?.onInspectActionClicked, - disabled: inspectActionProps?.isDisabled, - }), - ]; - } - if (action === 'addToExistingCase') { - return [ - ...acc, - getAddToExistingCaseAction({ - callback: onAddToExistingCaseClicked, - disabled: isAddToExistingCaseDisabled, - }), - ]; - } - if (action === 'addToNewCase') { - return [ - ...acc, - getAddToNewCaseAction({ - callback: onAddToNewCaseClicked, - disabled: isAddToNewCaseDisabled, - }), - ]; - } - if (action === 'openInLens') { - return [...acc, getOpenInLensAction({ callback: onOpenInLens })]; - } - if (action === 'saveToLibrary') { - return [ - ...acc, - getSaveToLibraryAction({ - callback: openSaveVisualizationFlyout, - disabled: disableVisualizations, - }), - ]; - } - - return acc; - }, []), + const allActions: Action[] = useMemo( + () => + [ + createAction({ + ...ACTION_DEFINITION[VisualizationContextMenuActions.inspect], + execute: async () => { + inspectActionProps.handleInspectClick(); + }, + disabled: inspectActionProps.isInspectButtonDisabled, + isCompatible: async () => withActions.includes(VisualizationContextMenuActions.inspect), + }), + createAction({ + ...ACTION_DEFINITION[VisualizationContextMenuActions.addToNewCase], + execute: async () => { + onAddToNewCaseClicked(); + }, + disabled: isAddToNewCaseDisabled, + isCompatible: async () => + withActions.includes(VisualizationContextMenuActions.addToNewCase), + }), + createAction({ + ...ACTION_DEFINITION[VisualizationContextMenuActions.addToExistingCase], + execute: async () => { + onAddToExistingCaseClicked(); + }, + disabled: isAddToExistingCaseDisabled, + isCompatible: async () => + withActions.includes(VisualizationContextMenuActions.addToExistingCase), + order: 2, + }), + createAction({ + ...ACTION_DEFINITION[VisualizationContextMenuActions.saveToLibrary], + execute: async () => { + openSaveVisualizationFlyout(); + }, + disabled: disableVisualizations, + isCompatible: async () => + withActions.includes(VisualizationContextMenuActions.saveToLibrary), + order: 1, + }), + createAction({ + ...ACTION_DEFINITION[VisualizationContextMenuActions.openInLens], + execute: async () => { + onOpenInLens(); + }, + isCompatible: async () => + canUseEditor() && withActions.includes(VisualizationContextMenuActions.openInLens), + order: 0, + }), + ...extraActions, + ].map((a, i, totalActions) => { + const order = Math.max(totalActions.length - (1 + i), 0); + return { + ...a, + order, + }; + }), [ - defaultActions, + canUseEditor, + disableVisualizations, + extraActions, inspectActionProps, - onAddToExistingCaseClicked, isAddToExistingCaseDisabled, - onAddToNewCaseClicked, isAddToNewCaseDisabled, + onAddToExistingCaseClicked, + onAddToNewCaseClicked, onOpenInLens, openSaveVisualizationFlyout, - disableVisualizations, + withActions, ] ); - const withExtraActions = actions.concat(extraActions ?? []).map((a, i, totalActions) => { - const order = Math.max(totalActions.length - (1 + i), 0); - return { - ...a, - order, - }; - }); - - return withExtraActions; -}; - -const getOpenInLensAction = ({ callback }: { callback: () => void }): Action => { - return { - id: 'openInLens', - - getDisplayName(context: ActionExecutionContext): string { - return OPEN_IN_LENS; - }, - getIconType(context: ActionExecutionContext): string | undefined { - return 'visArea'; - }, - type: 'actionButton', - async isCompatible(context: ActionExecutionContext): Promise { - return true; - }, - async execute(context: ActionExecutionContext): Promise { - callback(); - }, - order: 0, - }; -}; - -const getSaveToLibraryAction = ({ - callback, - disabled, -}: { - callback: () => void; - disabled?: boolean; -}): Action => { - return { - id: 'saveToLibrary', - getDisplayName(context: ActionExecutionContext): string { - return ADDED_TO_LIBRARY; - }, - getIconType(context: ActionExecutionContext): string | undefined { - return 'save'; - }, - type: 'actionButton', - async isCompatible(context: ActionExecutionContext): Promise { - return true; - }, - async execute(context: ActionExecutionContext): Promise { - callback(); - }, - disabled, - order: 1, - }; -}; - -const getAddToExistingCaseAction = ({ - callback, - disabled, -}: { - callback: () => void; - disabled?: boolean; -}): Action => { - return { - id: 'addToExistingCase', - getDisplayName(context: ActionExecutionContext): string { - return ADD_TO_EXISTING_CASE; - }, - getIconType(context: ActionExecutionContext): string | undefined { - return 'casesApp'; - }, - type: 'actionButton', - async isCompatible(context: ActionExecutionContext): Promise { - return true; - }, - async execute(context: ActionExecutionContext): Promise { - callback(); - }, - disabled, - order: 2, - }; -}; - -const getAddToNewCaseAction = ({ - callback, - disabled, -}: { - callback: () => void; - disabled?: boolean; -}): Action => { - return { - id: 'addToNewCase', - getDisplayName(context: ActionExecutionContext): string { - return ADD_TO_NEW_CASE; - }, - getIconType(context: ActionExecutionContext): string | undefined { - return 'casesApp'; - }, - type: 'actionButton', - async isCompatible(context: ActionExecutionContext): Promise { - return true; - }, - async execute(context: ActionExecutionContext): Promise { - callback(); - }, - disabled, - order: 3, - }; -}; - -const getInspectAction = ({ - callback, - disabled, -}: { - callback: () => void; - disabled?: boolean; -}): Action => { - return { - id: 'inspect', - getDisplayName(context: ActionExecutionContext): string { - return INSPECT; - }, - getIconType(context: ActionExecutionContext): string | undefined { - return 'inspect'; - }, - type: 'actionButton', - async isCompatible(context: ActionExecutionContext): Promise { - return true; - }, - async execute(context: ActionExecutionContext): Promise { - callback(); - }, - disabled, - order: 4, - }; + return allActions; }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx new file mode 100644 index 0000000000000..ca80999a81062 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import type { OnEmbeddableLoaded } from './types'; + +import { getRequestsAndResponses } from './utils'; + +export const useEmbeddableInspect = (onEmbeddableLoad?: OnEmbeddableLoaded) => { + const setInspectData = useCallback( + (isLoading, adapters) => { + if (!adapters) { + return; + } + const data = getRequestsAndResponses(adapters?.requests?.getRequests()); + + onEmbeddableLoad?.({ + requests: data.requests, + responses: data.responses, + isLoading, + }); + }, + [onEmbeddableLoad] + ); + + return { setInspectData }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.test.tsx index 36d83e7793e59..68adb1dd8f20a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.test.tsx @@ -55,7 +55,7 @@ describe('useVisualizationResponse', () => { const { result } = renderHook(() => useVisualizationResponse({ visualizationId }), { wrapper: ({ children }) => {children}, }); - expect(result.current).toEqual( + expect(result.current.responses).toEqual( parseVisualizationData(mockState.inputs.global.queries[0].inspect.response) ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.tsx index 39e822744922c..601059cab2c2a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_visualization_response.tsx @@ -14,10 +14,17 @@ import type { VisualizationResponse } from './types'; export const useVisualizationResponse = ({ visualizationId }: { visualizationId: string }) => { const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); - const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, visualizationId)); + const { inspect, loading, searchSessionId } = useDeepEqualSelector((state) => + getGlobalQuery(state, visualizationId) + ); const response = useMemo( - () => (inspect ? parseVisualizationData(inspect?.response) : null), - [inspect] + () => ({ + requests: inspect ? parseVisualizationData(inspect?.dsl) : null, + responses: inspect ? parseVisualizationData(inspect?.response) : null, + loading, + searchSessionId, + }), + [inspect, loading, searchSessionId] ); return response; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index 580aad868d5c7..a5845b9bb0fc8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -40,7 +40,7 @@ const VisualizationEmbeddableComponent: React.FC = const memorizedTimerange = useRef(lensProps.timerange); const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); - const visualizationData = useVisualizationResponse({ visualizationId: id }); + const { responses: visualizationData } = useVisualizationResponse({ visualizationId: id }); const dataExists = visualizationData != null && visualizationData[0]?.hits?.total !== 0; const donutTextWrapperStyles = dataExists ? css` @@ -125,7 +125,7 @@ const VisualizationEmbeddableComponent: React.FC = isChartEmbeddablesEnabled={true} dataExists={dataExists} label={label} - title={dataExists ? : null} + title={visualizationData ? : null} donutTextWrapperClassName={donutTextWrapperClassName} donutTextWrapperStyles={donutTextWrapperStyles} > diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts index 3b072c2f91e2a..f1fbe5a06c1d1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts @@ -6,12 +6,10 @@ */ import { isEmpty } from 'lodash'; -import { Position, ScaleType } from '@elastic/charts'; import type { EuiSelectOption } from '@elastic/eui'; import type { Type, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import * as i18n from './translations'; -import { histogramDateTimeFormatter } from '../../../../common/components/utils'; -import type { ChartSeriesConfigs } from '../../../../common/components/charts/common'; + import type { FieldValueQueryBar } from '../query_bar'; import type { TimeframePreviewOptions } from '../../../../detections/pages/detection_engine/rules/types'; import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; @@ -61,54 +59,6 @@ export const getTimeframeOptions = (ruleType: Type): EuiSelectOption[] => { } }; -/** - * Config passed into elastic-charts settings. - * @param to - * @param from - */ -export const getHistogramConfig = ( - to: string, - from: string, - showLegend = false -): ChartSeriesConfigs => { - return { - series: { - xScaleType: ScaleType.Time, - yScaleType: ScaleType.Linear, - stackAccessors: ['g'], - }, - axis: { - xTickFormatter: histogramDateTimeFormatter([to, from]), - yTickFormatter: (value: string | number): string => value.toLocaleString(), - tickSize: 8, - }, - yAxisTitle: i18n.QUERY_GRAPH_COUNT, - settings: { - legendPosition: Position.Right, - showLegend, - showLegendExtra: showLegend, - theme: { - scales: { - barsPadding: 0.08, - }, - chartMargins: { - left: 0, - right: 0, - top: 0, - bottom: 0, - }, - chartPaddings: { - left: 0, - right: 0, - top: 0, - bottom: 0, - }, - }, - }, - customHeight: 200, - }; -}; - const isNewTermsPreviewDisabled = (newTermsFields: string[]): boolean => { return newTermsFields.length === 0 || newTermsFields.length > MAX_NUMBER_OF_NEW_TERMS_FIELDS; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx index 9d39b68626907..69eebec3452d5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx @@ -15,7 +15,6 @@ import { TestProviders } from '../../../../common/mock'; import type { RulePreviewProps } from '.'; import { RulePreview, REASONABLE_INVOCATION_COUNT } from '.'; import { usePreviewRoute } from './use_preview_route'; -import { usePreviewHistogram } from './use_preview_histogram'; import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; import { getStepScheduleDefaultValue, @@ -26,7 +25,6 @@ import { usePreviewInvocationCount } from './use_preview_invocation_count'; jest.mock('../../../../common/lib/kibana'); jest.mock('./use_preview_route'); -jest.mock('./use_preview_histogram'); jest.mock('../../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', @@ -88,17 +86,6 @@ const defaultProps: RulePreviewProps = { describe('PreviewQuery', () => { beforeEach(() => { - (usePreviewHistogram as jest.Mock).mockReturnValue([ - false, - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]); - (usePreviewRoute as jest.Mock).mockReturnValue({ hasNoiseWarning: false, addNoiseWarning: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.test.tsx index 9a490bec1ce25..80e98a8f288d8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.test.tsx @@ -10,6 +10,7 @@ import moment from 'moment'; import type { DataViewBase } from '@kbn/es-query'; import { fields } from '@kbn/data-plugin/common/mocks'; +import { render } from '@testing-library/react'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { @@ -19,21 +20,17 @@ import { SUB_PLUGINS_REDUCER, TestProviders, } from '../../../../common/mock'; -import { usePreviewHistogram } from './use_preview_histogram'; +import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; +import { useVisualizationResponse } from '../../../../common/components/visualization_actions/use_visualization_response'; import { PreviewHistogram } from './preview_histogram'; -import { ALL_VALUES_ZEROS_TITLE } from '../../../../common/components/charts/translation'; import { useTimelineEvents } from '../../../../common/components/events_viewer/use_timelines_events'; import { TableId } from '@kbn/securitysolution-data-table'; import { createStore } from '../../../../common/store'; import { mockEventViewerResponse } from '../../../../common/components/events_viewer/mock'; -import type { ReactWrapper } from 'enzyme'; -import { mount } from 'enzyme'; import type { UseFieldBrowserOptionsProps } from '../../../../timelines/components/fields_browser'; import type { TransformColumnsProps } from '../../../../common/components/control_columns'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; +import { INSPECT_ACTION } from '../../../../common/components/visualization_actions/use_actions'; jest.mock('../../../../common/components/control_columns', () => ({ transformControlColumns: (props: TransformColumnsProps) => [], @@ -46,17 +43,17 @@ jest.mock('../../../../common/components/control_columns', () => ({ })); jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/containers/use_global_time'); -jest.mock('./use_preview_histogram'); jest.mock('../../../../common/utils/normalize_time_range'); jest.mock('../../../../common/components/events_viewer/use_timelines_events'); jest.mock('../../../../common/components/visualization_actions/visualization_embeddable'); +jest.mock('../../../../common/components/visualization_actions/use_visualization_response', () => ({ + useVisualizationResponse: jest.fn(), +})); + jest.mock('../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn(), })); -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -const getMockUseIsExperimentalFeatureEnabled = - (mockMapping?: Partial) => (flag: keyof typeof allowedExperimentalValues) => - mockMapping ? mockMapping?.[flag] : allowedExperimentalValues?.[flag]; +const mockVisualizationEmbeddable = VisualizationEmbeddable as unknown as jest.Mock; const mockUseFieldBrowserOptions = jest.fn(); jest.mock('../../../../timelines/components/fields_browser', () => ({ @@ -82,9 +79,6 @@ describe('PreviewHistogram', () => { const mockSetQuery = jest.fn(); beforeEach(() => { - mockUseIsExperimentalFeatureEnabled.mockImplementation( - getMockUseIsExperimentalFeatureEnabled({ alertsPreviewChartEmbeddablesEnabled: false }) - ); (useGlobalTime as jest.Mock).mockReturnValue({ from: '2020-07-07T08:20:18.966Z', isInitializing: false, @@ -116,27 +110,15 @@ describe('PreviewHistogram', () => { jest.clearAllMocks(); }); - describe('when there is no data', () => { - (usePreviewHistogram as jest.Mock).mockReturnValue([ - false, - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]); + describe('PreviewHistogram', () => { + test('should render Lens embeddable', () => { + (useVisualizationResponse as jest.Mock).mockReturnValue({ + loading: false, + requests: [], + responses: [{ hits: { total: 1 } }], + }); - test('it renders an empty histogram and table', async () => { - (useTimelineEvents as jest.Mock).mockReturnValue([ - false, - { - ...mockEventViewerResponse, - totalCount: 1, - }, - ]); - const wrapper = mount( + const { getByTestId } = render( { /> ); - expect(wrapper.findWhere((node) => node.text() === '1 alert').exists()).toBeTruthy(); - expect( - wrapper.findWhere((node) => node.text() === ALL_VALUES_ZEROS_TITLE).exists() - ).toBeTruthy(); + + expect(getByTestId('visualization-embeddable')).toBeInTheDocument(); }); - }); - describe('when there is data', () => { - test('it renders loader when isLoading is true', () => { - (usePreviewHistogram as jest.Mock).mockReturnValue([ - true, - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]); + test('should render inspect action', () => { + (useVisualizationResponse as jest.Mock).mockReturnValue({ + loading: false, + requests: [], + responses: [{ hits: { total: 1 } }], + }); - const wrapper = mount( + render( { ); - expect(wrapper.find(`[data-test-subj="preview-histogram-loading"]`).exists()).toBeTruthy(); + expect(mockVisualizationEmbeddable.mock.calls[0][0].withActions).toEqual(INSPECT_ACTION); }); - }); - describe('when advanced options passed', () => { - test('it uses timeframeStart and timeframeEnd to specify the time range of the preview', () => { - const format = 'YYYY-MM-DD HH:mm:ss'; - const start = '2015-03-12 05:17:10'; - const end = '2020-03-12 05:17:10'; - (useTimelineEvents as jest.Mock).mockReturnValue([ - false, - { - ...mockEventViewerResponse, - totalCount: 0, - }, - ]); - const usePreviewHistogramMock = usePreviewHistogram as jest.Mock; - usePreviewHistogramMock.mockReturnValue([ - true, - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]); + test('should disable filter when clicking on the chart', () => { + (useVisualizationResponse as jest.Mock).mockReturnValue({ + loading: false, + requests: [], + responses: [{ hits: { total: 1 } }], + }); - usePreviewHistogramMock.mockImplementation( - ({ startDate, endDate }: { startDate: string; endDate: string }) => { - expect(startDate).toEqual('2015-03-12T09:17:10.000Z'); - expect(endDate).toEqual('2020-03-12T09:17:10.000Z'); - return [ - true, - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]; - } + render( + + + ); - const wrapper = mount( + expect(mockVisualizationEmbeddable.mock.calls[0][0].disableOnClickFilter).toBeTruthy(); + }); + + test('should show chart legend when if it is not EQL rule', () => { + (useVisualizationResponse as jest.Mock).mockReturnValue({ + loading: false, + requests: [], + responses: [{ hits: { total: 1 } }], + }); + + render( ); - expect(wrapper.find(`[data-test-subj="preview-histogram-loading"]`).exists()).toBeTruthy(); + expect(mockVisualizationEmbeddable.mock.calls[0][0].extraOptions.showLegend).toBeTruthy(); }); }); - describe('when the alertsPreviewChartEmbeddablesEnabled experimental feature flag is enabled', () => { - let wrapper: ReactWrapper; - beforeEach(() => { - mockUseIsExperimentalFeatureEnabled.mockImplementation( - getMockUseIsExperimentalFeatureEnabled({ - alertsPreviewChartEmbeddablesEnabled: true, - }) - ); - - (usePreviewHistogram as jest.Mock).mockReturnValue([ + describe('when advanced options passed', () => { + test('it uses timeframeStart and timeframeEnd to specify the time range of the preview', () => { + const format = 'YYYY-MM-DD HH:mm:ss'; + const start = '2015-03-12 05:17:10'; + const end = '2020-03-12 05:17:10'; + (useTimelineEvents as jest.Mock).mockReturnValue([ false, { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], + ...mockEventViewerResponse, + totalCount: 0, }, ]); - wrapper = mount( + (useVisualizationResponse as jest.Mock).mockReturnValue({ + loading: false, + requests: [], + responses: [{ hits: { total: 0 } }], + }); + + render( { spaceId={'default'} ruleType={'query'} indexPattern={getMockIndexPattern()} - timeframeOptions={getLastMonthTimeframe()} + timeframeOptions={{ + timeframeStart: moment(start, format), + timeframeEnd: moment(end, format), + interval: '5m', + lookback: '1m', + }} /> ); - }); - - test('should not fetch preview data', () => { - expect((usePreviewHistogram as jest.Mock).mock.calls[0][0].skip).toEqual(true); - }); - test('should render Lens embeddable', () => { - expect(wrapper.find('[data-test-subj="visualization-embeddable"]').exists()).toBeTruthy(); + expect(mockVisualizationEmbeddable.mock.calls[0][0].timerange).toEqual({ + from: moment(start, format).toISOString(), + to: moment(end, format).toISOString(), + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.tsx index 7de2f70aa381a..487fc3a4a4e29 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_histogram.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useMemo } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiLoadingChart } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; @@ -17,16 +17,10 @@ import { TableId } from '@kbn/securitysolution-data-table'; import { StatefulEventsViewer } from '../../../../common/components/events_viewer'; import { defaultRowRenderers } from '../../../../timelines/components/timeline/body/renderers'; import * as i18n from './translations'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { getHistogramConfig, isNoisy } from './helpers'; -import type { - ChartSeriesConfigs, - ChartSeriesData, -} from '../../../../common/components/charts/common'; +import { isNoisy } from './helpers'; import { Panel } from '../../../../common/components/panel'; import { HeaderSection } from '../../../../common/components/header_section'; -import { BarChart } from '../../../../common/components/charts/barchart'; -import { usePreviewHistogram } from './use_preview_histogram'; + import { getAlertsPreviewDefaultModel } from '../../../../detections/components/alerts_table/default_config'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { DEFAULT_PREVIEW_INDEX } from '../../../../../common/constants'; @@ -38,14 +32,10 @@ import { useGlobalFullScreen } from '../../../../common/containers/use_full_scre import type { TimeframePreviewOptions } from '../../../../detections/pages/detection_engine/rules/types'; import { useLicense } from '../../../../common/hooks/use_license'; import { useKibana } from '../../../../common/lib/kibana'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { getRulePreviewLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/rule_preview'; import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; - -const LoadingChart = styled(EuiLoadingChart)` - display: block; - margin: 0 auto; -`; +import { useVisualizationResponse } from '../../../../common/components/visualization_actions/use_visualization_response'; +import { INSPECT_ACTION } from '../../../../common/components/visualization_actions/use_actions'; const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>` height: ${({ $isFullScreen }) => ($isFullScreen ? '100%' : undefined)}; @@ -78,7 +68,6 @@ const PreviewHistogramComponent = ({ timeframeOptions, }: PreviewHistogramProps) => { const { uiSettings } = useKibana().services; - const { setQuery, isInitializing } = useGlobalTime(); const startDate = useMemo( () => timeframeOptions.timeframeStart.toISOString(), [timeframeOptions] @@ -94,34 +83,29 @@ const PreviewHistogramComponent = ({ const isEqlRule = useMemo(() => ruleType === 'eql', [ruleType]); const isMlRule = useMemo(() => ruleType === 'machine_learning', [ruleType]); - const isAlertsPreviewChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled( - 'alertsPreviewChartEmbeddablesEnabled' - ); const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]); const extraVisualizationOptions = useMemo( () => ({ ruleId: previewId, spaceId, + showLegend: !isEqlRule, }), - [previewId, spaceId] + [isEqlRule, previewId, spaceId] ); - const [isLoading, { data, inspect, totalCount, refetch }] = usePreviewHistogram({ - previewId, - startDate, - endDate, - spaceId, - indexPattern, - ruleType, - skip: isAlertsPreviewChartEmbeddablesEnabled, - }); const license = useLicense(); const { browserFields, runtimeMappings } = useSourcererDataView(SourcererScopeName.detections); const { globalFullScreen } = useGlobalFullScreen(); const previousPreviewId = usePrevious(previewId); const previewQueryId = `${ID}-${previewId}`; + const previewEmbeddableId = `${previewQueryId}-embeddable`; + const { responses: visualizationResponse } = useVisualizationResponse({ + visualizationId: previewEmbeddableId, + }); + + const totalCount = visualizationResponse?.[0]?.hits?.total ?? 0; useEffect(() => { if (previousPreviewId !== previewId && totalCount > 0) { @@ -129,34 +113,8 @@ const PreviewHistogramComponent = ({ addNoiseWarning(); } } - }, [totalCount, addNoiseWarning, previousPreviewId, previewId, timeframeOptions]); - - useEffect((): void => { - if (!isLoading && !isInitializing) { - setQuery({ - id: previewQueryId, - inspect, - loading: isLoading, - refetch, - }); - } - }, [ - setQuery, - inspect, - isLoading, - isInitializing, - refetch, - previewId, - isAlertsPreviewChartEmbeddablesEnabled, - previewQueryId, - ]); + }, [addNoiseWarning, previewId, previousPreviewId, timeframeOptions, totalCount]); - const barConfig = useMemo( - (): ChartSeriesConfigs => getHistogramConfig(endDate, startDate, !isEqlRule), - [endDate, startDate, isEqlRule] - ); - - const chartData = useMemo((): ChartSeriesData[] => [{ key: 'hits', value: data }], [data]); const config = getEsQueryConfig(uiSettings); const pageFilters = useMemo(() => { const filterQuery = buildEsQuery( @@ -195,32 +153,24 @@ const PreviewHistogramComponent = ({ id={previewQueryId} title={i18n.QUERY_GRAPH_HITS_TITLE} titleSize="xs" - showInspectButton={!isAlertsPreviewChartEmbeddablesEnabled} + showInspectButton={false} /> - {isLoading ? ( - - ) : isAlertsPreviewChartEmbeddablesEnabled ? ( - - ) : ( - - )} + <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_histogram.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_histogram.tsx deleted file mode 100644 index 89600fa014099..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_histogram.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useMemo } from 'react'; -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { getEsQueryConfig } from '@kbn/data-plugin/common'; -import type { DataViewBase } from '@kbn/es-query'; -import { useMatrixHistogramCombined } from '../../../../common/containers/matrix_histogram'; -import { MatrixHistogramType } from '../../../../../common/search_strategy'; -import { convertToBuildEsQuery } from '../../../../common/lib/kuery'; -import { useKibana } from '../../../../common/lib/kibana'; -import { QUERY_PREVIEW_ERROR } from './translations'; -import { DEFAULT_PREVIEW_INDEX } from '../../../../../common/constants'; - -interface PreviewHistogramParams { - previewId: string | undefined; - endDate: string; - startDate: string; - spaceId: string; - ruleType: Type; - indexPattern: DataViewBase | undefined; - skip?: boolean; -} - -export const usePreviewHistogram = ({ - previewId, - startDate, - endDate, - spaceId, - ruleType, - indexPattern, - skip, -}: PreviewHistogramParams) => { - const { uiSettings } = useKibana().services; - - const [filterQuery, error] = convertToBuildEsQuery({ - config: getEsQueryConfig(uiSettings), - indexPattern, - queries: [{ query: `kibana.alert.rule.uuid:${previewId}`, language: 'kuery' }], - filters: [], - }); - - const stackByField = useMemo(() => { - return ruleType === 'machine_learning' ? 'host.name' : 'event.category'; - }, [ruleType]); - - const matrixHistogramRequest = useMemo(() => { - return { - endDate, - errorMessage: QUERY_PREVIEW_ERROR, - filterQuery, - histogramType: MatrixHistogramType.preview, - indexNames: [`${DEFAULT_PREVIEW_INDEX}-${spaceId}`], - stackByField, - startDate, - includeMissingData: false, - skip: skip || error != null, - }; - }, [endDate, filterQuery, spaceId, stackByField, startDate, skip, error]); - - return useMatrixHistogramCombined(matrixHistogramRequest); -}; diff --git a/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.test.tsx b/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.test.tsx index 76699742e6b8a..d86b9b37c568f 100644 --- a/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.test.tsx @@ -10,7 +10,7 @@ import { TestProviders } from '../../../common/mock'; import { useAlertHistogramCount } from './use_alert_histogram_count'; jest.mock('../../../common/components/visualization_actions/use_visualization_response', () => ({ - useVisualizationResponse: jest.fn().mockReturnValue([{ hits: { total: 100 } }]), + useVisualizationResponse: jest.fn().mockReturnValue({ responses: [{ hits: { total: 100 } }] }), })); describe('useAlertHistogramCount', () => { diff --git a/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.ts b/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.ts index b16ff08c6e919..39365401a68df 100644 --- a/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.ts +++ b/x-pack/plugins/security_solution/public/detections/hooks/alerts_visualization/use_alert_histogram_count.ts @@ -24,7 +24,7 @@ export const useAlertHistogramCount = ({ isChartEmbeddablesEnabled: boolean; }): string => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const visualizationResponse = useVisualizationResponse({ visualizationId }); + const { responses: visualizationResponse } = useVisualizationResponse({ visualizationId }); const totalAlerts = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.test.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.test.ts index 937dae7024ba4..44ad581103393 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.test.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.test.ts @@ -23,9 +23,9 @@ describe('useAlertsByStatusVisualizationData', () => { (useVisualizationResponse as jest.Mock).mockImplementation( ({ visualizationId }: { visualizationId: string }) => { const mockCount = { - [openAlertsVisualizationId]: [{ hits: { total: 10 } }], - [acknowledgedAlertsVisualizationId]: [{ hits: { total: 20 } }], - [closedAlertsVisualizationId]: [{ hits: { total: 30 } }], + [openAlertsVisualizationId]: { responses: [{ hits: { total: 10 } }] }, + [acknowledgedAlertsVisualizationId]: { responses: [{ hits: { total: 20 } }] }, + [closedAlertsVisualizationId]: { responses: [{ hits: { total: 30 } }] }, }; return mockCount[visualizationId]; } diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts index 31ed355c1b475..218d69b4183a7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts @@ -13,15 +13,15 @@ export const acknowledgedAlertsVisualizationId = `${DETECTION_RESPONSE_ALERTS_BY export const closedAlertsVisualizationId = `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-closed`; export const useAlertsByStatusVisualizationData = () => { - const openAlertsResponse = useVisualizationResponse({ + const { responses: openAlertsResponse } = useVisualizationResponse({ visualizationId: openAlertsVisualizationId, }); - const acknowledgedAlertsResponse = useVisualizationResponse({ + const { responses: acknowledgedAlertsResponse } = useVisualizationResponse({ visualizationId: acknowledgedAlertsVisualizationId, }); - const closedAlertsResponse = useVisualizationResponse({ + const { responses: closedAlertsResponse } = useVisualizationResponse({ visualizationId: closedAlertsVisualizationId, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx index 54592e6a494cf..b76565989fb74 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx @@ -27,13 +27,14 @@ interface Props { onComplete?: () => void; isModalOpen: boolean; savedObjectIds: string[]; + savedSearchIds?: string[]; title: string | null; } /** * Renders a button that when clicked, displays the `Delete Timeline` modal */ export const DeleteTimelineModalOverlay = React.memo( - ({ deleteTimelines, isModalOpen, savedObjectIds, title, onComplete }) => { + ({ deleteTimelines, isModalOpen, savedObjectIds, title, onComplete, savedSearchIds }) => { const { addSuccess } = useAppToasts(); const { tabName: timelineType } = useParams<{ tabName: TimelineType }>(); @@ -43,9 +44,16 @@ export const DeleteTimelineModalOverlay = React.memo( } }, [onComplete]); const onDelete = useCallback(() => { - if (savedObjectIds.length > 0) { + if (savedObjectIds.length > 0 && savedSearchIds != null && savedSearchIds.length > 0) { + deleteTimelines(savedObjectIds, savedSearchIds); + addSuccess({ + title: + timelineType === TimelineType.template + ? i18n.SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES(savedObjectIds.length) + : i18n.SUCCESSFULLY_DELETED_TIMELINES(savedObjectIds.length), + }); + } else if (savedObjectIds.length > 0) { deleteTimelines(savedObjectIds); - addSuccess({ title: timelineType === TimelineType.template @@ -53,10 +61,11 @@ export const DeleteTimelineModalOverlay = React.memo( : i18n.SUCCESSFULLY_DELETED_TIMELINES(savedObjectIds.length), }); } + if (onComplete != null) { onComplete(); } - }, [deleteTimelines, savedObjectIds, onComplete, addSuccess, timelineType]); + }, [deleteTimelines, savedObjectIds, onComplete, addSuccess, timelineType, savedSearchIds]); return ( <> {isModalOpen && } diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx index 7504e38db6ddb..67d0c5a9e4599 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx @@ -15,14 +15,7 @@ import * as i18n from './translations'; import type { DeleteTimelines, OpenTimelineResult } from './types'; import { EditTimelineActions } from './export_timeline'; import { useEditTimelineActions } from './edit_timeline_actions'; - -const getExportedIds = (selectedTimelines: OpenTimelineResult[]) => { - const array = Array.isArray(selectedTimelines) ? selectedTimelines : [selectedTimelines]; - return array.reduce( - (acc, item) => (item.savedObjectId != null ? [...acc, item.savedObjectId] : [...acc]), - [] as string[] - ); -}; +import { getSelectedTimelineIdsAndSearchIds, getRequestIds } from '.'; export const useEditTimelineBatchActions = ({ deleteTimelines, @@ -56,7 +49,13 @@ export const useEditTimelineBatchActions = ({ [disableExportTimelineDownloader, onCloseDeleteTimelineModal, tableRef] ); - const selectedIds = useMemo(() => getExportedIds(selectedItems ?? []), [selectedItems]); + const { timelineIds, searchIds } = useMemo(() => { + if (selectedItems != null) { + return getRequestIds(getSelectedTimelineIdsAndSearchIds(selectedItems)); + } else { + return { timelineIds: [], searchIds: undefined }; + } + }, [selectedItems]); const handleEnableExportTimelineDownloader = useCallback( () => enableExportTimelineDownloader(), @@ -102,7 +101,8 @@ export const useEditTimelineBatchActions = ({ <> void; @@ -27,6 +28,7 @@ export const EditTimelineActionsComponent: React.FC<{ }> = ({ deleteTimelines, ids, + savedSearchIds, isEnableDownloader, isDeleteTimelineModalOpen, onComplete, @@ -46,6 +48,7 @@ export const EditTimelineActionsComponent: React.FC<{ isModalOpen={isDeleteTimelineModalOpen} onComplete={onComplete} savedObjectIds={ids} + savedSearchIds={savedSearchIds} title={title} /> )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index dc2cca5104497..a7751cfb02d2e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -74,14 +74,51 @@ export type OpenTimelineOwnProps = OwnProps & >; /** Returns a collection of selected timeline ids */ -export const getSelectedTimelineIds = (selectedItems: OpenTimelineResult[]): string[] => - selectedItems.reduce( - (validSelections, timelineResult) => - timelineResult.savedObjectId != null - ? [...validSelections, timelineResult.savedObjectId] - : validSelections, - [] +export const getSelectedTimelineIdsAndSearchIds = ( + selectedItems: OpenTimelineResult[] +): Array<{ timelineId: string; searchId?: string | null }> => { + return selectedItems.reduce>( + (validSelections, timelineResult) => { + if (timelineResult.savedObjectId != null && timelineResult.savedSearchId != null) { + return [ + ...validSelections, + { timelineId: timelineResult.savedObjectId, searchId: timelineResult.savedSearchId }, + ]; + } else if (timelineResult.savedObjectId != null) { + return [...validSelections, { timelineId: timelineResult.savedObjectId }]; + } else { + return validSelections; + } + }, + [] as Array<{ timelineId: string; searchId?: string | null }> + ); +}; + +interface DeleteTimelinesValues { + timelineIds: string[]; + searchIds: string[]; +} + +export const getRequestIds = ( + timelineIdsWithSearch: Array<{ timelineId: string; searchId?: string | null }> +) => { + return timelineIdsWithSearch.reduce( + (acc, { timelineId, searchId }) => { + let requestValues = acc; + if (searchId != null) { + requestValues = { ...requestValues, searchIds: [...requestValues.searchIds, searchId] }; + } + if (timelineId != null) { + requestValues = { + ...requestValues, + timelineIds: [...requestValues.timelineIds, timelineId], + }; + } + return requestValues; + }, + { timelineIds: [], searchIds: [] } ); +}; /** Manages the state (e.g table selection) of the (pure) `OpenTimeline` component */ // eslint-disable-next-line react/display-name @@ -208,7 +245,7 @@ export const StatefulOpenTimelineComponent = React.memo( // }; const deleteTimelines: DeleteTimelines = useCallback( - async (timelineIds: string[]) => { + async (timelineIds: string[], searchIds?: string[]) => { startTransaction({ name: timelineIds.length > 1 ? TIMELINE_ACTIONS.BULK_DELETE : TIMELINE_ACTIONS.DELETE, }); @@ -225,16 +262,16 @@ export const StatefulOpenTimelineComponent = React.memo( ); } - await deleteTimelinesByIds(timelineIds); + await deleteTimelinesByIds(timelineIds, searchIds); refetch(); }, [startTransaction, timelineSavedObjectId, refetch, dispatch, dataViewId, selectedPatterns] ); const onDeleteOneTimeline: OnDeleteOneTimeline = useCallback( - async (timelineIds: string[]) => { + async (timelineIds: string[], searchIds?: string[]) => { // The type for `deleteTimelines` is incorrect, it returns a Promise - await deleteTimelines(timelineIds); + await deleteTimelines(timelineIds, searchIds); }, [deleteTimelines] ); @@ -242,7 +279,9 @@ export const StatefulOpenTimelineComponent = React.memo( /** Invoked when the user clicks the action to delete the selected timelines */ const onDeleteSelected: OnDeleteSelected = useCallback(async () => { // The type for `deleteTimelines` is incorrect, it returns a Promise - await deleteTimelines(getSelectedTimelineIds(selectedItems)); + const timelineIdsWithSearch = getSelectedTimelineIdsAndSearchIds(selectedItems); + const { timelineIds, searchIds } = getRequestIds(timelineIdsWithSearch); + await deleteTimelines(timelineIds, searchIds); // NOTE: we clear the selection state below, but if the server fails to // delete a timeline, it will remain selected in the table: diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index de993c8aa4ff9..d1392a65192f8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -129,6 +129,12 @@ export const OpenTimeline = React.memo( [actionItem] ); + const actionItemSavedSearchId = useMemo(() => { + return actionItem != null && actionItem.savedSearchId != null + ? [actionItem.savedSearchId] + : undefined; + }, [actionItem]); + const onRefreshBtnClick = useCallback(() => { if (refetch != null) { refetch(); @@ -197,6 +203,7 @@ export const OpenTimeline = React.memo( > | null; queryType?: { hasEql: boolean; hasQuery: boolean }; savedObjectId?: string | null; + savedSearchId?: string | null; status?: TimelineStatus | null; title?: string | null; templateTimelineId?: string | null; @@ -77,7 +77,7 @@ export interface EuiSearchBarQuery { } /** Performs IO to delete the specified timelines */ -export type DeleteTimelines = (timelineIds: string[], variables?: AllTimelinesVariables) => void; +export type DeleteTimelines = (timelineIds: string[], searchIds?: string[]) => void; /** Invoked when the user clicks the action create rule from timeline */ export type OnCreateRuleFromTimeline = (savedObjectId: string) => void; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx index 72e85c77b0dbf..5e88cf8b63cfe 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx @@ -88,6 +88,7 @@ export const getAllTimeline = memoizeOne( ) : null, savedObjectId: timeline.savedObjectId, + savedSearchId: timeline.savedSearchId, status: timeline.status, title: timeline.title, updated: timeline.updated, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index f39143bbfa767..4b1c106230fdd 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -480,13 +480,20 @@ export const persistFavorite = async ({ return decodeResponseFavoriteTimeline(response); }; -export const deleteTimelinesByIds = async (savedObjectIds: string[]) => { +export const deleteTimelinesByIds = async (savedObjectIds: string[], searchIds?: string[]) => { let requestBody; try { - requestBody = JSON.stringify({ - savedObjectIds, - }); + if (searchIds) { + requestBody = JSON.stringify({ + savedObjectIds, + searchIds, + }); + } else { + requestBody = JSON.stringify({ + savedObjectIds, + }); + } } catch (err) { return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`)); } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts index c9a515f5566c2..602d29ae061ab 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts @@ -42,9 +42,9 @@ export const deleteTimelinesRoute = ( try { const frameworkRequest = await buildFrameworkRequest(context, security, request); - const { savedObjectIds } = request.body; + const { savedObjectIds, searchIds } = request.body; - await deleteTimeline(frameworkRequest, savedObjectIds); + await deleteTimeline(frameworkRequest, savedObjectIds, searchIds); return response.ok({ body: { data: { deleteTimeline: true } } }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/saved_search/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/saved_search/index.ts new file mode 100644 index 0000000000000..de90a09248eba --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/saved_search/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FrameworkRequest } from '../../../framework'; + +export const deleteSearchByTimelineId = async ( + request: FrameworkRequest, + savedSearchIds?: string[] +) => { + if (savedSearchIds !== undefined) { + const savedObjectsClient = (await request.context.core).savedObjects.client; + const objects = savedSearchIds.map((id) => ({ id, type: 'search' })); + + await savedObjectsClient.bulkDelete(objects); + } else { + return Promise.resolve(); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index 9cdc9189b16fa..037639464a3e8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -38,6 +38,7 @@ import type { SavedObjectTimelineWithoutExternalRefs } from '../../../../../comm import type { FrameworkRequest } from '../../../framework'; import * as note from '../notes/saved_object'; import * as pinnedEvent from '../pinned_events'; +import { deleteSearchByTimelineId } from '../saved_search'; import { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; import { pickSavedTimeline } from './pick_saved_timeline'; import { timelineSavedObjectType } from '../../saved_object_mappings'; @@ -572,18 +573,23 @@ export const resetTimeline = async ( return response; }; -export const deleteTimeline = async (request: FrameworkRequest, timelineIds: string[]) => { +export const deleteTimeline = async ( + request: FrameworkRequest, + timelineIds: string[], + searchIds?: string[] +) => { const savedObjectsClient = (await request.context.core).savedObjects.client; - await Promise.all( - timelineIds.map((timelineId) => + await Promise.all([ + ...timelineIds.map((timelineId) => Promise.all([ savedObjectsClient.delete(timelineSavedObjectType, timelineId), note.deleteNoteByTimelineId(request, timelineId), pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), ]) - ) - ); + ), + deleteSearchByTimelineId(request, searchIds), + ]); }; export const copyTimeline = async ( diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts index 89e6df3c68cd2..a73480c051ee4 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts @@ -51,7 +51,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s servers, services, junit: { - reportName: 'X-Pack Detection Engine API Integration Tests', + reportName: 'X-Pack Security Solution API Integration Tests', }, esTestCluster: { ...xPackApiIntegrationTestsConfig.get('esTestCluster'), diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index b3b75eed86ca3..4dc18852ec0e9 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -13,36 +13,43 @@ "run-tests:ea:default": "node ./scripts/index.js runner entity_analytics default_license", "initialize-server:lists:default": "node ./scripts/index.js server lists_and_exception_lists default_license", "run-tests:lists:default": "node ./scripts/index.js runner lists_and_exception_lists default_license", + "exception_workflows:server:serverless": "npm run initialize-server:dr:default exceptions/workflows serverless", "exception_workflows:runner:serverless": "npm run run-tests:dr:default exceptions/workflows serverless serverlessEnv", "exception_workflows:qa:serverless": "npm run run-tests:dr:default exceptions/workflows serverless qaEnv", "exception_workflows:server:ess": "npm run initialize-server:dr:default exceptions/workflows ess", "exception_workflows:runner:ess": "npm run run-tests:dr:default exceptions/workflows ess essEnv", + "exception_operators_date_numeric_types:server:serverless": "npm run initialize-server:dr:default exceptions/operators_data_types/date_numeric_types serverless", "exception_operators_date_numeric_types:runner:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/date_numeric_types serverless serverlessEnv", "exception_operators_date_numeric_types:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/date_numeric_types serverless qaEnv", "exception_operators_date_numeric_types:server:ess": "npm run initialize-server:dr:default exceptions/operators_data_types/date_numeric_types ess", "exception_operators_date_numeric_types:runner:ess": "npm run run-tests:dr:default exceptions/operators_data_types/date_numeric_types ess essEnv", + "exception_operators_keyword:server:serverless": "npm run initialize-server:dr:default exceptions/operators_data_types/keyword serverless", "exception_operators_keyword:runner:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/keyword serverless serverlessEnv", "exception_operators_keyword:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/keyword serverless qaEnv", "exception_operators_keyword:server:ess": "npm run initialize-server:dr:default exceptions/operators_data_types/keyword ess", "exception_operators_keyword:runner:ess": "npm run run-tests:dr:default exceptions/operators_data_types/keyword ess essEnv", + "exception_operators_ips:server:serverless": "npm run initialize-server:dr:default exceptions/operators_data_types/ips serverless", "exception_operators_ips:runner:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/ips serverless serverlessEnv", "exception_operators_ips:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/ips serverless qaEnv", "exception_operators_ips:server:ess": "npm run initialize-server:dr:default exceptions/operators_data_types/ips ess", "exception_operators_ips:runner:ess": "npm run run-tests:dr:default exceptions/operators_data_types/ips ess essEnv", + "exception_operators_long:server:serverless": "npm run initialize-server:dr:default exceptions/operators_data_types/long serverless", "exception_operators_long:runner:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/long serverless serverlessEnv", "exception_operators_long:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/long serverless qaEnv", "exception_operators_long:server:ess": "npm run initialize-server:dr:default exceptions/operators_data_types/long ess", "exception_operators_long:runner:ess": "npm run run-tests:dr:default exceptions/operators_data_types/long ess essEnv", + "exception_operators_text:server:serverless": "npm run initialize-server:dr:default exceptions/operators_data_types/text serverless", "exception_operators_text:runner:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/text serverless serverlessEnv", "exception_operators_text:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/text serverless qaEnv", "exception_operators_text:server:ess": "npm run initialize-server:dr:default exceptions/operators_data_types/text ess", "exception_operators_text:runner:ess": "npm run run-tests:dr:default exceptions/operators_data_types/text ess essEnv", + "exception_operators_ips_text_array:server:serverless": "npm run initialize-server:dr:default exceptions/operators_data_types/ips_text_array serverless", "exception_operators_ips_text_array:runner:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/ips_text_array serverless serverlessEnv", "exception_operators_ips_text_array:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/ips_text_array serverless qaEnv", @@ -54,31 +61,37 @@ "actions:qa:serverless": "npm run run-tests:dr:default actions serverless qaEnv", "actions:server:ess": "npm run initialize-server:dr:default actions ess", "actions:runner:ess": "npm run run-tests:dr:default actions ess essEnv", + "alerts:server:serverless": "npm run initialize-server:dr:default alerts serverless", "alerts:runner:serverless": "npm run run-tests:dr:default alerts serverless serverlessEnv", "alerts:qa:serverless": "npm run run-tests:dr:default alerts serverless qaEnv", "alerts:server:ess": "npm run initialize-server:dr:default alerts ess", "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv", + "entity_analytics:server:serverless": "npm run initialize-server:ea:default risk_engine serverless", "entity_analytics:runner:serverless": "npm run run-tests:ea:default risk_engine serverless serverlessEnv", "entity_analytics:qa:serverless": "npm run run-tests:ea:default risk_engine serverless qaEnv", "entity_analytics:server:ess": "npm run initialize-server:ea:default risk_engine ess", "entity_analytics:runner:ess": "npm run run-tests:ea:default risk_engine ess essEnv", + "prebuilt_rules_management:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/management serverless", "prebuilt_rules_management:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/management serverless serverlessEnv", "prebuilt_rules_management:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/management serverless qaEnv", "prebuilt_rules_management:server:ess": "npm run initialize-server:dr:default prebuilt_rules/management ess", "prebuilt_rules_management:runner:ess": "npm run run-tests:dr:default prebuilt_rules/management ess essEnv", + "prebuilt_rules_bundled_prebuilt_rules_package:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/bundled_prebuilt_rules_package serverless", "prebuilt_rules_bundled_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/bundled_prebuilt_rules_package serverless serverlessEnv", "prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/bundled_prebuilt_rules_package serverless qaEnv", "prebuilt_rules_bundled_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/bundled_prebuilt_rules_package ess", "prebuilt_rules_bundled_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/bundled_prebuilt_rules_package ess essEnv", + "prebuilt_rules_large_prebuilt_rules_package:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/large_prebuilt_rules_package serverless", "prebuilt_rules_large_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/large_prebuilt_rules_package serverless serverlessEnv", "prebuilt_rules_large_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/large_prebuilt_rules_package serverless qaEnv", "prebuilt_rules_large_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/large_prebuilt_rules_package ess", "prebuilt_rules_large_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/large_prebuilt_rules_package ess essEnv", + "prebuilt_rules_update_prebuilt_rules_package:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/update_prebuilt_rules_package serverless", "prebuilt_rules_update_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless serverlessEnv", "prebuilt_rules_update_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless qaEnv", @@ -151,17 +164,11 @@ "rule_read:server:ess": "npm run initialize-server:dr:default rule_read ess", "rule_read:runner:ess": "npm run run-tests:dr:default rule_read ess essEnv", - "detection_engine_basicessentionals:server:serverless": "npm run initialize-server:dr:basicEssentials detection_engine serverless", - "detection_engine_basicessentionals:runner:serverless": "npm run run-tests:dr:basicEssentials detection_engine serverless serverlessEnv", - "detection_engine_basicessentionals:qa:serverless": "npm run run-tests:dr:basicEssentials detection_engine serverless qaEnv", - "detection_engine_basicessentionals:server:ess": "npm run initialize-server:dr:basicEssentials detection_engine ess", - "detection_engine_basicessentionals:runner:ess": "npm run run-tests:dr:basicEssentials detection_engine ess essEnv", - - "rule_management_basicessentionals:server:serverless": "npm run initialize-server:dr:basicEssentials rule_management serverless", - "rule_management_basicessentionals:runner:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless serverlessEnv", - "rule_management_basicessentionals:qa:serverless": "npm run run-tests:dr:basicEssentials rule_management serverless qaEnv", - "rule_management_basicessentionals:server:ess": "npm run initialize-server:dr:basicEssentials rule_management ess", - "rule_management_basicessentionals:runner:ess": "npm run run-tests:dr:basicEssentials rule_management ess essEnv", + "detection_engine:essentials:server:serverless": "npm run initialize-server:dr:essentials detection_engine serverless", + "detection_engine:essentials:runner:serverless": "npm run run-tests:dr:essentials detection_engine serverless serverlessEnv", + "detection_engine:essentials:qa:serverless": "npm run run-tests:dr:essentials detection_engine serverless qaEnv", + "detection_engine:basic:server:ess": "npm run initialize-server:dr:basic detection:engine ess", + "detection_engine:basic:runner:ess": "npm run run-tests:dr:basic detection_engine ess essEnv", "exception_lists_items:server:serverless": "npm run initialize-server:lists:default exception_lists_items serverless", "exception_lists_items:runner:serverless": "npm run run-tests:lists:default exception_lists_items serverless serverlessEnv", diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/alerts/open_close_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/alerts/open_close_alerts.ts index 4af66d1da4a93..ae9533d8d3ce2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/alerts/open_close_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/alerts/open_close_alerts.ts @@ -34,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/ess.config.ts index b980aef5f783a..7176cc1421ec6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS - Basic Integration Tests', + reportName: 'Detection Engine - Integration Tests - ESS Env - Basic License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/serverless.config.ts index 8a4199ccfb44d..c920ca94da57b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/configs/serverless.config.ts @@ -10,6 +10,6 @@ import { createTestConfig } from '../../../../../config/serverless/config.base.e export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless - Essentials Integration Tests', + reportName: 'Detection Engine - Integration Tests - Serverless Env - Essentials License ', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_ml_rules_privileges.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_ml_rules_privileges.ts index 0b4bcea421c70..a9537d0426c01 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_ml_rules_privileges.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_ml_rules_privileges.ts @@ -25,7 +25,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); const isServerless = config.get('serverless'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_rules.ts index 6a3fff87611da..281fa37bb2d5d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine/rules/create_rules.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); const isServerless = config.get('serverless'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts index e508918b0538d..883267119e173 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS - Actions API Integration Tests', + reportName: 'Detection Engine - Rule Actions Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts index ea876833ea839..22a7c56a7c434 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless - Actions API Integration Tests', + reportName: + 'Detection Engine - Rule Actions Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts index 2a8468856732f..94a2ae7368534 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine API Integration Tests - ESS - Alerts', + reportName: 'Detection Engine - Alerts Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts index 9c61a18b25abc..b4d510ae05174 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts @@ -10,6 +10,6 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine API Integration Tests - Serverless - Alerts', + reportName: 'Detection Engine - Alerts Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts index 20b52f7be5059..44718cc823529 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts @@ -44,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/set_alert_tags.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/set_alert_tags.ts index 15920ab3993b0..775a6da06d9d8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/set_alert_tags.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/set_alert_tags.ts @@ -34,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts index 9d03e3503a480..74bbb3e6fe9a8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts @@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine ESS - Exception Operators Data Types API - Date_numeric_types Integration Tests', + 'Detection Engine - Exception Operators Date & Numeric Types Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts index df64ace832d80..3e030f426d993 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts @@ -11,6 +11,6 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless - Exception Operators Data Types API - Date_numeric_types Integration Tests', + 'Detection Engine - Exception Operators Date & Numeric Types Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/ess.config.ts index 114f4e628b7ac..966c0d58c57f3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/ess.config.ts @@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine ESS - Exception Operators Data Types API- IPS Integration Tests', + 'Detection Engine - Exception Operators IP Types Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/serverless.config.ts index 80ec0198524b3..4ce0ff0d41059 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips/configs/serverless.config.ts @@ -11,6 +11,6 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless - Exception Operators Data Types API- IPS API Integration Tests', + 'Detection Engine - Exception Operators IP Types Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/ess.config.ts index 8b19e9b0d8c6d..f58f354407f5f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/ess.config.ts @@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine ESS - Exception Operators Data Types API- Keyword Integration Tests', + 'Detection Engine - Exception Operators Keyword Types Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/serverless.config.ts index 3e209f3c04e85..f5093ce32ed63 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/keyword/configs/serverless.config.ts @@ -11,6 +11,6 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless - Exception Operators Data Types API - Keyword Integration Tests', + 'Detection Engine - Exception Operators Keyword Types Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/ess.config.ts index 5438e929d9b22..e18b5debbcd51 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/ess.config.ts @@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine ESS - Exception Operators Data Types API - Long Integration Tests', + 'Detection Engine - Exception Operators Long Types Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/serverless.config.ts index 646062b09db91..735bb46a9a6b0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/long/configs/serverless.config.ts @@ -11,6 +11,6 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless - Exception Operators Data Types API - Long Integration Tests', + 'Detection Engine - Exception Operators Long Types Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/ess.config.ts index 01bb5ebdd21eb..c9774e4f590a5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/ess.config.ts @@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine ESS - - Exception Operators Data Types API - Text Integration Tests', + 'Detection Engine - Exception Operators Text Types Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/serverless.config.ts index 3c67f4c7ad06c..c7a7beb13099d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/text/configs/serverless.config.ts @@ -11,6 +11,6 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless - Exception Operators Data Types API - Text Integration Tests', + 'Detection Engine - Exception Operators Text Types Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts index 4a9004910d3b5..04bc56b399b77 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts @@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS - Exception - Workflows API Integration Tests', + reportName: + 'Detection Engine - Exception Workflows Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts index 32e5ca5e8d061..64763286226ae 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../../config/serverless/config.bas export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless - Exception - Workflows API Integration Tests', + reportName: + 'Detection Engine - Exception Workflows Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts index 1f1e4d91d4a09..85bfc549a3a34 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/create_rule_exceptions.ts @@ -27,6 +27,7 @@ import { getRuleSOById, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, + checkInvestigationFieldSoValue, } from '../../../utils'; import { deleteAllExceptions, @@ -290,10 +291,14 @@ export default ({ getService }: FtrProviderContext) => { hits: [{ _source: ruleSO }], }, } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue(ruleSO, { + field_names: ['client.address', 'agent.name'], + }); + expect( ruleSO?.alert.params.exceptionsList.some((list) => list.type === 'rule_default') ).to.eql(true); - expect(ruleSO?.alert.params.investigationFields).to.eql(['client.address', 'agent.name']); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts index a0b7145dbc952..e67d7d9f5fc1e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts @@ -73,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); const isServerless = config.get('serverless'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts index 87c0b1b3c43d8..68bfe2e9314b0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts @@ -22,7 +22,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS / Bundled Prebuilt Rules Package API Integration Tests', + reportName: + 'Rules Management - Bundled Prebuilt Rules Integration Tests - ESS Env - Trial License', }, kbnTestServer: { ...functionalConfig.get('kbnTestServer'), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts index db6e8e11082e0..492c3c13870c6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless / Bundled Prebuilte Rules Package API Integration Tests', + 'Rules Management - Bundled Prebuilt Rules Integration Tests - Serverless Env - Complete License', }, kbnTestServerArgs: [ /* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR. diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts index 9b056de5b8252..2d96db1382f35 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts @@ -23,7 +23,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine ESS / Large Prebuilt Rules Package Installation API Integration Tests', + 'Rules Management - Large Prebuilt Rules Package Integration Tests - ESS Env - Trial License', }, kbnTestServer: { ...functionalConfig.get('kbnTestServer'), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts index 29b6ec1c4cc6c..89bd5f723a9fe 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless / Large Prebuilt Rules Package Installation API Integration Tests', + 'Rules Management - Large Prebuilt Rules Package Installation Integration Tests - Serverless Env - Complete License', }, kbnTestServerArgs: [ /* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR. diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts index 7fec27a5d9276..eebdce7697d3c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts @@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS / Prebuilt Rules Management API Integration Tests', + reportName: + 'Rules Management - Prebuilt Rules Management Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts index 89916d26e7a73..91836b3997774 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../../config/serverless/config.bas export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless / Prebuilt Rules Management API Integration Tests', + reportName: + 'Rules Management - Prebuilt Rules Management Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts index 0def0b0f17a5f..23b22b80b8573 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts @@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS / Update Prebuilt Rules Package - API Integration Tests', + reportName: + 'Rules Management - Update Prebuilt Rules Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts index 5f6716342c924..c05eef46de73b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts @@ -11,6 +11,6 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Detection Engine Serverless / Update Prebuilt Rules Package - API Integration Tests', + 'Rules Management - Update Prebuilt Rules Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts index 92303ddd2445f..1519833895210 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts @@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Bulk Actions API Integration Tests - ESS - Rule bulk actions logic', + reportName: + 'Rules Management - Rule Bulk Actions Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts index 9e4f790d3ded7..14da93e9eb6c2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Bulk Actions API Integration Tests - Serverless - Rule bulk actions logic', + reportName: + 'Rules Management - Rule Bulk Actions Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts index f7e48ac30a6eb..2c3a0570ea51a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts @@ -43,7 +43,7 @@ export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const log = getService('log'); const esArchiver = getService('esArchiver'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts index e85103b67cd22..98b711a5837e7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts @@ -34,6 +34,7 @@ import { createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, + checkInvestigationFieldSoValue, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -470,6 +471,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('legacy investigation fields', () => { let ruleWithLegacyInvestigationField: Rule; let ruleWithLegacyInvestigationFieldEmptyArray: Rule; + let ruleWithIntendedInvestigationField: RuleResponse; beforeEach(async () => { await deleteAllAlerts(supertest, log, es); @@ -483,7 +485,7 @@ export default ({ getService }: FtrProviderContext): void => { supertest, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() ); - await createRule(supertest, log, { + ruleWithIntendedInvestigationField = await createRule(supertest, log, { ...getSimpleRule('rule-with-investigation-field'), name: 'Test investigation fields object', investigation_fields: { field_names: ['host.name'] }, @@ -528,12 +530,14 @@ export default ({ getService }: FtrProviderContext): void => { * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should not include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, JSON.parse(rule1).id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['client.address', 'agent.name'] }, + es, + JSON.parse(rule1).id + ); + + expect(isInvestigationFieldMigratedInSo).to.eql(false); const exportDetails = JSON.parse(exportDetailsJson); expect(exportDetails).to.eql({ @@ -618,7 +622,6 @@ export default ({ getService }: FtrProviderContext): void => { (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' ); expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); - /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is @@ -629,7 +632,12 @@ export default ({ getService }: FtrProviderContext): void => { hits: [{ _source: ruleSO }], }, } = await getRuleSOById(es, ruleWithLegacyField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue(ruleSO, { + field_names: ['client.address', 'agent.name'], + }); + + expect(isInvestigationFieldMigratedInSo).to.eql(false); expect(ruleSO?.alert?.enabled).to.eql(true); const { @@ -688,26 +696,36 @@ export default ({ getService }: FtrProviderContext): void => { * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should not include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + const isInvestigationFieldForRuleWithLegacyFieldMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { + field_names: ['client.address', 'agent.name'], + }, + es, + ruleWithLegacyField.id + ); + expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).to.eql(false); + + const isInvestigationFieldForRuleWithEmptyArraydMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { + field_names: [], + }, + es, + ruleWithEmptyArray.id + ); + expect(isInvestigationFieldForRuleWithEmptyArraydMigratedInSo).to.eql(false); + + const isInvestigationFieldForRuleWithIntendedTypeMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['host.name'] }, + es, + ruleWithIntendedType.id + ); + expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).to.eql(true); }); it('should duplicate rules with legacy investigation fields and transform field in response', async () => { @@ -751,64 +769,75 @@ export default ({ getService }: FtrProviderContext): void => { returnedRule.name === 'Test investigation fields object [Duplicate]' ); + // DUPLICATED RULES /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, duplicated * rules should NOT have migrated value on write. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyField.id); - - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + const isInvestigationFieldForRuleWithLegacyFieldMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['client.address', 'agent.name'] }, + es, + ruleWithLegacyField.id + ); + expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).to.eql(false); + + const isInvestigationFieldForRuleWithEmptyArrayMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: [] }, + es, + ruleWithEmptyArray.id + ); + expect(isInvestigationFieldForRuleWithEmptyArrayMigratedInSo).to.eql(false); + + const isInvestigationFieldForRuleWithIntendedTypeMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['host.name'] }, + es, + ruleWithIntendedType.id + ); + expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).to.eql({ + field_names: ['host.name'], + }); + // ORIGINAL RULES - rules selected to be duplicated /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, the original * rules selected to be duplicated should not be migrated. */ - const { - hits: { - hits: [{ _source: ruleSOOriginalLegacy }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); - - expect(ruleSOOriginalLegacy?.alert?.params?.investigationFields).to.eql([ - 'client.address', - 'agent.name', - ]); - - const { - hits: { - hits: [{ _source: ruleSOOriginalLegacyEmptyArray }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); - expect(ruleSOOriginalLegacyEmptyArray?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSOOriginalNoLegacy }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSOOriginalNoLegacy?.alert?.params?.investigationFields).to.eql({ + const isInvestigationFieldForOriginalRuleWithLegacyFieldMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['client.address', 'agent.name'] }, + es, + ruleWithLegacyInvestigationField.id + ); + expect(isInvestigationFieldForOriginalRuleWithLegacyFieldMigratedInSo).to.eql(false); + + const isInvestigationFieldForOriginalRuleWithEmptyArrayMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: [] }, + es, + ruleWithLegacyInvestigationFieldEmptyArray.id + ); + expect(isInvestigationFieldForOriginalRuleWithEmptyArrayMigratedInSo).to.eql(false); + + const isInvestigationFieldForOriginalRuleWithIntendedTypeMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['host.name'] }, + es, + ruleWithIntendedInvestigationField.id + ); + expect(isInvestigationFieldForOriginalRuleWithIntendedTypeMigratedInSo).to.eql({ field_names: ['host.name'], }); }); @@ -860,26 +889,32 @@ export default ({ getService }: FtrProviderContext): void => { * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should not include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + const isInvestigationFieldForRuleWithLegacyFieldMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['client.address', 'agent.name'] }, + es, + ruleWithLegacyInvestigationField.id + ); + expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).to.eql(false); + + const isInvestigationFieldForRuleWithEmptyArrayFieldMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: [] }, + es, + ruleWithLegacyInvestigationFieldEmptyArray.id + ); + expect(isInvestigationFieldForRuleWithEmptyArrayFieldMigratedInSo).to.eql(false); + + const isInvestigationFieldForRuleWithIntendedTypeMigratedInSo = + await checkInvestigationFieldSoValue( + undefined, + { field_names: ['host.name'] }, + es, + ruleWithIntendedType.id + ); + expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).to.eql(true); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts index 4fbad71828a44..2f04f8c18d6b4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS/ Rule creation API Integration Tests', + reportName: 'Detection Engine - Rule Creation Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts index 3c214b340ab74..4a8c7d24f7b36 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless/ Rule creation API Integration Tests', + reportName: + 'Detection Engine - Rule Creation Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts index 49ed77a4dc48e..aad42c2e4ea6c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts @@ -45,7 +45,7 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts index aa07404205652..b3d954773b518 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts @@ -42,9 +42,7 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); const es = getService('es'); - // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already - // deprecated, it should cease being exposed in Serverless prior to GA, in which case this - // test would be run for ESS only. + // See https://github.com/elastic/kibana/issues/130963 for discussion on deprecation describe('@ess @brokenInServerless @skipInQA create_rules_bulk', () => { describe('deprecations', () => { afterEach(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/preview_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/preview_rules.ts index bcfbf77ef23e1..95ba7de98eab7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/preview_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/preview_rules.ts @@ -24,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts index 11f644695b9dc..3c8ba6dd0ba99 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - ESS - Rule Delete logic', + reportName: 'Detection Engine - Rule Deletion Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts index ed7c4e3d11a71..89f417d00f551 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - Serverless - Rule Delete logic', + reportName: + 'Detection Engine - Rule Deletion Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts index 1966ab101ab0c..ed325c15dae40 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts @@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts index 10d768152ddc3..a4f4df7868005 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts @@ -32,13 +32,11 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already - // deprecated, it should cease being exposed in Serverless prior to GA, in which case this - // test would be run for ESS only. + // See https://github.com/elastic/kibana/issues/130963 for discussion on deprecation describe('@ess @brokenInServerless @skipInQA delete_rules_bulk', () => { describe('deprecations', () => { it('should return a warning header', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts index 85a5814fdf732..53a8ac37e5abb 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts @@ -176,7 +176,7 @@ export default ({ getService }: FtrProviderContext): void => { // Test to ensure that we have exactly 0 legacy actions by querying the Alerting client REST API directly // See: https://www.elastic.co/guide/en/kibana/current/find-rules-api.html - // Note: We specifically query for both the filter of type "siem.notifications" and the "has_reference" to keep it very specific + // Note: We specifically filter for both the type "siem.notifications" and the "has_reference" field to ensure we only retrieve legacy actions const { body: bodyAfterDelete } = await supertest .get(`${BASE_ALERTING_API_PATH}/rules/_find`) .query({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts index 9db8143c6ad3c..214217cdbfe5b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts @@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); const es = getService('es'); - describe('@ess delete_rules_legacy', () => { + describe('@ess Legacy route for deleting rules', () => { describe('deleting rules', () => { beforeEach(async () => { await createAlertsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/ess.config.ts index bbf6c6c0e3f7b..392716ccea85b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/ess.config.ts @@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine API Integration Tests - ESS - Rule Execution Logic', + reportName: + 'Detection Engine - Rule Execution Logic Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/serverless.config.ts index 1f43395efcd90..0a425d6845878 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/serverless.config.ts @@ -9,7 +9,8 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine API Integration Tests - Serverless - Rule Execution Logic', + reportName: + 'Detection Engine - Rule Execution Logic Integration Tests - Serverless Env - Complete License', }, kbnTestServerArgs: [ `--xpack.securitySolution.alertIgnoreFields=${JSON.stringify([ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts index 03af11e239c68..3b2921f90dce2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/eql.ts @@ -53,7 +53,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts index 8787a51871125..ad5f546d2fd6c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/machine_learning.ts @@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts index 9aea83afb95d0..aff5d52eeac94 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/new_terms.ts @@ -41,7 +41,7 @@ export default ({ getService }: FtrProviderContext) => { index: 'new_terms', log, }); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); const isServerless = config.get('serverless'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/query.ts index 38930bafa564e..feabae41ebea1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/query.ts @@ -88,7 +88,7 @@ export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const log = getService('log'); const esDeleteAllIndices = getService('esDeleteAllIndices'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/saved_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/saved_query.ts index e387a2f840c41..c0a197e64f292 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/saved_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/saved_query.ts @@ -37,7 +37,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts index 734583d009ca3..7d97563f4c1b5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threat_match.ts @@ -147,7 +147,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold.ts index dce4886bc1ba5..c13702f37bef5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold.ts @@ -39,7 +39,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/timestamps.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/timestamps.ts index d7c645c115082..2a51b1da2e444 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/timestamps.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/timestamps.ts @@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts index 0221afa650a09..ee0cff6c55b86 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts @@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - ESS - Rule Import and Export logic', + reportName: + 'Rules Management - Rule Import And Export Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts index 5be8cda08a16d..5d9fdbac927df 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - Serverless - Rule Import and Export logic', + reportName: + 'Rules Management - Rule Import And Export Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts index bde3148c84320..42ec2a27c5d7f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts @@ -28,7 +28,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts index 0a58efd57359f..5af0d9a8814cd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts @@ -26,9 +26,9 @@ import { removeServerGeneratedProperties, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, - getRuleSOById, updateUsername, createRuleThroughAlertingEndpoint, + checkInvestigationFieldSoValue, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -36,7 +36,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); @@ -417,21 +417,20 @@ export default ({ getService }: FtrProviderContext): void => { expect(exportedRule.investigation_fields).toEqual({ field_names: ['client.address', 'agent.name'], }); + /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); - expect(ruleSO?.alert?.params?.investigationFields).toEqual([ - 'client.address', - 'agent.name', - ]); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['client.address', 'agent.name'] }, + es, + ruleWithLegacyInvestigationField.id + ); + expect(isInvestigationFieldMigratedInSo).toEqual(false); }); it('exports a rule that has a legacy investigation field set to empty array and unsets field in response', async () => { @@ -455,12 +454,13 @@ export default ({ getService }: FtrProviderContext): void => { * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); - expect(ruleSO?.alert?.params?.investigationFields).toEqual([]); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: [] }, + es, + ruleWithLegacyInvestigationFieldEmptyArray.id + ); + expect(isInvestigationFieldMigratedInSo).toEqual(false); }); it('exports rule with investigation fields as intended object type', async () => { @@ -484,12 +484,14 @@ export default ({ getService }: FtrProviderContext): void => { * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should * NOT include a migration on SO. - */ const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, exportedRule.id); - expect(ruleSO?.alert?.params?.investigationFields).toEqual({ field_names: ['host.name'] }); + */ + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['host.name'] }, + es, + exportedRule.id + ); + expect(isInvestigationFieldMigratedInSo).toEqual(true); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts index aaeb01904e066..bd63c3150588a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts @@ -22,9 +22,9 @@ import { getLegacyActionSO, createRule, fetchRule, - getRuleSOById, getWebHookAction, getSimpleRuleAsNdjson, + checkInvestigationFieldSoValue, } from '../../utils'; import { createUserAndRole, @@ -308,18 +308,20 @@ export default ({ getService }: FtrProviderContext): void => { const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); expect(rule.investigation_fields).to.eql({ field_names: ['foo', 'bar'] }); + /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should * include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, rule.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['foo', 'bar'] }); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['foo', 'bar'] }, + es, + rule.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(true); }); it('imports rule with investigation fields as empty array', async () => { @@ -342,18 +344,20 @@ export default ({ getService }: FtrProviderContext): void => { const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); expect(rule.investigation_fields).to.eql(undefined); + /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should * include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, rule.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(undefined); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + undefined, + es, + rule.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(true); }); it('imports rule with investigation fields as intended object type', async () => { @@ -381,12 +385,13 @@ export default ({ getService }: FtrProviderContext): void => { * happening just on the response. In this case, change should * include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, rule.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['foo'] }); + const isInvestigationFieldIntendedTypeInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['foo'] }, + es, + rule.id + ); + expect(isInvestigationFieldIntendedTypeInSo).to.eql(true); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts index 94ea13264eaab..978d5f2268dee 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - ESS - Rule management logic', + reportName: 'Rules Management - Rule Management Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts index 0f86bfe4d5ebb..86c288c6dacea 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - Serverless - Rule management logic', + reportName: + 'Rules Management - Rule Management Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts index 3463518a51af4..a45636b08ea0d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts @@ -41,7 +41,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for loading archiver files similar to "getService('es')" const config = getService('config'); const isServerless = config.get('serverless'); const dataPathBuilder = new EsArchivePathBuilder(isServerless); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts index f8c742a881ded..30b7daf5d02b3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - ESS - Rule Patch logic', + reportName: 'Detection Engine - Rule Patch Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts index 7ed12808c452e..e95130ab73891 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - Serverless - Rule Patch logic', + reportName: + 'Detection Engine - Rule Patch Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts index d267e6398eca0..43abe1c3b591b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts @@ -41,7 +41,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts index 0bf1bd43ab99c..94c07f20d7d60 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts @@ -29,6 +29,7 @@ import { createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, getRuleSavedObjectWithLegacyInvestigationFields, + checkInvestigationFieldSoValue, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -36,13 +37,11 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already - // deprecated, it should cease being exposed in Serverless prior to GA, in which case this - // test would be run for ESS only. + // See https://github.com/elastic/kibana/issues/130963 for discussion on deprecation describe('@ess @brokenInServerless @skipInQA patch_rules_bulk', () => { describe('deprecations', () => { afterEach(async () => { @@ -588,18 +587,20 @@ export default ({ getService }: FtrProviderContext) => { field_names: ['client.address', 'agent.name'], }); expect(bodyToCompareLegacyField.name).to.eql('some other name'); + /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body[0].id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['client.address', 'agent.name'] }, + es, + body[0].id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); it('should patch a rule with a legacy investigation field - empty array - and transform field in response', async () => { @@ -619,18 +620,20 @@ export default ({ getService }: FtrProviderContext) => { const bodyToCompareLegacyFieldEmptyArray = removeServerGeneratedProperties(body[0]); expect(bodyToCompareLegacyFieldEmptyArray.investigation_fields).to.eql(undefined); expect(bodyToCompareLegacyFieldEmptyArray.name).to.eql('some other name 2'); + /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body[0].id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql([]); + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: [] }, + es, + body[0].id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); it('should patch a rule with an investigation field', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts index 06b530c113352..613ed6b5de9b3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts @@ -16,7 +16,6 @@ import { deleteAllRules, deleteAllAlerts, removeServerGeneratedProperties, - getRuleSOById, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, @@ -26,6 +25,7 @@ import { updateUsername, createLegacyRuleAction, getSimpleRule, + checkInvestigationFieldSoValue, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -33,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); @@ -158,15 +158,15 @@ export default ({ getService }: FtrProviderContext) => { * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { + field_names: ['client.address', 'agent.name'], }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql([ - 'client.address', - 'agent.name', - ]); + es, + body.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); it('should patch a rule with a legacy investigation field - empty array - and transform response', async () => { @@ -188,12 +188,15 @@ export default ({ getService }: FtrProviderContext) => { * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { + field_names: [], }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql([]); + es, + body.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts index 5c1925861aa39..9d0830b927c6e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - ESS - Rule Read logic', + reportName: 'Rules Management - Rule Read Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts index 81c0e71881466..853ffc6443890 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - Serverless - Rule Read logic', + reportName: + 'Rules Management - Rule Read Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.ts index 8c8804bc59c68..4a9740358e928 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.ts @@ -27,7 +27,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts index 9b380d3a0a40a..f15ea25fbdd16 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts @@ -18,7 +18,6 @@ import { createRule, createRuleThroughAlertingEndpoint, deleteAllRules, - getRuleSOById, getSimpleRule, getSimpleRuleOutput, getWebHookAction, @@ -26,6 +25,7 @@ import { removeServerGeneratedProperties, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, + checkInvestigationFieldSoValue, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -33,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); @@ -171,24 +171,36 @@ export default ({ getService }: FtrProviderContext): void => { * happening just on the response. In this case, change should * NOT include a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { + field_names: ['client.address', 'agent.name'], }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - const { - hits: { - hits: [{ _source: ruleSO3 }], + es, + ruleWithLegacyInvestigationField.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); + + const isInvestigationFieldMigratedInSoForRuleWithEmptyArray = + await checkInvestigationFieldSoValue( + undefined, + { + field_names: [], + }, + es, + ruleWithLegacyInvestigationFieldEmptyArray.id + ); + expect(isInvestigationFieldMigratedInSoForRuleWithEmptyArray).to.eql(false); + + const isInvestigationFieldSoExpectedType = await checkInvestigationFieldSoValue( + undefined, + { + field_names: ['host.name'], }, - } = await getRuleSOById(es, ruleWithExpectedTyping.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + es, + ruleWithExpectedTyping.id + ); + expect(isInvestigationFieldSoExpectedType).to.eql(true); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts index a26c3dba358c5..d7a4ba65b98da 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts @@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts index dcbaf8b10615e..6780a639cbd8c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts @@ -21,11 +21,11 @@ import { getSimpleRuleOutput, getWebHookAction, removeServerGeneratedProperties, - getRuleSOById, updateUsername, getRuleSavedObjectWithLegacyInvestigationFields, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, + checkInvestigationFieldSoValue, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -33,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); @@ -164,12 +164,15 @@ export default ({ getService }: FtrProviderContext) => { * happening just on the response. In this case, change should * just be a transform on read, not a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { + field_names: ['client.address', 'agent.name'], }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + es, + body.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); it('should be able to read a rule with a legacy investigation field - empty array', async () => { @@ -190,12 +193,15 @@ export default ({ getService }: FtrProviderContext) => { * happening just on the response. In this case, change should * just be a transform on read, not a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], + const isInvestigationFieldMigratedInSo = await checkInvestigationFieldSoValue( + undefined, + { + field_names: [], }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql([]); + es, + body.id + ); + expect(isInvestigationFieldMigratedInSo).to.eql(false); }); it('does not migrate investigation fields when intended object type', async () => { @@ -214,12 +220,13 @@ export default ({ getService }: FtrProviderContext) => { * happening just on the response. In this case, change should * just be a transform on read, not a migration on SO. */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + const isInvestigationFieldIntendedTypeInSo = await checkInvestigationFieldSoValue( + undefined, + { field_names: ['host.name'] }, + es, + body.id + ); + expect(isInvestigationFieldIntendedTypeInSo).to.eql(true); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts index 1774ff3ae28ea..fa76a9537ab3a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - ESS - Rule Update logic', + reportName: 'Detection Engine - Rule Update Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts index 017b5dec486b1..ea732668ee155 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts @@ -9,6 +9,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Rule Management API Integration Tests - Serverless - Rule Update logic', + reportName: + 'Detection Engine - Rule Update Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts index 3c6a3e7735a4a..69dc28fddf6b4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts @@ -44,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts index 49c9ffdd817fd..d3a7a124ae59c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts @@ -48,13 +48,11 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); - // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already - // deprecated, it should cease being exposed in Serverless prior to GA, in which case this - // test would be run for ESS only. + // See https://github.com/elastic/kibana/issues/130963 for discussion on deprecation describe('@ess @brokenInServerless @skipInQA update_rules_bulk', () => { describe('deprecations', () => { afterEach(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts index 38a5a5a07a9f0..3338634ea0511 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts @@ -34,7 +34,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - // TODO: add a new service + // TODO: add a new service for pulling kibana username, similar to getService('es') const config = getService('config'); const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/ess.config.ts index 787542036e084..2626c12f9a825 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine API Integration Tests - ESS - Telemetry', + reportName: 'Detection Engine - Telemetry Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/serverless.config.ts index 99bd2458c69a4..2601dba13f00c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/telemetry/configs/serverless.config.ts @@ -10,7 +10,8 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine API Integration Tests - Serverless - Telemetry', + reportName: + 'Detection Engine - Telemetry Integration Tests - Serverless Env - Complete License', }, kbnTestServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify(['previewTelemetryUrlEnabled'])}`, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/ess.config.ts index 59e01e74c719c..51ea7037f1d40 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS - User roles API Integration Tests', + reportName: 'Detection Engine - User Roles Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/serverless.config.ts index d8e9843c3eb92..a2dd062fa0ac3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless - User roles API Integration Tests', + reportName: + 'Detection Engine - User Roles Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/check_investigation_field_in_so.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/check_investigation_field_in_so.ts new file mode 100644 index 0000000000000..36804d6c0f50f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/check_investigation_field_in_so.ts @@ -0,0 +1,38 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { SavedObjectReference } from '@kbn/core/server'; +import { InvestigationFields } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { Rule } from '@kbn/alerting-plugin/common'; +import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { isEqual } from 'lodash/fp'; +import { getRuleSOById } from './get_rule_so_by_id'; + +interface RuleSO { + alert: Rule; + references: SavedObjectReference[]; +} + +export const checkInvestigationFieldSoValue = async ( + ruleSO: RuleSO | undefined, + expectedSoValue: undefined | InvestigationFields, + es?: Client, + ruleId?: string +): Promise => { + if (!ruleSO && es && ruleId) { + const { + hits: { + hits: [{ _source: rule }], + }, + } = await getRuleSOById(es, ruleId); + + return isEqual(rule?.alert.params.investigationFields, expectedSoValue); + } + + return isEqual(ruleSO?.alert.params.investigationFields, expectedSoValue); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts index 90f3ae07871c8..501a5579fbfde 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts @@ -10,6 +10,7 @@ export * from './create_rule_with_exception_entries'; export * from './create_rule_saved_object'; export * from './create_rule_with_auth'; export * from './create_non_security_rule'; +export * from './check_investigation_field_in_so'; export * from './downgrade_immutable_rule'; export * from './delete_all_rules'; export * from './delete_rule'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts index 97686465c8073..db1ed95945baf 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts @@ -24,7 +24,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }, testFiles: [require.resolve('..')], junit: { - reportName: 'Entity Analytics API Integration Tests - ESS - Risk Engine', + reportName: 'Entity Analytics - Risk Engine Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts index ccbbcd9dc8cb8..35f50c7ad9f40 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts @@ -15,6 +15,7 @@ export default createTestConfig({ ], testFiles: [require.resolve('..')], junit: { - reportName: 'Entity Analytics API Integration Tests - Serverless - Risk Engine', + reportName: + 'Entity Analytics - Risk Engine Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/ess.config.ts index 366e0b956e370..3a0a941f48020 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS - Execption Lists and Items Integration Tests APIS', + reportName: 'Detection Engine - Exception Lists Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/serverless.config.ts index bb1410030e0db..989ebcd4a34f5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless - Execption Lists and Items Integration Tests APIS', + reportName: + 'Detection Engine - Exception Lists Integration Tests - Serverless Env - Complete License', }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts index 522c44b41d85a..0af6ce99fbbb3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine ESS - Lists and Items Integration Tests APIS', + reportName: 'Detection Engine - Value Lists Integration Tests - ESS Env - Trial License', }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/serverless.config.ts index 7e324d6e29836..f2e5509441e09 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/serverless.config.ts @@ -10,6 +10,7 @@ import { createTestConfig } from '../../../../../config/serverless/config.base'; export default createTestConfig({ testFiles: [require.resolve('..')], junit: { - reportName: 'Detection Engine Serverless - Lists and Items Integration Tests APIS', + reportName: + 'Detection Engine - Value Lists Integration Tests - Serverless Env - Complete License', }, });