Skip to content

Commit

Permalink
[ML] Add population job wizard test (#45765) (#45906)
Browse files Browse the repository at this point in the history
This PR adds functional UI tests to create a machine learning job using the population wizard.
  • Loading branch information
pheyos authored Sep 18, 2019
1 parent a81a178 commit d198fff
Show file tree
Hide file tree
Showing 17 changed files with 1,630 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const InfluencersSelect: FC<Props> = ({ fields, changeHandler, selectedIn
selectedOptions={selection}
onChange={onChange}
isClearable={false}
data-test-subj="influencerSelect"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ export const MultiMetricSettings: FC<Props> = ({ setIsValid }) => {
return (
<Fragment>
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem data-test-subj="mlJobWizardSplitFieldSelection">
<EuiFlexItem>
<SplitFieldSelector />
</EuiFlexItem>
<EuiFlexItem data-test-subj="mlJobWizardInfluencerSelection">
<EuiFlexItem>
<Influencers />
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const ChartGrid: FC<ChartGridProps> = ({
return (
<EuiFlexGrid columns={chartSettings.cols}>
{aggFieldPairList.map((af, i) => (
<EuiFlexItem key={i}>
<EuiFlexItem key={i} data-test-subj={`detector ${i}`}>
<Fragment>
<EuiFlexGroup>
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const ByFieldSelector: FC<Props> = ({ detectorIndex }) => {
changeHandler={setByField}
selectedField={byField}
isClearable={true}
testSubject="byFieldSelect"
placeholder={i18n.translate(
'xpack.ml.newJob.wizard.pickFieldsStep.populationField.placeholder',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export const SplitFieldSelector: FC = () => {
changeHandler={setSplitField}
selectedField={splitField}
isClearable={canClearSelection}
testSubject={
isMultiMetricJobCreator(jc)
? 'multiMetricSplitFieldSelect'
: isPopulationJobCreator(jc)
? 'populationSplitFieldSelect'
: undefined
}
/>
</Description>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface Props {
changeHandler(f: SplitField): void;
selectedField: SplitField;
isClearable: boolean;
testSubject?: string;
placeholder?: string;
}

Expand All @@ -27,6 +28,7 @@ export const SplitFieldSelect: FC<Props> = ({
changeHandler,
selectedField,
isClearable,
testSubject,
placeholder,
}) => {
const options: EuiComboBoxOptionProps[] = fields.map(
Expand Down Expand Up @@ -59,6 +61,7 @@ export const SplitFieldSelect: FC<Props> = ({
onChange={onChange}
isClearable={isClearable}
placeholder={placeholder}
data-test-subj={testSubject}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function({ getService }: FtrProviderContext) {
await ml.jobSourceSelection.selectSourceIndexPattern('farequote');
});

it('loads the single metric job wizard page', async () => {
it('loads the multi metric job wizard page', async () => {
await ml.jobTypeSelection.selectMultiMetricJob();
});

Expand All @@ -73,13 +73,13 @@ export default function({ getService }: FtrProviderContext) {
});

it('inputs the split field and displays split cards', async () => {
await ml.jobWizardCommon.assertMultiMetricSplitFieldInputExists();
await ml.jobWizardCommon.selectMultiMetricSplitField(splitField);
await ml.jobWizardCommon.assertMultiMetricSplitFieldSelection(splitField);
await ml.jobWizardMultiMetric.assertSplitFieldInputExists();
await ml.jobWizardMultiMetric.selectSplitField(splitField);
await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField);

await ml.jobWizardCommon.assertDetectorSplitExists(splitField);
await ml.jobWizardCommon.assertDetectorSplitFrontCardTitle('AAL');
await ml.jobWizardCommon.assertDetectorSplitNumberOfBackCards(9);
await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField);
await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL');
await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9);

await ml.jobWizardCommon.assertInfluencerSelection([splitField]);
});
Expand Down
243 changes: 243 additions & 0 deletions x-pack/test/functional/apps/machine_learning/create_population_job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* 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';

// eslint-disable-next-line import/no-default-export
export default function({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const ml = getService('ml');
const log = getService('log');

const jobId = `ec_population_1_${Date.now()}`;
const jobDescription =
'Create population job based on the ecommerce sample dataset with 2h bucketspan over customer_id' +
' - detectors: (Mean(products.base_price) by customer_gender), (Mean(products.quantity) by category.leyword)';
const jobGroups = ['automated', 'ecommerce', 'population'];
const populationField = 'customer_id';
const detectors = [
{
identifier: 'Mean(products.base_price)',
splitField: 'customer_gender',
frontCardTitle: 'FEMALE',
numberOfBackCards: 1,
},
{
identifier: 'Mean(products.quantity)',
splitField: 'category.keyword',
frontCardTitle: "Men's Clothing",
numberOfBackCards: 5,
},
];
const bucketSpan = '2h';
const memoryLimit = '8MB';

describe('population job creation', function() {
this.tags(['smoke', 'mlqa']);
before(async () => {
await esArchiver.loadIfNeeded('ml/ecommerce');
});

after(async () => {
await esArchiver.unload('ml/farequote');
await ml.api.cleanMlIndices();
await ml.api.cleanDataframeIndices();
});

it('loads the job management page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();
});

it('loads the new job source selection page', async () => {
await ml.jobManagement.navigateToNewJobSourceSelection();
});

it('loads the job type selection page', async () => {
await ml.jobSourceSelection.selectSourceIndexPattern('ecommerce');
});

it('loads the population job wizard page', async () => {
await ml.jobTypeSelection.selectPopulationJob();
});

it('displays the time range step', async () => {
await ml.jobWizardCommon.assertTimeRangeSectionExists();
});

it('displays the event rate chart', async () => {
await ml.jobWizardCommon.clickUseFullDataButton();
await ml.jobWizardCommon.assertEventRateChartExists();
});

it('displays the pick fields step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertPickFieldsSectionExists();
});

it('selects the population field', async () => {
await ml.jobWizardPopulation.assertPopulationFieldInputExists();
await ml.jobWizardPopulation.selectPopulationField(populationField);
await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField);
});

it('selects detectors and displays detector previews', async () => {
for (const [index, detector] of detectors.entries()) {
await ml.jobWizardCommon.assertAggAndFieldInputExists();
await ml.jobWizardCommon.selectAggAndField(detector.identifier);
await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER');
}
});

it('inputs detector split fields and displays split cards', async () => {
for (const [index, detector] of detectors.entries()) {
log.debug(detector);
await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index);
await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField);
await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, detector.splitField);

await ml.jobWizardPopulation.assertDetectorSplitExists(index);
await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle(
index,
detector.frontCardTitle
);
await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards(
index,
detector.numberOfBackCards
);
}
});

it('displays the influencer field', async () => {
await ml.jobWizardCommon.assertInfluencerInputExists();
await ml.jobWizardCommon.assertInfluencerSelection(
[populationField].concat(detectors.map(detector => detector.splitField))
);
});

it('inputs the bucket span', async () => {
await ml.jobWizardCommon.assertBucketSpanInputExists();
await ml.jobWizardCommon.setBucketSpan(bucketSpan);
await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan);
});

it('displays the job details step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertJobDetailsSectionExists();
});

it('inputs the job id', async () => {
await ml.jobWizardCommon.assertJobIdInputExists();
await ml.jobWizardCommon.setJobId(jobId);
await ml.jobWizardCommon.assertJobIdValue(jobId);
});

it('inputs the job description', async () => {
await ml.jobWizardCommon.assertJobDescriptionInputExists();
await ml.jobWizardCommon.setJobDescription(jobDescription);
await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription);
});

it('inputs job groups', async () => {
await ml.jobWizardCommon.assertJobGroupInputExists();
for (const jobGroup of jobGroups) {
await ml.jobWizardCommon.addJobGroup(jobGroup);
}
await ml.jobWizardCommon.assertJobGroupSelection(jobGroups);
});

it('opens the advanced section', async () => {
await ml.jobWizardCommon.ensureAdvancedSectionOpen();
});

it('displays the model plot switch', async () => {
await ml.jobWizardCommon.assertModelPlotSwitchExists();
});

it('enables the dedicated index switch', async () => {
await ml.jobWizardCommon.assertDedicatedIndexSwitchExists();
await ml.jobWizardCommon.activateDedicatedIndexSwitch();
await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true);
});

it('inputs the model memory limit', async () => {
await ml.jobWizardCommon.assertModelMemoryLimitInputExists();
await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit);
await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit);
});

it('displays the validation step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertValidationSectionExists();
});

it('displays the summary step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertSummarySectionExists();
});

it('creates the job and finishes processing', async () => {
await ml.jobWizardCommon.assertCreateJobButtonExists();
await ml.jobWizardCommon.createJobAndWaitForCompletion();
});

it('displays the created job in the job list', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();

await ml.jobTable.waitForJobsToLoad();
await ml.jobTable.filterWithSearchString(jobId);
const rows = await ml.jobTable.parseJobTable();
expect(rows.filter(row => row.id === jobId)).to.have.length(1);
});

it('displays details for the created job in the job list', async () => {
const expectedRow = {
id: jobId,
description: jobDescription,
jobGroups,
recordCount: '4,675',
memoryStatus: 'ok',
jobState: 'closed',
datafeedState: 'stopped',
latestTimestamp: '2019-07-12 23:45:36',
};
await ml.jobTable.assertJobRowFields(jobId, expectedRow);

const expectedCounts = {
job_id: jobId,
processed_record_count: '4,675',
processed_field_count: '23,375',
input_bytes: '867.7 KB',
input_field_count: '23,375',
invalid_date_count: '0',
missing_field_count: '0',
out_of_order_timestamp_count: '0',
empty_bucket_count: '0',
sparse_bucket_count: '0',
bucket_count: '371',
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 22:00:00',
};
const expectedModelSizeStats = {
job_id: jobId,
result_type: 'model_size_stats',
model_bytes_exceeded: '0',
model_bytes_memory_limit: '8388608',
total_by_field_count: '25',
total_over_field_count: '92',
total_partition_field_count: '3',
bucket_allocation_failures_count: '0',
memory_status: 'ok',
timestamp: '2019-07-12 20:00:00',
};
await ml.jobTable.assertJobRowDetailsCounts(jobId, expectedCounts, expectedModelSizeStats);
});
});
}
1 change: 1 addition & 0 deletions x-pack/test/functional/apps/machine_learning/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./pages'));
loadTestFile(require.resolve('./create_single_metric_job'));
loadTestFile(require.resolve('./create_multi_metric_job'));
loadTestFile(require.resolve('./create_population_job'));
});
}
Binary file not shown.
Loading

0 comments on commit d198fff

Please sign in to comment.