diff --git a/x-pack/plugins/ml/common/util/job_utils.js b/x-pack/plugins/ml/common/util/job_utils.js index 2319ced16edcb..59e9273b236a8 100644 --- a/x-pack/plugins/ml/common/util/job_utils.js +++ b/x-pack/plugins/ml/common/util/job_utils.js @@ -251,20 +251,14 @@ export function basicJobValidation(job, fields, limits) { messages.push({ id: 'job_id_valid' }); } - if (job.groups !== undefined) { - let groupIdValid = true; - job.groups.forEach(group => { - if (isJobIdValid(group) === false) { - groupIdValid = false; - valid = false; - } - }); - if (job.groups.length > 0 && groupIdValid) { - messages.push({ id: 'job_group_id_valid' }); - } else if (job.groups.length > 0 && !groupIdValid) { - messages.push({ id: 'job_group_id_invalid' }); - } - } + // group names + const { + messages: groupsMessages, + valid: groupsValid, + } = validateGroupNames(job); + + messages.push(...groupsMessages); + valid = (valid && groupsValid); // Analysis Configuration if (job.analysis_config.categorization_filters) { @@ -372,22 +366,13 @@ export function basicJobValidation(job, fields, limits) { } // model memory limit - if (typeof job.analysis_limits !== 'undefined' && typeof job.analysis_limits.model_memory_limit !== 'undefined') { - if (typeof limits === 'object' && typeof limits.max_model_memory_limit !== 'undefined') { - const max = limits.max_model_memory_limit.toUpperCase(); - const mml = job.analysis_limits.model_memory_limit.toUpperCase(); - - const mmlBytes = numeral(mml).value(); - const maxBytes = numeral(max).value(); - - if(mmlBytes > maxBytes) { - messages.push({ id: 'model_memory_limit_invalid' }); - valid = false; - } else { - messages.push({ id: 'model_memory_limit_valid' }); - } - } - } + const { + messages: mmlMessages, + valid: mmlValid, + } = validateModelMemoryLimit(job, limits); + + messages.push(...mmlMessages); + valid = (valid && mmlValid); } else { valid = false; @@ -396,7 +381,60 @@ export function basicJobValidation(job, fields, limits) { return { messages, valid, - contains(id) { return _.some(messages, { id }); }, - find(id) { return _.find(messages, { id }); } + contains: id => (messages.some(m => id === m.id)), + find: id => (messages.find(m => id === m.id)), + }; +} + +export function validateModelMemoryLimit(job, limits) { + const messages = []; + let valid = true; + // model memory limit + if (typeof job.analysis_limits !== 'undefined' && typeof job.analysis_limits.model_memory_limit !== 'undefined') { + if (typeof limits === 'object' && typeof limits.max_model_memory_limit !== 'undefined') { + const max = limits.max_model_memory_limit.toUpperCase(); + const mml = job.analysis_limits.model_memory_limit.toUpperCase(); + + const mmlBytes = numeral(mml).value(); + const maxBytes = numeral(max).value(); + + if(mmlBytes > maxBytes) { + messages.push({ id: 'model_memory_limit_invalid' }); + valid = false; + } else { + messages.push({ id: 'model_memory_limit_valid' }); + } + } + } + return { + valid, + messages, + contains: id => (messages.some(m => id === m.id)), + find: id => (messages.find(m => id === m.id)), + }; +} + +export function validateGroupNames(job) { + const messages = []; + let valid = true; + if (job.groups !== undefined) { + let groupIdValid = true; + job.groups.forEach(group => { + if (isJobIdValid(group) === false) { + groupIdValid = false; + valid = false; + } + }); + if (job.groups.length > 0 && groupIdValid) { + messages.push({ id: 'job_group_id_valid' }); + } else if (job.groups.length > 0 && !groupIdValid) { + messages.push({ id: 'job_group_id_invalid' }); + } + } + return { + valid, + messages, + contains: id => (messages.some(m => id === m.id)), + find: id => (messages.find(m => id === m.id)), }; } diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_job_flyout.js index 67474be4f9e2f..d256c8f0868c3 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_job_flyout.js @@ -26,6 +26,7 @@ import { import { JobDetails, Detectors, Datafeed, CustomUrls } from './tabs'; import { saveJob } from './edit_utils'; import { loadFullJob } from '../utils'; +import { validateModelMemoryLimit, validateGroupNames } from './validate_job'; import { toastNotifications } from 'ui/notify'; export class EditJobFlyout extends Component { @@ -46,6 +47,9 @@ export class EditJobFlyout extends Component { datafeedQueryDelay: '', datafeedFrequency: '', datafeedScrollSize: '', + jobModelMemoryLimitValidationError: '', + jobGroupsValidationError: '', + valid: true, }; this.refreshJobs = this.props.refreshJobs; @@ -111,12 +115,29 @@ export class EditJobFlyout extends Component { datafeedQueryDelay: (hasDatafeed) ? datafeedConfig.query_delay : '', datafeedFrequency: (hasDatafeed) ? frequency : '', datafeedScrollSize: (hasDatafeed) ? +datafeedConfig.scroll_size : '', + jobModelMemoryLimitValidationError: '', + jobGroupsValidationError: '', }); } setJobDetails = (jobDetails) => { + let { jobModelMemoryLimitValidationError, jobGroupsValidationError } = this.state; + + if (jobDetails.jobModelMemoryLimit !== undefined) { + jobModelMemoryLimitValidationError = validateModelMemoryLimit(jobDetails.jobModelMemoryLimit).message; + } + + if (jobDetails.jobGroups !== undefined) { + jobGroupsValidationError = validateGroupNames(jobDetails.jobGroups).message; + } + + const valid = (jobModelMemoryLimitValidationError === '' && jobGroupsValidationError === ''); + this.setState({ - ...jobDetails + ...jobDetails, + jobModelMemoryLimitValidationError, + jobGroupsValidationError, + valid, }); } @@ -180,6 +201,9 @@ export class EditJobFlyout extends Component { datafeedQueryDelay, datafeedFrequency, datafeedScrollSize, + jobGroupsValidationError, + jobModelMemoryLimitValidationError, + valid, } = this.state; const tabs = [{ @@ -190,6 +214,8 @@ export class EditJobFlyout extends Component { jobGroups={jobGroups} jobModelMemoryLimit={jobModelMemoryLimit} setJobDetails={this.setJobDetails} + jobGroupsValidationError={jobGroupsValidationError} + jobModelMemoryLimitValidationError={jobModelMemoryLimitValidationError} />, }, { id: 'detectors', @@ -258,6 +284,7 @@ export class EditJobFlyout extends Component { Save diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_utils.js index 0d186afbda2bd..95bab7a8bb5d3 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/edit_utils.js @@ -46,8 +46,11 @@ export function saveJob(job, newJobData, finish) { if (resp.success) { saveDatafeedWrapper(); } else { - reject(); + reject(resp); } + }) + .catch((error) => { + reject(error); }); } else { saveDatafeedWrapper(); diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/tabs/job_details.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/tabs/job_details.js index 876f70994a85a..95cc2c352a800 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/tabs/job_details.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/tabs/job_details.js @@ -30,6 +30,8 @@ export class JobDetails extends Component { groups: [], selectedGroups: [], mml: '', + mmlValidationError: '', + groupsValidationError: '', }; this.setJobDetails = props.setJobDetails; @@ -56,6 +58,8 @@ export class JobDetails extends Component { description: props.jobDescription, selectedGroups, mml: props.jobModelMemoryLimit, + mmlValidationError: props.jobModelMemoryLimitValidationError, + groupsValidationError: props.jobGroupsValidationError, }; } @@ -104,6 +108,8 @@ export class JobDetails extends Component { selectedGroups, mml, groups, + mmlValidationError, + groupsValidationError, } = this.state; return ( @@ -119,6 +125,8 @@ export class JobDetails extends Component { diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/validate_job.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/validate_job.js new file mode 100644 index 0000000000000..3aec2c395cec5 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/edit_job_flyout/validate_job.js @@ -0,0 +1,52 @@ +/* + * 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 { newJobLimits } from 'plugins/ml/jobs/new_job/utils/new_job_defaults'; +import { populateValidationMessages } from 'plugins/ml/jobs/new_job/simple/components/utils/validate_job'; + +import { + validateModelMemoryLimit as validateModelMemoryLimitUtils, + validateGroupNames as validateGroupNamesUtils, +} from 'plugins/ml/../common/util/job_utils'; + +export function validateModelMemoryLimit(mml) { + const limits = newJobLimits(); + const tempJob = { + analysis_limits: { + model_memory_limit: mml + } + }; + const validationResults = validateModelMemoryLimitUtils(tempJob, limits); + const { valid } = validationResults; + + const modelMemoryLimit = { + valid, + message: '', + }; + + populateValidationMessages(validationResults, { modelMemoryLimit }); + + return modelMemoryLimit; +} + +export function validateGroupNames(groups) { + const tempJob = { + groups + }; + + const validationResults = validateGroupNamesUtils(tempJob); + const { valid } = validationResults; + + const groupIds = { + valid, + message: '', + }; + + populateValidationMessages(validationResults, { groupIds }); + + return groupIds; +} diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/multi_job_actions/actions_menu.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/multi_job_actions/actions_menu.js index 43581db20ca06..c4fbc39eba7c7 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/multi_job_actions/actions_menu.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/multi_job_actions/actions_menu.js @@ -58,7 +58,7 @@ export class MultiJobActionsMenu extends Component { size="s" onClick={this.onButtonClick} iconType="gear" - aria-label="Next" + aria-label="Management actions" color="text" disabled={(this.canDeleteJob === false && this.canStartStopDatafeed === false)} /> diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/validate_job.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/validate_job.js index 752976debbc9e..01278ca06e386 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/validate_job.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/utils/validate_job.js @@ -20,6 +20,20 @@ export function validateJob(job, checks) { item.valid = true; }); + populateValidationMessages(validationResults, checks); + + _.each(checks, (item) => { + if (item.valid === false) { + valid = false; + } + }); + + return valid; +} + +export function populateValidationMessages(validationResults, checks) { + const limits = newJobLimits(); + if (validationResults.contains('job_id_empty')) { checks.jobId.valid = false; } else if (validationResults.contains('job_id_invalid')) { @@ -47,12 +61,4 @@ export function validateJob(job, checks) { const msg = 'Duplicate detectors were found.'; checks.duplicateDetectors.message = msg; } - - _.each(checks, (item) => { - if (item.valid === false) { - valid = false; - } - }); - - return valid; }