From f825cfa521231ef67ff5ee854297c4fa1431f7cd Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 6 Dec 2019 16:37:49 +0100 Subject: [PATCH] [ML] Functional tests for Additional settings in the Job wizards (#52269) * [ML] test custom urls in multi-metric wizard * [ML] calendars test * [ML] tests for job cloning * [ML] single metric * [ML] advanced job * [ML] population job * [ML] update snapshot * [ML] ensure calendar deleted and created * [ML] improve custom urls assertation * [ML] update snapshot * [ML] update snapshot, fix data-test-subject * [ML] remove redundant functions * [ML] add ensureAdditionalSettingsSectionOpen check * [ML] remove assignCalendar method * [ML] ensure model window disappears after adding a custom url * [ML] create calendar logging, remove unused deleteCalendar method, parameterized saveCustomUrl --- .../__snapshots__/editor.test.tsx.snap | 6 ++ .../__snapshots__/list.test.tsx.snap | 12 +++- .../components/custom_url_editor/editor.tsx | 1 + .../components/custom_url_editor/list.tsx | 5 +- .../edit_job_flyout/tabs/custom_urls.tsx | 19 +++++- .../additional_section/additional_section.tsx | 27 +++++---- .../calendars/calendars_selection.tsx | 2 +- .../anomaly_detection/advanced_job.ts | 25 ++++++++ .../anomaly_detection/multi_metric_job.ts | 25 ++++++++ .../anomaly_detection/population_job.ts | 25 ++++++++ .../anomaly_detection/single_metric_job.ts | 25 ++++++++ .../services/machine_learning/api.ts | 20 +++++++ .../services/machine_learning/custom_urls.ts | 45 ++++++++++++++ .../services/machine_learning/index.ts | 1 + .../machine_learning/job_wizard_common.ts | 58 ++++++++++++++++++- x-pack/test/functional/services/ml.ts | 5 +- 16 files changed, 279 insertions(+), 22 deletions(-) create mode 100644 x-pack/test/functional/services/machine_learning/custom_urls.ts diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 6e768cc301852..d98dcb26ee238 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -40,6 +40,7 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe > +
- +
`; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx index a7308514de182..98acb1cfa8045 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -228,6 +228,7 @@ export const CustomUrlEditor: FC = ({ onChange={onLabelChange} isInvalid={isInvalidLabel} compressed + data-test-subj="mlJobCustomUrlLabelInput" /> = ({ job, customUrls, setCust : []; return ( - + = ({ job, customUrls, setCust value={label} isInvalid={isInvalidLabel} onChange={e => onLabelChange(e, index)} + data-test-subj={`mlJobEditCustomUrlLabelInput_${index}`} /> @@ -266,5 +267,5 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust ); }); - return <>{customUrlRows}; + return
{customUrlRows}
; }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index ed2c880c1b65f..c36b4ceed7d57 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -223,7 +223,11 @@ export class CustomUrls extends Component { : true; const addButton = ( - + { ) : ( - + { <> {(!editorOpen || editMode === 'modal') && ( - + = ({ additionalExpanded, setAdditional buttonContent={ButtonContent} onToggle={setAdditionalExpanded} initialIsOpen={additionalExpanded} + data-test-subj="mlJobWizardToggleAdditionalSettingsSection" > - +
+ - - - - - + + + + + - - - - - - + + + + + + +
); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx index c441d1fe6270c..919972186761a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx @@ -67,7 +67,7 @@ export const CalendarsSelection: FC = () => { - + { await esArchiver.load('ml/ecommerce'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -464,6 +465,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation displays the model plot switch', async () => { await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); }); @@ -709,6 +722,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning pre-fills the model plot switch', async () => { await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false, { 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 935fbc0102149..d41d96e40e2be 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 @@ -75,6 +75,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/farequote'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -170,6 +171,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); @@ -306,6 +319,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); 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 ff2275837ce2e..296af3179ce3e 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 @@ -89,6 +89,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/ecommerce'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -197,6 +198,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); @@ -344,6 +357,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); 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 1983e98a0123d..f6cd7b40bc7b1 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 @@ -74,6 +74,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/farequote'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -151,6 +152,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); @@ -271,6 +284,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index be65896950cfc..1995f37782948 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -247,5 +247,25 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { } }); }, + + async getCalendar(calendarId: string) { + return await esSupertest.get(`/_ml/calendars/${calendarId}`).expect(200); + }, + + async createCalendar(calendarId: string, body = { description: '', job_ids: [] }) { + log.debug(`Creating calendar with id '${calendarId}'...`); + await esSupertest + .put(`/_ml/calendars/${calendarId}`) + .send(body) + .expect(200); + + await retry.waitForWithTimeout(`'${calendarId}' to be created`, 30 * 1000, async () => { + if (await this.getCalendar(calendarId)) { + return true; + } else { + throw new Error(`expected calendar '${calendarId}' to be created`); + } + }); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/custom_urls.ts b/x-pack/test/functional/services/machine_learning/custom_urls.ts new file mode 100644 index 0000000000000..dc6e4a2fccb10 --- /dev/null +++ b/x-pack/test/functional/services/machine_learning/custom_urls.ts @@ -0,0 +1,45 @@ +/* + * 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 MachineLearningCustomUrlsProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async assertCustomUrlLabelValue(expectedValue: string) { + const actualCustomUrlLabel = await testSubjects.getAttribute( + 'mlJobCustomUrlLabelInput', + 'value' + ); + expect(actualCustomUrlLabel).to.eql(expectedValue); + }, + + async setCustomUrlLabel(customUrlsLabel: string) { + await testSubjects.setValue('mlJobCustomUrlLabelInput', customUrlsLabel, { + clearWithKeyboard: true, + }); + await this.assertCustomUrlLabelValue(customUrlsLabel); + }, + + async assertCustomUrlItem(index: number, label: string) { + await testSubjects.existOrFail(`mlJobEditCustomUrlItem_${index}`); + expect( + await testSubjects.getAttribute(`mlJobEditCustomUrlLabelInput_${index}`, 'value') + ).to.eql(label); + }, + + /** + * Submits the custom url form and adds it to the list. + * @param formContainerSelector - selector for the element that wraps the custom url creation form. + */ + async saveCustomUrl(formContainerSelector: string) { + await testSubjects.click('mlJobAddCustomUrl'); + await testSubjects.missingOrFail(formContainerSelector, { timeout: 10 * 1000 }); + }, + }; +} diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts index c62b714f566e9..adaded0832522 100644 --- a/x-pack/test/functional/services/machine_learning/index.ts +++ b/x-pack/test/functional/services/machine_learning/index.ts @@ -6,6 +6,7 @@ export { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer'; export { MachineLearningAPIProvider } from './api'; +export { MachineLearningCustomUrlsProvider } from './custom_urls'; export { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytics'; export { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation'; export { MachineLearningDataFrameAnalyticsTableProvider } from './data_frame_analytics_table'; 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 0ebc4cb959412..235e597f8c280 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 @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningCustomUrlsProvider } from './custom_urls'; -export function MachineLearningJobWizardCommonProvider({ getService }: FtrProviderContext) { +export function MachineLearningJobWizardCommonProvider( + { getService }: FtrProviderContext, + customUrls: ProvidedType +) { const comboBox = getService('comboBox'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -166,6 +171,23 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(await this.getSelectedJobGroups()).to.contain(jobGroup); }, + async getSelectedCalendars(): Promise { + await this.ensureAdditionalSettingsSectionOpen(); + return await comboBox.getComboBoxSelectedOptions( + 'mlJobWizardComboBoxCalendars > comboBoxInput' + ); + }, + + async assertCalendarsSelection(calendars: string[]) { + expect(await this.getSelectedCalendars()).to.eql(calendars); + }, + + async addCalendar(calendarId: string) { + await this.ensureAdditionalSettingsSectionOpen(); + await comboBox.setCustom('mlJobWizardComboBoxCalendars > comboBoxInput', calendarId); + expect(await this.getSelectedCalendars()).to.contain(calendarId); + }, + async assertModelPlotSwitchExists( sectionOptions: SectionOptions = { withAdvancedSection: true } ) { @@ -358,6 +380,40 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid await this.assertDateRangeSelection(expectedStartDate, expectedEndDate); }, + async ensureAdditionalSettingsSectionOpen() { + await retry.tryForTime(5000, async () => { + if ((await testSubjects.exists('mlJobWizardAdditionalSettingsSection')) === false) { + await testSubjects.click('mlJobWizardToggleAdditionalSettingsSection'); + await testSubjects.existOrFail('mlJobWizardAdditionalSettingsSection', { timeout: 1000 }); + } + }); + }, + + async ensureNewCustomUrlFormModalOpen() { + await retry.tryForTime(5000, async () => { + if ((await testSubjects.exists('mlJobNewCustomUrlFormModal')) === false) { + await testSubjects.click('mlJobOpenCustomUrlFormButton'); + await testSubjects.existOrFail('mlJobNewCustomUrlFormModal', { timeout: 1000 }); + } + }); + }, + + async addCustomUrl(customUrl: { label: string }) { + await this.ensureAdditionalSettingsSectionOpen(); + + const existingCustomUrls = await testSubjects.findAll('mlJobEditCustomUrlsList > *'); + + await this.ensureNewCustomUrlFormModalOpen(); + // Fill-in the form + await customUrls.setCustomUrlLabel(customUrl.label); + // Save custom URL + await customUrls.saveCustomUrl('mlJobNewCustomUrlFormModal'); + + const expectedIndex = existingCustomUrls.length; + + await customUrls.assertCustomUrlItem(expectedIndex, customUrl.label); + }, + async ensureAdvancedSectionOpen() { await retry.tryForTime(5000, async () => { if ((await testSubjects.exists(advancedSectionSelector())) === false) { diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts index 86967dfd1e273..4b6f77262b7f9 100644 --- a/x-pack/test/functional/services/ml.ts +++ b/x-pack/test/functional/services/ml.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; import { MachineLearningAnomalyExplorerProvider, MachineLearningAPIProvider, + MachineLearningCustomUrlsProvider, MachineLearningDataFrameAnalyticsProvider, MachineLearningDataFrameAnalyticsCreationProvider, MachineLearningDataFrameAnalyticsTableProvider, @@ -30,6 +31,7 @@ import { export function MachineLearningProvider(context: FtrProviderContext) { const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context); const api = MachineLearningAPIProvider(context); + const customUrls = MachineLearningCustomUrlsProvider(context); const dataFrameAnalytics = MachineLearningDataFrameAnalyticsProvider(context, api); const dataFrameAnalyticsCreation = MachineLearningDataFrameAnalyticsCreationProvider(context); const dataFrameAnalyticsTable = MachineLearningDataFrameAnalyticsTableProvider(context); @@ -40,7 +42,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context); - const jobWizardCommon = MachineLearningJobWizardCommonProvider(context); + const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, customUrls); const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context); const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context); const navigation = MachineLearningNavigationProvider(context); @@ -50,6 +52,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { return { anomalyExplorer, api, + customUrls, dataFrameAnalytics, dataFrameAnalyticsCreation, dataFrameAnalyticsTable,