diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7585d87a30c98..b010afc77d8ce 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2317,6 +2317,8 @@ x-pack/platform/test/functional/page_objects/search_profiler_page.ts @elastic/se /x-pack/solutions/security/plugins/security_solution/public/flyout/ai_for_soc/components @elastic/security-threat-hunting-investigations @elastic/security-generative-ai +/x-pack/platform/test/functional/apps/cases_attachments/** @elastic/kibana-cases + /x-pack/solutions/security/plugins/security_solution/server/routes @elastic/security-detections-response @elastic/security-threat-hunting /x-pack/solutions/security/plugins/security_solution/server/utils @elastic/security-detections-response @elastic/security-threat-hunting x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/utils @elastic/security-detections-response diff --git a/x-pack/platform/test/functional/apps/aiops/index.ts b/x-pack/platform/test/functional/apps/aiops/index.ts index 852dade2b9f95..9b03cd492104b 100644 --- a/x-pack/platform/test/functional/apps/aiops/index.ts +++ b/x-pack/platform/test/functional/apps/aiops/index.ts @@ -32,10 +32,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./log_rate_analysis')); loadTestFile(require.resolve('./log_rate_analysis_anomaly_table')); loadTestFile(require.resolve('./log_rate_analysis_dashboard_embeddable')); - loadTestFile(require.resolve('./log_rate_analysis_cases')); loadTestFile(require.resolve('./change_point_detection')); loadTestFile(require.resolve('./change_point_detection_dashboard')); - loadTestFile(require.resolve('./change_point_detection_cases')); loadTestFile(require.resolve('./log_pattern_analysis')); loadTestFile(require.resolve('./log_pattern_analysis_in_discover')); loadTestFile(require.resolve('./log_pattern_analysis_esql_in_discover')); diff --git a/x-pack/platform/test/functional/apps/aiops/log_pattern_analysis.ts b/x-pack/platform/test/functional/apps/aiops/log_pattern_analysis.ts index 4e27a771c0f42..19436488a99b4 100644 --- a/x-pack/platform/test/functional/apps/aiops/log_pattern_analysis.ts +++ b/x-pack/platform/test/functional/apps/aiops/log_pattern_analysis.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; -import { USER } from '../../../api_integration/services/ml/security_common'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); @@ -25,8 +24,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); } - // Failing: See https://github.com/elastic/kibana/issues/200836 - describe.skip('log pattern analysis', function () { + describe('log pattern analysis', function () { let tabsCount = 1; afterEach(async () => { @@ -118,29 +116,5 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await aiops.logPatternAnalysisPage.attachToDashboard(); }); - - it('attaches log pattern analysis table to a case', async () => { - // Start navigation from the base of the ML app. - await ml.navigation.navigateToMl(); - await elasticChart.setNewChartUiDebugFlag(true); - await aiops.logPatternAnalysisPage.navigateToDataViewSelection(); - await ml.jobSourceSelection.selectSourceForLogPatternAnalysisDetection('logstash-*'); - await aiops.logPatternAnalysisPage.assertLogPatternAnalysisPageExists(); - - await aiops.logPatternAnalysisPage.clickUseFullDataButton(totalDocCount); - await aiops.logPatternAnalysisPage.selectCategoryField(selectedField); - await aiops.logPatternAnalysisPage.clickRunButton(); - - const caseParams = { - title: 'ML Log pattern analysis case', - description: 'Case with a log pattern analysis attachment', - tag: 'ml_log_pattern_analysis', - reporter: USER.ML_POWERUSER, - }; - - await aiops.logPatternAnalysisPage.attachToCase(caseParams); - - await ml.cases.assertCaseWithLogPatternAnalysisAttachment(caseParams); - }); }); } diff --git a/x-pack/platform/test/functional/apps/aiops/change_point_detection_cases.ts b/x-pack/platform/test/functional/apps/cases_attachments/aiops/change_point_detection_cases.ts similarity index 92% rename from x-pack/platform/test/functional/apps/aiops/change_point_detection_cases.ts rename to x-pack/platform/test/functional/apps/cases_attachments/aiops/change_point_detection_cases.ts index a61943e019869..29165384a946b 100644 --- a/x-pack/platform/test/functional/apps/aiops/change_point_detection_cases.ts +++ b/x-pack/platform/test/functional/apps/cases_attachments/aiops/change_point_detection_cases.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; -import { USER } from '../../../api_integration/services/ml/security_common'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../services/ml/security_common'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); @@ -18,8 +18,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // aiops lives in the ML UI so we need some related services. const ml = getService('ml'); - // Failing: See https://github.com/elastic/kibana/issues/202342 - describe.skip('change point detection in cases', function () { + describe('change point detection in cases', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/platform/test/fixtures/es_archives/ml/ecommerce'); await ml.testResources.createDataViewIfNeeded('ft_ecommerce', 'order_date'); diff --git a/x-pack/platform/test/functional/apps/cases_attachments/aiops/log_pattern_analysis_cases.ts b/x-pack/platform/test/functional/apps/cases_attachments/aiops/log_pattern_analysis_cases.ts new file mode 100644 index 0000000000000..0ed92a87c5d6f --- /dev/null +++ b/x-pack/platform/test/functional/apps/cases_attachments/aiops/log_pattern_analysis_cases.ts @@ -0,0 +1,59 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../services/ml/security_common'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const elasticChart = getService('elasticChart'); + const esArchiver = getService('esArchiver'); + const aiops = getService('aiops'); + const ml = getService('ml'); + const selectedField = '@message'; + const totalDocCount = 14005; + const cases = getService('cases'); + + describe('log pattern analysis in cases', function () { + before(async () => { + await esArchiver.loadIfNeeded( + 'x-pack/platform/test/fixtures/es_archives/logstash_functional' + ); + await ml.testResources.createDataViewIfNeeded('logstash-*', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.testResources.deleteDataViewByTitle('logstash-*'); + await cases.api.deleteAllCases(); + }); + + it('attaches log pattern analysis table to a case', async () => { + // Start navigation from the base of the ML app. + await ml.navigation.navigateToMl(); + await elasticChart.setNewChartUiDebugFlag(true); + await aiops.logPatternAnalysisPage.navigateToDataViewSelection(); + await ml.jobSourceSelection.selectSourceForLogPatternAnalysisDetection('logstash-*'); + await aiops.logPatternAnalysisPage.assertLogPatternAnalysisPageExists(); + + await aiops.logPatternAnalysisPage.clickUseFullDataButton(totalDocCount); + await aiops.logPatternAnalysisPage.selectCategoryField(selectedField); + await aiops.logPatternAnalysisPage.clickRunButton(); + + const caseParams = { + title: 'ML Log pattern analysis case', + description: 'Case with a log pattern analysis attachment', + tag: 'ml_log_pattern_analysis', + reporter: USER.ML_POWERUSER, + }; + + await aiops.logPatternAnalysisPage.attachToCase(caseParams); + + await ml.cases.assertCaseWithLogPatternAnalysisAttachment(caseParams); + }); + }); +} diff --git a/x-pack/platform/test/functional/apps/aiops/log_rate_analysis_cases.ts b/x-pack/platform/test/functional/apps/cases_attachments/aiops/log_rate_analysis_cases.ts similarity index 91% rename from x-pack/platform/test/functional/apps/aiops/log_rate_analysis_cases.ts rename to x-pack/platform/test/functional/apps/cases_attachments/aiops/log_rate_analysis_cases.ts index 438222884f284..62db65e43fdaa 100644 --- a/x-pack/platform/test/functional/apps/aiops/log_rate_analysis_cases.ts +++ b/x-pack/platform/test/functional/apps/cases_attachments/aiops/log_rate_analysis_cases.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; -import { USER } from '../../../api_integration/services/ml/security_common'; -import { logRateAnalysisTestData } from './log_rate_analysis_test_data'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../services/ml/security_common'; +import { logRateAnalysisTestData } from '../../aiops/log_rate_analysis_test_data'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); diff --git a/x-pack/platform/test/functional/apps/cases_attachments/index.ts b/x-pack/platform/test/functional/apps/cases_attachments/index.ts new file mode 100644 index 0000000000000..bae5e789a3e99 --- /dev/null +++ b/x-pack/platform/test/functional/apps/cases_attachments/index.ts @@ -0,0 +1,35 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + describe('cases attachments', function () { + // AIOps / Log Rate Analysis lives in the ML UI so we need some related services. + const ml = getService('ml'); + + this.tags(['skipFirefox', 'cases']); + + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await ml.securityUI.logout(); + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + await ml.testResources.resetKibanaTimeZone(); + }); + + loadTestFile(require.resolve('./ml/anomaly_detection_charts_cases')); + loadTestFile(require.resolve('./aiops/log_rate_analysis_cases')); + loadTestFile(require.resolve('./aiops/change_point_detection_cases')); + loadTestFile(require.resolve('./aiops/log_pattern_analysis_cases')); + }); +} diff --git a/x-pack/platform/test/functional/apps/cases_attachments/ml/anomaly_detection_charts_cases.ts b/x-pack/platform/test/functional/apps/cases_attachments/ml/anomaly_detection_charts_cases.ts new file mode 100644 index 0000000000000..7125d29dbe16d --- /dev/null +++ b/x-pack/platform/test/functional/apps/cases_attachments/ml/anomaly_detection_charts_cases.ts @@ -0,0 +1,179 @@ +/* + * 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 { Job, Datafeed } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs'; +import type { AnomalySwimLaneEmbeddableState } from '@kbn/ml-plugin/public'; +import { SWIMLANE_TYPE } from '@kbn/ml-plugin/public/application/explorer/explorer_constants'; +import { stringHash } from '@kbn/ml-string-hash'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../services/ml/security_common'; + +// @ts-expect-error not full interface +const JOB_CONFIG: Job = { + job_id: `fq_multi_1_ae`, + description: + 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', + groups: ['farequote', 'automated', 'multi-metric'], + analysis_config: { + bucket_span: '1h', + influencers: ['airline'], + detectors: [ + { function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'min', field_name: 'responsetime', partition_field_name: 'airline' }, + { function: 'max', field_name: 'responsetime', partition_field_name: 'airline' }, + ], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '20mb' }, + model_plot_config: { enabled: true }, +}; + +// @ts-expect-error not full interface +const DATAFEED_CONFIG: Datafeed = { + datafeed_id: 'datafeed-fq_multi_1_ae', + indices: ['ft_farequote'], + job_id: 'fq_multi_1_ae', + query: { bool: { must: [{ match_all: {} }] } }, +}; + +const overallSwimLaneTestSubj = 'mlAnomalyExplorerSwimlaneOverall'; + +const cellSize = 15; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + const elasticChart = getService('elasticChart'); + + describe('anomaly detection result views - cases attachments', function () { + this.tags(['ml', 'cases']); + + before(async () => { + await esArchiver.loadIfNeeded('x-pack/platform/test/fixtures/es_archives/ml/farequote'); + await ml.testResources.createDataViewIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.testResources.deleteDataViewByTitle('ft_farequote'); + }); + + describe('with farequote based multi metric job', function () { + before(async () => { + await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG); + }); + + after(async () => { + await elasticChart.setNewChartUiDebugFlag(false); + await ml.api.cleanMlIndices(); + }); + + describe('Anomaly Swim Lane as embeddable', function () { + beforeEach(async () => { + await ml.navigation.navigateToAnomalyExplorer(JOB_CONFIG.job_id, { + from: '2016-02-07T00%3A00%3A00.000Z', + to: '2016-02-11T23%3A59%3A54.000Z', + }); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + }); + + it('attaches swim lane embeddable to a case', async () => { + await ml.anomalyExplorer.attachSwimLaneToCase('viewBy', { + title: 'ML Test case', + description: 'Case with an anomaly swim lane', + tag: 'ml_swim_lane_case', + }); + + const attachmentData: Omit = { + swimlaneType: SWIMLANE_TYPE.VIEW_BY, + viewBy: 'airline', + jobIds: [JOB_CONFIG.job_id], + timeRange: { + from: '2016-02-07T00:00:00.000Z', + to: '2016-02-11T23:59:54.000Z', + }, + }; + + const expectedAttachment: AnomalySwimLaneEmbeddableState = { + ...attachmentData, + id: stringHash(JSON.stringify(attachmentData)).toString(), + }; + + await ml.cases.assertCaseWithAnomalySwimLaneAttachment( + { + title: 'ML Test case', + description: 'Case with an anomaly swim lane', + tag: 'ml_swim_lane_case', + reporter: USER.ML_POWERUSER, + }, + expectedAttachment, + { + yAxisLabelCount: 10, + } + ); + }); + }); + + describe('Anomaly Charts as embeddable', function () { + beforeEach(async () => { + await ml.navigation.navigateToAnomalyExplorer( + JOB_CONFIG.job_id, + { + from: '2016-02-07T00%3A00%3A00.000Z', + to: '2016-02-11T23%3A59%3A54.000Z', + }, + () => elasticChart.setNewChartUiDebugFlag(true) + ); + + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + + await ml.testExecution.logTestStep('clicks on the Overall swim lane cell'); + const sampleCell = (await ml.swimLane.getCells(overallSwimLaneTestSubj))[0]; + await ml.swimLane.selectSingleCell(overallSwimLaneTestSubj, { + x: sampleCell.x + cellSize, + y: sampleCell.y + cellSize, + }); + await ml.swimLane.waitForSwimLanesToLoad(); + }); + + it('attaches an embeddable to a case', async () => { + await ml.anomalyExplorer.attachAnomalyChartsToCase({ + title: 'ML Charts Test case', + description: 'Case with an anomaly charts attachment', + tag: 'ml_anomaly_charts', + }); + + const expectedAttachment = { + jobIds: [JOB_CONFIG.job_id], + maxSeriesToPlot: 6, + }; + + // @ts-expect-error Setting id to be undefined here + // since time range expected is of the chart plotEarliest/plotLatest, not of the global time range + // but, chart time range might vary depends on the time of the test + // we don't know the hashed string id for sure + expectedAttachment.id = undefined; + + await ml.cases.assertCaseWithAnomalyChartsAttachment( + { + title: 'ML Charts Test case', + description: 'Case with an anomaly charts attachment', + tag: 'ml_anomaly_charts', + reporter: USER.ML_POWERUSER, + }, + expectedAttachment, + 6 + ); + }); + }); + }); + }); +} diff --git a/x-pack/platform/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts b/x-pack/platform/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts index 652ceec671de8..69ac0c040d0a7 100644 --- a/x-pack/platform/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts +++ b/x-pack/platform/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts @@ -6,9 +6,6 @@ */ import type { Job, Datafeed } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs'; -import type { AnomalySwimLaneEmbeddableState } from '@kbn/ml-plugin/public'; -import { stringHash } from '@kbn/ml-string-hash'; -import { USER } from '../../../services/ml/security_common'; import type { FtrProviderContext } from '../../../ftr_provider_context'; // @ts-expect-error not full interface @@ -94,8 +91,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'timePicker']); - // Failing: See https://github.com/elastic/kibana/issues/196307 - describe.skip('anomaly explorer', function () { + describe('anomaly explorer', function () { this.tags(['ml']); before(async () => { await esArchiver.loadIfNeeded('x-pack/platform/test/fixtures/es_archives/ml/farequote'); @@ -475,39 +471,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await ml.commonUI.waitForDatePickerIndicatorLoaded(); }); - it('attaches swim lane embeddable to a case', async () => { - await ml.anomalyExplorer.attachSwimLaneToCase('viewBy', { - title: 'ML Test case', - description: 'Case with an anomaly swim lane', - tag: 'ml_swim_lane_case', - }); - - const expectedAttachment = { - swimlaneType: 'viewBy', - viewBy: 'airline', - jobIds: [testData.jobConfig.job_id], - timeRange: { - from: '2016-02-07T00:00:00.000Z', - to: '2016-02-11T23:59:54.000Z', - }, - } as AnomalySwimLaneEmbeddableState; - - expectedAttachment.id = stringHash(JSON.stringify(expectedAttachment)).toString(); - - await ml.cases.assertCaseWithAnomalySwimLaneAttachment( - { - title: 'ML Test case', - description: 'Case with an anomaly swim lane', - tag: 'ml_swim_lane_case', - reporter: USER.ML_POWERUSER, - }, - expectedAttachment, - { - yAxisLabelCount: 10, - } - ); - }); - it('adds swim lane embeddable to a dashboard', async () => { await ml.testExecution.logTestStep( 'should allow to attach anomaly swim lane embeddable to the dashboard' @@ -517,60 +480,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('Anomaly Charts as embeddable', function () { - beforeEach(async () => { - await ml.navigation.navigateToAnomalyExplorer( - testData.jobConfig.job_id, - { - from: '2016-02-07T00%3A00%3A00.000Z', - to: '2016-02-11T23%3A59%3A54.000Z', - }, - () => elasticChart.setNewChartUiDebugFlag(true) - ); - - await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); - await ml.commonUI.waitForDatePickerIndicatorLoaded(); - - await ml.testExecution.logTestStep('clicks on the Overall swim lane cell'); - const sampleCell = (await ml.swimLane.getCells(overallSwimLaneTestSubj))[0]; - await ml.swimLane.selectSingleCell(overallSwimLaneTestSubj, { - x: sampleCell.x + cellSize, - y: sampleCell.y + cellSize, - }); - await ml.swimLane.waitForSwimLanesToLoad(); - }); - - it('attaches an embeddable to a case', async () => { - await ml.anomalyExplorer.attachAnomalyChartsToCase({ - title: 'ML Charts Test case', - description: 'Case with an anomaly charts attachment', - tag: 'ml_anomaly_charts', - }); - - const expectedAttachment = { - jobIds: [testData.jobConfig.job_id], - maxSeriesToPlot: 6, - }; - - // @ts-expect-error Setting id to be undefined here - // since time range expected is of the chart plotEarliest/plotLatest, not of the global time range - // but, chart time range might vary depends on the time of the test - // we don't know the hashed string id for sure - expectedAttachment.id = undefined; - - await ml.cases.assertCaseWithAnomalyChartsAttachment( - { - title: 'ML Charts Test case', - description: 'Case with an anomaly charts attachment', - tag: 'ml_anomaly_charts', - reporter: USER.ML_POWERUSER, - }, - expectedAttachment, - 6 - ); - }); - }); - describe('Use anomaly table action to view in Discover', function () { beforeEach(async () => { await ml.navigation.navigateToAnomalyExplorer(