Repository: ambari Updated Branches: refs/heads/trunk 1b49c6c97 -> c26e2ff61
AMBARI-16166 Refactor config validation.(ababiichuk) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/c26e2ff6 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/c26e2ff6 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/c26e2ff6 Branch: refs/heads/trunk Commit: c26e2ff61b2fd02c2c72232de154d62401287847 Parents: 1b49c6c Author: ababiichuk <[email protected]> Authored: Thu Apr 28 19:38:41 2016 +0300 Committer: ababiichuk <[email protected]> Committed: Thu Apr 28 20:18:51 2016 +0300 ---------------------------------------------------------------------- .../hawq/activateStandby/step2_controller.js | 1 - .../hawq/addStandby/step3_controller.js | 1 - .../nameNode/step3_controller.js | 1 - .../resourceManager/step3_controller.js | 1 - .../app/controllers/wizard/step7_controller.js | 1 - .../configs/stack_config_properties_mapper.js | 1 + ambari-web/app/messages.js | 14 +- .../configs/config_recommendation_parser.js | 2 - .../configs/objects/service_config_property.js | 262 ++----------------- ambari-web/app/utils/config.js | 154 ++++++++++- ambari-web/app/utils/validator.js | 2 +- .../notification_configs_view.js | 7 - .../configs/widgets/list_config_widget_view.js | 7 +- .../config_recommendation_parser_test.js | 21 +- .../objects/service_config_property_test.js | 86 ------ ambari-web/test/models/form_test.js | 22 -- ambari-web/test/utils/config_test.js | 24 ++ .../notification_configs_view_test.js | 18 +- 18 files changed, 224 insertions(+), 401 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js index a14817a..ff338de 100644 --- a/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js +++ b/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js @@ -90,7 +90,6 @@ App.ActivateHawqStandbyWizardStep2Controller = Em.Controller.extend({ var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty); componentConfig.configs.pushObject(serviceConfigProperty); serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable')); - serviceConfigProperty.validate(); }, this); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js index fd28ad5..3378cd5 100644 --- a/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js +++ b/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js @@ -133,7 +133,6 @@ App.AddHawqStandbyWizardStep3Controller = Em.Controller.extend({ var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty); componentConfig.configs.pushObject(serviceConfigProperty); serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable')); - serviceConfigProperty.validate(); }, this); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js index 7305861..1dcf2b7 100644 --- a/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js +++ b/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js @@ -228,7 +228,6 @@ App.HighAvailabilityWizardStep3Controller = Em.Controller.extend({ var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty); componentConfig.configs.pushObject(serviceConfigProperty); serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable')); - serviceConfigProperty.validate(); }, this); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js index 00ae0c3..36dcfb8 100644 --- a/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js +++ b/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js @@ -170,7 +170,6 @@ App.RMHighAvailabilityWizardStep3Controller = Em.Controller.extend(App.Blueprint var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty); componentConfig.configs.pushObject(serviceConfigProperty); serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable')); - serviceConfigProperty.validate(); }, this); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/wizard/step7_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step7_controller.js b/ambari-web/app/controllers/wizard/step7_controller.js index 8ed465d..c97de10 100644 --- a/ambari-web/app/controllers/wizard/step7_controller.js +++ b/ambari-web/app/controllers/wizard/step7_controller.js @@ -748,7 +748,6 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E if (!this.get('content.serviceConfigProperties.length') && !serviceConfigProperty.get('hasInitialValue')) { App.ConfigInitializer.initialValue(serviceConfigProperty, localDB, dependencies); } - serviceConfigProperty.validate(); configsByService[_config.serviceName].pushObject(serviceConfigProperty); }, this); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/mappers/configs/stack_config_properties_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/configs/stack_config_properties_mapper.js b/ambari-web/app/mappers/configs/stack_config_properties_mapper.js index 59f9916..5c34f62 100644 --- a/ambari-web/app/mappers/configs/stack_config_properties_mapper.js +++ b/ambari-web/app/mappers/configs/stack_config_properties_mapper.js @@ -144,6 +144,7 @@ App.stackConfigPropertiesMapper = App.QuickDataMapper.create({ var v = Em.isNone(staticConfigInfo.recommendedValue) ? staticConfigInfo.recommendedValue : staticConfigInfo.value; staticConfigInfo.value = staticConfigInfo.recommendedValue = App.config.formatPropertyValue(staticConfigInfo, v); staticConfigInfo.isSecureConfig = App.config.getIsSecure(staticConfigInfo.name); + staticConfigInfo.description = App.config.getDescription(staticConfigInfo.description, staticConfigInfo.displayType); staticConfigInfo.isUserProperty = false; App.configsCollection.add(staticConfigInfo); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index a1ed7be..e195b7a 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -480,7 +480,6 @@ Em.I18n.translations = { 'users.userName.validationFail': 'Only lowercase letters and numbers are recommended; must start with a letter', 'host.spacesValidation': 'Cannot contain whitespace', - 'host.trimspacesValidation': 'Cannot contain leading or trailing whitespace', 'services.hdfs.rebalance.title' : 'HDFS Rebalance', 'services.ganglia.description':'Ganglia Metrics Collection system', @@ -994,7 +993,6 @@ Em.I18n.translations = { 'form.validator.alertGroupName':'Invalid Alert Group Name. Only alphanumerics, hyphens, spaces and underscores are allowed.', 'form.validator.alertNotificationName':'Invalid Alert Notification Name. Only alphanumerics, hyphens, spaces and underscores are allowed.', 'form.validator.configKey.specific':'"{0}" is invalid Key. Only alphanumerics, hyphens, underscores, asterisks and periods are allowed.', - 'form.validator.error.trailingSpaces': 'Cannot contain trailing whitespace', 'alerts.add.header': 'Create Alert Definition', 'alerts.add.step1.header': 'Choose Type', @@ -2964,6 +2962,18 @@ Em.I18n.translations = { 'config.warnMessage.outOfBoundaries.greater': 'Values greater than {0} are not recommended', 'config.warnMessage.outOfBoundaries.less': 'Values smaller than {0} are not recommended', + 'errorMessage.config.required': 'This is required', + 'errorMessage.config.number.integer': 'Must contain digits only', + 'errorMessage.config.number.float': 'Must be a valid number', + 'errorMessage.config.mail': 'Must be a valid email address', + 'errorMessage.config.user': 'Value is not valid', + 'errorMessage.config.password': 'Passwords do not match', + 'errorMessage.config.directory.heterogeneous': 'dir format is wrong, can be "[{storage type}]/{dir name}"', + 'errorMessage.config.directory.default': 'Must be a slash or drive at the start, and must not contain white spaces', + 'errorMessage.config.directory.allowed': 'Can\'t start with "home(s)"', + 'errorMessage.config.spaces.trailing': 'Cannot contain trailing whitespace', + 'errorMessage.config.spaces.trim': 'Cannot contain leading or trailing whitespace', + 'utils.ajax.errorMessage': 'Error message', 'utils.ajax.defaultErrorPopupBody.message': 'received on {0} method for API: {1}', 'utils.ajax.defaultErrorPopupBody.statusCode': '{0} status code', http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/mixins/common/configs/config_recommendation_parser.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/configs/config_recommendation_parser.js b/ambari-web/app/mixins/common/configs/config_recommendation_parser.js index 82916aa..65cc20f 100644 --- a/ambari-web/app/mixins/common/configs/config_recommendation_parser.js +++ b/ambari-web/app/mixins/common/configs/config_recommendation_parser.js @@ -171,8 +171,6 @@ App.ConfigRecommendationParser = Em.Mixin.create(App.ConfigRecommendations, { newConfig = App.config.getDefaultConfig(name, fileName, coreObject), addedPropertyObject = App.ServiceConfigProperty.create(newConfig); - addedPropertyObject.validate(); - this.applyRecommendation(name, fileName, "Default", recommendedValue, null, parentProperties); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/models/configs/objects/service_config_property.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/configs/objects/service_config_property.js b/ambari-web/app/models/configs/objects/service_config_property.js index 8e6327b..a244dfd 100644 --- a/ambari-web/app/models/configs/objects/service_config_property.js +++ b/ambari-web/app/models/configs/objects/service_config_property.js @@ -17,7 +17,6 @@ */ var App = require('app'); -var validator = require('utils/validator'); /** * @class ServiceConfigProperty @@ -143,8 +142,11 @@ App.ServiceConfigProperty = Em.Object.extend({ isComparison: false, hasCompareDiffs: false, showLabel: true, - error: false, - warn: false, + + error: Em.computed.bool('errorMessage.length'), + warn: Em.computed.bool('warnMessage.length'), + isValid: Em.computed.equal('errorMessage', ''), + previousValue: null, // cached value before changing config <code>value</code> /** @@ -159,17 +161,9 @@ App.ServiceConfigProperty = Em.Object.extend({ * true if property has warning or error * @type {boolean} */ - hasIssues: function () { - var originalSCPIssued = (this.get('errorMessage') + this.get('warnMessage')) !== ""; - var overridesIssue = false; - (this.get('overrides') || []).forEach(function(override) { - if (override.get('errorMessage') + override.get('warnMessage') !== "") { - overridesIssue = true; - return; - } - }); - return originalSCPIssued || overridesIssue; - }.property('errorMessage', 'warnMessage', '[email protected]', '[email protected]'), + hasIssues: Em.computed.or('error', 'warn', 'overridesWithIssues.length'), + + overridesWithIssues: Em.computed.filterBy('overrides', 'hasIssues', true), index: null, //sequence number in category editDone: false, //Text field: on focusOut: true, on focusIn: false @@ -253,6 +247,13 @@ App.ServiceConfigProperty = Em.Object.extend({ }.property('isUserProperty', 'isOriginalSCP', 'overrides.length', 'isRequiredByAgent'), init: function () { + this.setInitialValues(); + this.set('viewClass', App.config.getViewClass(this.get("displayType"), this.get('dependentConfigPattern'), this.get('unit'))); + this.set('validator', App.config.getValidator(this.get("displayType"))); + this.validate(); + }, + + setInitialValues: function () { if (Em.isNone(this.get('value'))) { if (!Em.isNone(this.get('savedValue'))) { this.set('value', this.get('savedValue')); @@ -260,12 +261,11 @@ App.ServiceConfigProperty = Em.Object.extend({ this.set('value', this.get('recommendedValue')); } } - if(this.get("displayType") === "password") { + if (this.get("displayType") === "password") { this.set('retypedPassword', this.get('value')); this.set('recommendedValue', ''); } this.set('initialValue', this.get('value')); - this.updateDescription(); }, /** @@ -313,211 +313,19 @@ App.ServiceConfigProperty = Em.Object.extend({ */ cantBeUndone: Em.computed.existsIn('displayType', ["componentHost", "componentHosts", "radio button"]), - isValid: Em.computed.equal('errorMessage', ''), - - viewClass: function () { - switch (this.get('displayType')) { - case 'checkbox': - case 'boolean': - if (this.get('dependentConfigPattern')) { - return App.ServiceConfigCheckboxWithDependencies; - } else { - return App.ServiceConfigCheckbox; - } - case 'password': - return App.ServiceConfigPasswordField; - case 'combobox': - return App.ServiceConfigComboBox; - case 'radio button': - return App.ServiceConfigRadioButtons; - break; - case 'directories': - return App.ServiceConfigTextArea; - break; - case 'directory': - return App.ServiceConfigTextField; - break; - case 'content': - return App.ServiceConfigTextAreaContent; - break; - case 'multiLine': - return App.ServiceConfigTextArea; - break; - case 'custom': - return App.ServiceConfigBigTextArea; - case 'componentHost': - return App.ServiceConfigMasterHostView; - case 'label': - return App.ServiceConfigLabelView; - case 'componentHosts': - return App.ServiceConfigComponentHostsView; - case 'supportTextConnection': - return App.checkConnectionView; - case 'capacityScheduler': - return App.CapacitySceduler; - default: - if (this.get('unit')) { - return App.ServiceConfigTextFieldWithUnit; - } else { - return App.ServiceConfigTextField; - } - } - }.property('displayType'), - validate: function () { - var value = this.get('value'); - var supportsFinal = this.get('supportsFinal'); - var isFinal = this.get('isFinal'); - var valueRange = this.get('valueRange'); - - var isError = false; - var isWarn = false; - - if (typeof value === 'string' && value.length === 0) { - if (this.get('isRequired') && this.get('widgetType') != 'test-db-connection') { - this.set('errorMessage', 'This is required'); - isError = true; - } else { - return; - } - } - - if (!isError) { - switch (this.get('displayType')) { - case 'int': - if (('' + value).trim().length === 0) { - this.set('errorMessage', ''); - isError = false; - return; - } - if (validator.isConfigValueLink(value)) { - isError = false; - } else if (!validator.isValidInt(value)) { - this.set('errorMessage', 'Must contain digits only'); - isError = true; - } else { - if(valueRange){ - if(value < valueRange[0] || value > valueRange[1]){ - this.set('errorMessage', 'Must match the range'); - isError = true; - } - } - } - break; - case 'float': - if (validator.isConfigValueLink(value)) { - isError = false; - } else if (!validator.isValidFloat(value)) { - this.set('errorMessage', 'Must be a valid number'); - isError = true; - } - break; - case 'checkbox': - break; - case 'directories': - case 'directory': - if (this.get('configSupportHeterogeneous')) { - if (!validator.isValidDataNodeDir(value)) { - this.set('errorMessage', 'dir format is wrong, can be "[{storage type}]/{dir name}"'); - isError = true; - } - } else { - if (!validator.isValidDir(value)) { - this.set('errorMessage', 'Must be a slash or drive at the start, and must not contain white spaces'); - isError = true; - } - } - if (!isError) { - if (!validator.isAllowedDir(value)) { - this.set('errorMessage', 'Can\'t start with "home(s)"'); - isError = true; - } else { - // Invalidate values which end with spaces. - if (value !== ' ' && validator.isNotTrimmedRight(value)) { - this.set('errorMessage', Em.I18n.t('form.validator.error.trailingSpaces')); - isError = true; - } - } - } - break; - case 'custom': - break; - case 'email': - if (!validator.isValidEmail(value)) { - this.set('errorMessage', 'Must be a valid email address'); - isError = true; - } - break; - case 'supportTextConnection': - case 'host': - var connectionProperties = ['kdc_hosts']; - if ((validator.isNotTrimmed(value) && connectionProperties.contains(this.get('name')) || validator.isNotTrimmed(value))) { - this.set('errorMessage', Em.I18n.t('host.trimspacesValidation')); - isError = true; - } - break; - case 'password': - // retypedPassword is set by the retypePasswordView child view of App.ServiceConfigPasswordField - if (value !== this.get('retypedPassword')) { - this.set('errorMessage', 'Passwords do not match'); - isError = true; - } - break; - case 'user': - case 'database': - case 'db_user': - if (!validator.isValidDbName(value)){ - this.set('errorMessage', 'Value is not valid'); - isError = true; - } - break; - case 'multiLine': - case 'content': - default: - if(this.get('name')=='javax.jdo.option.ConnectionURL' || this.get('name')=='oozie.service.JPAService.jdbc.url') { - if (validator.isConfigValueLink(value)) { - isError = false; - } else if (validator.isNotTrimmed(value)) { - this.set('errorMessage', Em.I18n.t('host.trimspacesValidation')); - isError = true; - } - } else { - // Avoid single space values which is work around for validate empty properties. - // Invalidate values which end with spaces. - if (value !== ' ' && validator.isNotTrimmedRight(value)) { - this.set('errorMessage', Em.I18n.t('form.validator.error.trailingSpaces')); - isError = true; - } - } - break; - } - } - - if (!isWarn || isError) { // Errors get priority - this.set('warnMessage', ''); - this.set('warn', false); + if (!this.get('isEditable')) { + this.set('errorMessage', ''); // do not perform validation for not editable configs + } else if ((this.get('value') + '').length === 0) { + this.set('errorMessage', this.get('isRequired') ? Em.I18n.t('errorMessage.config.required') : ''); } else { - this.set('warn', true); + this.set('errorMessage', this.validator(this.get('value'), this.get('name'), this.get('retypedPassword'))); } + }.observes('value', 'retypedPassword', 'isEditable'), - if (!isError) { - this.set('errorMessage', ''); - this.set('error', false); - } else { - this.set('error', true); - } - }.observes('value', 'isFinal', 'retypedPassword'), + viewClass: App.ServiceConfigTextField, - /** - * defines specific directory properties that - * allows setting drive type before dir name - * ex: [SSD]/usr/local/my_dir - * @param config - * @returns {*|Boolean|boolean} - */ - configSupportHeterogeneous: function() { - return ['directories', 'directory'].contains(this.get('displayType')) && ['dfs.datanode.data.dir'].contains(this.get('name')); - }.property('displayType', 'name'), + validator: function() { return '' }, /** * Get override for selected group @@ -531,28 +339,6 @@ App.ServiceConfigProperty = Em.Object.extend({ return this.get('overrides').findProperty('group.name', groupName); } return null; - }, - - /** - * Update description for `password`-config - * Add extra-message about their comparison - * - * @method updateDescription - */ - updateDescription: function () { - var description = this.get('description'); - var displayType = this.get('displayType'); - var additionalDescription = Em.I18n.t('services.service.config.password.additionalDescription'); - if ('password' === displayType) { - if (description) { - if (!description.contains(additionalDescription)) { - description += '<br />' + additionalDescription; - } - } else { - description = additionalDescription; - } - } - this.set('description', description); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/utils/config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/config.js b/ambari-web/app/utils/config.js index 8b12a50..2bc5273 100644 --- a/ambari-web/app/utils/config.js +++ b/ambari-web/app/utils/config.js @@ -238,9 +238,7 @@ App.config = Em.Object.create({ } if (useEmberObject) { - var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj); - serviceConfigProperty.validate(); - configs.push(serviceConfigProperty); + configs.push(App.ServiceConfigProperty.create(serviceConfigObj)); } else { configs.push(serviceConfigObj); } @@ -422,13 +420,7 @@ App.config = Em.Object.create({ * @returns {string} */ getDefaultCategory: function(stackConfigProperty, fileName) { - var tag = this.getConfigTagFromFileName(fileName); - switch (tag) { - case 'capacity-scheduler': - return 'CapacityScheduler'; - default : - return (stackConfigProperty ? 'Advanced ' : 'Custom ') + tag; - } + return (stackConfigProperty ? 'Advanced ' : 'Custom ') + this.getConfigTagFromFileName(fileName); }, /** @@ -441,6 +433,146 @@ App.config = Em.Object.create({ }, /** + * Returns description, formatted if needed + * + * @param {String} description + * @param {String} displayType + * @returns {String} + */ + getDescription: function(description, displayType) { + var additionalDescription = Em.I18n.t('services.service.config.password.additionalDescription'); + if ('password' === displayType) { + if (description && !description.contains(additionalDescription)) { + return description + '<br />' + additionalDescription; + } else { + return additionalDescription; + } + } + return description + }, + + /** + * Get view class based on display type of config + * + * @param displayType + * @param dependentConfigPattern + * @param unit + * @returns {*} + */ + getViewClass: function (displayType, dependentConfigPattern, unit) { + switch (displayType) { + case 'checkbox': + case 'boolean': + return dependentConfigPattern ? App.ServiceConfigCheckboxWithDependencies : App.ServiceConfigCheckbox; + case 'password': + return App.ServiceConfigPasswordField; + case 'combobox': + return App.ServiceConfigComboBox; + case 'radio button': + return App.ServiceConfigRadioButtons; + case 'directories': + return App.ServiceConfigTextArea; + case 'directory': + return App.ServiceConfigTextField; + case 'content': + return App.ServiceConfigTextAreaContent; + case 'multiLine': + return App.ServiceConfigTextArea; + case 'custom': + return App.ServiceConfigBigTextArea; + case 'componentHost': + return App.ServiceConfigMasterHostView; + case 'label': + return App.ServiceConfigLabelView; + case 'componentHosts': + return App.ServiceConfigComponentHostsView; + case 'supportTextConnection': + return App.checkConnectionView; + case 'capacityScheduler': + return App.CapacitySceduler; + default: + return unit ? App.ServiceConfigTextFieldWithUnit : App.ServiceConfigTextField; + } + }, + + /** + * Returns validator function based on config type + * + * @param displayType + * @returns {Function} + */ + getValidator: function (displayType) { + switch (displayType) { + case 'checkbox': + case 'custom': + return function () { + return '' + }; + case 'int': + return function (value) { + return !validator.isValidInt(value) && !validator.isConfigValueLink(value) + ? Em.I18n.t('errorMessage.config.number.integer') : ''; + }; + case 'float': + return function (value) { + return !validator.isValidFloat(value) && !validator.isConfigValueLink(value) + ? Em.I18n.t('errorMessage.config.number.float') : ''; + }; + case 'directories': + case 'directory': + return function (value, name) { + if (App.config.isDirHeterogeneous(name)) { + if (!validator.isValidDataNodeDir(value)) return Em.I18n.t('errorMessage.config.directory.heterogeneous'); + } else { + if (!validator.isValidDir(value)) return Em.I18n.t('errorMessage.config.directory.default'); + } + if (!validator.isAllowedDir(value)) { + return Em.I18n.t('errorMessage.config.directory.allowed'); + } + return validator.isNotTrimmedRight(value) ? Em.I18n.t('errorMessage.config.spaces.trailing') : ''; + }; + case 'email': + return function (value) { + return !validator.isValidEmail(value) ? Em.I18n.t('errorMessage.config.mail') : ''; + }; + case 'supportTextConnection': + case 'host': + return function (value) { + return validator.isNotTrimmed(value) ? Em.I18n.t('errorMessage.config.spaces.trim') : ''; + }; + case 'password': + return function (value, name, retypedPassword) { + return value !== retypedPassword ? Em.I18n.t('errorMessage.config.password') : ''; + }; + case 'user': + case 'database': + case 'db_user': + return function (value) { + return !validator.isValidDbName(value) ? Em.I18n.t('errorMessage.config.user') : ''; + }; + default: + return function (value, name) { + if (['javax.jdo.option.ConnectionURL', 'oozie.service.JPAService.jdbc.url'].contains(name) + && !validator.isConfigValueLink(value) && validator.isConfigValueLink(value)) { + return Em.I18n.t('errorMessage.config.spaces.trim'); + } else { + return validator.isNotTrimmedRight(value) ? Em.I18n.t('errorMessage.config.spaces.trailing') : ''; + } + }; + } + }, + + /** + * Defines if config support heterogeneous devices + * + * @param {string} name + * @returns {boolean} + */ + isDirHeterogeneous: function(name) { + return ['dfs.datanode.data.dir'].contains(name); + }, + + /** * format property value depending on displayType * and one exception for 'kdc_type' * @param serviceConfigProperty @@ -636,6 +768,7 @@ App.config = Em.Object.create({ 'isFinal': isFinal, 'savedIsFinal': savedIsFinal, 'recommendedIsFinal': recommendedIsFinal, + 'category': 'CapacityScheduler', 'displayName': 'Capacity Scheduler', 'description': 'Capacity Scheduler properties', 'displayType': 'capacityScheduler' @@ -944,7 +1077,6 @@ App.config = Em.Object.create({ serviceConfigProperty.set('overrideValues', savedOverrides.mapProperty('savedValue')); serviceConfigProperty.set('overrideIsFinalValues', savedOverrides.mapProperty('savedIsFinal')); - newOverride.validate(); return newOverride; }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/utils/validator.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/validator.js b/ambari-web/app/utils/validator.js index 896228b..9d11746 100644 --- a/ambari-web/app/utils/validator.js +++ b/ambari-web/app/utils/validator.js @@ -145,7 +145,7 @@ module.exports = { * @returns {Boolean} - <code>true</code> if ends with spaces */ isNotTrimmedRight: function(value) { - return /\s+$/.test(("" + value).split(/\n/).slice(-1)[0]); + return value !== ' ' && /\s+$/.test(("" + value).split(/\n/).slice(-1)[0]); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js b/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js index 3da387a..9ad8762 100644 --- a/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js +++ b/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js @@ -122,13 +122,6 @@ App.NotificationsConfigsView = App.ServiceConfigsByCategoryView.extend({ updateConfig: function (config, flag) { config.set('isRequired', flag); config.set('isEditable', flag); - if (flag) { - config.validate(); - } - else { - config.set('errorMessage', ''); - config.propertyDidChange('isValid'); - } } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js b/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js index 3bcd81e..92e814c 100644 --- a/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js +++ b/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js @@ -219,8 +219,11 @@ App.ListConfigWidgetView = App.ConfigWidgetView.extend({ currentlySelected = this.get('options').filterProperty('isSelected').length, selectionDisabled = allowedToSelect <= currentlySelected; this.get('options').filterProperty('isSelected', false).setEach('isDisabled', selectionDisabled); - this.set('config.errorMessage', currentlySelected < neededToSelect ? 'You should select at least ' + neededToSelect + ' item(s)' : ''); - this.get('config').validate(); + if (currentlySelected < neededToSelect) { + this.set('config.errorMessage', 'You should select at least ' + neededToSelect + ' item(s)'); + } else { + this.get('config').validate(); + } }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js b/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js index 403ab98..ae668c7 100644 --- a/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js +++ b/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js @@ -166,10 +166,8 @@ describe('App.ConfigRecommendationParser', function() { it ('adds new property', function() { expect(instanceObject._createNewProperty.calledWith('p1', 'file-name', 'serviceName1', 'v1', [])).to.be.true; - expect(stepConfig.get('configs.0')).to.eql(App.ServiceConfigProperty.create({ - 'name': 'p1', - 'filename': 'file-name' - })); + expect(stepConfig.get('configs.0.name')).to.equal('p1'); + expect(stepConfig.get('configs.0.filename')).to.equal('file-name'); }); } else { @@ -276,15 +274,22 @@ describe('App.ConfigRecommendationParser', function() { }); it('adds new config', function() { - expect(instanceObject._createNewProperty('name', 'fileName', 'recommendedValue', null)).to.eql(App.ServiceConfigProperty.create({ + var res = { 'value': 'recommendedValue', 'recommendedValue': 'recommendedValue', - 'initialValue': 'initialValue', 'savedValue': null, 'name': 'name', - 'filename': 'fileName' - })); + 'filename': 'fileName', + 'errorMessage': '' + }; + + var test = instanceObject._createNewProperty('name', 'fileName', 'recommendedValue', null); + for (var k in res) { + if (res.hasOwnProperty(k)) { + expect(test.get(k)).to.eql(res[k]); + } + } expect(instanceObject.applyRecommendation.calledOnce).to.be.true; }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/models/configs/objects/service_config_property_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/models/configs/objects/service_config_property_test.js b/ambari-web/test/models/configs/objects/service_config_property_test.js index b8a6f4e..3a1d14c 100644 --- a/ambari-web/test/models/configs/objects/service_config_property_test.js +++ b/ambari-web/test/models/configs/objects/service_config_property_test.js @@ -385,96 +385,10 @@ describe('App.ServiceConfigProperty', function () { }); }); - describe('#viewClass', function () { - classCases.forEach(function (item) { - it ('should be ' + item.viewClass, function () { - Em.keys(item.initial).forEach(function (prop) { - serviceConfigProperty.set(prop, item.initial[prop]); - }); - expect(serviceConfigProperty.get('viewClass')).to.eql(item.viewClass); - }); - }); - }); - - describe('#validate', function () { - it('not required', function () { - serviceConfigProperty.setProperties({ - isRequired: false, - value: '' - }); - expect(serviceConfigProperty.get('errorMessage')).to.be.empty; - expect(serviceConfigProperty.get('error')).to.be.false; - }); - it('test-db-connection widget', function () { - serviceConfigProperty.setProperties({ - isRequired: true, - widgetType: 'test-db-connection', - value: '' - }); - expect(serviceConfigProperty.get('errorMessage')).to.be.empty; - expect(serviceConfigProperty.get('error')).to.be.false; - }); - it('should validate', function () { - serviceConfigProperty.setProperties({ - isRequired: true, - value: 'value' - }); - expect(serviceConfigProperty.get('errorMessage')).to.be.empty; - expect(serviceConfigProperty.get('error')).to.be.false; - }); - it('should fail', function () { - serviceConfigProperty.setProperties({ - isRequired: true, - value: 'value' - }); - serviceConfigProperty.set('value', ''); - expect(serviceConfigProperty.get('errorMessage')).to.equal('This is required'); - expect(serviceConfigProperty.get('error')).to.be.true; - }); - }); - describe('#overrideIsFinalValues', function () { it('should be defined as empty array', function () { expect(serviceConfigProperty.get('overrideIsFinalValues')).to.eql([]); }); }); - describe('#updateDescription', function () { - - beforeEach(function () { - serviceConfigProperty.setProperties({ - displayType: 'password', - description: '' - }); - }); - - it('should add extra-message to the description for `password`-configs', function () { - - var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription'); - serviceConfigProperty.updateDescription(); - expect(serviceConfigProperty.get('description')).to.contain(extraMessage); - - }); - - it('should not add extra-message to the description if it already contains it', function () { - - var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription'); - serviceConfigProperty.updateDescription(); - serviceConfigProperty.updateDescription(); - serviceConfigProperty.updateDescription(); - expect(serviceConfigProperty.get('description')).to.contain(extraMessage); - var subd = serviceConfigProperty.get('description').replace(extraMessage, ''); - expect(subd).to.not.contain(extraMessage); - }); - - it('should add extra-message to the description if description is not defined', function () { - - serviceConfigProperty.set('description', undefined); - var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription'); - serviceConfigProperty.updateDescription(); - expect(serviceConfigProperty.get('description')).to.contain(extraMessage); - }); - - }); - }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/models/form_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/models/form_test.js b/ambari-web/test/models/form_test.js index 2d45ada..573deae 100644 --- a/ambari-web/test/models/form_test.js +++ b/ambari-web/test/models/form_test.js @@ -185,28 +185,6 @@ describe('App.FormField', function () { }); }); - describe('#viewClass', function () { - displayTypeCases.forEach(function (item) { - it('should be ' + item.classString, function () { - formField.set('displayType', item.type); - expect(formField.get('viewClass').toString()).to.contain(item.classString); - }); - }); - }); - - /*eslint-disable mocha-cleanup/asserts-limit */ - describe('#validate', function () { - it('should return error message', function () { - formField.set('isRequired', true); - expectError('This is required'); - }); - it('should return empty error message', function () { - formField.set('isRequired', false); - expectError(''); - formField.set('value', 'value'); - expectError(''); - }); - }); /*eslint-enable mocha-cleanup/asserts-limit */ describe('#isHiddenField', function () { http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/utils/config_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/utils/config_test.js b/ambari-web/test/utils/config_test.js index fd7c188..97c3cd7 100644 --- a/ambari-web/test/utils/config_test.js +++ b/ambari-web/test/utils/config_test.js @@ -891,4 +891,28 @@ describe('App.config', function () { }); }); + describe('#getDescription', function () { + + it('should add extra-message to the description for `password`-configs', function () { + var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription'); + expect(App.config.getDescription('', 'password')).to.contain(extraMessage); + }); + + it('should not add extra-message to the description if it already contains it', function () { + + var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription'); + var res = App.config.getDescription(extraMessage, 'password'); + expect(res).to.contain(extraMessage); + expect(res).to.contain(extraMessage); + var subd = res.replace(extraMessage, ''); + expect(subd).to.not.contain(extraMessage); + }); + + it('should add extra-message to the description if description is not defined', function () { + + var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription'); + expect(App.config.getDescription(undefined, 'password')).to.contain(extraMessage); + }); + + }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js b/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js index 3af85ac..8f03434 100644 --- a/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js +++ b/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js @@ -217,14 +217,7 @@ describe('App.NotificationsConfigsView', function () { var config; beforeEach(function () { - config = Em.Object.create({ - validate: Em.K - }); - sinon.spy(config, 'validate'); - }); - - afterEach(function () { - config.validate.restore(); + config = Em.Object.create(); }); describe("flag is true", function () { @@ -238,9 +231,6 @@ describe('App.NotificationsConfigsView', function () { it('isEditable is true', function () { expect(config.get('isEditable')).to.be.true; }); - it('validate is called once', function () { - expect(config.validate.calledOnce).to.be.true; - }); }); describe("flag is false", function () { @@ -253,12 +243,6 @@ describe('App.NotificationsConfigsView', function () { it('isEditable is false', function () { expect(config.get('isEditable')).to.be.false; }); - it('errorMessage is empty', function () { - expect(config.get('errorMessage')).to.be.empty; - }); - it('validate is not called', function () { - expect(config.validate.called).to.be.false; - }); }); }); });
