From 21c364d14cac9ef0943844dcf81a9286d88460ad Mon Sep 17 00:00:00 2001 From: kialam Date: Wed, 1 Aug 2018 10:36:07 -0400 Subject: [PATCH 1/7] Adjust `edit template` controller to handle deferred promise errors --- .../job-template-edit.controller.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index 41528a57d8..972c2a49d2 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -588,6 +588,19 @@ export default .then(function() { Wait('stop'); saveCompleted(); + }) + .catch(err => { + // Handle any potential errors + let { data, status, config } = err; + const { url } = config; + + // Handle edge case for LABELS endpoint + const labelName = "labels"; + if (url.match(labelName)) { + data = { [labelName]: [data['name'][0]] }; + } + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to update job template. PUT returned status: ' + status }); }); }); }); From 7207d7caf45bc8a90163423af8eaea9088ed538d Mon Sep 17 00:00:00 2001 From: kialam Date: Fri, 3 Aug 2018 11:12:35 -0400 Subject: [PATCH 2/7] Validate JT add and edit forms client-side - Use form generator to add new `helperText` field to show the character limit next to the label in the UI - Style helper text like the checkbox text - Update both `add` and `edit` controllers to handle client-side validation for the `labels` field. --- .../client/lib/components/input/_index.less | 13 ++++ awx/ui/client/src/shared/form-generator.js | 27 +++++++++ .../job-template-add.controller.js | 46 +++++++++++++++ .../job-template-edit.controller.js | 59 +++++++++++++++---- .../job_templates/job-template.form.js | 5 ++ 5 files changed, 137 insertions(+), 13 deletions(-) diff --git a/awx/ui/client/lib/components/input/_index.less b/awx/ui/client/lib/components/input/_index.less index 1cc1c5f21d..026303efd2 100644 --- a/awx/ui/client/lib/components/input/_index.less +++ b/awx/ui/client/lib/components/input/_index.less @@ -171,6 +171,19 @@ padding: 0; } +.at-InputLabel--helpertext { + float: right; + font-size: 10px; + color: @default-stdout-txt; + text-transform: uppercase; + margin-top: 2px; + font-weight: 400; +} + +.at-InputLabel--error { + color: @at-color-error; +} + .at-InputLabel-required { color: @at-color-error; font-weight: @at-font-weight-heading; diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index b18e9a5397..7398d430a2 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -684,6 +684,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += createCheckbox(options.checkbox); } + if (options && options.helpertext) { + html += createHelpertext(options.helpertext); + } + if (field.labelAction) { let action = field.labelAction; let href = action.href || ""; @@ -1041,6 +1045,17 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat }; } + if (field.helperText) { + labelOptions.helpertext = { + id: `${this.form.name}_${fld}_sub_text`, + ngShow: field.helperText.ngShow, + ngModel: field.helperText.variable, + ngClass: field.helperText.ngClass, + classCondition: field.helperText.classCondition, + text: field.helperText.text + }; + } + html += label(labelOptions); html += "
`; } + + function createHelpertext (options) { + let ngModel = options.ngModel ? `ng-model="${options.ngModel}"` : ''; + let ngShow = options.ngShow ? `ng-show="${options.ngShow}"` : ''; + let ngClass = options.ngClass ? options.ngClass : ''; + let classCondition = options.classCondition ? options.classCondition : ''; + + return ` + `; + } } ]); diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index d25d41e706..a51213251d 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -495,5 +495,51 @@ $scope.formCancel = function () { $state.transitionTo('templates'); }; + + let handleLabelCount = () => { + /** + * This block of code specifically handles the client-side validation of the `labels` field. + * Due to it's detached nature in relation to the other job template fields, we must + * validate this field client-side in order to avoid the edge case where a user can make a + * successful POST to the `job_templates` endpoint but however encounter a 200 error from + * the `labels` endpoint due to a character limit. + * + * We leverage two of select2's available events, `select` and `unselect`, to detect when the user + * has either added or removed a label. From there, we set a flag and do simple string length + * checks to make sure a label's chacacter count remains under 512. Otherwise, we disable the "Save" button + * by invalidating the field and inform the user of the error. + */ + + + $scope.job_template_labels_isValid = true; + const maxCount = 512; + const jt_label_id = 'job_template_labels'; + + // Detect when a new label is added + $(`#${jt_label_id}`).on('select2:select', (e) => { + const { text } = e.params.data; + + // If the character count of an added label is greater than 512, we set `labels` field as invalid + if (text.length > maxCount) { + $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, false); + $scope.job_template_labels_isValid = false; + } + }); + + // Detect when a label is removed + $(`#${jt_label_id}`).on('select2:unselect', (e) => { + const maxCount = 512; + const { text } = e.params.data; + + /* If the character count of a removed label is greater than 512 AND the field is currently marked + as invalid, we set it back to valid */ + if (text.length > maxCount && $scope.job_template_form.labels.$error) { + $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, true); + $scope.job_template_labels_isValid = true; + } + }); + }; + + handleLabelCount(); } ]; diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index 972c2a49d2..cf5977baa3 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -588,19 +588,6 @@ export default .then(function() { Wait('stop'); saveCompleted(); - }) - .catch(err => { - // Handle any potential errors - let { data, status, config } = err; - const { url } = config; - - // Handle edge case for LABELS endpoint - const labelName = "labels"; - if (url.match(labelName)) { - data = { [labelName]: [data['name'][0]] }; - } - ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to update job template. PUT returned status: ' + status }); }); }); }); @@ -717,5 +704,51 @@ export default $scope.formCancel = function () { $state.go('templates'); }; + + let handleLabelCount = () => { + /** + * This block of code specifically handles the client-side validation of the `labels` field. + * Due to it's detached nature in relation to the other job template fields, we must + * validate this field client-side in order to avoid the edge case where a user can make a + * successful POST to the `job_templates` endpoint but however encounter a 200 error from + * the `labels` endpoint due to a character limit. + * + * We leverage two of select2's available events, `select` and `unselect`, to detect when the user + * has either added or removed a label. From there, we set a flag and do simple string length + * checks to make sure a label's chacacter count remains under 512. Otherwise, we disable the "Save" button + * by invalidating the field and inform the user of the error. + */ + + + $scope.job_template_labels_isValid = true; + const maxCount = 512; + const jt_label_id = 'job_template_labels'; + + // Detect when a new label is added + $(`#${jt_label_id}`).on('select2:select', (e) => { + const { text } = e.params.data; + + // If the character count of an added label is greater than 512, we set `labels` field as invalid + if (text.length > maxCount) { + $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, false); + $scope.job_template_labels_isValid = false; + } + }); + + // Detect when a label is removed + $(`#${jt_label_id}`).on('select2:unselect', (e) => { + const maxCount = 512; + const { text } = e.params.data; + + /* If the character count of a removed label is greater than 512 AND the field is currently marked + as invalid, we set it back to valid */ + if (text.length > maxCount && $scope.job_template_form.labels.$error) { + $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, true); + $scope.job_template_labels_isValid = true; + } + }); + }; + + handleLabelCount(); } ]; diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index d87d3c603e..1e571cf121 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -229,6 +229,11 @@ function(NotificationsList, i18n) { dataPlacement: 'right', awPopOver: "

" + i18n._("Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs.") + "

", dataContainer: 'body', + helperText: { + classCondition: 'job_template_labels_isValid === true', + ngClass: 'at-InputLabel--error', + text: i18n._('Max 512 Char'), + }, ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' }, custom_virtualenv: { From 0fb98642d4058d430b58edc753f885f172e416c4 Mon Sep 17 00:00:00 2001 From: kialam Date: Fri, 3 Aug 2018 15:32:55 -0400 Subject: [PATCH 3/7] Only show the character limit if the user exceeds it --- awx/ui/client/src/templates/job_templates/job-template.form.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 1e571cf121..590140c1e8 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -232,7 +232,8 @@ function(NotificationsList, i18n) { helperText: { classCondition: 'job_template_labels_isValid === true', ngClass: 'at-InputLabel--error', - text: i18n._('Max 512 Char'), + ngShow: 'job_template_labels_isValid !== true', + text: i18n._('Max 512 Characters'), }, ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' }, From efc8991aa83daab82d3cc356efe32428dc06cf16 Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 6 Aug 2018 11:46:29 -0400 Subject: [PATCH 4/7] Move error message to underneath form field for consistent UI. --- awx/ui/client/src/shared/form-generator.js | 34 ++++++------------- .../job_templates/job-template.form.js | 4 +-- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 7398d430a2..a916815bd6 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -684,10 +684,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += createCheckbox(options.checkbox); } - if (options && options.helpertext) { - html += createHelpertext(options.helpertext); - } - if (field.labelAction) { let action = field.labelAction; let href = action.href || ""; @@ -1045,14 +1041,12 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat }; } - if (field.helperText) { - labelOptions.helpertext = { - id: `${this.form.name}_${fld}_sub_text`, - ngShow: field.helperText.ngShow, - ngModel: field.helperText.variable, - ngClass: field.helperText.ngClass, - classCondition: field.helperText.classCondition, - text: field.helperText.text + if (field.onError) { + labelOptions.onError = { + id: `${this.form.name}_${fld}_error_text`, + ngShow: field.onError.ngShow, + ngModel: field.onError.variable, + text: field.onError.text }; } @@ -1106,7 +1100,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat } html += "
\n"; } - html += "
\n"; + if (field.label === "Labels") { + html += `
${field.onError.text}
`; + } + // Add help panel(s) html += (field.helpCollapse) ? this.buildHelpCollapse(field.helpCollapse) : ''; @@ -2037,16 +2034,5 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat `; } - function createHelpertext (options) { - let ngModel = options.ngModel ? `ng-model="${options.ngModel}"` : ''; - let ngShow = options.ngShow ? `ng-show="${options.ngShow}"` : ''; - let ngClass = options.ngClass ? options.ngClass : ''; - let classCondition = options.classCondition ? options.classCondition : ''; - - return ` - `; - } } ]); diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 590140c1e8..93facc4470 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -229,9 +229,7 @@ function(NotificationsList, i18n) { dataPlacement: 'right', awPopOver: "

" + i18n._("Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs.") + "

", dataContainer: 'body', - helperText: { - classCondition: 'job_template_labels_isValid === true', - ngClass: 'at-InputLabel--error', + onError: { ngShow: 'job_template_labels_isValid !== true', text: i18n._('Max 512 Characters'), }, From 6e49c25cfdea47cf69ef263a2906f8f6acb14183 Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 6 Aug 2018 12:24:05 -0400 Subject: [PATCH 5/7] Adjust error message copy. --- awx/ui/client/src/templates/job_templates/job-template.form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 93facc4470..7b75e16415 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -231,7 +231,7 @@ function(NotificationsList, i18n) { dataContainer: 'body', onError: { ngShow: 'job_template_labels_isValid !== true', - text: i18n._('Max 512 Characters'), + text: i18n._('Max 512 characters per label.'), }, ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' }, From b0586cc1974040cdfea9dec75597fc27685a4e5a Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 6 Aug 2018 12:26:43 -0400 Subject: [PATCH 6/7] Remove unused CSS. --- awx/ui/client/lib/components/input/_index.less | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/awx/ui/client/lib/components/input/_index.less b/awx/ui/client/lib/components/input/_index.less index 026303efd2..1cc1c5f21d 100644 --- a/awx/ui/client/lib/components/input/_index.less +++ b/awx/ui/client/lib/components/input/_index.less @@ -171,19 +171,6 @@ padding: 0; } -.at-InputLabel--helpertext { - float: right; - font-size: 10px; - color: @default-stdout-txt; - text-transform: uppercase; - margin-top: 2px; - font-weight: 400; -} - -.at-InputLabel--error { - color: @at-color-error; -} - .at-InputLabel-required { color: @at-color-error; font-weight: @at-font-weight-heading; From 64caeeff36216832c8fb4ffa9a6c19a27b275598 Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 6 Aug 2018 13:27:59 -0400 Subject: [PATCH 7/7] Restore missing API form errors for selects. --- awx/ui/client/src/shared/form-generator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index a916815bd6..f9e1b9e426 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1103,6 +1103,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat if (field.label === "Labels") { html += `
${field.onError.text}
`; } + html += "
\n"; // Add help panel(s)