diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/event_rate_chart/event_rate_chart.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/event_rate_chart/event_rate_chart.tsx index 030d622bed61b..7130a24cffb7d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/event_rate_chart/event_rate_chart.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/event_rate_chart/event_rate_chart.tsx @@ -30,7 +30,10 @@ export const EventRateChart: FC = ({ loading = false, }) => { return ( -
+
0} loading={loading}> {showAxis === true && } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx index 5a2c5fd42bc64..a6ef18d4931b9 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx @@ -79,7 +79,7 @@ export const PostSaveOptions: FC = ({ jobRunner }) => { datafeedState === DATAFEED_STATE.STARTING || datafeedState === DATAFEED_STATE.STARTED } onClick={startJobInRealTime} - data-test-subj="mlButtonUseFullData3" + data-test-subj="mlJobWizardButtonRunInRealTime" > = ({ jobRunner }) => { watchCreated === true } onClick={() => setWatchFlyoutVisible(true)} - data-test-subj="mlButtonUseFullData" + data-test-subj="mlJobWizardButtonCreateWatch" > = ({ setTimeRange, timeRange }) => { return ( -
+
= ({ existingJobsAndGroups, jobType }) => { return ( - + { - await esArchiver.loadIfNeeded('ml/farequote'); - }); - - 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('farequote'); - }); - - it('loads the multi metric job wizard page', async () => { - await ml.jobTypeSelection.selectMultiMetricJob(); - }); - - 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 detectors and displays detector previews', async () => { - for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { - await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier); - await ml.jobWizardCommon.assertDetectorPreviewExists(aggAndFieldIdentifier, index, 'LINE'); - } - }); - - it('inputs the split field and displays split cards', async () => { - await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); - await ml.jobWizardMultiMetric.selectSplitField(splitField); - await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField); - - await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); - await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); - await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); - - await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - - it('displays the influencer field', async () => { - await ml.jobWizardCommon.assertInfluencerInputExists(); - await ml.jobWizardCommon.assertInfluencerSelection([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: '86,274', - memoryStatus: 'ok', - jobState: 'closed', - datafeedState: 'stopped', - latestTimestamp: '2016-02-11 23:59:54', - }; - await ml.jobTable.assertJobRowFields(jobId, expectedRow); - - const expectedCounts = { - job_id: jobId, - processed_record_count: '86,274', - processed_field_count: '172,548', - input_bytes: '6.4 MB', - input_field_count: '172,548', - invalid_date_count: '0', - missing_field_count: '0', - out_of_order_timestamp_count: '0', - empty_bucket_count: '0', - sparse_bucket_count: '0', - bucket_count: '479', - earliest_record_timestamp: '2016-02-07 00:00:00', - latest_record_timestamp: '2016-02-11 23:59:54', - input_record_count: '86,274', - latest_bucket_timestamp: '2016-02-11 23:45:00', - }; - const expectedModelSizeStats = { - job_id: jobId, - result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', - total_by_field_count: '59', - total_over_field_count: '0', - total_partition_field_count: '58', - bucket_allocation_failures_count: '0', - memory_status: 'ok', - timestamp: '2016-02-11 23:30:00', - }; - await ml.jobTable.assertJobRowDetailsCounts(jobId, expectedCounts, expectedModelSizeStats); - }); - }); -} diff --git a/x-pack/test/functional/apps/machine_learning/create_population_job.ts b/x-pack/test/functional/apps/machine_learning/create_population_job.ts deleted file mode 100644 index d588c602db36e..0000000000000 --- a/x-pack/test/functional/apps/machine_learning/create_population_job.ts +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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); - }); - }); -} diff --git a/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts deleted file mode 100644 index ad7048717f49f..0000000000000 --- a/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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 jobId = `fq_single_1_${Date.now()}`; - const jobDescription = - 'Create single metric job based on the farequote dataset with 30m bucketspan and mean(responsetime)'; - const jobGroups = ['automated', 'farequote', 'single-metric']; - const aggAndFieldIdentifier = 'Mean(responsetime)'; - const bucketSpan = '30m'; - const memoryLimit = '15MB'; - - describe('single metric job creation', function() { - this.tags(['smoke', 'mlqa']); - before(async () => { - await esArchiver.loadIfNeeded('ml/farequote'); - }); - - 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('farequote'); - }); - - it('loads the single metric job wizard page', async () => { - await ml.jobTypeSelection.selectSingleMetricJob(); - }); - - 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 field and aggregation', async () => { - await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier); - await ml.jobWizardCommon.assertAggAndFieldSelection(aggAndFieldIdentifier); - }); - - 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: '2,399', - memoryStatus: 'ok', - jobState: 'closed', - datafeedState: 'stopped', - latestTimestamp: '2016-02-11 23:56:59', - }; - await ml.jobTable.assertJobRowFields(jobId, expectedRow); - - const expectedCounts = { - job_id: jobId, - processed_record_count: '2,399', - processed_field_count: '4,798', - input_bytes: '180.6 KB', - input_field_count: '4,798', - invalid_date_count: '0', - missing_field_count: '0', - out_of_order_timestamp_count: '0', - empty_bucket_count: '0', - sparse_bucket_count: '0', - bucket_count: '239', - earliest_record_timestamp: '2016-02-07 00:02:50', - latest_record_timestamp: '2016-02-11 23:56:59', - input_record_count: '2,399', - latest_bucket_timestamp: '2016-02-11 23:30:00', - }; - const expectedModelSizeStats = { - job_id: jobId, - result_type: 'model_size_stats', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '15728640', - total_by_field_count: '3', - total_over_field_count: '0', - total_partition_field_count: '2', - bucket_allocation_failures_count: '0', - memory_status: 'ok', - timestamp: '2016-02-11 23:00:00', - }; - await ml.jobTable.assertJobRowDetailsCounts(jobId, expectedCounts, expectedModelSizeStats); - }); - }); -} diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts index ec7eb53fe83c2..8b6b20e1aa0a7 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('feature controls', function() { - this.tags('skipFirefox'); + this.tags(['skipFirefox', 'mlqa']); loadTestFile(require.resolve('./ml_security')); loadTestFile(require.resolve('./ml_spaces')); }); diff --git a/x-pack/test/functional/apps/machine_learning/index.ts b/x-pack/test/functional/apps/machine_learning/index.ts index 5ddbfe59fb64d..09641d8a75d63 100644 --- a/x-pack/test/functional/apps/machine_learning/index.ts +++ b/x-pack/test/functional/apps/machine_learning/index.ts @@ -11,8 +11,8 @@ export default function({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./pages')); - loadTestFile(require.resolve('./create_single_metric_job')); - loadTestFile(require.resolve('./create_multi_metric_job')); - loadTestFile(require.resolve('./create_population_job')); + loadTestFile(require.resolve('./single_metric_job')); + loadTestFile(require.resolve('./multi_metric_job')); + loadTestFile(require.resolve('./population_job')); }); } diff --git a/x-pack/test/functional/apps/machine_learning/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/multi_metric_job.ts new file mode 100644 index 0000000000000..2fe5cfe1269d9 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/multi_metric_job.ts @@ -0,0 +1,365 @@ +/* + * 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 jobId = `fq_multi_1_${Date.now()}`; + const jobIdClone = `${jobId}_clone`; + const jobDescription = + 'Create multi metric job based on the farequote dataset with 15m bucketspan and min/max/mean(responsetime) split by airline'; + const jobGroups = ['automated', 'farequote', 'multi-metric']; + const jobGroupsClone = [...jobGroups, 'clone']; + const aggAndFieldIdentifiers = ['Min(responsetime)', 'Max(responsetime)', 'Mean(responsetime)']; + const splitField = 'airline'; + const bucketSpan = '15m'; + const memoryLimit = '20mb'; + + function getExpectedRow(expectedJobId: string, expectedJobGroups: string[]) { + return { + id: expectedJobId, + description: jobDescription, + jobGroups: [...new Set(expectedJobGroups)].sort(), + recordCount: '86,274', + memoryStatus: 'ok', + jobState: 'closed', + datafeedState: 'stopped', + latestTimestamp: '2016-02-11 23:59:54', + }; + } + + function getExpectedCounts(expectedJobId: string) { + return { + job_id: expectedJobId, + processed_record_count: '86,274', + processed_field_count: '172,548', + input_bytes: '6.4 MB', + input_field_count: '172,548', + invalid_date_count: '0', + missing_field_count: '0', + out_of_order_timestamp_count: '0', + empty_bucket_count: '0', + sparse_bucket_count: '0', + bucket_count: '479', + earliest_record_timestamp: '2016-02-07 00:00:00', + latest_record_timestamp: '2016-02-11 23:59:54', + input_record_count: '86,274', + latest_bucket_timestamp: '2016-02-11 23:45:00', + }; + } + + function getExpectedModelSizeStats(expectedJobId: string) { + return { + job_id: expectedJobId, + result_type: 'model_size_stats', + model_bytes_exceeded: '0', + model_bytes_memory_limit: '20971520', + total_by_field_count: '59', + total_over_field_count: '0', + total_partition_field_count: '58', + bucket_allocation_failures_count: '0', + memory_status: 'ok', + timestamp: '2016-02-11 23:30:00', + }; + } + + describe('multi metric', function() { + this.tags(['smoke', 'mlqa']); + before(async () => { + await esArchiver.loadIfNeeded('ml/farequote'); + }); + + after(async () => { + await esArchiver.unload('ml/farequote'); + await ml.api.cleanMlIndices(); + await ml.api.cleanDataframeIndices(); + }); + + describe('job creation', function() { + 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('farequote'); + }); + + it('loads the multi metric job wizard page', async () => { + await ml.jobTypeSelection.selectMultiMetricJob(); + }); + + it('displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('displays the event rate chart', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('selects detectors and displays detector previews', async () => { + for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists( + aggAndFieldIdentifier, + index, + 'LINE' + ); + } + }); + + it('inputs the split field and displays split cards', async () => { + await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); + await ml.jobWizardMultiMetric.selectSplitField(splitField); + + await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); + await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); + await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); + + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + }); + + it('displays the influencer field', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + }); + + it('inputs the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + }); + + it('displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('inputs the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + }); + + it('inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(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(); + }); + + it('inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + }); + + it('displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + 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 () => { + await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobId, + getExpectedCounts(jobId), + getExpectedModelSizeStats(jobId) + ); + }); + }); + + describe('job cloning', function() { + it('clicks the clone action and loads the multi metric wizard', async () => { + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertMultiMetricJobWizardOpen(); + }); + + it('displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('displays the event rate chart', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('pre-fills detectors and shows preview with split cards', async () => { + for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { + await ml.jobWizardCommon.assertDetectorPreviewExists( + aggAndFieldIdentifier, + index, + 'LINE' + ); + } + + await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); + await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); + await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); + }); + + it('pre-fills the split field', async () => { + await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); + await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField); + }); + + it('pre-fills influencers', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + }); + + it('pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + }); + + it('displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(jobIdClone); + }); + + it('pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + }); + + it('pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); + }); + + it('opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); + }); + + it('pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); + }); + + it('pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); + }); + + it('displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + 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(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); + }); + + it('displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields( + jobIdClone, + getExpectedRow(jobIdClone, jobGroupsClone) + ); + + await ml.jobTable.assertJobRowDetailsCounts( + jobIdClone, + getExpectedCounts(jobIdClone), + getExpectedModelSizeStats(jobIdClone) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/population_job.ts b/x-pack/test/functional/apps/machine_learning/population_job.ts new file mode 100644 index 0000000000000..fa24673c21044 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/population_job.ts @@ -0,0 +1,404 @@ +/* + * 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 jobId = `ec_population_1_${Date.now()}`; + const jobIdClone = `${jobId}_clone`; + 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 jobGroupsClone = [...jobGroups, 'clone']; + 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'; + + function getExpectedRow(expectedJobId: string, expectedJobGroups: string[]) { + return { + id: expectedJobId, + description: jobDescription, + jobGroups: [...new Set(expectedJobGroups)].sort(), + recordCount: '4,675', + memoryStatus: 'ok', + jobState: 'closed', + datafeedState: 'stopped', + latestTimestamp: '2019-07-12 23:45:36', + }; + } + + function getExpectedCounts(expectedJobId: string) { + return { + job_id: expectedJobId, + 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', + }; + } + + function getExpectedModelSizeStats(expectedJobId: string) { + return { + job_id: expectedJobId, + 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', + }; + } + + describe('population', 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(); + }); + + describe('job creation', function() { + 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( + 'Jun 12, 2019 @ 00:04:19.000', + 'Jul 12, 2019 @ 23:45:36.000' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('selects the population field', async () => { + await ml.jobWizardPopulation.assertPopulationFieldInputExists(); + await ml.jobWizardPopulation.selectPopulationField(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, false); + 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()) { + await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); + await ml.jobWizardPopulation.selectDetectorSplitField(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); + }); + + it('displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('inputs the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + }); + + it('inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(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(); + }); + + it('inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + }); + + it('displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + 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 () => { + await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobId, + getExpectedCounts(jobId), + getExpectedModelSizeStats(jobId) + ); + }); + }); + + describe('job cloning', function() { + it('clicks the clone action and loads the population wizard', async () => { + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertPopulationJobWizardOpen(); + }); + + it('displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('displays the event rate chart', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Jun 12, 2019 @ 00:04:19.000', + 'Jul 12, 2019 @ 23:45:36.000' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('pre-fills the population field', async () => { + await ml.jobWizardPopulation.assertPopulationFieldInputExists(); + await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField); + }); + + it('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.assertDetectorSplitExists(index); + await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( + index, + detector.frontCardTitle + ); + await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards( + index, + detector.numberOfBackCards + ); + } + }); + + it('pre-fills influencers', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection( + [populationField].concat(detectors.map(detector => detector.splitField)) + ); + }); + + it('pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + }); + + it('displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(jobIdClone); + }); + + it('pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + }); + + it('pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); + }); + + it('opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); + }); + + it('pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); + }); + + it('pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); + }); + + it('displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + 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(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); + }); + + it('displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields( + jobIdClone, + getExpectedRow(jobIdClone, jobGroupsClone) + ); + + await ml.jobTable.assertJobRowDetailsCounts( + jobIdClone, + getExpectedCounts(jobIdClone), + getExpectedModelSizeStats(jobIdClone) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/single_metric_job.ts new file mode 100644 index 0000000000000..4b112e63758d6 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/single_metric_job.ts @@ -0,0 +1,324 @@ +/* + * 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 jobId = `fq_single_1_${Date.now()}`; + const jobIdClone = `${jobId}_clone`; + const jobDescription = + 'Create single metric job based on the farequote dataset with 30m bucketspan and mean(responsetime)'; + const jobGroups = ['automated', 'farequote', 'single-metric']; + const jobGroupsClone = [...jobGroups, 'clone']; + const aggAndFieldIdentifier = 'Mean(responsetime)'; + const bucketSpan = '30m'; + const memoryLimit = '15mb'; + + function getExpectedRow(expectedJobId: string, expectedJobGroups: string[]) { + return { + id: expectedJobId, + description: jobDescription, + jobGroups: [...new Set(expectedJobGroups)].sort(), + recordCount: '2,399', + memoryStatus: 'ok', + jobState: 'closed', + datafeedState: 'stopped', + latestTimestamp: '2016-02-11 23:56:59', + }; + } + + function getExpectedCounts(expectedJobId: string) { + return { + job_id: expectedJobId, + processed_record_count: '2,399', + processed_field_count: '4,798', + input_bytes: '180.6 KB', + input_field_count: '4,798', + invalid_date_count: '0', + missing_field_count: '0', + out_of_order_timestamp_count: '0', + empty_bucket_count: '0', + sparse_bucket_count: '0', + bucket_count: '239', + earliest_record_timestamp: '2016-02-07 00:02:50', + latest_record_timestamp: '2016-02-11 23:56:59', + input_record_count: '2,399', + latest_bucket_timestamp: '2016-02-11 23:30:00', + }; + } + + function getExpectedModelSizeStats(expectedJobId: string) { + return { + job_id: expectedJobId, + result_type: 'model_size_stats', + model_bytes_exceeded: '0', + model_bytes_memory_limit: '15728640', + total_by_field_count: '3', + total_over_field_count: '0', + total_partition_field_count: '2', + bucket_allocation_failures_count: '0', + memory_status: 'ok', + timestamp: '2016-02-11 23:00:00', + }; + } + + describe('single metric', function() { + this.tags(['smoke', 'mlqa']); + before(async () => { + await esArchiver.loadIfNeeded('ml/farequote'); + }); + + after(async () => { + await esArchiver.unload('ml/farequote'); + await ml.api.cleanMlIndices(); + await ml.api.cleanDataframeIndices(); + }); + + describe('job creation', function() { + 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('farequote'); + }); + + it('loads the single metric job wizard page', async () => { + await ml.jobTypeSelection.selectSingleMetricJob(); + }); + + it('displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('displays the event rate chart', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('selects field and aggregation', async () => { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, true); + await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); + }); + + it('inputs the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + }); + + it('displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('inputs the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + }); + + it('inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(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(); + }); + + it('inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + }); + + it('displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + 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 () => { + await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobId, + getExpectedCounts(jobId), + getExpectedModelSizeStats(jobId) + ); + }); + }); + + describe('job cloning', function() { + it('clicks the clone action and loads the single metric wizard', async () => { + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertSingleMetricJobWizardOpen(); + }); + + it('displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('displays the event rate chart', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('pre-fills field and aggregation', async () => { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.assertAggAndFieldSelection(aggAndFieldIdentifier); + await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); + }); + + it('pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + }); + + it('displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(jobIdClone); + }); + + it('pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + }); + + it('pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); + }); + + it('opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(true); + }); + + it('pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); + }); + + it('pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); + }); + + it('displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + 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(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); + }); + + it('displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields( + jobIdClone, + getExpectedRow(jobIdClone, jobGroupsClone) + ); + + await ml.jobTable.assertJobRowDetailsCounts( + jobIdClone, + getExpectedCounts(jobIdClone), + getExpectedModelSizeStats(jobIdClone) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/services/machine_learning/job_table.ts b/x-pack/test/functional/services/machine_learning/job_table.ts index 33b6c3aeb78f6..bc2788e93b967 100644 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ b/x-pack/test/functional/services/machine_learning/job_table.ts @@ -10,6 +10,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningJobTableProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const find = getService('find'); + const log = getService('log'); return new (class MlJobTable { public async parseJobTable() { @@ -210,5 +212,42 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte expect(modelSizeStats).to.eql(expectedModelSizeStats); } + + public async clickActionsMenu(jobId: string) { + const jobRow = await testSubjects.find(this.rowSelector(jobId)); + const actionsCell = await jobRow.findByCssSelector(`[id=${jobId}-actions]`); + const actionsMenuButton = await actionsCell.findByTagName('button'); + + log.debug(`Clicking actions menu button for job id ${jobId}`); + await actionsMenuButton.click(); + if (!(await find.existsByDisplayedByCssSelector('[class~=euiContextMenuPanel]'))) { + throw new Error(`expected euiContextMenuPanel to exist`); + } + } + + public async clickActionsMenuEntry(jobId: string, entryText: string) { + await this.clickActionsMenu(jobId); + const actionsMenuPanel = await find.byCssSelector('[class~=euiContextMenuPanel]'); + const actionButtons = await actionsMenuPanel.findAllByTagName('button'); + + const filteredButtons = []; + for (const button of actionButtons) { + if ((await button.getVisibleText()) === entryText) { + filteredButtons.push(button); + } + } + + if (!(filteredButtons.length === 1)) { + throw new Error( + `expected action button ${entryText} to exist exactly once, but found ${filteredButtons.length} matching buttons` + ); + } + log.debug(`Clicking action button ${entryText} for job id ${jobId}`); + await filteredButtons[0].click(); + } + + public async clickCloneJobAction(jobId: string) { + await this.clickActionsMenuEntry(jobId, 'Clone job'); + } })(); } diff --git a/x-pack/test/functional/services/machine_learning/job_type_selection.ts b/x-pack/test/functional/services/machine_learning/job_type_selection.ts index eedcdcfea119e..0957558f62165 100644 --- a/x-pack/test/functional/services/machine_learning/job_type_selection.ts +++ b/x-pack/test/functional/services/machine_learning/job_type_selection.ts @@ -12,17 +12,29 @@ export function MachineLearningJobTypeSelectionProvider({ getService }: FtrProvi return { async selectSingleMetricJob() { await testSubjects.clickWhenNotDisabled('mlJobTypeLinkSingleMetricJob'); - await testSubjects.existOrFail('mlPageJobWizard'); + await this.assertSingleMetricJobWizardOpen(); + }, + + async assertSingleMetricJobWizardOpen() { + await testSubjects.existOrFail('mlPageJobWizard single_metric'); }, async selectMultiMetricJob() { await testSubjects.clickWhenNotDisabled('mlJobTypeLinkMultiMetricJob'); - await testSubjects.existOrFail('mlPageJobWizard'); + await this.assertMultiMetricJobWizardOpen(); + }, + + async assertMultiMetricJobWizardOpen() { + await testSubjects.existOrFail('mlPageJobWizard multi_metric'); }, async selectPopulationJob() { await testSubjects.clickWhenNotDisabled('mlJobTypeLinkPopulationJob'); - await testSubjects.existOrFail('mlPageJobWizard'); + await this.assertPopulationJobWizardOpen(); + }, + + async assertPopulationJobWizardOpen() { + await testSubjects.existOrFail('mlPageJobWizard population'); }, }; } diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 19a270cdab5f5..f0ecb1289c9c8 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -13,6 +13,18 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid const testSubjects = getService('testSubjects'); return { + async waitForNextButtonVisible() { + await retry.waitFor( + 'next button to be visible', + async () => await testSubjects.isDisplayed('mlJobWizardNavButtonNext') + ); + }, + + async clickNextButton() { + await this.waitForNextButtonVisible(); + await testSubjects.clickWhenNotDisabled('mlJobWizardNavButtonNext'); + }, + async assertTimeRangeSectionExists() { await testSubjects.existOrFail('mlJobWizardStepTitleTimeRange'); }, @@ -33,20 +45,49 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid await testSubjects.existOrFail('mlJobWizardStepTitleSummary'); }, + async advanceToPickFieldsSection() { + await this.clickNextButton(); + await this.assertPickFieldsSectionExists(); + }, + + async advanceToJobDetailsSection() { + await this.clickNextButton(); + await this.assertJobDetailsSectionExists(); + }, + + async advanceToValidationSection() { + await this.clickNextButton(); + await this.assertValidationSectionExists(); + }, + + async advanceToSummarySection() { + await this.clickNextButton(); + await this.assertSummarySectionExists(); + }, + async assertEventRateChartExists() { - await testSubjects.existOrFail('mlEventRateChart'); + await testSubjects.existOrFail('~mlEventRateChart'); + }, + + async assertEventRateChartHasData() { + await testSubjects.existOrFail('mlEventRateChart withData'); }, async assertAggAndFieldInputExists() { await testSubjects.existOrFail('mlJobWizardAggSelection > comboBoxInput'); }, - async assertAggAndFieldSelection(identifier: string) { + async assertAggAndFieldSelection(expectedIdentifier: string) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlJobWizardAggSelection > comboBoxInput' ); expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions[0]).to.eql(expectedIdentifier); + }, + + async selectAggAndField(identifier: string, isIdentifierKeptInField: boolean) { + await comboBox.set('mlJobWizardAggSelection > comboBoxInput', identifier); + await this.assertAggAndFieldSelection(isIdentifierKeptInField ? identifier : ''); }, async assertBucketSpanInputExists() { @@ -61,6 +102,11 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(actualBucketSpan).to.eql(expectedValue); }, + async setBucketSpan(bucketSpan: string) { + await testSubjects.setValue('mlJobWizardInputBucketSpan', bucketSpan, { withKeyboard: true }); + await this.assertBucketSpanValue(bucketSpan); + }, + async assertJobIdInputExists() { await testSubjects.existOrFail('mlJobWizardInputJobId'); }, @@ -70,6 +116,11 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(actualJobId).to.eql(expectedValue); }, + async setJobId(jobId: string) { + await testSubjects.setValue('mlJobWizardInputJobId', jobId, { withKeyboard: true }); + await this.assertJobIdValue(jobId); + }, + async assertJobDescriptionInputExists() { await testSubjects.existOrFail('mlJobWizardInputJobDescription'); }, @@ -81,15 +132,30 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(actualJobDescription).to.eql(expectedValue); }, + async setJobDescription(jobDescription: string) { + await testSubjects.setValue('mlJobWizardInputJobDescription', jobDescription, { + withKeyboard: true, + }); + await this.assertJobDescriptionValue(jobDescription); + }, + async assertJobGroupInputExists() { await testSubjects.existOrFail('mlJobWizardComboBoxJobGroups > comboBoxInput'); }, - async assertJobGroupSelection(jobGroups: string[]) { - const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + async getSelectedJobGroups(): Promise { + return await comboBox.getComboBoxSelectedOptions( 'mlJobWizardComboBoxJobGroups > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(jobGroups); + }, + + async assertJobGroupSelection(jobGroups: string[]) { + expect(await this.getSelectedJobGroups()).to.eql(jobGroups); + }, + + async addJobGroup(jobGroup: string) { + await comboBox.setCustom('mlJobWizardComboBoxJobGroups > comboBoxInput', jobGroup); + expect(await this.getSelectedJobGroups()).to.contain(jobGroup); }, async assertModelPlotSwitchExists() { @@ -99,31 +165,47 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid }); }, - async assertDedicatedIndexSwitchExists() { + async getModelPlotSwitchCheckedState(): Promise { await this.ensureAdvancedSectionOpen(); - await testSubjects.existOrFail( - 'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex', - { allowHidden: true } + return await testSubjects.isSelected( + 'mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot' ); }, - async assertDedicatedIndexSwitchCheckedState(expectedValue: boolean) { + async assertModelPlotSwitchCheckedState(expectedValue: boolean) { await this.ensureAdvancedSectionOpen(); - const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState(); + const actualCheckedState = await this.getModelPlotSwitchCheckedState(); expect(actualCheckedState).to.eql(expectedValue); }, - async assertCreateJobButtonExists() { - await testSubjects.existOrFail('mlJobWizardButtonCreateJob'); + async assertDedicatedIndexSwitchExists() { + await this.ensureAdvancedSectionOpen(); + await testSubjects.existOrFail( + 'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex', + { allowHidden: true } + ); }, - async getDedicatedIndexSwitchCheckedState() { + async getDedicatedIndexSwitchCheckedState(): Promise { 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'); + } + await this.assertDedicatedIndexSwitchCheckedState(true); + }, + async assertModelMemoryLimitInputExists() { await this.ensureAdvancedSectionOpen(); await testSubjects.existOrFail( @@ -140,15 +222,33 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(actualModelMemoryLimit).to.eql(expectedValue); }, + async setModelMemoryLimit(modelMemoryLimit: string) { + await testSubjects.setValue('mlJobWizardInputModelMemoryLimit', modelMemoryLimit, { + withKeyboard: true, + }); + await this.assertModelMemoryLimitValue(modelMemoryLimit); + }, + async assertInfluencerInputExists() { await testSubjects.existOrFail('influencerSelect > comboBoxInput'); }, + async getSelectedInfluencers(): Promise { + return await comboBox.getComboBoxSelectedOptions('influencerSelect > comboBoxInput'); + }, + async assertInfluencerSelection(influencers: string[]) { - const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( - 'influencerSelect > comboBoxInput' - ); - expect(comboBoxSelectedOptions).to.eql(influencers); + expect(await this.getSelectedInfluencers()).to.eql(influencers); + }, + + async addInfluencer(influencer: string) { + await comboBox.setCustom('influencerSelect > comboBoxInput', influencer); + expect(await this.getSelectedInfluencers()).to.contain(influencer); + }, + + async assertAnomalyChartExists(chartType: string, preSelector?: string) { + let chartSelector = `mlAnomalyChart ${chartType}`; + chartSelector = !preSelector ? chartSelector : `${preSelector} > ${chartSelector}`; }, async assertDetectorPreviewExists( @@ -161,45 +261,55 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect( await testSubjects.getVisibleText(`detector ${detectorPosition} > detectorTitle`) ).to.eql(aggAndFieldIdentifier); - await testSubjects.existOrFail(`detector ${detectorPosition} > mlAnomalyChart ${chartType}`); - }, - async clickNextButton() { - await testSubjects.clickWhenNotDisabled('mlJobWizardNavButtonNext'); + await this.assertAnomalyChartExists(chartType, `detector ${detectorPosition}`); }, - async clickPreviousButton() { - await testSubjects.clickWhenNotDisabled('mlJobWizardNavButtonPrevious'); + async assertDetectorSplitExists(splitField: string) { + await testSubjects.existOrFail(`dataSplit > dataSplitTitle ${splitField}`); + await testSubjects.existOrFail(`dataSplit > splitCard front`); + await testSubjects.existOrFail(`dataSplit > splitCard back`); }, - async clickUseFullDataButton() { - await testSubjects.clickWhenNotDisabled('mlButtonUseFullData'); + async assertDetectorSplitFrontCardTitle(frontCardTitle: string) { + expect( + await testSubjects.getVisibleText(`dataSplit > splitCard front > splitCardTitle`) + ).to.eql(frontCardTitle); }, - async selectAggAndField(identifier: string) { - await comboBox.set('mlJobWizardAggSelection > comboBoxInput', identifier); + async assertDetectorSplitNumberOfBackCards(numberOfBackCards: number) { + expect(await testSubjects.findAll(`dataSplit > splitCard back`)).to.have.length( + numberOfBackCards + ); }, - async setBucketSpan(bucketSpan: string) { - await testSubjects.setValue('mlJobWizardInputBucketSpan', bucketSpan, { withKeyboard: true }); + async assertCreateJobButtonExists() { + await testSubjects.existOrFail('mlJobWizardButtonCreateJob'); }, - async setJobId(jobId: string) { - await testSubjects.setValue('mlJobWizardInputJobId', jobId, { withKeyboard: true }); + async assertDateRangeSelectionExists() { + await testSubjects.existOrFail('jobWizardDateRange'); }, - async setJobDescription(jobDescription: string) { - await testSubjects.setValue('mlJobWizardInputJobDescription', jobDescription, { - withKeyboard: true, - }); + async getSelectedDateRange() { + const dateRange = await testSubjects.find('jobWizardDateRange'); + const [startPicker, endPicker] = await dateRange.findAllByClassName('euiFieldText'); + return { + startDate: await startPicker.getAttribute('value'), + endDate: await endPicker.getAttribute('value'), + }; }, - async addJobGroup(jobGroup: string) { - await comboBox.setCustom('mlJobWizardComboBoxJobGroups > comboBoxInput', jobGroup); + async assertDateRangeSelection(expectedStartDate: string, expectedEndDate: string) { + expect(await this.getSelectedDateRange()).to.eql({ + startDate: expectedStartDate, + endDate: expectedEndDate, + }); }, - async addInfluencer(influencer: string) { - await comboBox.setCustom('influencerSelect > comboBoxInput', influencer); + async clickUseFullDataButton(expectedStartDate: string, expectedEndDate: string) { + await testSubjects.clickWhenNotDisabled('mlButtonUseFullData'); + await this.assertDateRangeSelection(expectedStartDate, expectedEndDate); }, async ensureAdvancedSectionOpen() { @@ -211,24 +321,12 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid }); }, - async activateDedicatedIndexSwitch() { - if ((await this.getDedicatedIndexSwitchCheckedState()) === false) { - await testSubjects.clickWhenNotDisabled('mlJobWizardSwitchUseDedicatedIndex'); - } - }, - - async setModelMemoryLimit(modelMemoryLimit: string) { - await testSubjects.setValue('mlJobWizardInputModelMemoryLimit', modelMemoryLimit, { - withKeyboard: true, - }); - }, - async createJobAndWaitForCompletion() { await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob'); await retry.waitForWithTimeout( 'job processing to finish', 5 * 60 * 1000, - async () => (await testSubjects.exists('mlJobWizardButtonCreateJob')) === false + async () => await testSubjects.exists('mlJobWizardButtonRunInRealTime') ); }, }; diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts b/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts index f1e6319d6e866..d57e92aaa3468 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts @@ -26,6 +26,7 @@ export function MachineLearningJobWizardMultiMetricProvider({ getService }: FtrP async selectSplitField(identifier: string) { await comboBox.set('multiMetricSplitFieldSelect > comboBoxInput', identifier); + await this.assertSplitFieldSelection(identifier); }, async assertDetectorSplitExists(splitField: string) { diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_population.ts b/x-pack/test/functional/services/machine_learning/job_wizard_population.ts index 12e59e0d5279d..2b0a8c6521bd1 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_population.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_population.ts @@ -26,6 +26,7 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr async selectPopulationField(identifier: string) { await comboBox.set('populationSplitFieldSelect > comboBoxInput', identifier); + await this.assertPopulationFieldSelection(identifier); }, async assertDetectorSplitFieldInputExists(detectorPosition: number) { @@ -47,6 +48,7 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr `detector ${detectorPosition} > byFieldSelect > comboBoxInput`, identifier ); + await this.assertDetectorSplitFieldSelection(detectorPosition, identifier); }, async assertDetectorSplitExists(detectorPosition: number) {