mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
This PR adds basic functional UI tests for the single metric viewer and the anomaly explorer.
This commit is contained in:
parent
b1d2c00c3c
commit
2a3a9f7260
18 changed files with 437 additions and 4 deletions
|
@ -195,6 +195,7 @@ class AnomaliesTable extends Component {
|
|||
return {
|
||||
onMouseOver: () => this.onMouseOverRow(item),
|
||||
onMouseLeave: () => this.onMouseLeaveRow(),
|
||||
'data-test-subj': `mlAnomaliesListRow row-${item.rowId}`,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -80,11 +80,13 @@ export function getColumns(
|
|||
})
|
||||
}
|
||||
data-row-id={item.rowId}
|
||||
data-test-subj="mlJobListRowDetailsToggle"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'time',
|
||||
'data-test-subj': 'mlAnomaliesListColumnTime',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.timeColumnName', {
|
||||
defaultMessage: 'time',
|
||||
}),
|
||||
|
@ -95,6 +97,7 @@ export function getColumns(
|
|||
},
|
||||
{
|
||||
field: 'severity',
|
||||
'data-test-subj': 'mlAnomaliesListColumnSeverity',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.severityColumnName', {
|
||||
defaultMessage: 'severity',
|
||||
}),
|
||||
|
@ -105,6 +108,7 @@ export function getColumns(
|
|||
},
|
||||
{
|
||||
field: 'detector',
|
||||
'data-test-subj': 'mlAnomaliesListColumnDetector',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.detectorColumnName', {
|
||||
defaultMessage: 'detector',
|
||||
}),
|
||||
|
@ -119,6 +123,7 @@ export function getColumns(
|
|||
if (items.some(item => item.entityValue !== undefined)) {
|
||||
columns.push({
|
||||
field: 'entityValue',
|
||||
'data-test-subj': 'mlAnomaliesListColumnFoundFor',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.entityValueColumnName', {
|
||||
defaultMessage: 'found for',
|
||||
}),
|
||||
|
@ -138,6 +143,7 @@ export function getColumns(
|
|||
if (items.some(item => item.influencers !== undefined)) {
|
||||
columns.push({
|
||||
field: 'influencers',
|
||||
'data-test-subj': 'mlAnomaliesListColumnInfluencers',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.influencersColumnName', {
|
||||
defaultMessage: 'influenced by',
|
||||
}),
|
||||
|
@ -159,6 +165,7 @@ export function getColumns(
|
|||
if (items.some(item => item.actual !== undefined)) {
|
||||
columns.push({
|
||||
field: 'actualSort',
|
||||
'data-test-subj': 'mlAnomaliesListColumnActual',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.actualSortColumnName', {
|
||||
defaultMessage: 'actual',
|
||||
}),
|
||||
|
@ -176,6 +183,7 @@ export function getColumns(
|
|||
if (items.some(item => item.typical !== undefined)) {
|
||||
columns.push({
|
||||
field: 'typicalSort',
|
||||
'data-test-subj': 'mlAnomaliesListColumnTypical',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.typicalSortColumnName', {
|
||||
defaultMessage: 'typical',
|
||||
}),
|
||||
|
@ -198,6 +206,7 @@ export function getColumns(
|
|||
if (nonTimeOfDayOrWeek === true) {
|
||||
columns.push({
|
||||
field: 'metricDescriptionSort',
|
||||
'data-test-subj': 'mlAnomaliesListColumnDescription',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.metricDescriptionSortColumnName', {
|
||||
defaultMessage: 'description',
|
||||
}),
|
||||
|
@ -213,6 +222,7 @@ export function getColumns(
|
|||
if (jobIds && jobIds.length > 1) {
|
||||
columns.push({
|
||||
field: 'jobId',
|
||||
'data-test-subj': 'mlAnomaliesListColumnJobID',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.jobIdColumnName', {
|
||||
defaultMessage: 'job ID',
|
||||
}),
|
||||
|
@ -223,6 +233,7 @@ export function getColumns(
|
|||
const showExamples = items.some(item => item.entityName === 'mlcategory');
|
||||
if (showExamples === true) {
|
||||
columns.push({
|
||||
'data-test-subj': 'mlAnomaliesListColumnCategoryExamples',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.categoryExamplesColumnName', {
|
||||
defaultMessage: 'category examples',
|
||||
}),
|
||||
|
@ -254,6 +265,7 @@ export function getColumns(
|
|||
|
||||
if (showLinks === true) {
|
||||
columns.push({
|
||||
'data-test-subj': 'mlAnomaliesListColumnAction',
|
||||
name: i18n.translate('xpack.ml.anomaliesTable.actionsColumnName', {
|
||||
defaultMessage: 'actions',
|
||||
}),
|
||||
|
|
|
@ -56,7 +56,7 @@ function Influencer({ influencerFieldName, influencerFilter, valueData }) {
|
|||
const tooltipContent = getTooltipContent(maxScoreLabel, totalScoreLabel);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-test-subj={`mlInfluencerEntry field-${influencerFieldName}`}>
|
||||
<div className="field-label">
|
||||
{influencerFieldName !== 'mlcategory' ? (
|
||||
<EntityCell
|
||||
|
@ -114,7 +114,7 @@ function InfluencersByName({ influencerFieldName, influencerFilter, fieldValues
|
|||
|
||||
return (
|
||||
<React.Fragment key={influencerFieldName}>
|
||||
<EuiTitle size="xs">
|
||||
<EuiTitle size="xs" data-test-subj={`mlInfluencerFieldName ${influencerFieldName}`}>
|
||||
<h4>{influencerFieldName}</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xs" />
|
||||
|
|
|
@ -33,7 +33,7 @@ export function JobSelectorBadge({ icon, id, isGroup = false, numJobs, removeId
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiBadge key={`${id}-id`} {...props}>
|
||||
<EuiBadge key={`${id}-id`} data-test-subj={`mlJobSelectionBadge ${id}`} {...props}>
|
||||
{`${id}${jobCount ? jobCount : ''}`}
|
||||
</EuiBadge>
|
||||
);
|
||||
|
|
|
@ -12,7 +12,11 @@ import { EuiLoadingChart, EuiSpacer } from '@elastic/eui';
|
|||
export function LoadingIndicator({ height, label }) {
|
||||
height = height ? +height : 100;
|
||||
return (
|
||||
<div className="ml-loading-indicator" style={{ height: `${height}px` }}>
|
||||
<div
|
||||
className="ml-loading-indicator"
|
||||
style={{ height: `${height}px` }}
|
||||
data-test-subj="mlLoadingIndicator"
|
||||
>
|
||||
<EuiLoadingChart size="xl" mono />
|
||||
{label && (
|
||||
<>
|
||||
|
|
|
@ -1314,6 +1314,7 @@ export class TimeSeriesExplorer extends React.Component {
|
|||
onChange={this.detectorIndexChangeHandler}
|
||||
value={selectedDetectorIndex}
|
||||
options={detectorSelectOptions}
|
||||
data-test-subj="mlSingleMetricViewerDetectorSelect"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import {
|
||||
Job,
|
||||
Datafeed,
|
||||
} from '../../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs';
|
||||
|
||||
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 },
|
||||
};
|
||||
|
||||
const DATAFEED_CONFIG: Datafeed = {
|
||||
datafeed_id: 'datafeed-fq_multi_1_se',
|
||||
indices: ['farequote'],
|
||||
job_id: 'fq_multi_1_ae',
|
||||
query: { bool: { must: [{ match_all: {} }] } },
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const ml = getService('ml');
|
||||
|
||||
describe('anomaly explorer', function() {
|
||||
this.tags(['smoke', 'mlqa']);
|
||||
before(async () => {
|
||||
await esArchiver.load('ml/farequote');
|
||||
await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('ml/farequote');
|
||||
await ml.api.cleanMlIndices();
|
||||
});
|
||||
|
||||
it('loads from job list row link', async () => {
|
||||
await ml.navigation.navigateToMl();
|
||||
await ml.navigation.navigateToJobManagement();
|
||||
|
||||
await ml.jobTable.waitForJobsToLoad();
|
||||
await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id);
|
||||
const rows = await ml.jobTable.parseJobTable();
|
||||
expect(rows.filter(row => row.id === JOB_CONFIG.job_id)).to.have.length(1);
|
||||
|
||||
await ml.jobTable.clickOpenJobInAnomalyExplorerButton(JOB_CONFIG.job_id);
|
||||
await ml.common.waitForMlLoadingIndicatorToDisappear();
|
||||
});
|
||||
|
||||
it('pre-fills the job selection', async () => {
|
||||
await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]);
|
||||
});
|
||||
|
||||
it('displays the influencers list', async () => {
|
||||
await ml.anomalyExplorer.assertInfluencerListExists();
|
||||
for (const influencerField of JOB_CONFIG.analysis_config.influencers) {
|
||||
await ml.anomalyExplorer.assertInfluencerFieldExists(influencerField);
|
||||
await ml.anomalyExplorer.assertInfluencerFieldListNotEmpty(influencerField);
|
||||
}
|
||||
});
|
||||
|
||||
it('displays the swimlanes', async () => {
|
||||
await ml.anomalyExplorer.assertOverallSwimlaneExists();
|
||||
await ml.anomalyExplorer.assertSwimlaneViewByExists();
|
||||
});
|
||||
|
||||
it('displays the anomalies table', async () => {
|
||||
await ml.anomaliesTable.assertTableExists();
|
||||
});
|
||||
|
||||
it('anomalies table is not empty', async () => {
|
||||
await ml.anomaliesTable.assertTableNotEmpty();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -14,5 +14,7 @@ export default function({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./population_job'));
|
||||
loadTestFile(require.resolve('./saved_search_job'));
|
||||
loadTestFile(require.resolve('./advanced_job'));
|
||||
loadTestFile(require.resolve('./single_metric_viewer'));
|
||||
loadTestFile(require.resolve('./anomaly_explorer'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import {
|
||||
Job,
|
||||
Datafeed,
|
||||
} from '../../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs';
|
||||
|
||||
const JOB_CONFIG: Job = {
|
||||
job_id: `fq_single_1_smv`,
|
||||
description: 'mean(responsetime) on farequote dataset with 15m bucket span',
|
||||
groups: ['farequote', 'automated', 'single-metric'],
|
||||
analysis_config: {
|
||||
bucket_span: '15m',
|
||||
influencers: [],
|
||||
detectors: [
|
||||
{
|
||||
function: 'mean',
|
||||
field_name: 'responsetime',
|
||||
},
|
||||
],
|
||||
},
|
||||
data_description: { time_field: '@timestamp' },
|
||||
analysis_limits: { model_memory_limit: '10mb' },
|
||||
model_plot_config: { enabled: true },
|
||||
};
|
||||
|
||||
const DATAFEED_CONFIG: Datafeed = {
|
||||
datafeed_id: 'datafeed-fq_single_1_smv',
|
||||
indices: ['farequote'],
|
||||
job_id: 'fq_single_1_smv',
|
||||
query: { bool: { must: [{ match_all: {} }] } },
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const ml = getService('ml');
|
||||
|
||||
describe('single metric viewer', function() {
|
||||
this.tags(['smoke', 'mlqa']);
|
||||
before(async () => {
|
||||
await esArchiver.load('ml/farequote');
|
||||
await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('ml/farequote');
|
||||
await ml.api.cleanMlIndices();
|
||||
});
|
||||
|
||||
it('loads from job list row link', async () => {
|
||||
await ml.navigation.navigateToMl();
|
||||
await ml.navigation.navigateToJobManagement();
|
||||
|
||||
await ml.jobTable.waitForJobsToLoad();
|
||||
await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id);
|
||||
const rows = await ml.jobTable.parseJobTable();
|
||||
expect(rows.filter(row => row.id === JOB_CONFIG.job_id)).to.have.length(1);
|
||||
|
||||
await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id);
|
||||
await ml.common.waitForMlLoadingIndicatorToDisappear();
|
||||
});
|
||||
|
||||
it('pre-fills the job selection', async () => {
|
||||
await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]);
|
||||
});
|
||||
|
||||
it('pre-fills the detector input', async () => {
|
||||
await ml.singleMetricViewer.assertDetectorInputExsist();
|
||||
await ml.singleMetricViewer.assertDetectorInputValue('0');
|
||||
});
|
||||
|
||||
it('displays the chart', async () => {
|
||||
await ml.singleMetricViewer.assertChartExsist();
|
||||
});
|
||||
|
||||
it('displays the anomalies table', async () => {
|
||||
await ml.anomaliesTable.assertTableExists();
|
||||
});
|
||||
|
||||
it('anomalies table is not empty', async () => {
|
||||
await ml.anomaliesTable.assertTableNotEmpty();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function MachineLearningAnomaliesTableProvider({ getService }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async assertTableExists() {
|
||||
await testSubjects.existOrFail('mlAnomaliesTable');
|
||||
},
|
||||
|
||||
async assertTableNotEmpty() {
|
||||
const tableRows = await testSubjects.findAll('mlAnomaliesTable > ~mlAnomaliesListRow');
|
||||
expect(tableRows.length).to.be.greaterThan(
|
||||
0,
|
||||
'Anomalies table should have at least one row (got 0)'
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
|
@ -13,5 +14,31 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid
|
|||
async assertAnomalyExplorerEmptyListMessageExists() {
|
||||
await testSubjects.existOrFail('mlNoJobsFound');
|
||||
},
|
||||
|
||||
async assertInfluencerListExists() {
|
||||
await testSubjects.existOrFail('mlAnomalyExplorerInfluencerList');
|
||||
},
|
||||
|
||||
async assertInfluencerFieldExists(influencerField: string) {
|
||||
await testSubjects.existOrFail(`mlInfluencerFieldName ${influencerField}`);
|
||||
},
|
||||
|
||||
async assertInfluencerFieldListNotEmpty(influencerField: string) {
|
||||
const influencerFieldEntries = await testSubjects.findAll(
|
||||
`mlInfluencerEntry field-${influencerField}`
|
||||
);
|
||||
expect(influencerFieldEntries.length).to.be.greaterThan(
|
||||
0,
|
||||
`Influencer list for field '${influencerField}' should have at least one entry (got 0)`
|
||||
);
|
||||
},
|
||||
|
||||
async assertOverallSwimlaneExists() {
|
||||
await testSubjects.existOrFail('mlAnomalyExplorerSwimlaneOverall');
|
||||
},
|
||||
|
||||
async assertSwimlaneViewByExists() {
|
||||
await testSubjects.existOrFail('mlAnomalyExplorerSwimlaneViewBy');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
|
||||
import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states';
|
||||
import { DATA_FRAME_TASK_STATE } from '../../../../legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common';
|
||||
import { Job } from '../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job';
|
||||
import { Datafeed } from '../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/datafeed';
|
||||
|
||||
export type MlApi = ProvidedType<typeof MachineLearningAPIProvider>;
|
||||
|
||||
|
@ -270,5 +272,89 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
async getAnomalyDetectionJob(jobId: string) {
|
||||
return await esSupertest.get(`/_ml/anomaly_detectors/${jobId}`).expect(200);
|
||||
},
|
||||
|
||||
async createAnomalyDetectionJob(jobConfig: Job) {
|
||||
const jobId = jobConfig.job_id;
|
||||
log.debug(`Creating anomaly detection job with id '${jobId}'...`);
|
||||
await esSupertest
|
||||
.put(`/_ml/anomaly_detectors/${jobId}`)
|
||||
.send(jobConfig)
|
||||
.expect(200);
|
||||
|
||||
await retry.waitForWithTimeout(`'${jobId}' to be created`, 5 * 1000, async () => {
|
||||
if (await this.getAnomalyDetectionJob(jobId)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`expected anomaly detection job '${jobId}' to be created`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async getDatafeed(datafeedId: string) {
|
||||
return await esSupertest.get(`/_ml/datafeeds/${datafeedId}`).expect(200);
|
||||
},
|
||||
|
||||
async createDatafeed(datafeedConfig: Datafeed) {
|
||||
const datafeedId = datafeedConfig.datafeed_id;
|
||||
log.debug(`Creating datafeed with id '${datafeedId}'...`);
|
||||
await esSupertest
|
||||
.put(`/_ml/datafeeds/${datafeedId}`)
|
||||
.send(datafeedConfig)
|
||||
.expect(200);
|
||||
|
||||
await retry.waitForWithTimeout(`'${datafeedId}' to be created`, 5 * 1000, async () => {
|
||||
if (await this.getDatafeed(datafeedId)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`expected datafeed '${datafeedId}' to be created`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async openAnomalyDetectionJob(jobId: string) {
|
||||
log.debug(`Opening anomaly detection job '${jobId}'...`);
|
||||
const openResponse = await esSupertest
|
||||
.post(`/_ml/anomaly_detectors/${jobId}/_open`)
|
||||
.send({ timeout: '10s' })
|
||||
.set({ 'Content-Type': 'application/json' })
|
||||
.expect(200)
|
||||
.then((res: any) => res.body);
|
||||
|
||||
expect(openResponse)
|
||||
.to.have.property('opened')
|
||||
.eql(true, 'Response for open job request should be acknowledged');
|
||||
},
|
||||
|
||||
async startDatafeed(
|
||||
datafeedId: string,
|
||||
startConfig: { start?: string; end?: string } = { start: '0' }
|
||||
) {
|
||||
log.debug(
|
||||
`Starting datafeed '${datafeedId}' with start: '${startConfig.start}', end: '${startConfig.end}'...`
|
||||
);
|
||||
const startResponse = await esSupertest
|
||||
.post(`/_ml/datafeeds/${datafeedId}/_start`)
|
||||
.send(startConfig)
|
||||
.set({ 'Content-Type': 'application/json' })
|
||||
.expect(200)
|
||||
.then((res: any) => res.body);
|
||||
|
||||
expect(startResponse)
|
||||
.to.have.property('started')
|
||||
.eql(true, 'Response for start datafeed request should be acknowledged');
|
||||
},
|
||||
|
||||
async createAndRunAnomalyDetectionLookbackJob(jobConfig: Job, datafeedConfig: Datafeed) {
|
||||
await this.createAnomalyDetectionJob(jobConfig);
|
||||
await this.createDatafeed(datafeedConfig);
|
||||
await this.openAnomalyDetectionJob(jobConfig.job_id);
|
||||
await this.startDatafeed(datafeedConfig.datafeed_id, { start: '0', end: `${Date.now()}` });
|
||||
await this.waitForDatafeedState(datafeedConfig.datafeed_id, DATAFEED_STATE.STOPPED);
|
||||
await this.waitForJobState(jobConfig.job_id, JOB_STATE.CLOSED);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -72,5 +72,11 @@ export function MachineLearningCommonProvider({ getService }: FtrProviderContext
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
async waitForMlLoadingIndicatorToDisappear() {
|
||||
await retry.tryForTime(10 * 1000, async () => {
|
||||
await testSubjects.missingOrFail('mlLoadingIndicator');
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { MachineLearningAnomaliesTableProvider } from './anomalies_table';
|
||||
export { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer';
|
||||
export { MachineLearningAPIProvider } from './api';
|
||||
export { MachineLearningCommonProvider } from './common';
|
||||
|
@ -14,6 +15,7 @@ export { MachineLearningDataFrameAnalyticsTableProvider } from './data_frame_ana
|
|||
export { MachineLearningDataVisualizerProvider } from './data_visualizer';
|
||||
export { MachineLearningDataVisualizerIndexBasedProvider } from './data_visualizer_index_based';
|
||||
export { MachineLearningJobManagementProvider } from './job_management';
|
||||
export { MachineLearningJobSelectionProvider } from './job_selection';
|
||||
export { MachineLearningJobSourceSelectionProvider } from './job_source_selection';
|
||||
export { MachineLearningJobTableProvider } from './job_table';
|
||||
export { MachineLearningJobTypeSelectionProvider } from './job_type_selection';
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function MachineLearningJobSelectionProvider({ getService }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async assertJobSelection(jobOrGroupIds: string[]) {
|
||||
const selectedJobsOrGroups = await testSubjects.findAll(
|
||||
'mlJobSelectionBadges > ~mlJobSelectionBadge'
|
||||
);
|
||||
const actualJobOrGroupLabels = await Promise.all(
|
||||
selectedJobsOrGroups.map(async badge => await badge.getVisibleText())
|
||||
);
|
||||
expect(actualJobOrGroupLabels).to.eql(
|
||||
jobOrGroupIds,
|
||||
`Job selection should display jobs or groups '${jobOrGroupIds}' (got '${actualJobOrGroupLabels}')`
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -235,5 +235,15 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte
|
|||
await testSubjects.click('mlDeleteJobConfirmModal > confirmModalConfirmButton');
|
||||
await testSubjects.missingOrFail('mlDeleteJobConfirmModal', { timeout: 30 * 1000 });
|
||||
}
|
||||
|
||||
public async clickOpenJobInSingleMetricViewerButton(jobId: string) {
|
||||
await testSubjects.click(`~openJobsInSingleMetricViewer-${jobId}`);
|
||||
await testSubjects.existOrFail('~mlPageSingleMetricViewer');
|
||||
}
|
||||
|
||||
public async clickOpenJobInAnomalyExplorerButton(jobId: string) {
|
||||
await testSubjects.click(`~openJobsInSingleAnomalyExplorer-${jobId}`);
|
||||
await testSubjects.existOrFail('~mlPageAnomalyExplorer');
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
|
@ -13,5 +14,40 @@ export function MachineLearningSingleMetricViewerProvider({ getService }: FtrPro
|
|||
async assertSingleMetricViewerEmptyListMessageExsist() {
|
||||
await testSubjects.existOrFail('mlNoSingleMetricJobsFound');
|
||||
},
|
||||
|
||||
async assertForecastButtonExistsExsist() {
|
||||
await testSubjects.existOrFail(
|
||||
'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast'
|
||||
);
|
||||
},
|
||||
|
||||
async assertDetectorInputExsist() {
|
||||
await testSubjects.existOrFail(
|
||||
'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerDetectorSelect'
|
||||
);
|
||||
},
|
||||
|
||||
async assertDetectorInputValue(expectedDetectorOptionValue: string) {
|
||||
const actualDetectorValue = await testSubjects.getAttribute(
|
||||
'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerDetectorSelect',
|
||||
'value'
|
||||
);
|
||||
expect(actualDetectorValue).to.eql(
|
||||
expectedDetectorOptionValue,
|
||||
`Detector input option value should be '${expectedDetectorOptionValue}' (got '${actualDetectorValue}')`
|
||||
);
|
||||
},
|
||||
|
||||
async setDetectorInputValue(detectorOptionValue: string) {
|
||||
await testSubjects.selectValue(
|
||||
'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerDetectorSelect',
|
||||
detectorOptionValue
|
||||
);
|
||||
await this.assertDetectorInputValue(detectorOptionValue);
|
||||
},
|
||||
|
||||
async assertChartExsist() {
|
||||
await testSubjects.existOrFail('mlSingleMetricViewerChart');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
import {
|
||||
MachineLearningAnomaliesTableProvider,
|
||||
MachineLearningAnomalyExplorerProvider,
|
||||
MachineLearningAPIProvider,
|
||||
MachineLearningCommonProvider,
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
MachineLearningDataVisualizerProvider,
|
||||
MachineLearningDataVisualizerIndexBasedProvider,
|
||||
MachineLearningJobManagementProvider,
|
||||
MachineLearningJobSelectionProvider,
|
||||
MachineLearningJobSourceSelectionProvider,
|
||||
MachineLearningJobTableProvider,
|
||||
MachineLearningJobTypeSelectionProvider,
|
||||
|
@ -32,6 +34,7 @@ import {
|
|||
export function MachineLearningProvider(context: FtrProviderContext) {
|
||||
const common = MachineLearningCommonProvider(context);
|
||||
|
||||
const anomaliesTable = MachineLearningAnomaliesTableProvider(context);
|
||||
const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context);
|
||||
const api = MachineLearningAPIProvider(context);
|
||||
const customUrls = MachineLearningCustomUrlsProvider(context);
|
||||
|
@ -41,6 +44,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
|
|||
const dataVisualizer = MachineLearningDataVisualizerProvider(context);
|
||||
const dataVisualizerIndexBased = MachineLearningDataVisualizerIndexBasedProvider(context);
|
||||
const jobManagement = MachineLearningJobManagementProvider(context, api);
|
||||
const jobSelection = MachineLearningJobSelectionProvider(context);
|
||||
const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context);
|
||||
const jobTable = MachineLearningJobTableProvider(context);
|
||||
const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context);
|
||||
|
@ -53,8 +57,10 @@ export function MachineLearningProvider(context: FtrProviderContext) {
|
|||
const singleMetricViewer = MachineLearningSingleMetricViewerProvider(context);
|
||||
|
||||
return {
|
||||
anomaliesTable,
|
||||
anomalyExplorer,
|
||||
api,
|
||||
common,
|
||||
customUrls,
|
||||
dataFrameAnalytics,
|
||||
dataFrameAnalyticsCreation,
|
||||
|
@ -62,6 +68,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
|
|||
dataVisualizer,
|
||||
dataVisualizerIndexBased,
|
||||
jobManagement,
|
||||
jobSelection,
|
||||
jobSourceSelection,
|
||||
jobTable,
|
||||
jobTypeSelection,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue