Skip to content

Commit

Permalink
[ML] Explain Log Rate Spikes: Additional API integration tests (elast…
Browse files Browse the repository at this point in the history
…ic#146113)

Additional API integration tests.

- The test data was moved to its own file `test_data.ts` and types for
its structure defined in `types.ts` to be in line with the structure
used for functional tests.
- The file that runs the test was extended so it can run an array of
test data definitions.
- The datasets used in the funcional tests (`ecommerce` with some
additional documents added to create a significant spike and the
computationally generated spike data set to create distinct groups) were
moved to a service `ExplainLogRateSpikesDataGenerator` so they can be
generated and used across functional and API integration tests.
- The computationally generated spike data set
`artificial_logs_with_spike` is now also used for API integration tests.
- Additional assertions have been added to check the grouping result.
`ecommerce` does not return any groups whereas
`artificial_logs_with_spike` does.
- The functional tests code is now consolidated and one test file is
able to run multiple test data definitions too.

(cherry picked from commit 5481f07)
  • Loading branch information
walterra committed Nov 24, 2022
1 parent fbf1981 commit 4f64551
Show file tree
Hide file tree
Showing 12 changed files with 667 additions and 589 deletions.
426 changes: 208 additions & 218 deletions x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts

Large diffs are not rendered by default.

157 changes: 157 additions & 0 deletions x-pack/test/api_integration/apis/aiops/test_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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 { TestData } from './types';

export const explainLogRateSpikesTestData: TestData[] = [
{
testName: 'ecommerce',
esArchive: 'x-pack/test/functional/es_archives/ml/ecommerce',
requestBody: {
baselineMax: 1561719083292,
baselineMin: 1560954147006,
deviationMax: 1562254538692,
deviationMin: 1561986810992,
end: 2147483647000,
index: 'ft_ecommerce',
searchQuery: '{"bool":{"filter":[],"must":[{"match_all":{}}],"must_not":[]}}',
start: 0,
timeFieldName: 'order_date',
grouping: true,
},
expected: {
chunksLength: 35,
actionsLength: 34,
noIndexChunksLength: 4,
noIndexActionsLength: 3,
changePointFilter: 'add_change_points',
groupFilter: 'add_change_point_group',
groupHistogramFilter: 'add_change_point_group_histogram',
histogramFilter: 'add_change_points_histogram',
errorFilter: 'add_error',
changePoints: [
{
fieldName: 'day_of_week',
fieldValue: 'Wednesday',
doc_count: 145,
bg_count: 142,
score: 36.31595998561873,
pValue: 1.6911377077437753e-16,
normalizedScore: 0.8055203624020835,
total_doc_count: 0,
total_bg_count: 0,
},
{
fieldName: 'day_of_week',
fieldValue: 'Thursday',
doc_count: 157,
bg_count: 224,
score: 20.366950718358762,
pValue: 1.428057484826135e-9,
normalizedScore: 0.7661649691018979,
total_doc_count: 0,
total_bg_count: 0,
},
],
groups: [],
histogramLength: 20,
},
},
{
testName: 'artificial_logs_with_spike',
dataGenerator: 'artificial_logs_with_spike',
requestBody: {
start: 1668760018793,
end: 1668931954793,
searchQuery: '{"match_all":{}}',
timeFieldName: '@timestamp',
index: 'artificial_logs_with_spike',
baselineMin: 1668769200000,
baselineMax: 1668837600000,
deviationMin: 1668855600000,
deviationMax: 1668924000000,
grouping: true,
},
expected: {
chunksLength: 25,
actionsLength: 24,
noIndexChunksLength: 4,
noIndexActionsLength: 3,
changePointFilter: 'add_change_points',
groupFilter: 'add_change_point_group',
groupHistogramFilter: 'add_change_point_group_histogram',
histogramFilter: 'add_change_points_histogram',
errorFilter: 'add_error',
changePoints: [
{
fieldName: 'response_code',
fieldValue: '500',
doc_count: 1821,
bg_count: 553,
total_doc_count: 4671,
total_bg_count: 1975,
score: 26.546201745993947,
pValue: 2.9589053032077285e-12,
normalizedScore: 0.7814127409489161,
},
{
fieldName: 'url',
fieldValue: 'home.php',
doc_count: 1742,
bg_count: 632,
total_doc_count: 4671,
total_bg_count: 1975,
score: 4.53094842981472,
pValue: 0.010770456205312423,
normalizedScore: 0.10333028878375965,
},
{
fieldName: 'url',
fieldValue: 'login.php',
doc_count: 1742,
bg_count: 632,
total_doc_count: 4671,
total_bg_count: 1975,
score: 4.53094842981472,
pValue: 0.010770456205312423,
normalizedScore: 0.10333028878375965,
},
{
fieldName: 'user',
fieldValue: 'Peter',
doc_count: 1981,
bg_count: 553,
total_doc_count: 4671,
total_bg_count: 1975,
score: 47.34435085428873,
pValue: 2.7454255728359757e-21,
normalizedScore: 0.8327337555873047,
},
],
groups: [
{
id: '2038579476',
group: [
{ fieldName: 'response_code', fieldValue: '500', duplicate: false },
{ fieldName: 'url', fieldValue: 'home.php', duplicate: false },
{ fieldName: 'url', fieldValue: 'home.php', duplicate: false },
{ fieldName: 'url', fieldValue: 'login.php', duplicate: false },
],
docCount: 792,
pValue: 0.010770456205312423,
},
{
id: '817080373',
group: [{ fieldName: 'user', fieldValue: 'Peter', duplicate: false }],
docCount: 1981,
pValue: 2.7454255728359757e-21,
},
],
histogramLength: 20,
},
},
];
30 changes: 30 additions & 0 deletions x-pack/test/api_integration/apis/aiops/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { ApiExplainLogRateSpikes } from '@kbn/aiops-plugin/common/api';
import type { ChangePoint, ChangePointGroup } from '@kbn/ml-agg-utils';

export interface TestData {
testName: string;
esArchive?: string;
dataGenerator?: string;
requestBody: ApiExplainLogRateSpikes['body'];
expected: {
chunksLength: number;
actionsLength: number;
noIndexChunksLength: number;
noIndexActionsLength: number;
changePointFilter: 'add_change_points';
groupFilter: 'add_change_point_group';
groupHistogramFilter: 'add_change_point_group_histogram';
histogramFilter: 'add_change_points_histogram';
errorFilter: 'add_error';
changePoints: ChangePoint[];
groups: ChangePointGroup[];
histogramLength: number;
};
}
18 changes: 18 additions & 0 deletions x-pack/test/api_integration/services/aiops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { FtrProviderContext } from '../../functional/ftr_provider_context';

import { ExplainLogRateSpikesDataGeneratorProvider } from '../../functional/services/aiops/explain_log_rate_spikes_data_generator';

export function AiopsProvider(context: FtrProviderContext) {
const explainLogRateSpikesDataGenerator = ExplainLogRateSpikesDataGeneratorProvider(context);

return {
explainLogRateSpikesDataGenerator,
};
}
2 changes: 2 additions & 0 deletions x-pack/test/api_integration/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SupertestWithoutAuthProvider } from './supertest_without_auth';

import { UsageAPIProvider } from './usage_api';

import { AiopsProvider } from './aiops';
import { InfraOpsSourceConfigurationProvider } from './infraops_source_configuration';
import { MachineLearningProvider } from './ml';
import { IngestManagerProvider } from '../../common/services/ingest_manager';
Expand All @@ -26,6 +27,7 @@ export const services = {
esSupertest: kibanaApiIntegrationServices.esSupertest,
supertest: kibanaApiIntegrationServices.supertest,

aiops: AiopsProvider,
esSupertestWithoutAuth: EsSupertestWithoutAuthProvider,
infraOpsSourceConfiguration: InfraOpsSourceConfigurationProvider,
supertestWithoutAuth: SupertestWithoutAuthProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@
import expect from '@kbn/expect';

import type { FtrProviderContext } from '../../ftr_provider_context';
import type { TestDataGenerated } from './types';
import { artificialLogDataViewTestData } from './test_data';
import type { TestData } from './types';
import { explainLogRateSpikesTestData } from './test_data';

export default function ({ getPageObject, getService }: FtrProviderContext) {
const es = getService('es');
const headerPage = getPageObject('header');
const elasticChart = getService('elasticChart');
const aiops = getService('aiops');
const log = getService('log');

// aiops / Explain Log Rate Spikes lives in the ML UI so we need some related services.
const ml = getService('ml');

function runTests(testData: TestDataGenerated) {
function runTests(testData: TestData) {
it(`${testData.suiteTitle} loads the source data in explain log rate spikes`, async () => {
await elasticChart.setNewChartUiDebugFlag(true);

Expand Down Expand Up @@ -93,7 +91,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await aiops.explainLogRateSpikesPage.adjustBrushHandler(
'aiopsBrushDeviation',
'handle--w',
targetPx - intervalPx * testData.brushIntervalFactor
targetPx - intervalPx * (testData.brushIntervalFactor - 1)
);

if (testData.brushBaselineTargetTimestamp) {
Expand All @@ -114,9 +112,10 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await aiops.explainLogRateSpikesPage.adjustBrushHandler(
'aiopsBrushBaseline',
'handle--w',
targetBaselinePx - intervalPx * testData.brushIntervalFactor
targetBaselinePx - intervalPx * (testData.brushIntervalFactor - 1)
);
}

// Get the new brush selection width for later comparison.
const brushSelectionWidthAfter = await aiops.explainLogRateSpikesPage.getBrushSelectionWidth(
'aiopsBrushDeviation'
Expand All @@ -127,7 +126,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
// Finally, the adjusted brush should trigger
// a warning on the "Rerun analysis" button.
expect(brushSelectionWidthBefore).not.to.be(brushSelectionWidthAfter);
expect(brushSelectionWidthAfter).not.to.be.greaterThan(intervalPx * 21);
expect(brushSelectionWidthAfter).not.to.be.greaterThan(
intervalPx * 2 * testData.brushIntervalFactor
);

await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(true);

Expand All @@ -146,6 +147,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {

const analysisGroupsTable =
await aiops.explainLogRateSpikesAnalysisGroupsTable.parseAnalysisTable();

expect(analysisGroupsTable).to.be.eql(testData.expected.analysisGroupsTable);

await ml.testExecution.logTestStep('expand table row');
Expand All @@ -157,69 +159,37 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
});
}

describe('explain log rate spikes - artificial log data', function () {
this.tags(['aiops']);
describe('explain log rate spikes', async function () {
for (const testData of explainLogRateSpikesTestData) {
describe(`with '${testData.sourceIndexOrSavedSearch}'`, function () {
before(async () => {
await aiops.explainLogRateSpikesDataGenerator.generateData(testData.dataGenerator);

before(async () => {
try {
await es.indices.delete({ index: artificialLogDataViewTestData.sourceIndexOrSavedSearch });
} catch (e) {
log.error(
`Error deleting index '${artificialLogDataViewTestData.sourceIndexOrSavedSearch}' in before() callback`
);
}
// Create index with mapping
await es.indices.create({
index: artificialLogDataViewTestData.sourceIndexOrSavedSearch,
mappings: {
properties: {
user: { type: 'keyword' },
response_code: { type: 'keyword' },
url: { type: 'keyword' },
version: { type: 'keyword' },
'@timestamp': { type: 'date' },
},
},
});
await ml.testResources.createIndexPatternIfNeeded(
testData.sourceIndexOrSavedSearch,
'@timestamp'
);

await es.bulk({
refresh: 'wait_for',
body: artificialLogDataViewTestData.bulkBody,
});
await ml.testResources.setKibanaTimeZoneToUTC();

await ml.testResources.createIndexPatternIfNeeded(
artificialLogDataViewTestData.sourceIndexOrSavedSearch,
'@timestamp'
);
await ml.securityUI.loginAsMlPowerUser();
});

await ml.testResources.setKibanaTimeZoneToUTC();
after(async () => {
await elasticChart.setNewChartUiDebugFlag(false);
await ml.testResources.deleteIndexPatternByTitle(testData.sourceIndexOrSavedSearch);

await ml.securityUI.loginAsMlPowerUser();
});
await aiops.explainLogRateSpikesDataGenerator.removeGeneratedData(testData.dataGenerator);
});

after(async () => {
await elasticChart.setNewChartUiDebugFlag(false);
await ml.testResources.deleteIndexPatternByTitle(
artificialLogDataViewTestData.sourceIndexOrSavedSearch
);
try {
await es.indices.delete({ index: artificialLogDataViewTestData.sourceIndexOrSavedSearch });
} catch (e) {
log.error(
`Error deleting index '${artificialLogDataViewTestData.sourceIndexOrSavedSearch}' in after() callback`
);
}
});
it(`${testData.suiteTitle} loads the explain log rate spikes page`, async () => {
// Start navigation from the base of the ML app.
await ml.navigation.navigateToMl();
await elasticChart.setNewChartUiDebugFlag(true);
});

describe('with artificial logs', function () {
// Run tests on full farequote index.
it(`${artificialLogDataViewTestData.suiteTitle} loads the explain log rate spikes page`, async () => {
// Start navigation from the base of the ML app.
await ml.navigation.navigateToMl();
await elasticChart.setNewChartUiDebugFlag(true);
runTests(testData);
});

runTests(artificialLogDataViewTestData);
});
}
});
}
Loading

0 comments on commit 4f64551

Please sign in to comment.