Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions x-pack/platform/test/functional/apps/aiops/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
35 changes: 35 additions & 0 deletions x-pack/platform/test/functional/apps/cases_attachments/index.ts
Original file line number Diff line number Diff line change
@@ -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'));
});
}
Original file line number Diff line number Diff line change
@@ -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<AnomalySwimLaneEmbeddableState, 'id'> = {
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
);
});
});
});
});
}
Loading