[ML] Advanced wizard functional UI tests (#49480) (#49623)

This PR adds tests for the ML advanced wizard.
This commit is contained in:
Robert Oskamp 2019-10-29 15:15:20 +01:00 committed by GitHub
parent d2b85636c2
commit b1f497fd78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1496 additions and 115 deletions

View file

@ -136,6 +136,7 @@ export class StartDatafeedModal extends Component {
onClose={this.closeModal}
style={{ width: '850px' }}
maxWidth={false}
data-test-subj="mlStartDatafeedModal"
>
<EuiModalHeader>
<EuiModalHeaderTitle>
@ -178,6 +179,7 @@ export class StartDatafeedModal extends Component {
<EuiModalFooter>
<EuiButtonEmpty
onClick={this.closeModal}
data-test-subj="mlStartDatafeedModalCancelButton"
>
<FormattedMessage
id="xpack.ml.jobsList.startDatafeedModal.cancelButtonLabel"
@ -189,6 +191,7 @@ export class StartDatafeedModal extends Component {
onClick={this.save}
isDisabled={startDisabled}
fill
data-test-subj="mlStartDatafeedModalStartButton"
>
<FormattedMessage
id="xpack.ml.jobsList.startDatafeedModal.startButtonLabel"

View file

@ -37,7 +37,7 @@ export const DatafeedStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =
{isCurrentStep && (
<Fragment>
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedDatafeedQueryEditor">
<QueryInput setIsValidQuery={setIsValidQuery} />
</EuiFlexItem>
<EuiFlexItem>

View file

@ -213,7 +213,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
<ModalWrapper onCreateClick={onCreateClick} closeModal={closeModal} saveEnabled={saveEnabled()}>
<Fragment>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedFunctionSelect">
<AggDescription>
<EuiComboBox
singleSelection={{ asPlainText: true }}
@ -224,7 +224,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
/>
</AggDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedFieldSelect">
<FieldDescription>
<EuiComboBox
singleSelection={{ asPlainText: true }}
@ -239,7 +239,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
</EuiFlexGroup>
<EuiHorizontalRule margin="l" />
<EuiFlexGrid columns={2}>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedByFieldSelect">
<ByFieldDescription>
<EuiComboBox
singleSelection={{ asPlainText: true }}
@ -251,7 +251,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
/>
</ByFieldDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedOverFieldSelect">
<OverFieldDescription>
<EuiComboBox
singleSelection={{ asPlainText: true }}
@ -263,7 +263,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
/>
</OverFieldDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedPartitionFieldSelect">
<PartitionFieldDescription>
<EuiComboBox
singleSelection={{ asPlainText: true }}
@ -275,7 +275,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
/>
</PartitionFieldDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlAdvancedExcludeFrequentSelect">
<ExcludeFrequentDescription>
<EuiComboBox
singleSelection={{ asPlainText: true }}
@ -301,6 +301,7 @@ export const AdvancedDetectorModal: FC<Props> = ({
placeholder={descriptionPlaceholder}
value={descriptionOption}
onChange={e => setDescriptionOption(e.target.value)}
data-test-subj="mlAdvancedDetectorDescriptionInput"
/>
</DescriptionDescription>
</EuiFlexItem>

View file

@ -28,7 +28,11 @@ interface Props {
export const ModalWrapper: FC<Props> = ({ onCreateClick, closeModal, saveEnabled, children }) => {
return (
<EuiOverlayMask>
<EuiModal onClose={closeModal} maxWidth={MAX_MODAL_WIDTH}>
<EuiModal
onClose={closeModal}
maxWidth={MAX_MODAL_WIDTH}
data-test-subj="mlCreateDetectorModal"
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
@ -41,14 +45,19 @@ export const ModalWrapper: FC<Props> = ({ onCreateClick, closeModal, saveEnabled
<EuiModalBody>{children}</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty onClick={closeModal}>
<EuiButtonEmpty onClick={closeModal} data-test-subj="mlCreateDetectorModalCancelButton">
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.advancedDetectorModal.cancelButton"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
<EuiButton onClick={onCreateClick} isDisabled={saveEnabled === false} fill>
<EuiButton
onClick={onCreateClick}
isDisabled={saveEnabled === false}
fill
data-test-subj="mlCreateDetectorModalSaveButton"
>
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.advancedDetectorModal.saveButton"
defaultMessage="Save"

View file

@ -25,6 +25,7 @@ import { JobCreatorContext } from '../../../job_creator_context';
import { AdvancedJobCreator } from '../../../../../common/job_creator';
import { Validation } from '../../../../../common/job_validator';
import { detectorToString } from '../../../../../../../util/string_utils';
import { Detector } from '../../../../../common/job_creator/configs';
interface Props {
isActive: boolean;
@ -62,6 +63,7 @@ export const DetectorList: FC<Props> = ({ isActive, onEditJob, onDeleteJob }) =>
defaultMessage: 'Edit',
}
)}
data-test-subj="mlAdvancedDetectorEditButton"
/>
</EuiFlexItem>
<EuiFlexItem>
@ -75,6 +77,7 @@ export const DetectorList: FC<Props> = ({ isActive, onEditJob, onDeleteJob }) =>
defaultMessage: 'Delete',
}
)}
data-test-subj="mlAdvancedDetectorDeleteButton"
/>
</EuiFlexItem>
</EuiFlexGroup>
@ -98,14 +101,16 @@ export const DetectorList: FC<Props> = ({ isActive, onEditJob, onDeleteJob }) =>
<EuiFlexGrid columns={3}>
{detectors.map((d, i) => (
<EuiFlexItem key={i}>
<EuiFlexItem key={i} data-test-subj={`mlAdvancedDetector ${i}`}>
<EuiPanel paddingSize="m">
<EuiFlexGroup>
<EuiFlexItem>
{d.detector_description !== undefined ? (
<div style={{ fontWeight: 'bold' }}>{d.detector_description}</div>
<div style={{ fontWeight: 'bold' }} data-test-subj="mlDetectorDescription">
{d.detector_description}
</div>
) : (
detectorToString(d)
<StringifiedDetector detector={d} />
)}
</EuiFlexItem>
{isActive && (
@ -117,7 +122,7 @@ export const DetectorList: FC<Props> = ({ isActive, onEditJob, onDeleteJob }) =>
{d.detector_description !== undefined && (
<Fragment>
<EuiHorizontalRule margin="s" />
{detectorToString(d)}
<StringifiedDetector detector={d} />
</Fragment>
)}
</EuiPanel>
@ -142,6 +147,7 @@ const NoDetectorsWarning: FC<{ show: boolean }> = ({ show }) => {
defaultMessage: 'No detectors',
})}
iconType="alert"
data-test-subj="mlAdvancedNoDetectorsMessage"
>
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.noDetectorsCallout.message"
@ -166,3 +172,7 @@ const DuplicateDetectorsWarning: FC<{ validation: Validation }> = ({ validation
</Fragment>
);
};
const StringifiedDetector: FC<{ detector: Detector }> = ({ detector }) => {
return <div data-test-subj="mlDetectorIdentifier">{detectorToString(detector)}</div>;
};

View file

@ -35,7 +35,7 @@ export const MetricSelector: FC<Props> = ({
<EuiFlexGroup style={{ maxWidth: MAX_WIDTH }}>
<EuiFlexItem>
<EuiFormRow>
<EuiButton onClick={showModal}>
<EuiButton onClick={showModal} data-test-subj="mlAddDetectorButton">
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.addDetectorButton"
defaultMessage="Add detector"

View file

@ -0,0 +1,805 @@
/*
* 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';
interface Detector {
identifier: string;
function: string;
field?: string;
byField?: string;
overField?: string;
partitionField?: string;
excludeFrequent?: string;
description?: string;
}
interface DatafeedConfig {
queryDelay?: string;
frequency?: string;
scrollSize?: string;
}
interface PickFieldsConfig {
detectors: Detector[];
influencers: string[];
bucketSpan: string;
memoryLimit: string;
categorizationField?: string;
summaryCountField?: string;
}
// type guards
// Detector
const isDetectorWithField = (arg: any): arg is Required<Pick<Detector, 'field'>> => {
return arg.hasOwnProperty('field');
};
const isDetectorWithByField = (arg: any): arg is Required<Pick<Detector, 'byField'>> => {
return arg.hasOwnProperty('byField');
};
const isDetectorWithOverField = (arg: any): arg is Required<Pick<Detector, 'overField'>> => {
return arg.hasOwnProperty('overField');
};
const isDetectorWithPartitionField = (
arg: any
): arg is Required<Pick<Detector, 'partitionField'>> => {
return arg.hasOwnProperty('partitionField');
};
const isDetectorWithExcludeFrequent = (
arg: any
): arg is Required<Pick<Detector, 'excludeFrequent'>> => {
return arg.hasOwnProperty('excludeFrequent');
};
const isDetectorWithDescription = (arg: any): arg is Required<Pick<Detector, 'description'>> => {
return arg.hasOwnProperty('description');
};
// DatafeedConfig
const isDatafeedConfigWithQueryDelay = (
arg: any
): arg is Required<Pick<DatafeedConfig, 'queryDelay'>> => {
return arg.hasOwnProperty('queryDelay');
};
const isDatafeedConfigWithFrequency = (
arg: any
): arg is Required<Pick<DatafeedConfig, 'frequency'>> => {
return arg.hasOwnProperty('frequency');
};
const isDatafeedConfigWithScrollSize = (
arg: any
): arg is Required<Pick<DatafeedConfig, 'scrollSize'>> => {
return arg.hasOwnProperty('scrollSize');
};
// PickFieldsConfig
const isPickFieldsConfigWithCategorizationField = (
arg: any
): arg is Required<Pick<PickFieldsConfig, 'categorizationField'>> => {
return arg.hasOwnProperty('categorizationField');
};
const isPickFieldsConfigWithSummaryCountField = (
arg: any
): arg is Required<Pick<PickFieldsConfig, 'summaryCountField'>> => {
return arg.hasOwnProperty('summaryCountField');
};
// eslint-disable-next-line import/no-default-export
export default function({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const ml = getService('ml');
const defaultValues = {
datafeedQuery: `{
"bool": {
"must": [
{
"match_all": {}
}
]
}
}`,
queryDelay: '60s',
frequency: '450s',
scrollSize: '1000',
};
const testDataList = [
{
suiteTitle: 'with multiple metric detectors and custom datafeed settings',
jobSource: 'ecommerce',
jobId: `ec_advanced_1_${Date.now()}`,
get jobIdClone(): string {
return `${this.jobId}_clone`;
},
jobDescription:
'Create advanced job from ecommerce dataset with multiple metric detectors and custom datafeed settings',
jobGroups: ['automated', 'ecommerce', 'advanced'],
get jobGroupsClone(): string[] {
return [...this.jobGroups, 'clone'];
},
pickFieldsConfig: {
detectors: [
{
identifier: 'high_count',
function: 'high_count',
description: 'high_count detector without split',
} as Detector,
{
identifier: 'mean("products.base_price") by "category.keyword"',
function: 'mean',
field: 'products.base_price',
byField: 'category.keyword',
} as Detector,
{
identifier: 'sum("products.discount_amount") over customer_id',
function: 'sum',
field: 'products.discount_amount',
overField: 'customer_id',
} as Detector,
{
identifier: 'median(total_quantity) partition_field_name=customer_gender',
function: 'median',
field: 'total_quantity',
partitionField: 'customer_gender',
} as Detector,
{
identifier:
'max(total_quantity) by "geoip.continent_name" over customer_id partition_field_name=customer_gender',
function: 'max',
field: 'total_quantity',
byField: 'geoip.continent_name',
overField: 'customer_id',
partitionField: 'customer_gender',
} as Detector,
],
influencers: ['customer_id', 'category.keyword', 'geoip.continent_name', 'customer_gender'],
bucketSpan: '1h',
memoryLimit: '10mb',
} as PickFieldsConfig,
datafeedConfig: {
queryDelay: '55s',
frequency: '350s',
scrollSize: '999',
} as DatafeedConfig,
expected: {
wizard: {
timeField: 'order_date',
},
row: {
recordCount: '4,675',
memoryStatus: 'ok',
jobState: 'closed',
datafeedState: 'stopped',
latestTimestamp: '2019-07-12 23:45:36',
},
counts: {
processed_record_count: '4,675',
processed_field_count: '32,725',
input_bytes: '1.1 MB',
input_field_count: '32,725',
invalid_date_count: '0',
missing_field_count: '0',
out_of_order_timestamp_count: '0',
empty_bucket_count: '0',
sparse_bucket_count: '0',
bucket_count: '743',
earliest_record_timestamp: '2019-06-12 00:04:19',
latest_record_timestamp: '2019-07-12 23:45:36',
input_record_count: '4,675',
latest_bucket_timestamp: '2019-07-12 23:00:00',
},
modelSizeStats: {
result_type: 'model_size_stats',
model_bytes_exceeded: '0',
model_bytes_memory_limit: '10485760',
total_by_field_count: '37',
total_over_field_count: '92',
total_partition_field_count: '8',
bucket_allocation_failures_count: '0',
memory_status: 'ok',
timestamp: '2019-07-12 22:00:00',
},
},
},
{
suiteTitle: 'with categorization detector and default datafeed settings',
jobSource: 'ecommerce',
jobId: `ec_advanced_2_${Date.now()}`,
get jobIdClone(): string {
return `${this.jobId}_clone`;
},
jobDescription:
'Create advanced job from ecommerce dataset with a categorization detector and default datafeed settings',
jobGroups: ['automated', 'ecommerce', 'advanced'],
get jobGroupsClone(): string[] {
return [...this.jobGroups, 'clone'];
},
pickFieldsConfig: {
categorizationField: 'products.product_name',
detectors: [
{
identifier: 'count by mlcategory',
function: 'count',
byField: 'mlcategory',
} as Detector,
],
influencers: ['mlcategory'],
bucketSpan: '12h',
memoryLimit: '100mb',
} as PickFieldsConfig,
datafeedConfig: {} as DatafeedConfig,
expected: {
wizard: {
timeField: 'order_date',
},
row: {
recordCount: '4,675',
memoryStatus: 'ok',
jobState: 'closed',
datafeedState: 'stopped',
latestTimestamp: '2019-07-12 23:45:36',
},
counts: {
processed_record_count: '4,675',
processed_field_count: '4,588',
input_bytes: '4.4 MB',
input_field_count: '6,154',
invalid_date_count: '0',
missing_field_count: '87',
out_of_order_timestamp_count: '0',
empty_bucket_count: '0',
sparse_bucket_count: '0',
bucket_count: '61',
earliest_record_timestamp: '2019-06-12 00:04:19',
latest_record_timestamp: '2019-07-12 23:45:36',
input_record_count: '4,675',
latest_bucket_timestamp: '2019-07-12 12:00:00',
},
modelSizeStats: {
result_type: 'model_size_stats',
model_bytes_exceeded: '0',
model_bytes_memory_limit: '104857600',
total_by_field_count: '3,787',
total_over_field_count: '0',
total_partition_field_count: '2',
bucket_allocation_failures_count: '0',
memory_status: 'ok',
timestamp: '2019-07-12 00:00:00',
},
},
},
];
describe('advanced job', function() {
this.tags(['smoke', 'mlqa']);
before(async () => {
await esArchiver.load('ml/ecommerce');
});
after(async () => {
await esArchiver.unload('ml/ecommerce');
await ml.api.cleanMlIndices();
});
for (const testData of testDataList) {
describe(`${testData.suiteTitle}`, function() {
it('job creation loads the job management page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();
});
it('job creation loads the new job source selection page', async () => {
await ml.jobManagement.navigateToNewJobSourceSelection();
});
it('job creation loads the job type selection page', async () => {
await ml.jobSourceSelection.selectSource(testData.jobSource);
});
it('job creation loads the advanced job wizard page', async () => {
await ml.jobTypeSelection.selectAdvancedJob();
});
it('job creation displays the configure datafeed step', async () => {
await ml.jobWizardCommon.assertConfigureDatafeedSectionExists();
});
it('job creation pre-fills the datafeed query editor', async () => {
await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists();
await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery);
});
it('job creation inputs the query delay', async () => {
await ml.jobWizardAdvanced.assertQueryDelayInputExists();
await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay);
if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) {
await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay);
}
});
it('job creation inputs the frequency', async () => {
await ml.jobWizardAdvanced.assertFrequencyInputExists();
await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency);
if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) {
await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency);
}
});
it('job creation inputs the scroll size', async () => {
await ml.jobWizardAdvanced.assertScrollSizeInputExists();
await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize);
if (isDatafeedConfigWithScrollSize(testData.datafeedConfig)) {
await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize);
}
});
it('job creation pre-fills the time field', async () => {
await ml.jobWizardAdvanced.assertTimeFieldInputExists();
await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]);
});
it('job creation displays the pick fields step', async () => {
await ml.jobWizardCommon.advanceToPickFieldsSection();
});
it('job creation selects the categorization field', async () => {
await ml.jobWizardAdvanced.assertCategorizationFieldInputExists();
if (isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig)) {
await ml.jobWizardAdvanced.selectCategorizationField(
testData.pickFieldsConfig.categorizationField
);
} else {
await ml.jobWizardAdvanced.assertCategorizationFieldSelection([]);
}
});
it('job creation selects the summary count field', async () => {
await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists();
if (isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)) {
await ml.jobWizardAdvanced.selectSummaryCountField(
testData.pickFieldsConfig.summaryCountField
);
} else {
await ml.jobWizardAdvanced.assertSummaryCountFieldSelection([]);
}
});
it('job creation adds detectors', async () => {
for (const detector of testData.pickFieldsConfig.detectors) {
await ml.jobWizardAdvanced.openCreateDetectorModal();
await ml.jobWizardAdvanced.assertDetectorFunctionInputExists();
await ml.jobWizardAdvanced.assertDetectorFunctionSelection([]);
await ml.jobWizardAdvanced.assertDetectorFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorFieldSelection([]);
await ml.jobWizardAdvanced.assertDetectorByFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorByFieldSelection([]);
await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorOverFieldSelection([]);
await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection([]);
await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists();
await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection([]);
await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists();
await ml.jobWizardAdvanced.assertDetectorDescriptionValue('');
await ml.jobWizardAdvanced.selectDetectorFunction(detector.function);
if (isDetectorWithField(detector)) {
await ml.jobWizardAdvanced.selectDetectorField(detector.field);
}
if (isDetectorWithByField(detector)) {
await ml.jobWizardAdvanced.selectDetectorByField(detector.byField);
}
if (isDetectorWithOverField(detector)) {
await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField);
}
if (isDetectorWithPartitionField(detector)) {
await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField);
}
if (isDetectorWithExcludeFrequent(detector)) {
await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent);
}
if (isDetectorWithDescription(detector)) {
await ml.jobWizardAdvanced.setDetectorDescription(detector.description);
}
await ml.jobWizardAdvanced.confirmAddDetectorModal();
}
});
it('job creation displays detector entries', async () => {
for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) {
await ml.jobWizardAdvanced.assertDetectorEntryExists(
index,
detector.identifier,
isDetectorWithDescription(detector) ? detector.description : undefined
);
}
});
it('job creation inputs the bucket span', async () => {
await ml.jobWizardCommon.assertBucketSpanInputExists();
await ml.jobWizardCommon.setBucketSpan(testData.pickFieldsConfig.bucketSpan);
});
it('job creation inputs influencers', async () => {
await ml.jobWizardCommon.assertInfluencerInputExists();
await ml.jobWizardCommon.assertInfluencerSelection([]);
for (const influencer of testData.pickFieldsConfig.influencers) {
await ml.jobWizardCommon.addInfluencer(influencer);
}
});
it('job creation inputs the model memory limit', async () => {
await ml.jobWizardCommon.assertModelMemoryLimitInputExists({
withAdvancedSection: false,
});
await ml.jobWizardCommon.setModelMemoryLimit(testData.pickFieldsConfig.memoryLimit, {
withAdvancedSection: false,
});
});
it('job creation displays the job details step', async () => {
await ml.jobWizardCommon.advanceToJobDetailsSection();
});
it('job creation inputs the job id', async () => {
await ml.jobWizardCommon.assertJobIdInputExists();
await ml.jobWizardCommon.setJobId(testData.jobId);
});
it('job creation inputs the job description', async () => {
await ml.jobWizardCommon.assertJobDescriptionInputExists();
await ml.jobWizardCommon.setJobDescription(testData.jobDescription);
});
it('job creation inputs job groups', async () => {
await ml.jobWizardCommon.assertJobGroupInputExists();
for (const jobGroup of testData.jobGroups) {
await ml.jobWizardCommon.addJobGroup(jobGroup);
}
await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups);
});
it('job creation displays the model plot switch', async () => {
await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false });
});
it('job creation enables the dedicated index switch', async () => {
await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false });
await ml.jobWizardCommon.activateDedicatedIndexSwitch({ withAdvancedSection: false });
});
it('job creation displays the validation step', async () => {
await ml.jobWizardCommon.advanceToValidationSection();
});
it('job creation displays the summary step', async () => {
await ml.jobWizardCommon.advanceToSummarySection();
});
it('job creation creates the job and finishes processing', async () => {
await ml.jobWizardCommon.assertCreateJobButtonExists();
await ml.jobWizardAdvanced.createJob();
await ml.jobManagement.assertStartDatafeedModalExists();
await ml.jobManagement.confirmStartDatafeedModal();
await ml.jobManagement.waitForJobCompletion(testData.jobId);
});
it('job creation displays the created job in the job list', async () => {
await ml.jobTable.refreshJobList();
await ml.jobTable.filterWithSearchString(testData.jobId);
const rows = await ml.jobTable.parseJobTable();
expect(rows.filter(row => row.id === testData.jobId)).to.have.length(1);
});
it('job creation displays details for the created job in the job list', async () => {
await ml.jobTable.assertJobRowFields(testData.jobId, {
id: testData.jobId,
description: testData.jobDescription,
jobGroups: [...new Set(testData.jobGroups)].sort(),
recordCount: testData.expected.row.recordCount,
memoryStatus: testData.expected.row.memoryStatus,
jobState: testData.expected.row.jobState,
datafeedState: testData.expected.row.datafeedState,
latestTimestamp: testData.expected.row.latestTimestamp,
});
await ml.jobTable.assertJobRowDetailsCounts(
testData.jobId,
{
job_id: testData.jobId,
processed_record_count: testData.expected.counts.processed_record_count,
processed_field_count: testData.expected.counts.processed_field_count,
input_bytes: testData.expected.counts.input_bytes,
input_field_count: testData.expected.counts.input_field_count,
invalid_date_count: testData.expected.counts.invalid_date_count,
missing_field_count: testData.expected.counts.missing_field_count,
out_of_order_timestamp_count: testData.expected.counts.out_of_order_timestamp_count,
empty_bucket_count: testData.expected.counts.empty_bucket_count,
sparse_bucket_count: testData.expected.counts.sparse_bucket_count,
bucket_count: testData.expected.counts.bucket_count,
earliest_record_timestamp: testData.expected.counts.earliest_record_timestamp,
latest_record_timestamp: testData.expected.counts.latest_record_timestamp,
input_record_count: testData.expected.counts.input_record_count,
latest_bucket_timestamp: testData.expected.counts.latest_bucket_timestamp,
},
{
job_id: testData.jobId,
result_type: testData.expected.modelSizeStats.result_type,
model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded,
model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit,
total_by_field_count: testData.expected.modelSizeStats.total_by_field_count,
total_over_field_count: testData.expected.modelSizeStats.total_over_field_count,
total_partition_field_count:
testData.expected.modelSizeStats.total_partition_field_count,
bucket_allocation_failures_count:
testData.expected.modelSizeStats.bucket_allocation_failures_count,
memory_status: testData.expected.modelSizeStats.memory_status,
timestamp: testData.expected.modelSizeStats.timestamp,
}
);
});
it('job creation has detector results', async () => {
for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) {
await ml.api.assertDetectorResultsExist(testData.jobId, i);
}
});
it('job cloning clicks the clone action and loads the advanced wizard', async () => {
await ml.jobTable.clickCloneJobAction(testData.jobId);
await ml.jobTypeSelection.assertAdvancedJobWizardOpen();
});
it('job cloning displays the configure datafeed step', async () => {
await ml.jobWizardCommon.assertConfigureDatafeedSectionExists();
});
it('job cloning pre-fills the datafeed query editor', async () => {
await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists();
await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery);
});
it('job cloning pre-fills the query delay', async () => {
await ml.jobWizardAdvanced.assertQueryDelayInputExists();
if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) {
await ml.jobWizardAdvanced.assertQueryDelayValue(testData.datafeedConfig.queryDelay);
}
});
it('job cloning pre-fills the frequency', async () => {
await ml.jobWizardAdvanced.assertFrequencyInputExists();
if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) {
await ml.jobWizardAdvanced.assertFrequencyValue(testData.datafeedConfig.frequency);
}
});
it('job cloning pre-fills the scroll size', async () => {
await ml.jobWizardAdvanced.assertScrollSizeInputExists();
await ml.jobWizardAdvanced.assertScrollSizeValue(
isDatafeedConfigWithScrollSize(testData.datafeedConfig)
? testData.datafeedConfig.scrollSize
: defaultValues.scrollSize
);
});
it('job creation pre-fills the time field', async () => {
await ml.jobWizardAdvanced.assertTimeFieldInputExists();
await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]);
});
it('job cloning displays the pick fields step', async () => {
await ml.jobWizardCommon.advanceToPickFieldsSection();
});
it('job cloning pre-fills the categorization field', async () => {
await ml.jobWizardAdvanced.assertCategorizationFieldInputExists();
await ml.jobWizardAdvanced.assertCategorizationFieldSelection(
isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig)
? [testData.pickFieldsConfig.categorizationField]
: []
);
});
it('job cloning pre-fills the summary count field', async () => {
await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists();
await ml.jobWizardAdvanced.assertSummaryCountFieldSelection(
isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)
? [testData.pickFieldsConfig.summaryCountField]
: []
);
});
it('job cloning pre-fills detectors', async () => {
for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) {
await ml.jobWizardAdvanced.assertDetectorEntryExists(
index,
detector.identifier,
isDetectorWithDescription(detector) ? detector.description : undefined
);
await ml.jobWizardAdvanced.clickEditDetector(index);
await ml.jobWizardAdvanced.assertDetectorFunctionInputExists();
await ml.jobWizardAdvanced.assertDetectorFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorByFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists();
await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists();
await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists();
await ml.jobWizardAdvanced.assertDetectorFunctionSelection([detector.function]);
await ml.jobWizardAdvanced.assertDetectorFieldSelection(
isDetectorWithField(detector) ? [detector.field] : []
);
await ml.jobWizardAdvanced.assertDetectorByFieldSelection(
isDetectorWithByField(detector) ? [detector.byField] : []
);
await ml.jobWizardAdvanced.assertDetectorOverFieldSelection(
isDetectorWithOverField(detector) ? [detector.overField] : []
);
await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection(
isDetectorWithPartitionField(detector) ? [detector.partitionField] : []
);
await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection(
isDetectorWithExcludeFrequent(detector) ? [detector.excludeFrequent] : []
);
// Currently, a description different form the identifier is generated for detectors with partition field
await ml.jobWizardAdvanced.assertDetectorDescriptionValue(
isDetectorWithDescription(detector)
? detector.description
: detector.identifier.replace('partition_field_name', 'partitionfield')
);
await ml.jobWizardAdvanced.cancelAddDetectorModal();
}
});
it('job cloning pre-fills the bucket span', async () => {
await ml.jobWizardCommon.assertBucketSpanInputExists();
await ml.jobWizardCommon.assertBucketSpanValue(testData.pickFieldsConfig.bucketSpan);
});
it('job cloning pre-fills influencers', async () => {
await ml.jobWizardCommon.assertInfluencerInputExists();
await ml.jobWizardCommon.assertInfluencerSelection(testData.pickFieldsConfig.influencers);
});
it('job cloning pre-fills the model memory limit', async () => {
await ml.jobWizardCommon.assertModelMemoryLimitInputExists({
withAdvancedSection: false,
});
await ml.jobWizardCommon.assertModelMemoryLimitValue(
testData.pickFieldsConfig.memoryLimit,
{
withAdvancedSection: false,
}
);
});
it('job cloning displays the job details step', async () => {
await ml.jobWizardCommon.advanceToJobDetailsSection();
});
it('job cloning does not pre-fill the job id', async () => {
await ml.jobWizardCommon.assertJobIdInputExists();
await ml.jobWizardCommon.assertJobIdValue('');
});
it('job cloning inputs the clone job id', async () => {
await ml.jobWizardCommon.setJobId(testData.jobIdClone);
});
it('job cloning pre-fills the job description', async () => {
await ml.jobWizardCommon.assertJobDescriptionInputExists();
await ml.jobWizardCommon.assertJobDescriptionValue(testData.jobDescription);
});
it('job cloning pre-fills job groups', async () => {
await ml.jobWizardCommon.assertJobGroupInputExists();
await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups);
});
it('job cloning inputs the clone job group', async () => {
await ml.jobWizardCommon.assertJobGroupInputExists();
await ml.jobWizardCommon.addJobGroup('clone');
await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroupsClone);
});
it('job cloning pre-fills the model plot switch', async () => {
await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false });
await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false, {
withAdvancedSection: false,
});
});
it('job cloning pre-fills the dedicated index switch', async () => {
await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false });
await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true, {
withAdvancedSection: false,
});
});
it('job cloning displays the validation step', async () => {
await ml.jobWizardCommon.advanceToValidationSection();
});
it('job cloning displays the summary step', async () => {
await ml.jobWizardCommon.advanceToSummarySection();
});
it('job cloning creates the job and finishes processing', async () => {
await ml.jobWizardCommon.assertCreateJobButtonExists();
await ml.jobWizardAdvanced.createJob();
await ml.jobManagement.assertStartDatafeedModalExists();
await ml.jobManagement.confirmStartDatafeedModal();
await ml.jobManagement.waitForJobCompletion(testData.jobIdClone);
});
it('job cloning displays the created job in the job list', async () => {
await ml.jobTable.refreshJobList();
await ml.jobTable.filterWithSearchString(testData.jobIdClone);
const rows = await ml.jobTable.parseJobTable();
expect(rows.filter(row => row.id === testData.jobIdClone)).to.have.length(1);
});
it('job creation displays details for the created job in the job list', async () => {
await ml.jobTable.assertJobRowFields(testData.jobIdClone, {
id: testData.jobIdClone,
description: testData.jobDescription,
jobGroups: [...new Set(testData.jobGroupsClone)].sort(),
recordCount: testData.expected.row.recordCount,
memoryStatus: testData.expected.row.memoryStatus,
jobState: testData.expected.row.jobState,
datafeedState: testData.expected.row.datafeedState,
latestTimestamp: testData.expected.row.latestTimestamp,
});
await ml.jobTable.assertJobRowDetailsCounts(
testData.jobIdClone,
{
job_id: testData.jobIdClone,
processed_record_count: testData.expected.counts.processed_record_count,
processed_field_count: testData.expected.counts.processed_field_count,
input_bytes: testData.expected.counts.input_bytes,
input_field_count: testData.expected.counts.input_field_count,
invalid_date_count: testData.expected.counts.invalid_date_count,
missing_field_count: testData.expected.counts.missing_field_count,
out_of_order_timestamp_count: testData.expected.counts.out_of_order_timestamp_count,
empty_bucket_count: testData.expected.counts.empty_bucket_count,
sparse_bucket_count: testData.expected.counts.sparse_bucket_count,
bucket_count: testData.expected.counts.bucket_count,
earliest_record_timestamp: testData.expected.counts.earliest_record_timestamp,
latest_record_timestamp: testData.expected.counts.latest_record_timestamp,
input_record_count: testData.expected.counts.input_record_count,
latest_bucket_timestamp: testData.expected.counts.latest_bucket_timestamp,
},
{
job_id: testData.jobIdClone,
result_type: testData.expected.modelSizeStats.result_type,
model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded,
model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit,
total_by_field_count: testData.expected.modelSizeStats.total_by_field_count,
total_over_field_count: testData.expected.modelSizeStats.total_over_field_count,
total_partition_field_count:
testData.expected.modelSizeStats.total_partition_field_count,
bucket_allocation_failures_count:
testData.expected.modelSizeStats.bucket_allocation_failures_count,
memory_status: testData.expected.modelSizeStats.memory_status,
timestamp: testData.expected.modelSizeStats.timestamp,
}
);
});
it('job creation has detector results', async () => {
for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) {
await ml.api.assertDetectorResultsExist(testData.jobIdClone, i);
}
});
});
}
});
}

View file

@ -11,5 +11,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./multi_metric_job'));
loadTestFile(require.resolve('./population_job'));
loadTestFile(require.resolve('./saved_search_job'));
loadTestFile(require.resolve('./advanced_job'));
});
}

View file

@ -221,6 +221,12 @@ export default function({ getService }: FtrProviderContext) {
);
});
it('job creation has detector results', async () => {
for (let i = 0; i < aggAndFieldIdentifiers.length; i++) {
await ml.api.assertDetectorResultsExist(jobId, i);
}
});
it('job cloning clicks the clone action and loads the multi metric wizard', async () => {
await ml.jobTable.clickCloneJobAction(jobId);
await ml.jobTypeSelection.assertMultiMetricJobWizardOpen();
@ -258,7 +264,7 @@ export default function({ getService }: FtrProviderContext) {
it('job cloning pre-fills the split field', async () => {
await ml.jobWizardMultiMetric.assertSplitFieldInputExists();
await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField);
await ml.jobWizardMultiMetric.assertSplitFieldSelection([splitField]);
});
it('job cloning pre-fills influencers', async () => {
@ -351,5 +357,11 @@ export default function({ getService }: FtrProviderContext) {
getExpectedModelSizeStats(jobIdClone)
);
});
it('job cloning has detector results', async () => {
for (let i = 0; i < aggAndFieldIdentifiers.length; i++) {
await ml.api.assertDetectorResultsExist(jobId, i);
}
});
});
}

View file

@ -248,6 +248,12 @@ export default function({ getService }: FtrProviderContext) {
);
});
it('job creation has detector results', async () => {
for (let i = 0; i < detectors.length; i++) {
await ml.api.assertDetectorResultsExist(jobId, i);
}
});
it('job cloning clicks the clone action and loads the population wizard', async () => {
await ml.jobTable.clickCloneJobAction(jobId);
await ml.jobTypeSelection.assertPopulationJobWizardOpen();
@ -275,14 +281,16 @@ export default function({ getService }: FtrProviderContext) {
it('job cloning pre-fills the population field', async () => {
await ml.jobWizardPopulation.assertPopulationFieldInputExists();
await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField);
await ml.jobWizardPopulation.assertPopulationFieldSelection([populationField]);
});
it('job cloning pre-fills detectors and shows preview with split cards', async () => {
for (const [index, detector] of detectors.entries()) {
await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER');
await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, detector.splitField);
await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, [
detector.splitField,
]);
await ml.jobWizardPopulation.assertDetectorSplitExists(index);
await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle(
index,
@ -387,5 +395,11 @@ export default function({ getService }: FtrProviderContext) {
getExpectedModelSizeStats(jobIdClone)
);
});
it('job cloning has detector results', async () => {
for (let i = 0; i < detectors.length; i++) {
await ml.api.assertDetectorResultsExist(jobId, i);
}
});
});
}

View file

@ -468,6 +468,12 @@ export default function({ getService }: FtrProviderContext) {
}
);
});
it('has detector results', async () => {
for (let i = 0; i < testData.aggAndFieldIdentifiers.length; i++) {
await ml.api.assertDetectorResultsExist(testData.jobId, i);
}
});
});
}
});

View file

@ -202,6 +202,10 @@ export default function({ getService }: FtrProviderContext) {
);
});
it('job creation has detector results', async () => {
await ml.api.assertDetectorResultsExist(jobId, 0);
});
it('job cloning clicks the clone action and loads the single metric wizard', async () => {
await ml.jobTable.clickCloneJobAction(jobId);
await ml.jobTypeSelection.assertSingleMetricJobWizardOpen();
@ -319,6 +323,10 @@ export default function({ getService }: FtrProviderContext) {
);
});
it('job cloning has detector results', async () => {
await ml.api.assertDetectorResultsExist(jobId, 0);
});
it('job deletion has results for the job before deletion', async () => {
await ml.api.assertJobResultsExist(jobIdClone);
});

View file

@ -21,24 +21,6 @@
"airline": {
"type": "keyword"
},
"host": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"path": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"responsetime": {
"type": "float"
},
@ -1102,4 +1084,4 @@
}
}
}
}
}

View file

@ -8,10 +8,13 @@ import expect from '@kbn/expect';
import { isEmpty } from 'lodash';
import { FtrProviderContext } from '../../ftr_provider_context';
import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states';
export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
const es = getService('es');
const log = getService('log');
const retry = getService('retry');
const esSupertest = getService('esSupertest');
return {
async hasJobResults(jobId: string): Promise<boolean> {
@ -54,6 +57,54 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
);
},
async hasDetectorResults(jobId: string, detectorIndex: number): Promise<boolean> {
const response = await es.search({
index: '.ml-anomalies-*',
body: {
size: 1,
query: {
bool: {
must: [
{
match: {
job_id: jobId,
},
},
{
match: {
result_type: 'record',
},
},
{
match: {
detector_index: detectorIndex,
},
},
],
},
},
},
});
return response.hits.hits.length > 0;
},
async assertDetectorResultsExist(jobId: string, detectorIndex: number) {
await retry.waitForWithTimeout(
`results for detector ${detectorIndex} on job ${jobId} to exist`,
30 * 1000,
async () => {
if ((await this.hasDetectorResults(jobId, detectorIndex)) === true) {
return true;
} else {
throw new Error(
`expected results for detector ${detectorIndex} on job '${jobId}' to exist`
);
}
}
);
},
async deleteIndices(indices: string) {
log.debug(`Deleting indices: '${indices}'...`);
const deleteResponse = await es.indices.delete({
@ -79,5 +130,61 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
async cleanMlIndices() {
await this.deleteIndices('.ml-*');
},
async getJobState(jobId: string): Promise<JOB_STATE> {
log.debug(`Fetching job state for job ${jobId}`);
const jobStats = await esSupertest
.get(`/_ml/anomaly_detectors/${jobId}/_stats`)
.expect(200)
.then((res: any) => res.body);
expect(jobStats.jobs).to.have.length(1);
const state: JOB_STATE = jobStats.jobs[0].state;
return state;
},
async waitForJobState(jobId: string, expectedJobState: JOB_STATE) {
await retry.waitForWithTimeout(
`job state to be ${expectedJobState}`,
2 * 60 * 1000,
async () => {
const state = await this.getJobState(jobId);
if (state === expectedJobState) {
return true;
} else {
throw new Error(`expected job state to be ${expectedJobState} but got ${state}`);
}
}
);
},
async getDatafeedState(datafeedId: string): Promise<DATAFEED_STATE> {
log.debug(`Fetching datafeed state for datafeed ${datafeedId}`);
const datafeedStats = await esSupertest
.get(`/_ml/datafeeds/${datafeedId}/_stats`)
.expect(200)
.then((res: any) => res.body);
expect(datafeedStats.datafeeds).to.have.length(1);
const state: DATAFEED_STATE = datafeedStats.datafeeds[0].state;
return state;
},
async waitForDatafeedState(datafeedId: string, expectedDatafeedState: DATAFEED_STATE) {
await retry.waitForWithTimeout(
`datafeed state to be ${expectedDatafeedState}`,
2 * 60 * 1000,
async () => {
const state = await this.getDatafeedState(datafeedId);
if (state === expectedDatafeedState) {
return true;
} else {
throw new Error(`expected job state to be ${expectedDatafeedState} but got ${state}`);
}
}
);
},
};
}

View file

@ -12,6 +12,7 @@ export { MachineLearningJobManagementProvider } from './job_management';
export { MachineLearningJobSourceSelectionProvider } from './job_source_selection';
export { MachineLearningJobTableProvider } from './job_table';
export { MachineLearningJobTypeSelectionProvider } from './job_type_selection';
export { MachineLearningJobWizardAdvancedProvider } from './job_wizard_advanced';
export { MachineLearningJobWizardCommonProvider } from './job_wizard_common';
export { MachineLearningJobWizardMultiMetricProvider } from './job_wizard_multi_metric';
export { MachineLearningJobWizardPopulationProvider } from './job_wizard_population';

View file

@ -3,11 +3,19 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ProvidedType } from '@kbn/test/types/ftr';
import { FtrProviderContext } from '../../ftr_provider_context';
import { MachineLearningAPIProvider } from './api';
export function MachineLearningJobManagementProvider({ getService }: FtrProviderContext) {
import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states';
export function MachineLearningJobManagementProvider(
{ getService }: FtrProviderContext,
mlApi: ProvidedType<typeof MachineLearningAPIProvider>
) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
return {
async navigateToNewJobSourceSelection() {
@ -26,5 +34,22 @@ export function MachineLearningJobManagementProvider({ getService }: FtrProvider
async assertJobStatsBarExists() {
await testSubjects.existOrFail('~mlJobStatsBar');
},
async assertStartDatafeedModalExists() {
// this retry can be removed as soon as #48734 is merged
await retry.tryForTime(5000, async () => {
await testSubjects.existOrFail('mlStartDatafeedModal');
});
},
async confirmStartDatafeedModal() {
await testSubjects.click('mlStartDatafeedModalStartButton');
await testSubjects.missingOrFail('mlStartDatafeedModal');
},
async waitForJobCompletion(jobId: string) {
await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DATAFEED_STATE.STOPPED);
await mlApi.waitForJobState(jobId, JOB_STATE.CLOSED);
},
};
}

View file

@ -157,6 +157,11 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte
});
}
public async refreshJobList() {
await testSubjects.click('mlRefreshJobListButton');
await this.waitForJobsToLoad();
}
public async waitForJobsToLoad() {
await testSubjects.existOrFail('~mlJobListTable', { timeout: 60 * 1000 });
await testSubjects.existOrFail('mlJobListTable loaded', { timeout: 30 * 1000 });
@ -206,7 +211,7 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte
}
public async clickActionsMenu(jobId: string) {
retry.tryForTime(30 * 1000, async () => {
await retry.tryForTime(30 * 1000, async () => {
if (!(await testSubjects.exists('mlActionButtonDeleteJob'))) {
await testSubjects.click(this.rowSelector(jobId, 'euiCollapsedItemActionsButton'));
await testSubjects.existOrFail('mlActionButtonDeleteJob', { timeout: 5000 });

View file

@ -36,5 +36,14 @@ export function MachineLearningJobTypeSelectionProvider({ getService }: FtrProvi
async assertPopulationJobWizardOpen() {
await testSubjects.existOrFail('mlPageJobWizard population');
},
async selectAdvancedJob() {
await testSubjects.clickWhenNotDisabled('mlJobTypeLinkAdvancedJob');
await this.assertAdvancedJobWizardOpen();
},
async assertAdvancedJobWizardOpen() {
await testSubjects.existOrFail('mlPageJobWizard advanced');
},
};
}

View file

@ -0,0 +1,315 @@
/*
* 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 MachineLearningJobWizardAdvancedProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const comboBox = getService('comboBox');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const aceEditor = getService('aceEditor');
return {
async getValueOrPlaceholder(inputLocator: string): Promise<string> {
const value = await testSubjects.getAttribute(inputLocator, 'value');
if (value !== '') {
return value;
} else {
return await testSubjects.getAttribute(inputLocator, 'placeholder');
}
},
async assertDatafeedQueryEditorExists() {
await testSubjects.existOrFail('mlAdvancedDatafeedQueryEditor > codeEditorContainer');
},
async assertDatafeedQueryEditorValue(expectedValue: string) {
const actualValue = await aceEditor.getValue(
'mlAdvancedDatafeedQueryEditor > codeEditorContainer'
);
expect(actualValue).to.eql(expectedValue);
},
async assertQueryDelayInputExists() {
await testSubjects.existOrFail('mlJobWizardInputQueryDelay');
},
async assertQueryDelayValue(expectedValue: string) {
const actualQueryDelay = await this.getValueOrPlaceholder('mlJobWizardInputQueryDelay');
expect(actualQueryDelay).to.eql(expectedValue);
},
async setQueryDelay(queryDelay: string) {
await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, {
clearWithKeyboard: true,
typeCharByChar: true,
});
await this.assertQueryDelayValue(queryDelay);
},
async assertFrequencyInputExists() {
await testSubjects.existOrFail('mlJobWizardInputFrequency');
},
async assertFrequencyValue(expectedValue: string) {
const actualFrequency = await this.getValueOrPlaceholder('mlJobWizardInputFrequency');
expect(actualFrequency).to.eql(expectedValue);
},
async setFrequency(frequency: string) {
await testSubjects.setValue('mlJobWizardInputFrequency', frequency, {
clearWithKeyboard: true,
typeCharByChar: true,
});
await this.assertFrequencyValue(frequency);
},
async assertScrollSizeInputExists() {
await testSubjects.existOrFail('mlJobWizardInputScrollSize');
},
async assertScrollSizeValue(expectedValue: string) {
const actualScrollSize = await this.getValueOrPlaceholder('mlJobWizardInputScrollSize');
expect(actualScrollSize).to.eql(expectedValue);
},
async setScrollSize(scrollSize: string) {
await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, {
clearWithKeyboard: true,
typeCharByChar: true,
});
await this.assertScrollSizeValue(scrollSize);
},
async assertTimeFieldInputExists() {
await testSubjects.existOrFail('mlTimeFieldNameSelect > comboBoxInput');
},
async assertTimeFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlTimeFieldNameSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectTimeField(identifier: string) {
await comboBox.set('mlTimeFieldNameSelect > comboBoxInput', identifier);
await this.assertTimeFieldSelection([identifier]);
},
async assertCategorizationFieldInputExists() {
await testSubjects.existOrFail('mlCategorizationFieldNameSelect > comboBoxInput');
},
async assertCategorizationFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlCategorizationFieldNameSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectCategorizationField(identifier: string) {
await comboBox.set('mlCategorizationFieldNameSelect > comboBoxInput', identifier);
await this.assertCategorizationFieldSelection([identifier]);
},
async assertSummaryCountFieldInputExists() {
await testSubjects.existOrFail('mlSummaryCountFieldNameSelect > comboBoxInput');
},
async assertSummaryCountFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlSummaryCountFieldNameSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectSummaryCountField(identifier: string) {
await comboBox.set('mlSummaryCountFieldNameSelect > comboBoxInput', identifier);
await this.assertSummaryCountFieldSelection([identifier]);
},
async assertAddDetectorButtonExists() {
await testSubjects.existOrFail('mlAddDetectorButton');
},
async openCreateDetectorModal() {
await testSubjects.click('mlAddDetectorButton');
await this.assertCreateDetectorModalExists();
},
async assertCreateDetectorModalExists() {
// this retry can be removed as soon as #48734 is merged
await retry.tryForTime(5000, async () => {
await testSubjects.existOrFail('mlCreateDetectorModal');
});
},
async assertDetectorFunctionInputExists() {
await testSubjects.existOrFail('mlAdvancedFunctionSelect > comboBoxInput');
},
async assertDetectorFunctionSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlAdvancedFunctionSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorFunction(identifier: string) {
await comboBox.set('mlAdvancedFunctionSelect > comboBoxInput', identifier);
await this.assertDetectorFunctionSelection([identifier]);
},
async assertDetectorFieldInputExists() {
await testSubjects.existOrFail('mlAdvancedFieldSelect > comboBoxInput');
},
async assertDetectorFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlAdvancedFieldSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorField(identifier: string) {
await comboBox.set('mlAdvancedFieldSelect > comboBoxInput', identifier);
await this.assertDetectorFieldSelection([identifier]);
},
async assertDetectorByFieldInputExists() {
await testSubjects.existOrFail('mlAdvancedByFieldSelect > comboBoxInput');
},
async assertDetectorByFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlAdvancedByFieldSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorByField(identifier: string) {
await comboBox.set('mlAdvancedByFieldSelect > comboBoxInput', identifier);
await this.assertDetectorByFieldSelection([identifier]);
},
async assertDetectorOverFieldInputExists() {
await testSubjects.existOrFail('mlAdvancedOverFieldSelect > comboBoxInput');
},
async assertDetectorOverFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlAdvancedOverFieldSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorOverField(identifier: string) {
await comboBox.set('mlAdvancedOverFieldSelect > comboBoxInput', identifier);
await this.assertDetectorOverFieldSelection([identifier]);
},
async assertDetectorPartitionFieldInputExists() {
await testSubjects.existOrFail('mlAdvancedPartitionFieldSelect > comboBoxInput');
},
async assertDetectorPartitionFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlAdvancedPartitionFieldSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorPartitionField(identifier: string) {
await comboBox.set('mlAdvancedPartitionFieldSelect > comboBoxInput', identifier);
await this.assertDetectorPartitionFieldSelection([identifier]);
},
async assertDetectorExcludeFrequentInputExists() {
await testSubjects.existOrFail('mlAdvancedExcludeFrequentSelect > comboBoxInput');
},
async assertDetectorExcludeFrequentSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlAdvancedExcludeFrequentSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorExcludeFrequent(identifier: string) {
await comboBox.set('mlAdvancedExcludeFrequentSelect > comboBoxInput', identifier);
await this.assertDetectorExcludeFrequentSelection([identifier]);
},
async assertDetectorDescriptionInputExists() {
await testSubjects.existOrFail('mlAdvancedDetectorDescriptionInput');
},
async assertDetectorDescriptionValue(expectedValue: string) {
const actualDetectorDescription = await testSubjects.getAttribute(
'mlAdvancedDetectorDescriptionInput',
'value'
);
expect(actualDetectorDescription).to.eql(expectedValue);
},
async setDetectorDescription(description: string) {
await testSubjects.setValue('mlAdvancedDetectorDescriptionInput', description, {
clearWithKeyboard: true,
});
await this.assertDetectorDescriptionValue(description);
},
async confirmAddDetectorModal() {
await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalSaveButton');
await testSubjects.missingOrFail('mlCreateDetectorModal');
},
async cancelAddDetectorModal() {
await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalCancelButton');
await testSubjects.missingOrFail('mlCreateDetectorModal');
},
async assertDetectorEntryExists(
detectorIndex: number,
expectedDetectorName: string,
expectedDetectorDescription?: string
) {
await testSubjects.existOrFail(`mlAdvancedDetector ${detectorIndex}`);
const actualDetectorIdentifier = await testSubjects.getVisibleText(
`mlAdvancedDetector ${detectorIndex} > mlDetectorIdentifier`
);
expect(actualDetectorIdentifier).to.eql(expectedDetectorName);
if (expectedDetectorDescription !== undefined) {
const actualDetectorDescription = await testSubjects.getVisibleText(
`mlAdvancedDetector ${detectorIndex} > mlDetectorDescription`
);
expect(actualDetectorDescription).to.eql(expectedDetectorDescription);
}
},
async clickEditDetector(detectorIndex: number) {
await testSubjects.click(
`mlAdvancedDetector ${detectorIndex} > mlAdvancedDetectorEditButton`
);
await this.assertCreateDetectorModalExists();
},
async createJob() {
await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
// this retry can be removed as soon as #48734 is merged
await retry.tryForTime(5000, async () => {
await testSubjects.existOrFail('mlStartDatafeedModal');
});
},
};
}

View file

@ -12,6 +12,15 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
const retry = getService('retry');
const testSubjects = getService('testSubjects');
interface SectionOptions {
withAdvancedSection: boolean;
}
function advancedSectionSelector(subSelector?: string) {
const subj = 'mlJobWizardAdvancedSection';
return !subSelector ? subj : `${subj} > ${subSelector}`;
}
return {
async clickNextButton() {
await testSubjects.existOrFail('mlJobWizardNavButtonNext');
@ -38,6 +47,10 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
await testSubjects.existOrFail('mlJobWizardStepTitleSummary');
},
async assertConfigureDatafeedSectionExists() {
await testSubjects.existOrFail('mlJobWizardStepTitleConfigureDatafeed');
},
async advanceToPickFieldsSection() {
await this.clickNextButton();
await this.assertPickFieldsSectionExists();
@ -153,75 +166,127 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
expect(await this.getSelectedJobGroups()).to.contain(jobGroup);
},
async assertModelPlotSwitchExists() {
await this.ensureAdvancedSectionOpen();
await testSubjects.existOrFail('mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot', {
allowHidden: true,
async assertModelPlotSwitchExists(
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
let subj = 'mlJobWizardSwitchModelPlot';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
await testSubjects.existOrFail(subj, { allowHidden: true });
},
async getModelPlotSwitchCheckedState(
sectionOptions: SectionOptions = { withAdvancedSection: true }
): Promise<boolean> {
let subj = 'mlJobWizardSwitchModelPlot';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
return await testSubjects.isSelected(subj);
},
async assertModelPlotSwitchCheckedState(
expectedValue: boolean,
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
const actualCheckedState = await this.getModelPlotSwitchCheckedState({
withAdvancedSection: sectionOptions.withAdvancedSection,
});
expect(actualCheckedState).to.eql(expectedValue);
},
async assertDedicatedIndexSwitchExists(
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
let subj = 'mlJobWizardSwitchUseDedicatedIndex';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
await testSubjects.existOrFail(subj, { allowHidden: true });
},
async getDedicatedIndexSwitchCheckedState(
sectionOptions: SectionOptions = { withAdvancedSection: true }
): Promise<boolean> {
let subj = 'mlJobWizardSwitchUseDedicatedIndex';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
return await testSubjects.isSelected(subj);
},
async assertDedicatedIndexSwitchCheckedState(
expectedValue: boolean,
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState({
withAdvancedSection: sectionOptions.withAdvancedSection,
});
expect(actualCheckedState).to.eql(expectedValue);
},
async activateDedicatedIndexSwitch(
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
let subj = 'mlJobWizardSwitchUseDedicatedIndex';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
if (
(await this.getDedicatedIndexSwitchCheckedState({
withAdvancedSection: sectionOptions.withAdvancedSection,
})) === false
) {
await testSubjects.clickWhenNotDisabled(subj);
}
await this.assertDedicatedIndexSwitchCheckedState(true, {
withAdvancedSection: sectionOptions.withAdvancedSection,
});
},
async getModelPlotSwitchCheckedState(): Promise<boolean> {
await this.ensureAdvancedSectionOpen();
return await testSubjects.isSelected(
'mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot'
);
},
async assertModelPlotSwitchCheckedState(expectedValue: boolean) {
await this.ensureAdvancedSectionOpen();
const actualCheckedState = await this.getModelPlotSwitchCheckedState();
expect(actualCheckedState).to.eql(expectedValue);
},
async assertDedicatedIndexSwitchExists() {
await this.ensureAdvancedSectionOpen();
await testSubjects.existOrFail(
'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex',
{ allowHidden: true }
);
},
async getDedicatedIndexSwitchCheckedState(): Promise<boolean> {
await this.ensureAdvancedSectionOpen();
return await testSubjects.isSelected(
'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex'
);
},
async assertDedicatedIndexSwitchCheckedState(expectedValue: boolean) {
await this.ensureAdvancedSectionOpen();
const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState();
expect(actualCheckedState).to.eql(expectedValue);
},
async activateDedicatedIndexSwitch() {
if ((await this.getDedicatedIndexSwitchCheckedState()) === false) {
await testSubjects.clickWhenNotDisabled('mlJobWizardSwitchUseDedicatedIndex');
async assertModelMemoryLimitInputExists(
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
let subj = 'mlJobWizardInputModelMemoryLimit';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
await this.assertDedicatedIndexSwitchCheckedState(true);
await testSubjects.existOrFail(subj);
},
async assertModelMemoryLimitInputExists() {
await this.ensureAdvancedSectionOpen();
await testSubjects.existOrFail(
'mlJobWizardAdvancedSection > mlJobWizardInputModelMemoryLimit'
);
},
async assertModelMemoryLimitValue(expectedValue: string) {
await this.ensureAdvancedSectionOpen();
const actualModelMemoryLimit = await testSubjects.getAttribute(
'mlJobWizardAdvancedSection > mlJobWizardInputModelMemoryLimit',
'value'
);
async assertModelMemoryLimitValue(
expectedValue: string,
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
let subj = 'mlJobWizardInputModelMemoryLimit';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
const actualModelMemoryLimit = await testSubjects.getAttribute(subj, 'value');
expect(actualModelMemoryLimit).to.eql(expectedValue);
},
async setModelMemoryLimit(modelMemoryLimit: string) {
await testSubjects.setValue('mlJobWizardInputModelMemoryLimit', modelMemoryLimit, {
clearWithKeyboard: true,
async setModelMemoryLimit(
modelMemoryLimit: string,
sectionOptions: SectionOptions = { withAdvancedSection: true }
) {
let subj = 'mlJobWizardInputModelMemoryLimit';
if (sectionOptions.withAdvancedSection === true) {
await this.ensureAdvancedSectionOpen();
subj = advancedSectionSelector(subj);
}
await testSubjects.setValue(subj, modelMemoryLimit, { clearWithKeyboard: true });
await this.assertModelMemoryLimitValue(modelMemoryLimit, {
withAdvancedSection: sectionOptions.withAdvancedSection,
});
await this.assertModelMemoryLimitValue(modelMemoryLimit);
},
async assertInfluencerInputExists() {
@ -237,7 +302,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
},
async addInfluencer(influencer: string) {
await comboBox.setCustom('mlInfluencerSelect > comboBoxInput', influencer);
await comboBox.set('mlInfluencerSelect > comboBoxInput', influencer);
expect(await this.getSelectedInfluencers()).to.contain(influencer);
},
@ -293,16 +358,16 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
async ensureAdvancedSectionOpen() {
await retry.tryForTime(5000, async () => {
if ((await testSubjects.exists('mlJobWizardAdvancedSection')) === false) {
if ((await testSubjects.exists(advancedSectionSelector())) === false) {
await testSubjects.click('mlJobWizardToggleAdvancedSection');
await testSubjects.existOrFail('mlJobWizardAdvancedSection', { timeout: 1000 });
await testSubjects.existOrFail(advancedSectionSelector(), { timeout: 1000 });
}
});
},
async createJobAndWaitForCompletion() {
await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 5 * 60 * 1000 });
await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 2 * 60 * 1000 });
},
};
}

View file

@ -16,17 +16,16 @@ export function MachineLearningJobWizardMultiMetricProvider({ getService }: FtrP
await testSubjects.existOrFail('mlMultiMetricSplitFieldSelect > comboBoxInput');
},
async assertSplitFieldSelection(identifier: string) {
async assertSplitFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlMultiMetricSplitFieldSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions.length).to.eql(1);
expect(comboBoxSelectedOptions[0]).to.eql(identifier);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectSplitField(identifier: string) {
await comboBox.set('mlMultiMetricSplitFieldSelect > comboBoxInput', identifier);
await this.assertSplitFieldSelection(identifier);
await this.assertSplitFieldSelection([identifier]);
},
async assertDetectorSplitExists(splitField: string) {

View file

@ -16,17 +16,16 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr
await testSubjects.existOrFail('mlPopulationSplitFieldSelect > comboBoxInput');
},
async assertPopulationFieldSelection(identifier: string) {
async assertPopulationFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlPopulationSplitFieldSelect > comboBoxInput'
);
expect(comboBoxSelectedOptions.length).to.eql(1);
expect(comboBoxSelectedOptions[0]).to.eql(identifier);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectPopulationField(identifier: string) {
await comboBox.set('mlPopulationSplitFieldSelect > comboBoxInput', identifier);
await this.assertPopulationFieldSelection(identifier);
await this.assertPopulationFieldSelection([identifier]);
},
async assertDetectorSplitFieldInputExists(detectorPosition: number) {
@ -35,12 +34,14 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr
);
},
async assertDetectorSplitFieldSelection(detectorPosition: number, identifier: string) {
async assertDetectorSplitFieldSelection(
detectorPosition: number,
expectedIdentifier: string[]
) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
`mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput`
);
expect(comboBoxSelectedOptions.length).to.eql(1);
expect(comboBoxSelectedOptions[0]).to.eql(identifier);
expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorSplitField(detectorPosition: number, identifier: string) {
@ -48,7 +49,7 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr
`mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput`,
identifier
);
await this.assertDetectorSplitFieldSelection(detectorPosition, identifier);
await this.assertDetectorSplitFieldSelection(detectorPosition, [identifier]);
},
async assertDetectorSplitExists(detectorPosition: number) {

View file

@ -15,6 +15,7 @@ import {
MachineLearningJobSourceSelectionProvider,
MachineLearningJobTableProvider,
MachineLearningJobTypeSelectionProvider,
MachineLearningJobWizardAdvancedProvider,
MachineLearningJobWizardCommonProvider,
MachineLearningJobWizardMultiMetricProvider,
MachineLearningJobWizardPopulationProvider,
@ -28,10 +29,11 @@ export function MachineLearningProvider(context: FtrProviderContext) {
const api = MachineLearningAPIProvider(context);
const dataFrameAnalytics = MachineLearningDataFrameAnalyticsProvider(context);
const dataVisualizer = MachineLearningDataVisualizerProvider(context);
const jobManagement = MachineLearningJobManagementProvider(context);
const jobManagement = MachineLearningJobManagementProvider(context, api);
const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context);
const jobTable = MachineLearningJobTableProvider(context);
const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context);
const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context);
const jobWizardCommon = MachineLearningJobWizardCommonProvider(context);
const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context);
const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context);
@ -48,6 +50,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
jobSourceSelection,
jobTable,
jobTypeSelection,
jobWizardAdvanced,
jobWizardCommon,
jobWizardMultiMetric,
jobWizardPopulation,