From 09a65c9212c3f2b8219cc8fbce28b8cfbb0c71ad Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 16 Oct 2019 17:25:25 +0200 Subject: [PATCH 01/15] Add data-test-subj attributes --- .../start_datafeed_modal/start_datafeed_modal.js | 3 +++ .../advanced_detector_modal.tsx | 12 ++++++------ .../advanced_detector_modal/modal_wrapper.tsx | 15 ++++++++++++--- .../components/advanced_view/detector_list.tsx | 5 ++++- .../components/advanced_view/metric_selector.tsx | 2 +- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js index 133adea7cff6e..0c356959cd2af 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js @@ -136,6 +136,7 @@ export class StartDatafeedModal extends Component { onClose={this.closeModal} style={{ width: '850px' }} maxWidth={false} + data-test-subj="mlStartDatafeedModal" > @@ -178,6 +179,7 @@ export class StartDatafeedModal extends Component { = ({ - + = ({ /> - + = ({ - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ onCreateClick, closeModal, saveEnabled, children }) => { return ( - + = ({ onCreateClick, closeModal, saveEnabled {children} - + - + = ({ isActive, onEditJob, onDeleteJob }) => defaultMessage: 'Edit', } )} + data-test-subj="mlAdvancedDetectorEditButton" /> @@ -75,6 +76,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => defaultMessage: 'Delete', } )} + data-test-subj="mlAdvancedDetectorDeleteButton" /> @@ -98,7 +100,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {detectors.map((d, i) => ( - + @@ -142,6 +144,7 @@ const NoDetectorsWarning: FC<{ show: boolean }> = ({ show }) => { defaultMessage: 'No detectors', })} iconType="alert" + data-test-subj="mlAdvancedNoDetectorsMessage" > = ({ - + Date: Wed, 16 Oct 2019 17:27:29 +0200 Subject: [PATCH 02/15] Add more service functions and update existing ones --- .../services/machine_learning/api.ts | 78 +++++++++ .../machine_learning/job_management.ts | 21 ++- .../services/machine_learning/job_table.ts | 5 + .../machine_learning/job_type_selection.ts | 9 + .../machine_learning/job_wizard_common.ts | 163 ++++++++++++------ 5 files changed, 226 insertions(+), 50 deletions(-) diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index 148e276dc4a14..b9c02c4c76104 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -8,10 +8,24 @@ import expect from '@kbn/expect'; import { isEmpty } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; +export enum JobState { + opening = 'opening', + opened = 'opened', + closing = 'closing', + closed = 'closed', +} + +export enum DatafeedState { + started = 'started', + stopping = 'stopping', + stopped = 'stopped', +} + export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const es = getService('es'); const log = getService('log'); const retry = getService('retry'); + const esSupertest = getService('esSupertest'); return { async hasJobResults(jobId: string): Promise { @@ -79,5 +93,69 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async cleanMlIndices() { await this.deleteIndices('.ml-*'); }, + + async getJobState(jobId: string): Promise { + log.debug(`Fetching job state for job ${jobId}`); + const jobStats = await esSupertest + .get(`/_ml/anomaly_detectors/${jobId}/_stats`) + .expect(200) + .then((res: any) => res.body); + + expect(jobStats.jobs).to.have.length(1); + const state = jobStats.jobs[0].state as JobState; + + if (state in JobState) { + return state; + } + + return undefined; + }, + + async waitForJobState(jobId: string, expectedJobState: JobState) { + await retry.waitForWithTimeout( + `job state to be ${expectedJobState}`, + 2 * 60 * 1000, + async () => { + const state = await this.getJobState(jobId); + if (state === expectedJobState) { + return true; + } else { + throw new Error(`expected job state to be ${expectedJobState} but got ${state}`); + } + } + ); + }, + + async getDatafeedState(datafeedId: string): Promise { + log.debug(`Fetching datafeed state for datafeed ${datafeedId}`); + const datafeedStats = await esSupertest + .get(`/_ml/datafeeds/${datafeedId}/_stats`) + .expect(200) + .then((res: any) => res.body); + + expect(datafeedStats.datafeeds).to.have.length(1); + const state = datafeedStats.datafeeds[0].state as DatafeedState; + + if (state in DatafeedState) { + return state; + } + + return undefined; + }, + + async waitForDatafeedState(datafeedId: string, expectedDatafeedState: DatafeedState) { + await retry.waitForWithTimeout( + `datafeed state to be ${expectedDatafeedState}`, + 2 * 60 * 1000, + async () => { + const state = await this.getDatafeedState(datafeedId); + if (state === expectedDatafeedState) { + return true; + } else { + throw new Error(`expected job state to be ${expectedDatafeedState} but got ${state}`); + } + } + ); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index 44cf06ab44735..4186446833d98 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -3,10 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningAPIProvider, JobState, DatafeedState } from './api'; -export function MachineLearningJobManagementProvider({ getService }: FtrProviderContext) { +export function MachineLearningJobManagementProvider( + { getService }: FtrProviderContext, + mlApi: ProvidedType +) { const testSubjects = getService('testSubjects'); return { @@ -26,5 +31,19 @@ export function MachineLearningJobManagementProvider({ getService }: FtrProvider async assertJobStatsBarExists() { await testSubjects.existOrFail('~mlJobStatsBar'); }, + + async assertStartDatafeedModalExists() { + await testSubjects.existOrFail('mlStartDatafeedModal'); + }, + + async confirmStartDatafeedModal() { + await testSubjects.click('mlStartDatafeedModalStartButton'); + await testSubjects.missingOrFail('mlStartDatafeedModal'); + }, + + async waitForJobCompletion(jobId: string) { + await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DatafeedState.stopped); + await mlApi.waitForJobState(jobId, JobState.closed); + }, }; } 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 17c5d0f54eaa3..af2200c3f67a7 100644 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ b/x-pack/test/functional/services/machine_learning/job_table.ts @@ -157,6 +157,11 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte }); } + public async refreshJobList() { + await testSubjects.click('mlRefreshJobListButton'); + await this.waitForJobsToLoad(); + } + public async waitForJobsToLoad() { await testSubjects.existOrFail('~mlJobListTable', { timeout: 60 * 1000 }); await testSubjects.existOrFail('mlJobListTable loaded', { timeout: 30 * 1000 }); 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 0957558f62165..6686b5b28f200 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 @@ -36,5 +36,14 @@ export function MachineLearningJobTypeSelectionProvider({ getService }: FtrProvi async assertPopulationJobWizardOpen() { await testSubjects.existOrFail('mlPageJobWizard population'); }, + + async selectAdvancedJob() { + await testSubjects.clickWhenNotDisabled('mlJobTypeLinkAdvancedJob'); + await this.assertAdvancedJobWizardOpen(); + }, + + async assertAdvancedJobWizardOpen() { + await testSubjects.existOrFail('mlPageJobWizard advanced'); + }, }; } 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 73764e8f36518..0253a6f00789a 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 @@ -12,6 +12,15 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid const retry = getService('retry'); const testSubjects = getService('testSubjects'); + interface SectionOptions { + withAdvancedSection: boolean; + } + + function advancedSectionSelector(subSelector?: string) { + const subj = ' mlJobWizardAdvancedSection'; + return !subSelector ? subj : `${subj} > ${subSelector}`; + } + return { async clickNextButton() { await testSubjects.existOrFail('mlJobWizardNavButtonNext'); @@ -38,6 +47,10 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid await testSubjects.existOrFail('mlJobWizardStepTitleSummary'); }, + async assertConfigureDatafeedSectionExists() { + await testSubjects.existOrFail('mlJobWizardStepTitleConfigureDatafeed'); + }, + async advanceToPickFieldsSection() { await this.clickNextButton(); await this.assertPickFieldsSectionExists(); @@ -153,75 +166,127 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(await this.getSelectedJobGroups()).to.contain(jobGroup); }, - async assertModelPlotSwitchExists() { - await this.ensureAdvancedSectionOpen(); - await testSubjects.existOrFail('mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot', { - allowHidden: true, - }); + async assertModelPlotSwitchExists( + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + let subj = 'mlJobWizardSwitchModelPlot'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + await testSubjects.existOrFail(subj, { allowHidden: true }); }, - async getModelPlotSwitchCheckedState(): Promise { - await this.ensureAdvancedSectionOpen(); - return await testSubjects.isSelected( - 'mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot' - ); + async getModelPlotSwitchCheckedState( + sectionOptions: SectionOptions = { withAdvancedSection: true } + ): Promise { + let subj = 'mlJobWizardSwitchModelPlot'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + return await testSubjects.isSelected(subj); }, - async assertModelPlotSwitchCheckedState(expectedValue: boolean) { - await this.ensureAdvancedSectionOpen(); - const actualCheckedState = await this.getModelPlotSwitchCheckedState(); + async assertModelPlotSwitchCheckedState( + expectedValue: boolean, + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + const actualCheckedState = await this.getModelPlotSwitchCheckedState({ + withAdvancedSection: sectionOptions.withAdvancedSection, + }); expect(actualCheckedState).to.eql(expectedValue); }, - async assertDedicatedIndexSwitchExists() { - await this.ensureAdvancedSectionOpen(); - await testSubjects.existOrFail( - 'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex', - { allowHidden: true } - ); + async assertDedicatedIndexSwitchExists( + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + let subj = 'mlJobWizardSwitchUseDedicatedIndex'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + await testSubjects.existOrFail(subj, { allowHidden: true }); }, - async getDedicatedIndexSwitchCheckedState(): Promise { - await this.ensureAdvancedSectionOpen(); - return await testSubjects.isSelected( - 'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex' - ); + async getDedicatedIndexSwitchCheckedState( + sectionOptions: SectionOptions = { withAdvancedSection: true } + ): Promise { + let subj = 'mlJobWizardSwitchUseDedicatedIndex'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + return await testSubjects.isSelected(subj); }, - async assertDedicatedIndexSwitchCheckedState(expectedValue: boolean) { - await this.ensureAdvancedSectionOpen(); - const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState(); + async assertDedicatedIndexSwitchCheckedState( + expectedValue: boolean, + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState({ + withAdvancedSection: sectionOptions.withAdvancedSection, + }); expect(actualCheckedState).to.eql(expectedValue); }, - async activateDedicatedIndexSwitch() { - if ((await this.getDedicatedIndexSwitchCheckedState()) === false) { - await testSubjects.clickWhenNotDisabled('mlJobWizardSwitchUseDedicatedIndex'); + async activateDedicatedIndexSwitch( + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + let subj = 'mlJobWizardSwitchUseDedicatedIndex'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + if ( + (await this.getDedicatedIndexSwitchCheckedState({ + withAdvancedSection: sectionOptions.withAdvancedSection, + })) === false + ) { + await testSubjects.clickWhenNotDisabled(subj); } - await this.assertDedicatedIndexSwitchCheckedState(true); + await this.assertDedicatedIndexSwitchCheckedState(true, { + withAdvancedSection: sectionOptions.withAdvancedSection, + }); }, - async assertModelMemoryLimitInputExists() { - await this.ensureAdvancedSectionOpen(); - await testSubjects.existOrFail( - 'mlJobWizardAdvancedSection > mlJobWizardInputModelMemoryLimit' - ); + async assertModelMemoryLimitInputExists( + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + let subj = 'mlJobWizardInputModelMemoryLimit'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + await testSubjects.existOrFail(subj); }, - async assertModelMemoryLimitValue(expectedValue: string) { - await this.ensureAdvancedSectionOpen(); - const actualModelMemoryLimit = await testSubjects.getAttribute( - 'mlJobWizardAdvancedSection > mlJobWizardInputModelMemoryLimit', - 'value' - ); + async assertModelMemoryLimitValue( + expectedValue: string, + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + let subj = 'mlJobWizardInputModelMemoryLimit'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + const actualModelMemoryLimit = await testSubjects.getAttribute(subj, 'value'); expect(actualModelMemoryLimit).to.eql(expectedValue); }, - async setModelMemoryLimit(modelMemoryLimit: string) { - await testSubjects.setValue('mlJobWizardInputModelMemoryLimit', modelMemoryLimit, { - clearWithKeyboard: true, + async setModelMemoryLimit( + modelMemoryLimit: string, + sectionOptions: SectionOptions = { withAdvancedSection: true } + ) { + let subj = 'mlJobWizardInputModelMemoryLimit'; + if (sectionOptions.withAdvancedSection === true) { + await this.ensureAdvancedSectionOpen(); + subj = advancedSectionSelector(subj); + } + await testSubjects.setValue(subj, modelMemoryLimit, { clearWithKeyboard: true }); + await this.assertModelMemoryLimitValue(modelMemoryLimit, { + withAdvancedSection: sectionOptions.withAdvancedSection, }); - await this.assertModelMemoryLimitValue(modelMemoryLimit); }, async assertInfluencerInputExists() { @@ -293,16 +358,16 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid async ensureAdvancedSectionOpen() { await retry.tryForTime(5000, async () => { - if ((await testSubjects.exists('mlJobWizardAdvancedSection')) === false) { + if ((await testSubjects.exists(advancedSectionSelector())) === false) { await testSubjects.click('mlJobWizardToggleAdvancedSection'); - await testSubjects.existOrFail('mlJobWizardAdvancedSection', { timeout: 1000 }); + await testSubjects.existOrFail(advancedSectionSelector(), { timeout: 1000 }); } }); }, async createJobAndWaitForCompletion() { await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob'); - await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 5 * 60 * 1000 }); + await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 2 * 60 * 1000 }); }, }; } From 292f2f73ebcabe01b085d84e9e756f39601cdef6 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 16 Oct 2019 17:28:40 +0200 Subject: [PATCH 03/15] Add jobWizardAdvanced service --- .../services/machine_learning/index.ts | 1 + .../machine_learning/job_wizard_advanced.ts | 178 ++++++++++++++++++ x-pack/test/functional/services/ml.ts | 5 +- 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts index 0dc588ffcc9e5..c5ebe3e9cc156 100644 --- a/x-pack/test/functional/services/machine_learning/index.ts +++ b/x-pack/test/functional/services/machine_learning/index.ts @@ -12,6 +12,7 @@ export { MachineLearningJobManagementProvider } from './job_management'; export { MachineLearningJobSourceSelectionProvider } from './job_source_selection'; export { MachineLearningJobTableProvider } from './job_table'; export { MachineLearningJobTypeSelectionProvider } from './job_type_selection'; +export { MachineLearningJobWizardAdvancedProvider } from './job_wizard_advanced'; export { MachineLearningJobWizardCommonProvider } from './job_wizard_common'; export { MachineLearningJobWizardMultiMetricProvider } from './job_wizard_multi_metric'; export { MachineLearningJobWizardPopulationProvider } from './job_wizard_population'; diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts new file mode 100644 index 0000000000000..b12b533aff1bf --- /dev/null +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProviderContext) { + const comboBox = getService('comboBox'); + const testSubjects = getService('testSubjects'); + + return { + async assertCategorizationFieldInputExists() { + await testSubjects.existOrFail('mlCategorizationFieldNameSelect > comboBoxInput'); + }, + + async assertCategorizationFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlCategorizationFieldNameSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectCategorizationField(identifier: string) { + await comboBox.set('mlCategorizationFieldNameSelect > comboBoxInput', identifier); + await this.assertCategorizationFieldSelection(identifier); + }, + + async assertSummaryCountFieldInputExists() { + await testSubjects.existOrFail('mlSummaryCountFieldNameSelect > comboBoxInput'); + }, + + async assertSummaryCountFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlSummaryCountFieldNameSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectSummaryCountField(identifier: string) { + await comboBox.set('mlSummaryCountFieldNameSelect > comboBoxInput', identifier); + await this.assertSummaryCountFieldSelection(identifier); + }, + + async assertAddDetectorButtonExists() { + await testSubjects.existOrFail('mlAddDetectorButton'); + }, + + async openCreateDetectorModal() { + await testSubjects.click('mlAddDetectorButton'); + await this.assertCreateDetectorModalExists(); + }, + + async assertCreateDetectorModalExists() { + await testSubjects.existOrFail('mlCreateDetectorModal'); + }, + + async assertDetectorFunctionInputExists() { + await testSubjects.existOrFail('mlAdvancedFunctionSelect > comboBoxInput'); + }, + + async assertDetectorFunctionSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlAdvancedFunctionSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectDetectorFunction(identifier: string) { + await comboBox.set('mlAdvancedFunctionSelect > comboBoxInput', identifier); + await this.assertDetectorFunctionSelection(identifier); + }, + + async assertDetectorFieldInputExists() { + await testSubjects.existOrFail('mlAdvancedFieldSelect > comboBoxInput'); + }, + + async assertDetectorFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlAdvancedFieldSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectDetectorField(identifier: string) { + await comboBox.set('mlAdvancedFieldSelect > comboBoxInput', identifier); + await this.assertDetectorFieldSelection(identifier); + }, + + async assertDetectorByFieldInputExists() { + await testSubjects.existOrFail('mlAdvancedByFieldSelect > comboBoxInput'); + }, + + async assertDetectorByFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlAdvancedByFieldSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectDetectorByField(identifier: string) { + await comboBox.set('mlAdvancedByFieldSelect > comboBoxInput', identifier); + await this.assertDetectorByFieldSelection(identifier); + }, + + async assertDetectorOverFieldInputExists() { + await testSubjects.existOrFail('mlAdvancedOverFieldSelect > comboBoxInput'); + }, + + async assertDetectorOverFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlAdvancedOverFieldSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectDetectorOverField(identifier: string) { + await comboBox.set('mlAdvancedOverFieldSelect > comboBoxInput', identifier); + await this.assertDetectorOverFieldSelection(identifier); + }, + + async assertDetectorPartitionFieldInputExists() { + await testSubjects.existOrFail('mlAdvancedPartitionFieldSelect > comboBoxInput'); + }, + + async assertDetectorPartitionFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlAdvancedPartitionFieldSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectDetectorPartitionField(identifier: string) { + await comboBox.set('mlAdvancedPartitionFieldSelect > comboBoxInput', identifier); + await this.assertDetectorPartitionFieldSelection(identifier); + }, + + async assertDetectorExcludeFrequentInputExists() { + await testSubjects.existOrFail('mlAdvancedExcludeFrequentSelect > comboBoxInput'); + }, + + async assertDetectorExcludeFrequentSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlAdvancedExcludeFrequentSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectDetectorExcludeFrequent(identifier: string) { + await comboBox.set('mlAdvancedExcludeFrequentSelect > comboBoxInput', identifier); + await this.assertDetectorExcludeFrequentSelection(identifier); + }, + + async confirmAddDetectorModal() { + await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalSaveButton'); + await testSubjects.missingOrFail('mlCreateDetectorModal'); + }, + + async assertDetectorEntryExists(detectorName: string) { + await testSubjects.existOrFail(`mlAdvancedDetector ${detectorName}`); + }, + + async createJob() { + await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob'); + await testSubjects.existOrFail('mlStartDatafeedModal'); + }, + }; +} diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts index 3feb45ae23bbc..8609552b5dc55 100644 --- a/x-pack/test/functional/services/ml.ts +++ b/x-pack/test/functional/services/ml.ts @@ -15,6 +15,7 @@ import { MachineLearningJobSourceSelectionProvider, MachineLearningJobTableProvider, MachineLearningJobTypeSelectionProvider, + MachineLearningJobWizardAdvancedProvider, MachineLearningJobWizardCommonProvider, MachineLearningJobWizardMultiMetricProvider, MachineLearningJobWizardPopulationProvider, @@ -28,10 +29,11 @@ export function MachineLearningProvider(context: FtrProviderContext) { const api = MachineLearningAPIProvider(context); const dataFrameAnalytics = MachineLearningDataFrameAnalyticsProvider(context); const dataVisualizer = MachineLearningDataVisualizerProvider(context); - const jobManagement = MachineLearningJobManagementProvider(context); + const jobManagement = MachineLearningJobManagementProvider(context, api); const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); + const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context); const jobWizardCommon = MachineLearningJobWizardCommonProvider(context); const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context); const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context); @@ -48,6 +50,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { jobSourceSelection, jobTable, jobTypeSelection, + jobWizardAdvanced, jobWizardCommon, jobWizardMultiMetric, jobWizardPopulation, From 25174f1a47b518670f96cc1e91203383ad32ca55 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 16 Oct 2019 17:30:52 +0200 Subject: [PATCH 04/15] Add test to create advanced job --- .../anomaly_detection/advanced_job.ts | 309 ++++++++++++++++++ .../anomaly_detection/index.ts | 1 + 2 files changed, 310 insertions(+) create mode 100644 x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts new file mode 100644 index 0000000000000..b6e790037fe0d --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -0,0 +1,309 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +interface Detector { + identifier: string; + function: string; + field?: string; + byField?: string; + overField?: string; + partitionField?: string; + excludeFrequent?: string; +} + +// type guards +const isDetectorWithField = (arg: any): arg is Required> => { + return arg.hasOwnProperty('field'); +}; +const isDetectorWithByField = (arg: any): arg is Required> => { + return arg.hasOwnProperty('byField'); +}; +const isDetectorWithOverField = (arg: any): arg is Required> => { + return arg.hasOwnProperty('overField'); +}; +const isDetectorWithPartitionField = ( + arg: any +): arg is Required> => { + return arg.hasOwnProperty('partitionField'); +}; +const isDetectorWithExcludeFrequent = ( + arg: any +): arg is Required> => { + return arg.hasOwnProperty('excludeFrequent'); +}; + +// eslint-disable-next-line import/no-default-export +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + const testDataList = [ + { + suiteTitle: 'with by field detector', + jobSource: 'farequote', + jobId: `fq_advanced_1_${Date.now()}`, + jobDescription: 'Create advanced job from farequote dataset with a by field detector', + jobGroups: ['automated', 'farequote', 'advanced'], + detectors: [ + { + identifier: 'count', + function: 'count', + } as Detector, + { + identifier: 'mean(responsetime) by airline', + function: 'mean', + field: 'responsetime', + byField: 'airline', + } as Detector, + ], + influencers: ['airline'], + bucketSpan: '15m', + memoryLimit: '20mb', + expected: { + row: { + recordCount: '86,274', + memoryStatus: 'ok', + jobState: 'closed', + datafeedState: 'stopped', + latestTimestamp: '2016-02-11 23:59:54', + }, + counts: { + 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', + }, + modelSizeStats: { + result_type: 'model_size_stats', + model_bytes_exceeded: '0', + model_bytes_memory_limit: '20971520', + total_by_field_count: '22', + total_over_field_count: '0', + total_partition_field_count: '3', + bucket_allocation_failures_count: '0', + memory_status: 'ok', + timestamp: '2016-02-11 23:30:00', + }, + }, + }, + ]; + + describe('advanced job', function() { + this.tags(['smoke', 'mlqa']); + + before(async () => { + await esArchiver.loadIfNeeded('ml/farequote'); + }); + + after(async () => { + await esArchiver.unload('ml/farequote'); + await ml.api.cleanMlIndices(); + }); + + for (const testData of testDataList) { + describe(`job creation ${testData.suiteTitle}`, 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.selectSource(testData.jobSource); + }); + + it('loads the advanced job wizard page', async () => { + await ml.jobTypeSelection.selectAdvancedJob(); + }); + + it('displays the configure datafeed step', async () => { + await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); + }); + + it('displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('adds detectors', async () => { + for (const detector of testData.detectors) { + await ml.jobWizardAdvanced.openCreateDetectorModal(); + await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); + await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorByFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); + + await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); + if (isDetectorWithField(detector)) { + await ml.jobWizardAdvanced.selectDetectorField(detector.field); + } + if (isDetectorWithByField(detector)) { + await ml.jobWizardAdvanced.selectDetectorByField(detector.byField); + } + if (isDetectorWithOverField(detector)) { + await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField); + } + if (isDetectorWithPartitionField(detector)) { + await ml.jobWizardAdvanced.selectDetectorField(detector.partitionField); + } + if (isDetectorWithExcludeFrequent(detector)) { + await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent); + } + + await ml.jobWizardAdvanced.confirmAddDetectorModal(); + } + }); + + it('displays detector entries', async () => { + for (const detector of testData.detectors) { + await ml.jobWizardAdvanced.assertDetectorEntryExists(detector.identifier); + } + }); + + it('inputs the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(testData.bucketSpan); + }); + + it('inputs influencers', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection([]); + for (const influencer of testData.influencers) { + await ml.jobWizardCommon.addInfluencer(influencer); + } + }); + + it('inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ + withAdvancedSection: false, + }); + await ml.jobWizardCommon.setModelMemoryLimit(testData.memoryLimit, { + withAdvancedSection: false, + }); + }); + + 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(testData.jobId); + }); + + it('inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(testData.jobDescription); + }); + + it('inputs job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of testData.jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); + }); + + it('displays the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); + }); + + it('enables the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false }); + await ml.jobWizardCommon.activateDedicatedIndexSwitch({ withAdvancedSection: false }); + }); + + 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.jobWizardAdvanced.createJob(); + await ml.jobManagement.assertStartDatafeedModalExists(); + await ml.jobManagement.confirmStartDatafeedModal(); + await ml.jobManagement.waitForJobCompletion(testData.jobId); + }); + + it('displays the created job in the job list', async () => { + await ml.jobTable.refreshJobList(); + await ml.jobTable.filterWithSearchString(testData.jobId); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === testData.jobId)).to.have.length(1); + }); + + it('displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(testData.jobId, { + id: testData.jobId, + description: testData.jobDescription, + jobGroups: [...new Set(testData.jobGroups)].sort(), + recordCount: testData.expected.row.recordCount, + memoryStatus: testData.expected.row.memoryStatus, + jobState: testData.expected.row.jobState, + datafeedState: testData.expected.row.datafeedState, + latestTimestamp: testData.expected.row.latestTimestamp, + }); + + await ml.jobTable.assertJobRowDetailsCounts( + testData.jobId, + { + job_id: testData.jobId, + processed_record_count: testData.expected.counts.processed_record_count, + processed_field_count: testData.expected.counts.processed_field_count, + input_bytes: testData.expected.counts.input_bytes, + input_field_count: testData.expected.counts.input_field_count, + invalid_date_count: testData.expected.counts.invalid_date_count, + missing_field_count: testData.expected.counts.missing_field_count, + out_of_order_timestamp_count: testData.expected.counts.out_of_order_timestamp_count, + empty_bucket_count: testData.expected.counts.empty_bucket_count, + sparse_bucket_count: testData.expected.counts.sparse_bucket_count, + bucket_count: testData.expected.counts.bucket_count, + earliest_record_timestamp: testData.expected.counts.earliest_record_timestamp, + latest_record_timestamp: testData.expected.counts.latest_record_timestamp, + input_record_count: testData.expected.counts.input_record_count, + latest_bucket_timestamp: testData.expected.counts.latest_bucket_timestamp, + }, + { + job_id: testData.jobId, + result_type: testData.expected.modelSizeStats.result_type, + model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded, + model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit, + total_by_field_count: testData.expected.modelSizeStats.total_by_field_count, + total_over_field_count: testData.expected.modelSizeStats.total_over_field_count, + total_partition_field_count: + testData.expected.modelSizeStats.total_partition_field_count, + bucket_allocation_failures_count: + testData.expected.modelSizeStats.bucket_allocation_failures_count, + memory_status: testData.expected.modelSizeStats.memory_status, + timestamp: testData.expected.modelSizeStats.timestamp, + } + ); + }); + }); + } + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts index 8d2e58bb0614d..ba307a24cd739 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts @@ -11,5 +11,6 @@ export default function({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./multi_metric_job')); loadTestFile(require.resolve('./population_job')); loadTestFile(require.resolve('./saved_search_job')); + loadTestFile(require.resolve('./advanced_job')); }); } From 543e7cc3c2bf749a11537a48f279b572fbc78f93 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 17 Oct 2019 17:25:03 +0200 Subject: [PATCH 05/15] Fix advanced section selector --- .../functional/services/machine_learning/job_wizard_common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0253a6f00789a..517194547e1f4 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 @@ -17,7 +17,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid } function advancedSectionSelector(subSelector?: string) { - const subj = ' mlJobWizardAdvancedSection'; + const subj = 'mlJobWizardAdvancedSection'; return !subSelector ? subj : `${subj} > ${subSelector}`; } From 5ef0edb9981901bbb801c229f5cded6220fdd3bd Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 17 Oct 2019 17:26:22 +0200 Subject: [PATCH 06/15] Add detector results existence check --- .../anomaly_detection/advanced_job.ts | 6 +++ .../anomaly_detection/multi_metric_job.ts | 12 +++++ .../anomaly_detection/population_job.ts | 12 +++++ .../anomaly_detection/saved_search_job.ts | 6 +++ .../anomaly_detection/single_metric_job.ts | 8 ++++ .../services/machine_learning/api.ts | 48 +++++++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index b6e790037fe0d..800dfb423f0fd 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -303,6 +303,12 @@ export default function({ getService }: FtrProviderContext) { } ); }); + + it('has detector results', async () => { + for (let i = 0; i < testData.detectors.length; i++) { + await ml.api.assertDetectorResultsExist(testData.jobId, i); + } + }); }); } }); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts index 06dd0df9e470c..72e2f6c93df41 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts @@ -221,6 +221,12 @@ export default function({ getService }: FtrProviderContext) { ); }); + it('job creation has detector results', async () => { + for (let i = 0; i < aggAndFieldIdentifiers.length; i++) { + await ml.api.assertDetectorResultsExist(jobId, i); + } + }); + it('job cloning clicks the clone action and loads the multi metric wizard', async () => { await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertMultiMetricJobWizardOpen(); @@ -351,5 +357,11 @@ export default function({ getService }: FtrProviderContext) { getExpectedModelSizeStats(jobIdClone) ); }); + + it('job cloning has detector results', async () => { + for (let i = 0; i < aggAndFieldIdentifiers.length; i++) { + await ml.api.assertDetectorResultsExist(jobId, i); + } + }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts index cd88a9bba1769..a7faec688dd86 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts @@ -248,6 +248,12 @@ export default function({ getService }: FtrProviderContext) { ); }); + it('job creation has detector results', async () => { + for (let i = 0; i < detectors.length; i++) { + await ml.api.assertDetectorResultsExist(jobId, i); + } + }); + it('job cloning clicks the clone action and loads the population wizard', async () => { await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertPopulationJobWizardOpen(); @@ -387,5 +393,11 @@ export default function({ getService }: FtrProviderContext) { getExpectedModelSizeStats(jobIdClone) ); }); + + it('job cloning has detector results', async () => { + for (let i = 0; i < detectors.length; i++) { + await ml.api.assertDetectorResultsExist(jobId, i); + } + }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts index 2cca37a944563..2ab3815bb2e13 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts @@ -468,6 +468,12 @@ export default function({ getService }: FtrProviderContext) { } ); }); + + it('has detector results', async () => { + for (let i = 0; i < testData.aggAndFieldIdentifiers.length; i++) { + await ml.api.assertDetectorResultsExist(testData.jobId, i); + } + }); }); } }); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts index 6e640f7d173d5..a044fe3667dc3 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts @@ -202,6 +202,10 @@ export default function({ getService }: FtrProviderContext) { ); }); + it('job creation has detector results', async () => { + await ml.api.assertDetectorResultsExist(jobId, 0); + }); + it('job cloning clicks the clone action and loads the single metric wizard', async () => { await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertSingleMetricJobWizardOpen(); @@ -319,6 +323,10 @@ export default function({ getService }: FtrProviderContext) { ); }); + it('job cloning has detector results', async () => { + await ml.api.assertDetectorResultsExist(jobId, 0); + }); + it('job deletion has results for the job before deletion', async () => { await ml.api.assertJobResultsExist(jobIdClone); }); diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index b9c02c4c76104..25af8c43652f8 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -68,6 +68,54 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { ); }, + async hasDetectorResults(jobId: string, detectorIndex: number): Promise { + const response = await es.search({ + index: '.ml-anomalies-*', + body: { + size: 1, + query: { + bool: { + must: [ + { + match: { + job_id: jobId, + }, + }, + { + match: { + result_type: 'record', + }, + }, + { + match: { + detector_index: detectorIndex, + }, + }, + ], + }, + }, + }, + }); + + return response.hits.hits.length > 0; + }, + + async assertDetectorResultsExist(jobId: string, detectorIndex: number) { + await retry.waitForWithTimeout( + `results for detector ${detectorIndex} on job ${jobId} to exist`, + 30 * 1000, + async () => { + if ((await this.hasDetectorResults(jobId, detectorIndex)) === true) { + return true; + } else { + throw new Error( + `expected results for detector ${detectorIndex} on job '${jobId}' to exist` + ); + } + } + ); + }, + async deleteIndices(indices: string) { log.debug(`Deleting indices: '${indices}'...`); const deleteResponse = await es.indices.delete({ From c58dca7c9b8bef29066c567c6a4c7bf155144171 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Fri, 25 Oct 2019 13:14:14 +0200 Subject: [PATCH 07/15] Add advanced job cloning and support for detector descriptions --- .../components/datafeed_step/datafeed.tsx | 2 +- .../advanced_detector_modal.tsx | 1 + .../advanced_view/detector_list.tsx | 4 +- .../anomaly_detection/advanced_job.ts | 262 ++++++++++++++++-- .../machine_learning/job_wizard_advanced.ts | 44 ++- 5 files changed, 283 insertions(+), 30 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/datafeed.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/datafeed.tsx index 7490f05e433cf..7e895ca206849 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/datafeed.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/datafeed.tsx @@ -37,7 +37,7 @@ export const DatafeedStep: FC = ({ setCurrentStep, isCurrentStep }) = {isCurrentStep && ( - + diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx index f9a97fde1a290..5f93361982ea0 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx @@ -301,6 +301,7 @@ export const AdvancedDetectorModal: FC = ({ placeholder={descriptionPlaceholder} value={descriptionOption} onChange={e => setDescriptionOption(e.target.value)} + data-test-subj="mlAdvancedDetectorDescriptionInput" /> diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index 3ace6b24aa77f..df43713e94bc1 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -105,7 +105,9 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {d.detector_description !== undefined ? ( -
{d.detector_description}
+
+ {d.detector_description} +
) : ( detectorToString(d) )} diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index 800dfb423f0fd..f831e414ab857 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -15,6 +15,7 @@ interface Detector { overField?: string; partitionField?: string; excludeFrequent?: string; + description?: string; } // type guards @@ -37,6 +38,9 @@ const isDetectorWithExcludeFrequent = ( ): arg is Required> => { return arg.hasOwnProperty('excludeFrequent'); }; +const isDetectorWithDescription = (arg: any): arg is Required> => { + return arg.hasOwnProperty('description'); +}; // eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { @@ -48,12 +52,19 @@ export default function({ getService }: FtrProviderContext) { suiteTitle: 'with by field detector', jobSource: 'farequote', jobId: `fq_advanced_1_${Date.now()}`, + get jobIdClone(): string { + return `${this.jobId}_clone`; + }, jobDescription: 'Create advanced job from farequote dataset with a by field detector', jobGroups: ['automated', 'farequote', 'advanced'], + get jobGroupsClone(): string[] { + return [...this.jobGroups, 'clone']; + }, detectors: [ { identifier: 'count', function: 'count', + description: 'count detector without split', } as Detector, { identifier: 'mean(responsetime) by airline', @@ -117,33 +128,33 @@ export default function({ getService }: FtrProviderContext) { }); for (const testData of testDataList) { - describe(`job creation ${testData.suiteTitle}`, function() { - it('loads the job management page', async () => { + describe(`${testData.suiteTitle}`, function() { + it('job creation loads the job management page', async () => { await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); }); - it('loads the new job source selection page', async () => { + it('job creation loads the new job source selection page', async () => { await ml.jobManagement.navigateToNewJobSourceSelection(); }); - it('loads the job type selection page', async () => { + it('job creation loads the job type selection page', async () => { await ml.jobSourceSelection.selectSource(testData.jobSource); }); - it('loads the advanced job wizard page', async () => { + it('job creation loads the advanced job wizard page', async () => { await ml.jobTypeSelection.selectAdvancedJob(); }); - it('displays the configure datafeed step', async () => { + it('job creation displays the configure datafeed step', async () => { await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); }); - it('displays the pick fields step', async () => { + it('job creation displays the pick fields step', async () => { await ml.jobWizardCommon.advanceToPickFieldsSection(); }); - it('adds detectors', async () => { + it('job creation adds detectors', async () => { for (const detector of testData.detectors) { await ml.jobWizardAdvanced.openCreateDetectorModal(); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); @@ -152,6 +163,7 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists(); await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists(); await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); + await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists(); await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); if (isDetectorWithField(detector)) { @@ -164,28 +176,34 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField); } if (isDetectorWithPartitionField(detector)) { - await ml.jobWizardAdvanced.selectDetectorField(detector.partitionField); + await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField); } if (isDetectorWithExcludeFrequent(detector)) { await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent); } + if (isDetectorWithDescription(detector)) { + await ml.jobWizardAdvanced.setDetectorDescription(detector.description); + } await ml.jobWizardAdvanced.confirmAddDetectorModal(); } }); - it('displays detector entries', async () => { + it('job creation displays detector entries', async () => { for (const detector of testData.detectors) { - await ml.jobWizardAdvanced.assertDetectorEntryExists(detector.identifier); + await ml.jobWizardAdvanced.assertDetectorEntryExists( + detector.identifier, + isDetectorWithDescription(detector) ? detector.description : undefined + ); } }); - it('inputs the bucket span', async () => { + it('job creation inputs the bucket span', async () => { await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(testData.bucketSpan); }); - it('inputs influencers', async () => { + it('job creation inputs influencers', async () => { await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([]); for (const influencer of testData.influencers) { @@ -193,7 +211,7 @@ export default function({ getService }: FtrProviderContext) { } }); - it('inputs the model memory limit', async () => { + it('job creation inputs the model memory limit', async () => { await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ withAdvancedSection: false, }); @@ -202,21 +220,21 @@ export default function({ getService }: FtrProviderContext) { }); }); - it('displays the job details step', async () => { + it('job creation displays the job details step', async () => { await ml.jobWizardCommon.advanceToJobDetailsSection(); }); - it('inputs the job id', async () => { + it('job creation inputs the job id', async () => { await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(testData.jobId); }); - it('inputs the job description', async () => { + it('job creation inputs the job description', async () => { await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(testData.jobDescription); }); - it('inputs job groups', async () => { + it('job creation inputs job groups', async () => { await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of testData.jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); @@ -224,24 +242,24 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); }); - it('displays the model plot switch', async () => { + it('job creation displays the model plot switch', async () => { await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); }); - it('enables the dedicated index switch', async () => { + it('job creation enables the dedicated index switch', async () => { await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.activateDedicatedIndexSwitch({ withAdvancedSection: false }); }); - it('displays the validation step', async () => { + it('job creation displays the validation step', async () => { await ml.jobWizardCommon.advanceToValidationSection(); }); - it('displays the summary step', async () => { + it('job creation displays the summary step', async () => { await ml.jobWizardCommon.advanceToSummarySection(); }); - it('creates the job and finishes processing', async () => { + it('job creation creates the job and finishes processing', async () => { await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardAdvanced.createJob(); await ml.jobManagement.assertStartDatafeedModalExists(); @@ -249,14 +267,14 @@ export default function({ getService }: FtrProviderContext) { await ml.jobManagement.waitForJobCompletion(testData.jobId); }); - it('displays the created job in the job list', async () => { + it('job creation displays the created job in the job list', async () => { await ml.jobTable.refreshJobList(); await ml.jobTable.filterWithSearchString(testData.jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter(row => row.id === testData.jobId)).to.have.length(1); }); - it('displays details for the created job in the job list', async () => { + it('job creation displays details for the created job in the job list', async () => { await ml.jobTable.assertJobRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -304,11 +322,203 @@ export default function({ getService }: FtrProviderContext) { ); }); - it('has detector results', async () => { + it('job creation has detector results', async () => { for (let i = 0; i < testData.detectors.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobId, i); } }); + + it('job cloning clicks the clone action and loads the advanced wizard', async () => { + await ml.jobTable.clickCloneJobAction(testData.jobId); + await ml.jobTypeSelection.assertAdvancedJobWizardOpen(); + }); + + it('job cloning displays the configure datafeed step', async () => { + await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); + }); + + it('job cloning displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job cloning pre-fills detectors', async () => { + for (const detector of testData.detectors) { + await ml.jobWizardAdvanced.assertDetectorEntryExists( + detector.identifier, + isDetectorWithDescription(detector) ? detector.description : undefined + ); + await ml.jobWizardAdvanced.clickEditDetector(detector.identifier); + + await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); + await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorByFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); + await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists(); + + await ml.jobWizardAdvanced.assertDetectorFunctionSelection(detector.function); + await ml.jobWizardAdvanced.assertDetectorFieldSelection( + isDetectorWithField(detector) ? detector.field : '' + ); + await ml.jobWizardAdvanced.assertDetectorByFieldSelection( + isDetectorWithByField(detector) ? detector.byField : '' + ); + await ml.jobWizardAdvanced.assertDetectorOverFieldSelection( + isDetectorWithOverField(detector) ? detector.overField : '' + ); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection( + isDetectorWithPartitionField(detector) ? detector.partitionField : '' + ); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection( + isDetectorWithExcludeFrequent(detector) ? detector.excludeFrequent : '' + ); + await ml.jobWizardAdvanced.assertDetectorDescriptionValue( + isDetectorWithDescription(detector) ? detector.description : detector.identifier + ); + + await ml.jobWizardAdvanced.cancelAddDetectorModal(); + } + }); + + it('job cloning pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(testData.bucketSpan); + }); + + it('job cloning pre-fills influencers', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection(testData.influencers); + }); + + it('job cloning pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ + withAdvancedSection: false, + }); + await ml.jobWizardCommon.assertModelMemoryLimitValue(testData.memoryLimit, { + withAdvancedSection: false, + }); + }); + + it('job cloning displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('job cloning does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('job cloning inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(testData.jobIdClone); + }); + + it('job cloning pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(testData.jobDescription); + }); + + it('job cloning pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); + }); + + it('job cloning inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroupsClone); + }); + + it('job cloning pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false, { + withAdvancedSection: false, + }); + }); + + it('job cloning pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false }); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true, { + withAdvancedSection: false, + }); + }); + + it('job cloning displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job cloning displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job cloning creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardAdvanced.createJob(); + await ml.jobManagement.assertStartDatafeedModalExists(); + await ml.jobManagement.confirmStartDatafeedModal(); + await ml.jobManagement.waitForJobCompletion(testData.jobIdClone); + }); + + it('job cloning displays the created job in the job list', async () => { + await ml.jobTable.refreshJobList(); + await ml.jobTable.filterWithSearchString(testData.jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === testData.jobIdClone)).to.have.length(1); + }); + + it('job creation displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(testData.jobIdClone, { + id: testData.jobIdClone, + description: testData.jobDescription, + jobGroups: [...new Set(testData.jobGroupsClone)].sort(), + recordCount: testData.expected.row.recordCount, + memoryStatus: testData.expected.row.memoryStatus, + jobState: testData.expected.row.jobState, + datafeedState: testData.expected.row.datafeedState, + latestTimestamp: testData.expected.row.latestTimestamp, + }); + + await ml.jobTable.assertJobRowDetailsCounts( + testData.jobIdClone, + { + job_id: testData.jobIdClone, + processed_record_count: testData.expected.counts.processed_record_count, + processed_field_count: testData.expected.counts.processed_field_count, + input_bytes: testData.expected.counts.input_bytes, + input_field_count: testData.expected.counts.input_field_count, + invalid_date_count: testData.expected.counts.invalid_date_count, + missing_field_count: testData.expected.counts.missing_field_count, + out_of_order_timestamp_count: testData.expected.counts.out_of_order_timestamp_count, + empty_bucket_count: testData.expected.counts.empty_bucket_count, + sparse_bucket_count: testData.expected.counts.sparse_bucket_count, + bucket_count: testData.expected.counts.bucket_count, + earliest_record_timestamp: testData.expected.counts.earliest_record_timestamp, + latest_record_timestamp: testData.expected.counts.latest_record_timestamp, + input_record_count: testData.expected.counts.input_record_count, + latest_bucket_timestamp: testData.expected.counts.latest_bucket_timestamp, + }, + { + job_id: testData.jobIdClone, + result_type: testData.expected.modelSizeStats.result_type, + model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded, + model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit, + total_by_field_count: testData.expected.modelSizeStats.total_by_field_count, + total_over_field_count: testData.expected.modelSizeStats.total_over_field_count, + total_partition_field_count: + testData.expected.modelSizeStats.total_partition_field_count, + bucket_allocation_failures_count: + testData.expected.modelSizeStats.bucket_allocation_failures_count, + memory_status: testData.expected.modelSizeStats.memory_status, + timestamp: testData.expected.modelSizeStats.timestamp, + } + ); + }); + + it('job creation has detector results', async () => { + for (let i = 0; i < testData.detectors.length; i++) { + await ml.api.assertDetectorResultsExist(testData.jobIdClone, i); + } + }); }); } }); diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index b12b533aff1bf..130593a7669fd 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProviderContext) { const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); + const retry = getService('retry'); return { async assertCategorizationFieldInputExists() { @@ -56,7 +57,10 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async assertCreateDetectorModalExists() { - await testSubjects.existOrFail('mlCreateDetectorModal'); + // this retry can be removed as soon as #48734 is merged + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlCreateDetectorModal'); + }); }, async assertDetectorFunctionInputExists() { @@ -161,13 +165,49 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv await this.assertDetectorExcludeFrequentSelection(identifier); }, + async assertDetectorDescriptionInputExists() { + await testSubjects.existOrFail('mlAdvancedDetectorDescriptionInput'); + }, + + async assertDetectorDescriptionValue(expectedValue: string) { + const actualDetectorDescription = await testSubjects.getAttribute( + 'mlAdvancedDetectorDescriptionInput', + 'value' + ); + expect(actualDetectorDescription).to.eql(expectedValue); + }, + + async setDetectorDescription(description: string) { + await testSubjects.setValue('mlAdvancedDetectorDescriptionInput', description, { + clearWithKeyboard: true, + }); + await this.assertDetectorDescriptionValue(description); + }, + async confirmAddDetectorModal() { await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalSaveButton'); await testSubjects.missingOrFail('mlCreateDetectorModal'); }, - async assertDetectorEntryExists(detectorName: string) { + async cancelAddDetectorModal() { + await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalCancelButton'); + await testSubjects.missingOrFail('mlCreateDetectorModal'); + }, + + async assertDetectorEntryExists(detectorName: string, expectedDetectorDescription?: string) { await testSubjects.existOrFail(`mlAdvancedDetector ${detectorName}`); + if (expectedDetectorDescription !== undefined) { + await testSubjects.existOrFail(`mlAdvancedDetector ${detectorName} > detectorDescription`); + const actualDetectorDescription = await testSubjects.getVisibleText( + `mlAdvancedDetector ${detectorName} > detectorDescription` + ); + expect(actualDetectorDescription).to.eql(expectedDetectorDescription); + } + }, + + async clickEditDetector(detectorName: string) { + await testSubjects.click(`mlAdvancedDetector ${detectorName} > mlAdvancedDetectorEditButton`); + await this.assertCreateDetectorModalExists(); }, async createJob() { From 594cc45a5a938880ed26568baf550f61477e95e0 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Fri, 25 Oct 2019 14:03:11 +0200 Subject: [PATCH 08/15] Add validations for datafeed config fields --- .../anomaly_detection/advanced_job.ts | 69 +++++++++++++++ .../machine_learning/job_wizard_advanced.ts | 83 +++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index f831e414ab857..883a022049c5f 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -76,7 +76,22 @@ export default function({ getService }: FtrProviderContext) { influencers: ['airline'], bucketSpan: '15m', memoryLimit: '20mb', + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', expected: { + wizard: { + datafeedQuery: `{ + "bool": { + "must": [ + { + "match_all": {} + } + ] + } +}`, + timeField: '@timestamp', + }, row: { recordCount: '86,274', memoryStatus: 'ok', @@ -150,6 +165,33 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); }); + it('job creation pre-fills the datafeed query editor', async () => { + await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); + await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue( + testData.expected.wizard.datafeedQuery + ); + }); + + it('job creation inputs the query delay', async () => { + await ml.jobWizardAdvanced.assertQueryDelayInputExists(); + await ml.jobWizardAdvanced.setQueryDelay(testData.queryDelay); + }); + + it('job creation inputs the frequency', async () => { + await ml.jobWizardAdvanced.assertFrequencyInputExists(); + await ml.jobWizardAdvanced.setFrequency(testData.frequency); + }); + + it('job creation inputs the scroll size', async () => { + await ml.jobWizardAdvanced.assertScrollSizeInputExists(); + await ml.jobWizardAdvanced.setScrollSize(testData.scrollSize); + }); + + it('job creation pre-fills the time field', async () => { + await ml.jobWizardAdvanced.assertTimeFieldInputExists(); + await ml.jobWizardAdvanced.assertTimeFieldSelection(testData.expected.wizard.timeField); + }); + it('job creation displays the pick fields step', async () => { await ml.jobWizardCommon.advanceToPickFieldsSection(); }); @@ -337,6 +379,33 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); }); + it('job cloning pre-fills the datafeed query editor', async () => { + await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); + await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue( + testData.expected.wizard.datafeedQuery + ); + }); + + it('job cloning pre-fills the query delay', async () => { + await ml.jobWizardAdvanced.assertQueryDelayInputExists(); + await ml.jobWizardAdvanced.assertQueryDelayValue(testData.queryDelay); + }); + + it('job cloning pre-fills the frequency', async () => { + await ml.jobWizardAdvanced.assertFrequencyInputExists(); + await ml.jobWizardAdvanced.assertFrequencyValue(testData.frequency); + }); + + it('job cloning pre-fills the scroll size', async () => { + await ml.jobWizardAdvanced.assertScrollSizeInputExists(); + await ml.jobWizardAdvanced.assertScrollSizeValue(testData.scrollSize); + }); + + it('job creation pre-fills the time field', async () => { + await ml.jobWizardAdvanced.assertTimeFieldInputExists(); + await ml.jobWizardAdvanced.assertTimeFieldSelection(testData.expected.wizard.timeField); + }); + it('job cloning displays the pick fields step', async () => { await ml.jobWizardCommon.advanceToPickFieldsSection(); }); diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 130593a7669fd..58938166f9f42 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -11,8 +11,91 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const aceEditor = getService('aceEditor'); return { + async assertDatafeedQueryEditorExists() { + await testSubjects.existOrFail('mlAdvancedDatafeedQueryEditor > codeEditorContainer'); + }, + + async assertDatafeedQueryEditorValue(expectedValue: string) { + const actualValue = await aceEditor.getValue( + 'mlAdvancedDatafeedQueryEditor > codeEditorContainer' + ); + expect(actualValue).to.eql(expectedValue); + }, + + async assertQueryDelayInputExists() { + await testSubjects.existOrFail('mlJobWizardInputQueryDelay'); + }, + + async assertQueryDelayValue(expectedValue: string) { + const actualQueryDelay = await testSubjects.getAttribute( + 'mlJobWizardInputQueryDelay', + 'value' + ); + expect(actualQueryDelay).to.eql(expectedValue); + }, + + async setQueryDelay(queryDelay: string) { + await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, { + clearWithKeyboard: true, + }); + await this.assertQueryDelayValue(queryDelay); + }, + + async assertFrequencyInputExists() { + await testSubjects.existOrFail('mlJobWizardInputFrequency'); + }, + + async assertFrequencyValue(expectedValue: string) { + const actualFrequency = await testSubjects.getAttribute('mlJobWizardInputFrequency', 'value'); + expect(actualFrequency).to.eql(expectedValue); + }, + + async setFrequency(frequency: string) { + await testSubjects.setValue('mlJobWizardInputFrequency', frequency, { + clearWithKeyboard: true, + }); + await this.assertFrequencyValue(frequency); + }, + + async assertScrollSizeInputExists() { + await testSubjects.existOrFail('mlJobWizardInputScrollSize'); + }, + + async assertScrollSizeValue(expectedValue: string) { + const actualScrollSize = await testSubjects.getAttribute( + 'mlJobWizardInputScrollSize', + 'value' + ); + expect(actualScrollSize).to.eql(expectedValue); + }, + + async setScrollSize(scrollSize: string) { + await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, { + clearWithKeyboard: true, + }); + await this.assertScrollSizeValue(scrollSize); + }, + + async assertTimeFieldInputExists() { + await testSubjects.existOrFail('mlTimeFieldNameSelect > comboBoxInput'); + }, + + async assertTimeFieldSelection(identifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlTimeFieldNameSelect > comboBoxInput' + ); + expect(comboBoxSelectedOptions.length).to.eql(1); + expect(comboBoxSelectedOptions[0]).to.eql(identifier); + }, + + async selectTimeField(identifier: string) { + await comboBox.set('mlTimeFieldNameSelect > comboBoxInput', identifier); + await this.assertTimeFieldSelection(identifier); + }, + async assertCategorizationFieldInputExists() { await testSubjects.existOrFail('mlCategorizationFieldNameSelect > comboBoxInput'); }, From 178a449c854d51115aace7f8a0da551fc21edbd2 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Fri, 25 Oct 2019 15:09:18 +0200 Subject: [PATCH 09/15] Support optional datafeed config values / default values --- .../anomaly_detection/advanced_job.ts | 172 ++++++++++++------ .../machine_learning/job_management.ts | 6 +- .../machine_learning/job_wizard_advanced.ts | 24 ++- 3 files changed, 139 insertions(+), 63 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index 883a022049c5f..6ca239999317b 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -18,7 +18,23 @@ interface Detector { description?: string; } +interface DatafeedConfig { + queryDelay?: string; + frequency?: string; + scrollSize?: string; +} + +interface PickFieldsConfig { + detectors: Detector[]; + influencers: string[]; + bucketSpan: string; + memoryLimit: string; + categorizationField?: string; + summaryCountField?: string; +} + // type guards +// Detector const isDetectorWithField = (arg: any): arg is Required> => { return arg.hasOwnProperty('field'); }; @@ -42,11 +58,43 @@ const isDetectorWithDescription = (arg: any): arg is Required> => { + return arg.hasOwnProperty('queryDelay'); +}; +const isDatafeedConfigWithFrequency = ( + arg: any +): arg is Required> => { + return arg.hasOwnProperty('frequency'); +}; +const isDatafeedConfigWithScrollSize = ( + arg: any +): arg is Required> => { + return arg.hasOwnProperty('scrollSize'); +}; + // eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); + const defaultValues = { + datafeedQuery: `{ + "bool": { + "must": [ + { + "match_all": {} + } + ] + } +}`, + queryDelay: '60s', + frequency: '450s', + scrollSize: '1000', + }; + const testDataList = [ { suiteTitle: 'with by field detector', @@ -60,36 +108,31 @@ export default function({ getService }: FtrProviderContext) { get jobGroupsClone(): string[] { return [...this.jobGroups, 'clone']; }, - detectors: [ - { - identifier: 'count', - function: 'count', - description: 'count detector without split', - } as Detector, - { - identifier: 'mean(responsetime) by airline', - function: 'mean', - field: 'responsetime', - byField: 'airline', - } as Detector, - ], - influencers: ['airline'], - bucketSpan: '15m', - memoryLimit: '20mb', - queryDelay: '55s', - frequency: '350s', - scrollSize: '999', + pickFieldsConfig: { + detectors: [ + { + identifier: 'count', + function: 'count', + description: 'count detector without split', + } as Detector, + { + identifier: 'mean(responsetime) by airline', + function: 'mean', + field: 'responsetime', + byField: 'airline', + } as Detector, + ], + influencers: ['airline'], + bucketSpan: '15m', + memoryLimit: '20mb', + } as PickFieldsConfig, + datafeedConfig: { + queryDelay: '55s', + frequency: '350s', + scrollSize: '999', + } as DatafeedConfig, expected: { wizard: { - datafeedQuery: `{ - "bool": { - "must": [ - { - "match_all": {} - } - ] - } -}`, timeField: '@timestamp', }, row: { @@ -167,24 +210,34 @@ export default function({ getService }: FtrProviderContext) { it('job creation pre-fills the datafeed query editor', async () => { await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); - await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue( - testData.expected.wizard.datafeedQuery - ); + await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery); }); it('job creation inputs the query delay', async () => { await ml.jobWizardAdvanced.assertQueryDelayInputExists(); - await ml.jobWizardAdvanced.setQueryDelay(testData.queryDelay); + if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) { + await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay); + } else { + await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay); + } }); it('job creation inputs the frequency', async () => { await ml.jobWizardAdvanced.assertFrequencyInputExists(); - await ml.jobWizardAdvanced.setFrequency(testData.frequency); + if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) { + await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency); + } else { + await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency); + } }); it('job creation inputs the scroll size', async () => { await ml.jobWizardAdvanced.assertScrollSizeInputExists(); - await ml.jobWizardAdvanced.setScrollSize(testData.scrollSize); + if (isDatafeedConfigWithScrollSize(testData.datafeedConfig)) { + await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize); + } else { + await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize); + } }); it('job creation pre-fills the time field', async () => { @@ -197,7 +250,7 @@ export default function({ getService }: FtrProviderContext) { }); it('job creation adds detectors', async () => { - for (const detector of testData.detectors) { + for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.openCreateDetectorModal(); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); @@ -232,7 +285,7 @@ export default function({ getService }: FtrProviderContext) { }); it('job creation displays detector entries', async () => { - for (const detector of testData.detectors) { + for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.assertDetectorEntryExists( detector.identifier, isDetectorWithDescription(detector) ? detector.description : undefined @@ -242,13 +295,13 @@ export default function({ getService }: FtrProviderContext) { it('job creation inputs the bucket span', async () => { await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.setBucketSpan(testData.bucketSpan); + await ml.jobWizardCommon.setBucketSpan(testData.pickFieldsConfig.bucketSpan); }); it('job creation inputs influencers', async () => { await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([]); - for (const influencer of testData.influencers) { + for (const influencer of testData.pickFieldsConfig.influencers) { await ml.jobWizardCommon.addInfluencer(influencer); } }); @@ -257,7 +310,7 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ withAdvancedSection: false, }); - await ml.jobWizardCommon.setModelMemoryLimit(testData.memoryLimit, { + await ml.jobWizardCommon.setModelMemoryLimit(testData.pickFieldsConfig.memoryLimit, { withAdvancedSection: false, }); }); @@ -365,7 +418,7 @@ export default function({ getService }: FtrProviderContext) { }); it('job creation has detector results', async () => { - for (let i = 0; i < testData.detectors.length; i++) { + for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobId, i); } }); @@ -381,24 +434,34 @@ export default function({ getService }: FtrProviderContext) { it('job cloning pre-fills the datafeed query editor', async () => { await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); - await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue( - testData.expected.wizard.datafeedQuery - ); + await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery); }); it('job cloning pre-fills the query delay', async () => { await ml.jobWizardAdvanced.assertQueryDelayInputExists(); - await ml.jobWizardAdvanced.assertQueryDelayValue(testData.queryDelay); + await ml.jobWizardAdvanced.assertQueryDelayValue( + isDatafeedConfigWithQueryDelay(testData.datafeedConfig) + ? testData.datafeedConfig.queryDelay + : defaultValues.queryDelay + ); }); it('job cloning pre-fills the frequency', async () => { await ml.jobWizardAdvanced.assertFrequencyInputExists(); - await ml.jobWizardAdvanced.assertFrequencyValue(testData.frequency); + await ml.jobWizardAdvanced.assertFrequencyValue( + isDatafeedConfigWithFrequency(testData.datafeedConfig) + ? testData.datafeedConfig.frequency + : defaultValues.frequency + ); }); it('job cloning pre-fills the scroll size', async () => { await ml.jobWizardAdvanced.assertScrollSizeInputExists(); - await ml.jobWizardAdvanced.assertScrollSizeValue(testData.scrollSize); + await ml.jobWizardAdvanced.assertScrollSizeValue( + isDatafeedConfigWithScrollSize(testData.datafeedConfig) + ? testData.datafeedConfig.scrollSize + : defaultValues.scrollSize + ); }); it('job creation pre-fills the time field', async () => { @@ -411,7 +474,7 @@ export default function({ getService }: FtrProviderContext) { }); it('job cloning pre-fills detectors', async () => { - for (const detector of testData.detectors) { + for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.assertDetectorEntryExists( detector.identifier, isDetectorWithDescription(detector) ? detector.description : undefined @@ -452,21 +515,24 @@ export default function({ getService }: FtrProviderContext) { it('job cloning pre-fills the bucket span', async () => { await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.assertBucketSpanValue(testData.bucketSpan); + await ml.jobWizardCommon.assertBucketSpanValue(testData.pickFieldsConfig.bucketSpan); }); it('job cloning pre-fills influencers', async () => { await ml.jobWizardCommon.assertInfluencerInputExists(); - await ml.jobWizardCommon.assertInfluencerSelection(testData.influencers); + await ml.jobWizardCommon.assertInfluencerSelection(testData.pickFieldsConfig.influencers); }); it('job cloning pre-fills the model memory limit', async () => { await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ withAdvancedSection: false, }); - await ml.jobWizardCommon.assertModelMemoryLimitValue(testData.memoryLimit, { - withAdvancedSection: false, - }); + await ml.jobWizardCommon.assertModelMemoryLimitValue( + testData.pickFieldsConfig.memoryLimit, + { + withAdvancedSection: false, + } + ); }); it('job cloning displays the job details step', async () => { @@ -584,7 +650,7 @@ export default function({ getService }: FtrProviderContext) { }); it('job creation has detector results', async () => { - for (let i = 0; i < testData.detectors.length; i++) { + for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobIdClone, i); } }); diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index 4186446833d98..8efa7ae08e6a9 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -13,6 +13,7 @@ export function MachineLearningJobManagementProvider( mlApi: ProvidedType ) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); return { async navigateToNewJobSourceSelection() { @@ -33,7 +34,10 @@ export function MachineLearningJobManagementProvider( }, async assertStartDatafeedModalExists() { - await testSubjects.existOrFail('mlStartDatafeedModal'); + // this retry can be removed as soon as #48734 is merged + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlStartDatafeedModal'); + }); }, async confirmStartDatafeedModal() { diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 58938166f9f42..eeaf128a35341 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -14,6 +14,15 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv const aceEditor = getService('aceEditor'); return { + async getValueOrPlaceholder(inputLocator: string): Promise { + const value = await testSubjects.getAttribute(inputLocator, 'value'); + if (value !== '') { + return value; + } else { + return await testSubjects.getAttribute(inputLocator, 'placeholder'); + } + }, + async assertDatafeedQueryEditorExists() { await testSubjects.existOrFail('mlAdvancedDatafeedQueryEditor > codeEditorContainer'); }, @@ -30,16 +39,14 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async assertQueryDelayValue(expectedValue: string) { - const actualQueryDelay = await testSubjects.getAttribute( - 'mlJobWizardInputQueryDelay', - 'value' - ); + const actualQueryDelay = await this.getValueOrPlaceholder('mlJobWizardInputQueryDelay'); expect(actualQueryDelay).to.eql(expectedValue); }, async setQueryDelay(queryDelay: string) { await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, { clearWithKeyboard: true, + typeCharByChar: true, }); await this.assertQueryDelayValue(queryDelay); }, @@ -49,13 +56,14 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async assertFrequencyValue(expectedValue: string) { - const actualFrequency = await testSubjects.getAttribute('mlJobWizardInputFrequency', 'value'); + const actualFrequency = await this.getValueOrPlaceholder('mlJobWizardInputFrequency'); expect(actualFrequency).to.eql(expectedValue); }, async setFrequency(frequency: string) { await testSubjects.setValue('mlJobWizardInputFrequency', frequency, { clearWithKeyboard: true, + typeCharByChar: true, }); await this.assertFrequencyValue(frequency); }, @@ -65,16 +73,14 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async assertScrollSizeValue(expectedValue: string) { - const actualScrollSize = await testSubjects.getAttribute( - 'mlJobWizardInputScrollSize', - 'value' - ); + const actualScrollSize = await this.getValueOrPlaceholder('mlJobWizardInputScrollSize'); expect(actualScrollSize).to.eql(expectedValue); }, async setScrollSize(scrollSize: string) { await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, { clearWithKeyboard: true, + typeCharByChar: true, }); await this.assertScrollSizeValue(scrollSize); }, From 38cb287fa186323846ce04071e323d75b5bb63c1 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Fri, 25 Oct 2019 16:32:30 +0200 Subject: [PATCH 10/15] Refactor combo box assertions to take string[] as parameter --- .../anomaly_detection/advanced_job.ts | 75 +++++++++++++++++-- .../anomaly_detection/multi_metric_job.ts | 2 +- .../anomaly_detection/population_job.ts | 6 +- .../machine_learning/job_wizard_advanced.ts | 70 ++++++++--------- .../job_wizard_multi_metric.ts | 7 +- .../machine_learning/job_wizard_population.ts | 17 +++-- 6 files changed, 117 insertions(+), 60 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index 6ca239999317b..e94ab23fdfd41 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -75,6 +75,18 @@ const isDatafeedConfigWithScrollSize = ( return arg.hasOwnProperty('scrollSize'); }; +// PickFieldsConfig +const isPickFieldsConfigWithCategorizationField = ( + arg: any +): arg is Required> => { + return arg.hasOwnProperty('categorizationField'); +}; +const isPickFieldsConfigWithSummaryCountField = ( + arg: any +): arg is Required> => { + return arg.hasOwnProperty('summaryCountField'); +}; + // eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); @@ -242,23 +254,52 @@ export default function({ getService }: FtrProviderContext) { it('job creation pre-fills the time field', async () => { await ml.jobWizardAdvanced.assertTimeFieldInputExists(); - await ml.jobWizardAdvanced.assertTimeFieldSelection(testData.expected.wizard.timeField); + await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]); }); it('job creation displays the pick fields step', async () => { await ml.jobWizardCommon.advanceToPickFieldsSection(); }); + it('job creation selects the categorization field', async () => { + await ml.jobWizardAdvanced.assertCategorizationFieldInputExists(); + if (isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig)) { + await ml.jobWizardAdvanced.selectCategorizationField( + testData.pickFieldsConfig.categorizationField + ); + } else { + await ml.jobWizardAdvanced.assertCategorizationFieldSelection([]); + } + }); + + it('job creation selects the summary count field', async () => { + await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists(); + if (isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)) { + await ml.jobWizardAdvanced.selectSummaryCountField( + testData.pickFieldsConfig.summaryCountField + ); + } else { + await ml.jobWizardAdvanced.assertSummaryCountFieldSelection([]); + } + }); + it('job creation adds detectors', async () => { for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.openCreateDetectorModal(); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); + await ml.jobWizardAdvanced.assertDetectorFunctionSelection(['']); await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorFieldSelection(['']); await ml.jobWizardAdvanced.assertDetectorByFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorByFieldSelection(['']); await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorOverFieldSelection(['']); await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists(); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection(['']); await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection(['']); await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists(); + await ml.jobWizardAdvanced.assertDetectorDescriptionValue(''); await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); if (isDetectorWithField(detector)) { @@ -466,13 +507,31 @@ export default function({ getService }: FtrProviderContext) { it('job creation pre-fills the time field', async () => { await ml.jobWizardAdvanced.assertTimeFieldInputExists(); - await ml.jobWizardAdvanced.assertTimeFieldSelection(testData.expected.wizard.timeField); + await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]); }); it('job cloning displays the pick fields step', async () => { await ml.jobWizardCommon.advanceToPickFieldsSection(); }); + it('job cloning pre-fills the categorization field', async () => { + await ml.jobWizardAdvanced.assertCategorizationFieldInputExists(); + await ml.jobWizardAdvanced.assertCategorizationFieldSelection( + isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig) + ? [testData.pickFieldsConfig.categorizationField] + : [] + ); + }); + + it('job cloning pre-fills the summary count field', async () => { + await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists(); + await ml.jobWizardAdvanced.assertSummaryCountFieldSelection( + isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig) + ? [testData.pickFieldsConfig.summaryCountField] + : [] + ); + }); + it('job cloning pre-fills detectors', async () => { for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.assertDetectorEntryExists( @@ -489,21 +548,21 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists(); - await ml.jobWizardAdvanced.assertDetectorFunctionSelection(detector.function); + await ml.jobWizardAdvanced.assertDetectorFunctionSelection([detector.function]); await ml.jobWizardAdvanced.assertDetectorFieldSelection( - isDetectorWithField(detector) ? detector.field : '' + isDetectorWithField(detector) ? [detector.field] : [''] ); await ml.jobWizardAdvanced.assertDetectorByFieldSelection( - isDetectorWithByField(detector) ? detector.byField : '' + isDetectorWithByField(detector) ? [detector.byField] : [''] ); await ml.jobWizardAdvanced.assertDetectorOverFieldSelection( - isDetectorWithOverField(detector) ? detector.overField : '' + isDetectorWithOverField(detector) ? [detector.overField] : [''] ); await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection( - isDetectorWithPartitionField(detector) ? detector.partitionField : '' + isDetectorWithPartitionField(detector) ? [detector.partitionField] : [''] ); await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection( - isDetectorWithExcludeFrequent(detector) ? detector.excludeFrequent : '' + isDetectorWithExcludeFrequent(detector) ? [detector.excludeFrequent] : [''] ); await ml.jobWizardAdvanced.assertDetectorDescriptionValue( isDetectorWithDescription(detector) ? detector.description : detector.identifier diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts index 72e2f6c93df41..b3c9dad0b4d79 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts @@ -264,7 +264,7 @@ export default function({ getService }: FtrProviderContext) { it('job cloning pre-fills the split field', async () => { await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); - await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField); + await ml.jobWizardMultiMetric.assertSplitFieldSelection([splitField]); }); it('job cloning pre-fills influencers', async () => { diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts index a7faec688dd86..0ed8af2396180 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts @@ -281,14 +281,16 @@ export default function({ getService }: FtrProviderContext) { it('job cloning pre-fills the population field', async () => { await ml.jobWizardPopulation.assertPopulationFieldInputExists(); - await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField); + await ml.jobWizardPopulation.assertPopulationFieldSelection([populationField]); }); it('job cloning pre-fills detectors and shows preview with split cards', async () => { for (const [index, detector] of detectors.entries()) { await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER'); - await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, detector.splitField); + await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, [ + detector.splitField, + ]); await ml.jobWizardPopulation.assertDetectorSplitExists(index); await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( index, diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index eeaf128a35341..e1cd1fbf6ebfa 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -7,7 +7,11 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProviderContext) { +export function MachineLearningJobWizardAdvancedProvider({ + getService, + getPageObjects, +}: FtrProviderContext) { + const PageObjects = getPageObjects(['header']); const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -89,51 +93,48 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv await testSubjects.existOrFail('mlTimeFieldNameSelect > comboBoxInput'); }, - async assertTimeFieldSelection(identifier: string) { + async assertTimeFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlTimeFieldNameSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectTimeField(identifier: string) { await comboBox.set('mlTimeFieldNameSelect > comboBoxInput', identifier); - await this.assertTimeFieldSelection(identifier); + await this.assertTimeFieldSelection([identifier]); }, async assertCategorizationFieldInputExists() { await testSubjects.existOrFail('mlCategorizationFieldNameSelect > comboBoxInput'); }, - async assertCategorizationFieldSelection(identifier: string) { + async assertCategorizationFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlCategorizationFieldNameSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectCategorizationField(identifier: string) { await comboBox.set('mlCategorizationFieldNameSelect > comboBoxInput', identifier); - await this.assertCategorizationFieldSelection(identifier); + await this.assertCategorizationFieldSelection([identifier]); }, async assertSummaryCountFieldInputExists() { await testSubjects.existOrFail('mlSummaryCountFieldNameSelect > comboBoxInput'); }, - async assertSummaryCountFieldSelection(identifier: string) { + async assertSummaryCountFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlSummaryCountFieldNameSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectSummaryCountField(identifier: string) { await comboBox.set('mlSummaryCountFieldNameSelect > comboBoxInput', identifier); - await this.assertSummaryCountFieldSelection(identifier); + await this.assertSummaryCountFieldSelection([identifier]); }, async assertAddDetectorButtonExists() { @@ -156,102 +157,96 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv await testSubjects.existOrFail('mlAdvancedFunctionSelect > comboBoxInput'); }, - async assertDetectorFunctionSelection(identifier: string) { + async assertDetectorFunctionSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedFunctionSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorFunction(identifier: string) { await comboBox.set('mlAdvancedFunctionSelect > comboBoxInput', identifier); - await this.assertDetectorFunctionSelection(identifier); + await this.assertDetectorFunctionSelection([identifier]); }, async assertDetectorFieldInputExists() { await testSubjects.existOrFail('mlAdvancedFieldSelect > comboBoxInput'); }, - async assertDetectorFieldSelection(identifier: string) { + async assertDetectorFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorField(identifier: string) { await comboBox.set('mlAdvancedFieldSelect > comboBoxInput', identifier); - await this.assertDetectorFieldSelection(identifier); + await this.assertDetectorFieldSelection([identifier]); }, async assertDetectorByFieldInputExists() { await testSubjects.existOrFail('mlAdvancedByFieldSelect > comboBoxInput'); }, - async assertDetectorByFieldSelection(identifier: string) { + async assertDetectorByFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedByFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorByField(identifier: string) { await comboBox.set('mlAdvancedByFieldSelect > comboBoxInput', identifier); - await this.assertDetectorByFieldSelection(identifier); + await this.assertDetectorByFieldSelection([identifier]); }, async assertDetectorOverFieldInputExists() { await testSubjects.existOrFail('mlAdvancedOverFieldSelect > comboBoxInput'); }, - async assertDetectorOverFieldSelection(identifier: string) { + async assertDetectorOverFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedOverFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorOverField(identifier: string) { await comboBox.set('mlAdvancedOverFieldSelect > comboBoxInput', identifier); - await this.assertDetectorOverFieldSelection(identifier); + await this.assertDetectorOverFieldSelection([identifier]); }, async assertDetectorPartitionFieldInputExists() { await testSubjects.existOrFail('mlAdvancedPartitionFieldSelect > comboBoxInput'); }, - async assertDetectorPartitionFieldSelection(identifier: string) { + async assertDetectorPartitionFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedPartitionFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorPartitionField(identifier: string) { await comboBox.set('mlAdvancedPartitionFieldSelect > comboBoxInput', identifier); - await this.assertDetectorPartitionFieldSelection(identifier); + await this.assertDetectorPartitionFieldSelection([identifier]); }, async assertDetectorExcludeFrequentInputExists() { await testSubjects.existOrFail('mlAdvancedExcludeFrequentSelect > comboBoxInput'); }, - async assertDetectorExcludeFrequentSelection(identifier: string) { + async assertDetectorExcludeFrequentSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedExcludeFrequentSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorExcludeFrequent(identifier: string) { await comboBox.set('mlAdvancedExcludeFrequentSelect > comboBoxInput', identifier); - await this.assertDetectorExcludeFrequentSelection(identifier); + await this.assertDetectorExcludeFrequentSelection([identifier]); }, async assertDetectorDescriptionInputExists() { @@ -301,6 +296,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv async createJob() { await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob'); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await testSubjects.existOrFail('mlStartDatafeedModal'); }, }; 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 d9df6a9d682a7..2fb768d924cff 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 @@ -16,17 +16,16 @@ export function MachineLearningJobWizardMultiMetricProvider({ getService }: FtrP await testSubjects.existOrFail('mlMultiMetricSplitFieldSelect > comboBoxInput'); }, - async assertSplitFieldSelection(identifier: string) { + async assertSplitFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlMultiMetricSplitFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectSplitField(identifier: string) { await comboBox.set('mlMultiMetricSplitFieldSelect > comboBoxInput', identifier); - await this.assertSplitFieldSelection(identifier); + await this.assertSplitFieldSelection([identifier]); }, async assertDetectorSplitExists(splitField: string) { 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 892bdaf394936..8ff9d5c12a642 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 @@ -16,17 +16,16 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr await testSubjects.existOrFail('mlPopulationSplitFieldSelect > comboBoxInput'); }, - async assertPopulationFieldSelection(identifier: string) { + async assertPopulationFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlPopulationSplitFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectPopulationField(identifier: string) { await comboBox.set('mlPopulationSplitFieldSelect > comboBoxInput', identifier); - await this.assertPopulationFieldSelection(identifier); + await this.assertPopulationFieldSelection([identifier]); }, async assertDetectorSplitFieldInputExists(detectorPosition: number) { @@ -35,12 +34,14 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr ); }, - async assertDetectorSplitFieldSelection(detectorPosition: number, identifier: string) { + async assertDetectorSplitFieldSelection( + detectorPosition: number, + expectedIdentifier: string[] + ) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( `mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput` ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(identifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectDetectorSplitField(detectorPosition: number, identifier: string) { @@ -48,7 +49,7 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr `mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput`, identifier ); - await this.assertDetectorSplitFieldSelection(detectorPosition, identifier); + await this.assertDetectorSplitFieldSelection(detectorPosition, [identifier]); }, async assertDetectorSplitExists(detectorPosition: number) { From dca9a83defcc942cb75bbf1f3cbb2d7d43363589 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 28 Oct 2019 14:21:02 +0100 Subject: [PATCH 11/15] Removed not needed fields from ml/farequote archive mapping --- .../es_archives/ml/farequote/mappings.json | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/x-pack/test/functional/es_archives/ml/farequote/mappings.json b/x-pack/test/functional/es_archives/ml/farequote/mappings.json index b4c6be7655805..4fe559cc85fe1 100644 --- a/x-pack/test/functional/es_archives/ml/farequote/mappings.json +++ b/x-pack/test/functional/es_archives/ml/farequote/mappings.json @@ -21,24 +21,6 @@ "airline": { "type": "keyword" }, - "host": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "path": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, "responsetime": { "type": "float" }, @@ -1102,4 +1084,4 @@ } } } -} \ No newline at end of file +} From cf6fc4571e045375ae05e9fe668fb9fdc521fae8 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 28 Oct 2019 14:23:20 +0100 Subject: [PATCH 12/15] Use ecommerce data instead of farequote, add categorization job --- .../advanced_view/detector_list.tsx | 11 +- .../anomaly_detection/advanced_job.ts | 223 ++++++++++++------ .../services/machine_learning/job_table.ts | 2 +- .../machine_learning/job_wizard_advanced.ts | 30 ++- .../machine_learning/job_wizard_common.ts | 2 +- 5 files changed, 186 insertions(+), 82 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index df43713e94bc1..ff21ba6cbf5b5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -25,6 +25,7 @@ import { JobCreatorContext } from '../../../job_creator_context'; import { AdvancedJobCreator } from '../../../../../common/job_creator'; import { Validation } from '../../../../../common/job_validator'; import { detectorToString } from '../../../../../../../util/string_utils'; +import { Detector } from '../../../../../common/job_creator/configs'; interface Props { isActive: boolean; @@ -100,7 +101,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {detectors.map((d, i) => ( - + @@ -109,7 +110,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {d.detector_description} ) : ( - detectorToString(d) + )} {isActive && ( @@ -121,7 +122,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {d.detector_description !== undefined && ( - {detectorToString(d)} + )} @@ -171,3 +172,7 @@ const DuplicateDetectorsWarning: FC<{ validation: Validation }> = ({ validation
); }; + +const DetectorIdentifier: FC<{ detector: Detector }> = ({ detector }) => { + return
{detectorToString(detector)}
; +}; diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index e94ab23fdfd41..545bd35763e49 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -109,34 +109,56 @@ export default function({ getService }: FtrProviderContext) { const testDataList = [ { - suiteTitle: 'with by field detector', - jobSource: 'farequote', - jobId: `fq_advanced_1_${Date.now()}`, + suiteTitle: 'with multiple metric detectors and custom datafeed settings', + jobSource: 'ecommerce', + jobId: `ec_advanced_1_${Date.now()}`, get jobIdClone(): string { return `${this.jobId}_clone`; }, - jobDescription: 'Create advanced job from farequote dataset with a by field detector', - jobGroups: ['automated', 'farequote', 'advanced'], + jobDescription: + 'Create advanced job from ecommerce dataset with multiple metric detectors and custom datafeed settings', + jobGroups: ['automated', 'ecommerce', 'advanced'], get jobGroupsClone(): string[] { return [...this.jobGroups, 'clone']; }, pickFieldsConfig: { detectors: [ { - identifier: 'count', - function: 'count', - description: 'count detector without split', + identifier: 'high_count', + function: 'high_count', + description: 'high_count detector without split', } as Detector, { - identifier: 'mean(responsetime) by airline', + identifier: 'mean("products.base_price") by "category.keyword"', function: 'mean', - field: 'responsetime', - byField: 'airline', + field: 'products.base_price', + byField: 'category.keyword', + } as Detector, + { + identifier: 'sum("products.discount_amount") over customer_id', + function: 'sum', + field: 'products.discount_amount', + overField: 'customer_id', + } as Detector, + { + identifier: 'median(total_quantity) partition_field_name=customer_gender', + function: 'median', + field: 'total_quantity', + partitionField: 'customer_gender', + } as Detector, + { + identifier: + 'max(total_quantity) by "geoip.continent_name" over customer_id partition_field_name=customer_gender', + function: 'max', + field: 'total_quantity', + byField: 'geoip.continent_name', + overField: 'customer_id', + partitionField: 'customer_gender', } as Detector, ], - influencers: ['airline'], - bucketSpan: '15m', - memoryLimit: '20mb', + influencers: ['customer_id', 'category.keyword', 'geoip.continent_name', 'customer_gender'], + bucketSpan: '1h', + memoryLimit: '10mb', } as PickFieldsConfig, datafeedConfig: { queryDelay: '55s', @@ -145,57 +167,124 @@ export default function({ getService }: FtrProviderContext) { } as DatafeedConfig, expected: { wizard: { - timeField: '@timestamp', + timeField: 'order_date', }, row: { - recordCount: '86,274', + recordCount: '4,675', memoryStatus: 'ok', jobState: 'closed', datafeedState: 'stopped', - latestTimestamp: '2016-02-11 23:59:54', + latestTimestamp: '2019-07-12 23:45:36', }, counts: { - processed_record_count: '86,274', - processed_field_count: '172,548', - input_bytes: '6.4 MB', - input_field_count: '172,548', + processed_record_count: '4,675', + processed_field_count: '32,725', + input_bytes: '1.1 MB', + input_field_count: '32,725', invalid_date_count: '0', missing_field_count: '0', out_of_order_timestamp_count: '0', empty_bucket_count: '0', sparse_bucket_count: '0', - bucket_count: '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', + bucket_count: '743', + earliest_record_timestamp: '2019-06-12 00:04:19', + latest_record_timestamp: '2019-07-12 23:45:36', + input_record_count: '4,675', + latest_bucket_timestamp: '2019-07-12 23:00:00', + }, + modelSizeStats: { + result_type: 'model_size_stats', + model_bytes_exceeded: '0', + model_bytes_memory_limit: '10485760', + total_by_field_count: '37', + total_over_field_count: '92', + total_partition_field_count: '8', + bucket_allocation_failures_count: '0', + memory_status: 'ok', + timestamp: '2019-07-12 22:00:00', + }, + }, + }, + { + suiteTitle: 'with categorization detector and default datafeed settings', + jobSource: 'ecommerce', + jobId: `ec_advanced_2_${Date.now()}`, + get jobIdClone(): string { + return `${this.jobId}_clone`; + }, + jobDescription: + 'Create advanced job from ecommerce dataset with a categorization detector and default datafeed settings', + jobGroups: ['automated', 'ecommerce', 'advanced'], + get jobGroupsClone(): string[] { + return [...this.jobGroups, 'clone']; + }, + pickFieldsConfig: { + categorizationField: 'products.product_name', + detectors: [ + { + identifier: 'count by mlcategory', + function: 'count', + byField: 'mlcategory', + } as Detector, + ], + influencers: ['mlcategory'], + bucketSpan: '12h', + memoryLimit: '100mb', + } as PickFieldsConfig, + datafeedConfig: {} as DatafeedConfig, + expected: { + wizard: { + timeField: 'order_date', + }, + row: { + recordCount: '4,675', + memoryStatus: 'ok', + jobState: 'closed', + datafeedState: 'stopped', + latestTimestamp: '2019-07-12 23:45:36', + }, + counts: { + processed_record_count: '4,675', + processed_field_count: '4,588', + input_bytes: '4.4 MB', + input_field_count: '6,154', + invalid_date_count: '0', + missing_field_count: '87', + out_of_order_timestamp_count: '0', + empty_bucket_count: '0', + sparse_bucket_count: '0', + bucket_count: '61', + earliest_record_timestamp: '2019-06-12 00:04:19', + latest_record_timestamp: '2019-07-12 23:45:36', + input_record_count: '4,675', + latest_bucket_timestamp: '2019-07-12 12:00:00', }, modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0', - model_bytes_memory_limit: '20971520', - total_by_field_count: '22', + model_bytes_memory_limit: '104857600', + total_by_field_count: '3,787', total_over_field_count: '0', - total_partition_field_count: '3', + total_partition_field_count: '2', bucket_allocation_failures_count: '0', memory_status: 'ok', - timestamp: '2016-02-11 23:30:00', + timestamp: '2019-07-12 00:00:00', }, }, }, ]; - describe('advanced job', function() { + // eslint-disable-next-line ban/ban + describe.only('advanced job', function() { this.tags(['smoke', 'mlqa']); - before(async () => { - await esArchiver.loadIfNeeded('ml/farequote'); + await esArchiver.load('ml/ecommerce'); }); - after(async () => { - await esArchiver.unload('ml/farequote'); - await ml.api.cleanMlIndices(); - }); + // after(async () => { + // await esArchiver.unload('ml/ecommerce'); + // await ml.api.cleanMlIndices(); + // }); for (const testData of testDataList) { describe(`${testData.suiteTitle}`, function() { @@ -227,28 +316,25 @@ export default function({ getService }: FtrProviderContext) { it('job creation inputs the query delay', async () => { await ml.jobWizardAdvanced.assertQueryDelayInputExists(); + await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay); if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) { await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay); - } else { - await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay); } }); it('job creation inputs the frequency', async () => { await ml.jobWizardAdvanced.assertFrequencyInputExists(); + await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency); if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) { await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency); - } else { - await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency); } }); it('job creation inputs the scroll size', async () => { await ml.jobWizardAdvanced.assertScrollSizeInputExists(); + await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize); if (isDatafeedConfigWithScrollSize(testData.datafeedConfig)) { await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize); - } else { - await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize); } }); @@ -287,17 +373,17 @@ export default function({ getService }: FtrProviderContext) { for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.openCreateDetectorModal(); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); - await ml.jobWizardAdvanced.assertDetectorFunctionSelection(['']); + await ml.jobWizardAdvanced.assertDetectorFunctionSelection([]); await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); - await ml.jobWizardAdvanced.assertDetectorFieldSelection(['']); + await ml.jobWizardAdvanced.assertDetectorFieldSelection([]); await ml.jobWizardAdvanced.assertDetectorByFieldInputExists(); - await ml.jobWizardAdvanced.assertDetectorByFieldSelection(['']); + await ml.jobWizardAdvanced.assertDetectorByFieldSelection([]); await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists(); - await ml.jobWizardAdvanced.assertDetectorOverFieldSelection(['']); + await ml.jobWizardAdvanced.assertDetectorOverFieldSelection([]); await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists(); - await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection(['']); + await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection([]); await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists(); - await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection(['']); + await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection([]); await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists(); await ml.jobWizardAdvanced.assertDetectorDescriptionValue(''); @@ -326,8 +412,9 @@ export default function({ getService }: FtrProviderContext) { }); it('job creation displays detector entries', async () => { - for (const detector of testData.pickFieldsConfig.detectors) { + for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) { await ml.jobWizardAdvanced.assertDetectorEntryExists( + index, detector.identifier, isDetectorWithDescription(detector) ? detector.description : undefined ); @@ -480,20 +567,16 @@ export default function({ getService }: FtrProviderContext) { it('job cloning pre-fills the query delay', async () => { await ml.jobWizardAdvanced.assertQueryDelayInputExists(); - await ml.jobWizardAdvanced.assertQueryDelayValue( - isDatafeedConfigWithQueryDelay(testData.datafeedConfig) - ? testData.datafeedConfig.queryDelay - : defaultValues.queryDelay - ); + if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) { + await ml.jobWizardAdvanced.assertQueryDelayValue(testData.datafeedConfig.queryDelay); + } }); it('job cloning pre-fills the frequency', async () => { await ml.jobWizardAdvanced.assertFrequencyInputExists(); - await ml.jobWizardAdvanced.assertFrequencyValue( - isDatafeedConfigWithFrequency(testData.datafeedConfig) - ? testData.datafeedConfig.frequency - : defaultValues.frequency - ); + if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) { + await ml.jobWizardAdvanced.assertFrequencyValue(testData.datafeedConfig.frequency); + } }); it('job cloning pre-fills the scroll size', async () => { @@ -533,12 +616,13 @@ export default function({ getService }: FtrProviderContext) { }); it('job cloning pre-fills detectors', async () => { - for (const detector of testData.pickFieldsConfig.detectors) { + for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) { await ml.jobWizardAdvanced.assertDetectorEntryExists( + index, detector.identifier, isDetectorWithDescription(detector) ? detector.description : undefined ); - await ml.jobWizardAdvanced.clickEditDetector(detector.identifier); + await ml.jobWizardAdvanced.clickEditDetector(index); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); await ml.jobWizardAdvanced.assertDetectorFieldInputExists(); @@ -550,22 +634,25 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.assertDetectorFunctionSelection([detector.function]); await ml.jobWizardAdvanced.assertDetectorFieldSelection( - isDetectorWithField(detector) ? [detector.field] : [''] + isDetectorWithField(detector) ? [detector.field] : [] ); await ml.jobWizardAdvanced.assertDetectorByFieldSelection( - isDetectorWithByField(detector) ? [detector.byField] : [''] + isDetectorWithByField(detector) ? [detector.byField] : [] ); await ml.jobWizardAdvanced.assertDetectorOverFieldSelection( - isDetectorWithOverField(detector) ? [detector.overField] : [''] + isDetectorWithOverField(detector) ? [detector.overField] : [] ); await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection( - isDetectorWithPartitionField(detector) ? [detector.partitionField] : [''] + isDetectorWithPartitionField(detector) ? [detector.partitionField] : [] ); await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection( - isDetectorWithExcludeFrequent(detector) ? [detector.excludeFrequent] : [''] + isDetectorWithExcludeFrequent(detector) ? [detector.excludeFrequent] : [] ); + // Currently, a description different form the identifier is generated for detectors with partition field await ml.jobWizardAdvanced.assertDetectorDescriptionValue( - isDetectorWithDescription(detector) ? detector.description : detector.identifier + isDetectorWithDescription(detector) + ? detector.description + : detector.identifier.replace('partition_field_name', 'partitionfield') ); await ml.jobWizardAdvanced.cancelAddDetectorModal(); 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 af2200c3f67a7..7eded43d1f058 100644 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ b/x-pack/test/functional/services/machine_learning/job_table.ts @@ -211,7 +211,7 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte } public async clickActionsMenu(jobId: string) { - retry.tryForTime(30 * 1000, async () => { + await retry.tryForTime(30 * 1000, async () => { if (!(await testSubjects.exists('mlActionButtonDeleteJob'))) { await testSubjects.click(this.rowSelector(jobId, 'euiCollapsedItemActionsButton')); await testSubjects.existOrFail('mlActionButtonDeleteJob', { timeout: 5000 }); diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index e1cd1fbf6ebfa..362441fe37690 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -11,7 +11,6 @@ export function MachineLearningJobWizardAdvancedProvider({ getService, getPageObjects, }: FtrProviderContext) { - const PageObjects = getPageObjects(['header']); const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -278,26 +277,39 @@ export function MachineLearningJobWizardAdvancedProvider({ await testSubjects.missingOrFail('mlCreateDetectorModal'); }, - async assertDetectorEntryExists(detectorName: string, expectedDetectorDescription?: string) { - await testSubjects.existOrFail(`mlAdvancedDetector ${detectorName}`); + async assertDetectorEntryExists( + detectorIndex: number, + expectedDetectorName: string, + expectedDetectorDescription?: string + ) { + await testSubjects.existOrFail(`mlAdvancedDetector ${detectorIndex}`); + + const actualDetectorIdentifier = await testSubjects.getVisibleText( + `mlAdvancedDetector ${detectorIndex} > detectorIdentifier` + ); + expect(actualDetectorIdentifier).to.eql(expectedDetectorName); + if (expectedDetectorDescription !== undefined) { - await testSubjects.existOrFail(`mlAdvancedDetector ${detectorName} > detectorDescription`); const actualDetectorDescription = await testSubjects.getVisibleText( - `mlAdvancedDetector ${detectorName} > detectorDescription` + `mlAdvancedDetector ${detectorIndex} > detectorDescription` ); expect(actualDetectorDescription).to.eql(expectedDetectorDescription); } }, - async clickEditDetector(detectorName: string) { - await testSubjects.click(`mlAdvancedDetector ${detectorName} > mlAdvancedDetectorEditButton`); + async clickEditDetector(detectorIndex: number) { + await testSubjects.click( + `mlAdvancedDetector ${detectorIndex} > mlAdvancedDetectorEditButton` + ); await this.assertCreateDetectorModalExists(); }, async createJob() { await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob'); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - await testSubjects.existOrFail('mlStartDatafeedModal'); + // this retry can be removed as soon as #48734 is merged + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('mlStartDatafeedModal'); + }); }, }; } 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 517194547e1f4..3a71f96fa3fbd 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 @@ -302,7 +302,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid }, async addInfluencer(influencer: string) { - await comboBox.setCustom('mlInfluencerSelect > comboBoxInput', influencer); + await comboBox.set('mlInfluencerSelect > comboBoxInput', influencer); expect(await this.getSelectedInfluencers()).to.contain(influencer); }, From 242be041239f4c8a205aa14476d9b8953825f4dd Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 28 Oct 2019 15:21:50 +0100 Subject: [PATCH 13/15] Remove debug comments and suite restriction --- .../anomaly_detection/advanced_job.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index 545bd35763e49..8dfbee84515b8 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -274,17 +274,16 @@ export default function({ getService }: FtrProviderContext) { }, ]; - // eslint-disable-next-line ban/ban - describe.only('advanced job', function() { + describe('advanced job', function() { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/ecommerce'); }); - // after(async () => { - // await esArchiver.unload('ml/ecommerce'); - // await ml.api.cleanMlIndices(); - // }); + after(async () => { + await esArchiver.unload('ml/ecommerce'); + await ml.api.cleanMlIndices(); + }); for (const testData of testDataList) { describe(`${testData.suiteTitle}`, function() { From f32ca3139499284b70783f4e5f49952611389490 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 29 Oct 2019 08:58:07 +0100 Subject: [PATCH 14/15] Use ML states enums, rename detector FC --- .../advanced_view/detector_list.tsx | 6 +-- .../services/machine_learning/api.ts | 37 +++++-------------- .../machine_learning/job_management.ts | 8 ++-- 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index ff21ba6cbf5b5..8612b9ac38ce8 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -110,7 +110,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {d.detector_description} ) : ( - + )}
{isActive && ( @@ -122,7 +122,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {d.detector_description !== undefined && ( - + )}
@@ -173,6 +173,6 @@ const DuplicateDetectorsWarning: FC<{ validation: Validation }> = ({ validation ); }; -const DetectorIdentifier: FC<{ detector: Detector }> = ({ detector }) => { +const StringifiedDetector: FC<{ detector: Detector }> = ({ detector }) => { return
{detectorToString(detector)}
; }; diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index 25af8c43652f8..270722a97d6b6 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -8,18 +8,7 @@ import expect from '@kbn/expect'; import { isEmpty } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; -export enum JobState { - opening = 'opening', - opened = 'opened', - closing = 'closing', - closed = 'closed', -} - -export enum DatafeedState { - started = 'started', - stopping = 'stopping', - stopped = 'stopped', -} +import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states'; export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const es = getService('es'); @@ -142,7 +131,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { await this.deleteIndices('.ml-*'); }, - async getJobState(jobId: string): Promise { + async getJobState(jobId: string): Promise { log.debug(`Fetching job state for job ${jobId}`); const jobStats = await esSupertest .get(`/_ml/anomaly_detectors/${jobId}/_stats`) @@ -150,16 +139,12 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { .then((res: any) => res.body); expect(jobStats.jobs).to.have.length(1); - const state = jobStats.jobs[0].state as JobState; + const state: JOB_STATE = jobStats.jobs[0].state; - if (state in JobState) { - return state; - } - - return undefined; + return state; }, - async waitForJobState(jobId: string, expectedJobState: JobState) { + async waitForJobState(jobId: string, expectedJobState: JOB_STATE) { await retry.waitForWithTimeout( `job state to be ${expectedJobState}`, 2 * 60 * 1000, @@ -174,7 +159,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { ); }, - async getDatafeedState(datafeedId: string): Promise { + async getDatafeedState(datafeedId: string): Promise { log.debug(`Fetching datafeed state for datafeed ${datafeedId}`); const datafeedStats = await esSupertest .get(`/_ml/datafeeds/${datafeedId}/_stats`) @@ -182,16 +167,12 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { .then((res: any) => res.body); expect(datafeedStats.datafeeds).to.have.length(1); - const state = datafeedStats.datafeeds[0].state as DatafeedState; - - if (state in DatafeedState) { - return state; - } + const state: DATAFEED_STATE = datafeedStats.datafeeds[0].state; - return undefined; + return state; }, - async waitForDatafeedState(datafeedId: string, expectedDatafeedState: DatafeedState) { + async waitForDatafeedState(datafeedId: string, expectedDatafeedState: DATAFEED_STATE) { await retry.waitForWithTimeout( `datafeed state to be ${expectedDatafeedState}`, 2 * 60 * 1000, diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index 8efa7ae08e6a9..ddab5fd68f13c 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -6,7 +6,9 @@ import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { MachineLearningAPIProvider, JobState, DatafeedState } from './api'; +import { MachineLearningAPIProvider } from './api'; + +import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states'; export function MachineLearningJobManagementProvider( { getService }: FtrProviderContext, @@ -46,8 +48,8 @@ export function MachineLearningJobManagementProvider( }, async waitForJobCompletion(jobId: string) { - await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DatafeedState.stopped); - await mlApi.waitForJobState(jobId, JobState.closed); + await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DATAFEED_STATE.STOPPED); + await mlApi.waitForJobState(jobId, JOB_STATE.CLOSED); }, }; } From dfe2e179f6373b7505a97b5bd35e078acf6acae4 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 29 Oct 2019 10:58:07 +0100 Subject: [PATCH 15/15] Add ml namespace to subj names --- .../components/advanced_view/detector_list.tsx | 4 ++-- .../services/machine_learning/job_wizard_advanced.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index 8612b9ac38ce8..f996a0e9728ba 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -106,7 +106,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => {d.detector_description !== undefined ? ( -
+
{d.detector_description}
) : ( @@ -174,5 +174,5 @@ const DuplicateDetectorsWarning: FC<{ validation: Validation }> = ({ validation }; const StringifiedDetector: FC<{ detector: Detector }> = ({ detector }) => { - return
{detectorToString(detector)}
; + return
{detectorToString(detector)}
; }; diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 362441fe37690..71b76a6885592 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -285,13 +285,13 @@ export function MachineLearningJobWizardAdvancedProvider({ await testSubjects.existOrFail(`mlAdvancedDetector ${detectorIndex}`); const actualDetectorIdentifier = await testSubjects.getVisibleText( - `mlAdvancedDetector ${detectorIndex} > detectorIdentifier` + `mlAdvancedDetector ${detectorIndex} > mlDetectorIdentifier` ); expect(actualDetectorIdentifier).to.eql(expectedDetectorName); if (expectedDetectorDescription !== undefined) { const actualDetectorDescription = await testSubjects.getVisibleText( - `mlAdvancedDetector ${detectorIndex} > detectorDescription` + `mlAdvancedDetector ${detectorIndex} > mlDetectorDescription` ); expect(actualDetectorDescription).to.eql(expectedDetectorDescription); }