Browse Source

AMBARI-14501 Improve config recommendations flow 2. (ababiichuk)

ababiichuk 9 years ago
parent
commit
73a850eb0b

+ 14 - 2
ambari-web/app/controllers/main/service/info/configs.js

@@ -264,7 +264,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ConfigsLoader, A
     this.get('requestsInProgress').clear();
     this.clearLoadInfo();
     this.clearSaveInfo();
-    this.clearDependentConfigs();
+    this.clearAllRecommendations();
     this.setProperties({
       saveInProgress: false,
       isInit: true,
@@ -535,6 +535,18 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ConfigsLoader, A
     }
   },
 
+  /**
+   * Allow update property if recommendations
+   * is based on changing property
+   *
+   * @param parentProperties
+   * @returns {boolean}
+   * @override
+   */
+  allowUpdateProperty: function(parentProperties) {
+    return !!(parentProperties && parentProperties.length);
+  },
+
   /**
    * trigger App.config.createOverride
    * @param {Object[]} stepConfig
@@ -576,7 +588,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ConfigsLoader, A
    */
   doCancel: function () {
     this.set('preSelectedConfigVersion', null);
-    this.clearDependentConfigs();
+    this.clearAllRecommendations();
     this.loadSelectedVersion(this.get('selectedVersion'), this.get('selectedConfigGroup'));
   },
 

+ 77 - 9
ambari-web/app/controllers/wizard/step7_controller.js

@@ -706,18 +706,11 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E
     if (rangerService && !rangerService.get('isInstalled') && !rangerService.get('isSelected')) {
       App.config.removeRangerConfigs(self.get('stepConfigs'));
     }
-    this.loadServerSideConfigsRecommendations().always(function() {
-      if (self.get('wizardController.name') == 'addServiceController') {
-        // for Add Service just remove or add dependent properties and ignore config values changes
-        // for installed services only
-        self.clearDependenciesForInstalledServices(self.get('installedServiceNames'), self.get('stepConfigs'));
-      }
-      self.completeConfigLoading();
-    });
+    this.loadServerSideConfigsRecommendations().always(this.completeConfigLoading.bind(this));
   },
 
   completeConfigLoading: function() {
-    this.clearDependentConfigsByService(App.StackService.find().filterProperty('isSelected').mapProperty('serviceName'));
+    this.clearRecommendationsByServiceName(App.StackService.find().filterProperty('isSelected').mapProperty('serviceName'));
     console.timeEnd('wizard loadStep: ');
     this.set('isRecommendedLoaded', true);
     if (this.get('content.skipConfigStep')) {
@@ -726,6 +719,17 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E
     this.set('hash', this.getHash());
   },
 
+  /**
+   * Update initialValues only while loading recommendations first time
+   *
+   * @param serviceName
+   * @returns {boolean}
+   * @override
+   */
+  updateInitialOnRecommendations: function(serviceName) {
+    return this._super(serviceName) && !this.get('isRecommendedLoaded');
+  },
+
   /**
    * Mark descriptor properties in configuration object.
    *
@@ -1223,6 +1227,70 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E
     return config;
   },
 
+  /**
+   * @param serviceName
+   * @returns {boolean}
+   * @override
+   */
+  useInitialValue: function(serviceName) {
+    return !App.Service.find(serviceName).get('serviceName', serviceName);
+  },
+
+  /**
+   *
+   * @param parentProperties
+   * @param name
+   * @param fileName
+   * @returns {*}
+   * @override
+   */
+  allowUpdateProperty: function(parentProperties, name, fileName) {
+    if (['installerController'].contains(this.get('wizardController.name')) || !!(parentProperties && parentProperties.length)) {
+      return true;
+    } else if (['addServiceController'].contains(this.get('wizardController.name'))) {
+      var stackProperty = App.configsCollection.getConfigByName(name, fileName);
+      if (!stackProperty || !this.get('installedServices')[stackProperty.serviceName]) {
+        return true;
+      } else if (stackProperty.propertyDependsOn.length) {
+        return stackProperty.propertyDependsOn.filter(function (p) {
+          var service = App.config.getServiceByConfigType(p.type);
+          return service && !this.get('installedServices')[service.get('serviceName')];
+        }, this).length;
+      } else {
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * remove config based on recommendations
+   * @param config
+   * @param configsCollection
+   * @param parentProperties
+   * @protected
+   * @override
+   */
+  _removeConfigByRecommendation: function (config, configsCollection, parentProperties) {
+    this._super(config, configsCollection, parentProperties);
+    /**
+     * need to update wizard info when removing configs for installed services;
+     */
+    var installedServices = this.get('installedServices'), wizardController = this.get('wizardController'),
+      fileNamesToUpdate = wizardController ? wizardController.getDBProperty('fileNamesToUpdate') || [] : [],
+      fileName = Em.get(config, 'filename'), serviceName = Em.get(config, 'serviceName');
+    var modifiedFileNames = this.get('modifiedFileNames');
+    if (modifiedFileNames && !modifiedFileNames.contains(fileName)) {
+      modifiedFileNames.push(fileName);
+    } else if (wizardController && installedServices[serviceName]) {
+      if (!fileNamesToUpdate.contains(fileName)) {
+        fileNamesToUpdate.push(fileName);
+      }
+    }
+    if (wizardController) {
+      wizardController.setDBProperty('fileNamesToUpdate', fileNamesToUpdate.uniq());
+    }
+  },
   /**
    * @method manageConfigurationGroup
    */

+ 3 - 0
ambari-web/app/mixins.js

@@ -45,6 +45,9 @@ require('mixins/wizard/selectHost');
 require('mixins/wizard/addSecurityConfigs');
 require('mixins/wizard/wizard_menu_view');
 require('mixins/wizard/assign_master_components');
+require('mixins/common/configs/config_recommendations');
+require('mixins/common/configs/config_recommendation_parser');
+require('mixins/common/configs/config_with_override_recommendation_parser');
 require('mixins/common/configs/enhanced_configs');
 require('mixins/common/configs/configs_saver');
 require('mixins/common/configs/configs_loader');

+ 248 - 0
ambari-web/app/mixins/common/configs/config_recommendation_parser.js

@@ -0,0 +1,248 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var validator = require('utils/validator');
+
+App.ConfigRecommendationParser = Em.Mixin.create(App.ConfigRecommendations, {
+
+	stepConfigs: [],
+
+	/**
+	 * Method that goes through all configs
+	 * and apply recommendations using callbacks
+	 *
+	 * @param recommendationObject
+	 * @param configs
+	 * @param parentProperties
+	 * @param configGroup
+	 * @param updateCallback
+	 * @param removeCallback
+	 * @param updateBoundariesCallback
+	 */
+	parseRecommendations: function(recommendationObject, configs, parentProperties, configGroup,
+	                               updateCallback, removeCallback, updateBoundariesCallback) {
+		var propertiesToDelete = [];
+		configs.forEach(function (config) {
+			var name = Em.get(config, 'name'), fileName = Em.get(config, 'filename'),
+				site = App.config.getConfigTagFromFileName(fileName);
+			if (recommendationObject[site]) {
+				var properties = recommendationObject[site].properties,
+					property_attributes = recommendationObject[site].property_attributes;
+				if (properties) {
+					var recommendedValue = App.config.formatValue(properties[name]);
+					if (!Em.isNone(recommendedValue)) {
+						/** update config **/
+						updateCallback(config, recommendedValue, parentProperties, configGroup);
+
+						delete recommendationObject[site].properties[name];
+					}
+				}
+				if (property_attributes) {
+					var propertyAttributes = property_attributes[name];
+					var stackProperty = App.configsCollection.getConfigByName(name, fileName);
+					for (var attr in propertyAttributes) {
+						if (attr == 'delete' && this.allowUpdateProperty(parentProperties, name, fileName)) {
+							propertiesToDelete.push(config);
+						} else if (stackProperty) {
+							/** update config boundaries **/
+							updateBoundariesCallback(stackProperty, attr, propertyAttributes[attr], configGroup);
+						}
+					}
+				}
+			}
+		}, this);
+
+		if (propertiesToDelete.length) {
+			propertiesToDelete.forEach(function (property) {
+				/** remove config **/
+				removeCallback(property, configs, parentProperties, configGroup);
+
+			}, this);
+		}
+	},
+
+	/**
+	 * Method that goes through all configs
+	 * and apply recommendations to configs when it's needed
+	 *
+	 * @param {Object} recommendationObject
+	 * @param {Object[]} configs
+	 * @param {Object[]} parentProperties
+	 */
+	updateConfigsByRecommendations: function (recommendationObject, configs, parentProperties) {
+		this.parseRecommendations(recommendationObject, configs, parentProperties, null,
+			this._updateConfigByRecommendation.bind(this), this._removeConfigByRecommendation.bind(this), this._updateBoundaries.bind(this));
+	},
+
+	/**
+	 * This method goes through all config recommendations
+	 * and trying to add new properties
+	 *
+	 * @param {Object} recommendationObject
+	 * @param {Object[]} parentProperties
+	 */
+	addByRecommendations: function (recommendationObject, parentProperties) {
+		for (var site in recommendationObject) {
+			if (Object.keys(recommendationObject[site].properties).length) {
+				var stepConfig = App.config.getStepConfigForProperty(this.get('stepConfigs'), site), configs = [];
+				if (stepConfig) {
+					for (var propertyName in recommendationObject[site].properties) {
+						if (this.allowUpdateProperty(parentProperties, propertyName, site)) {
+							this._addConfigByRecommendation(configs, propertyName, site, recommendationObject[site].properties[propertyName], parentProperties);
+						}
+					}
+					var mergedConfigs = configs.concat(stepConfig.get('configs'));
+					stepConfig.set('configs', mergedConfigs);
+				}
+			}
+		}
+	},
+
+	/**
+	 * Update config based on recommendations
+	 *
+	 * @param config
+	 * @param recommendedValue
+	 * @param parentProperties
+	 * @protected
+	 */
+	_updateConfigByRecommendation: function (config, recommendedValue, parentProperties) {
+		Em.assert('config should be defined', config);
+		Em.set(config, 'recommendedValue', recommendedValue);
+		if (this.allowUpdateProperty(parentProperties, Em.get(config, 'name'), Em.get(config, 'filename'))) {
+			Em.set(config, 'value', recommendedValue);
+			this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), Em.get(config, 'group.name'), recommendedValue, this._getInitialValue(config), parentProperties);
+		}
+		if (this.updateInitialOnRecommendations(Em.get(config, 'serviceName'))) {
+			Em.set(config, 'initialValue', recommendedValue);
+		}
+	},
+
+	/**
+	 * Add config based on recommendations
+	 *
+	 * @param configs
+	 * @param name
+	 * @param fileName
+	 * @param recommendedValue
+	 * @param parentProperties
+	 * @protected
+	 */
+	_addConfigByRecommendation: function (configs, name, fileName, recommendedValue, parentProperties) {
+		fileName = App.config.getOriginalFileName(fileName);
+		var stackConfig = App.configsCollection.getConfigByName(name, fileName),
+			service = App.config.get('serviceByConfigTypeMap')[App.config.getConfigTagFromFileName(fileName)];
+		if (service) {
+			var serviceName = stackConfig ? stackConfig.serviceName : service && service.get('serviceName'),
+				popupProperty = this.getRecommendation(name, fileName),
+				initialValue = popupProperty ? popupProperty.value : null;
+
+			var coreObject = {
+				"value": recommendedValue,
+				"recommendedValue": recommendedValue,
+				"initialValue": this.updateInitialOnRecommendations(serviceName) ? recommendedValue : initialValue,
+				"savedValue": !this.useInitialValue(serviceName) && !Em.isNone(initialValue) ? initialValue : null
+			};
+			var addedProperty = stackConfig || App.config.createDefaultConfig(name, serviceName, fileName, false);
+			Em.setProperties(addedProperty, coreObject);
+			var addedPropertyObject = App.ServiceConfigProperty.create(addedProperty);
+			configs.pushObject(addedPropertyObject);
+			addedPropertyObject.validate();
+
+			this.applyRecommendation(name, fileName, "Default",
+				recommendedValue, null, parentProperties);
+		}
+	},
+
+	/**
+	 * Remove config based on recommendations
+	 *
+	 * @param config
+	 * @param configsCollection
+	 * @param parentProperties
+	 * @protected
+	 */
+	_removeConfigByRecommendation: function (config, configsCollection, parentProperties) {
+		Em.assert('config and configsCollection should be defined', config && configsCollection);
+		configsCollection.removeObject(config);
+
+		this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), Em.get(config, 'group.name'),
+			null, this._getInitialValue(config), parentProperties);
+	},
+
+	/**
+	 * Update config valueAttributes by recommendations
+	 *
+	 * @param {Object} stackProperty
+	 * @param {string} attr
+	 * @param {Number|String|Boolean} value
+	 * @protected
+	 */
+	_updateBoundaries: function(stackProperty, attr, value) {
+		Em.set(stackProperty.valueAttributes, attr, value);
+	},
+
+	/**
+	 * Get default config value
+	 * <code>savedValue<code> for installed services
+	 * <code>initialValue<code> for new services
+	 *
+	 * @param configProperty
+	 * @returns {*}
+	 * @protected
+	 */
+	_getInitialValue: function (configProperty) {
+		if (!configProperty) return null;
+		return this.useInitialValue(Em.get(configProperty, 'serviceName')) ?
+			Em.get(configProperty, 'initialValue') : Em.get(configProperty, 'savedValue');
+	},
+
+	/**
+	 * Update initial only when <code>initialValue<code> is used
+	 *
+	 * @param {string} serviceName
+	 * @returns {boolean}
+	 */
+	updateInitialOnRecommendations: function(serviceName) {
+		return this.useInitialValue(serviceName);
+	},
+
+	/**
+	 * Defines if initialValue of config can be used on current controller
+	 * if not savedValue is used instead
+	 *
+	 * @param {String} serviceName
+	 * @return {boolean}
+	 */
+	useInitialValue: function (serviceName) {
+		return false;
+	},
+
+	/**
+	 * Defines if recommendation allowed to be applied
+	 *
+	 * @param parentProperties
+	 * @param name
+	 * @param fileName
+	 * @returns {boolean}
+	 */
+	allowUpdateProperty: function (parentProperties, name, fileName) {
+		return true;
+	}
+});

+ 201 - 0
ambari-web/app/mixins/common/configs/config_recommendations.js

@@ -0,0 +1,201 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+/**
+ * @typedef {object} recommendation
+ * @property {boolean} saveRecommended - by default is true (checkbox binding)
+ * @property {boolean} saveRecommendedDefault - used for cancel operation to restore previous state (saved checkbox value)
+ * @property {boolean} isDeleted - true if property was deleted
+ * @property {boolean} notDefined - true if property was added
+ * @property {string} propertyName
+ * @property {string} propertyFileName - file name without '.xml'
+ * @property {string} configGroup - name of config group, by default "Default"
+ * @property {string} serviceName
+ * @property {string} serviceDisplayName
+ * @property {string} initialValue
+ * @property {string} recommendedValue
+ * @property {boolean} allowChangeGroup - flag that allows to change config group for config from dependent not default group
+ * @property {string[]} parentConfigs - list of properties based on which current recommendation was performed
+ */
+
+App.ConfigRecommendations = Em.Mixin.create({
+
+    /**
+     * List of recommendations that was applied to configs
+     *
+     * @type {recommendation[]}
+     */
+    recommendations: [],
+
+    /**
+     * Update recommendation property if exists
+     * otherwise add new
+     *
+     * @param {string} name
+     * @param {string} fileName
+     * @param {string} configGroupName
+     * @param {string} recommendedValue
+     * @param {string} initialValue
+     * @param {Object[]}parentProperties
+     * @returns {recommendation}
+     */
+    applyRecommendation: function(name, fileName, configGroupName, recommendedValue, initialValue, parentProperties) {
+        var parentPropertiesNames = parentProperties ? parentProperties.map(function(p) {
+            return App.config.configId(p.name, p.type);
+        }) : [];
+        var updated = this.updateRecommendation(name, fileName, configGroupName, recommendedValue, parentPropertiesNames);
+        if (!updated)
+            var added = this.addRecommendation(name, fileName, configGroupName, recommendedValue, initialValue, parentPropertiesNames);
+        return updated || added;
+    },
+
+    /**
+     * Add new recommendation
+     *
+     * @param {string} name
+     * @param {string} fileName
+     * @param {string} configGroupName
+     * @param {string} recommendedValue
+     * @param {string} initialValue
+     * @param {string[]} parentPropertiesNames
+     * @returns {recommendation}
+     */
+    addRecommendation: function(name, fileName, configGroupName, recommendedValue, initialValue, parentPropertiesNames) {
+        Em.assert('name and fileName should be defined', name && fileName);
+        var site = App.config.getConfigTagFromFileName(fileName);
+        var service = App.config.get('serviceByConfigTypeMap')[site];
+        var recommendation = {
+            saveRecommended: true,
+            saveRecommendedDefault: true,
+            propertyFileName: site,
+            propertyName: name,
+
+            isDeleted: Em.isNone(recommendedValue),
+            notDefined: Em.isNone(initialValue),
+
+            configGroup: configGroupName || "Default",
+            initialValue: initialValue,
+            parentConfigs: parentPropertiesNames || [],
+            serviceName: service.get('serviceName'),
+            allowChangeGroup: false,//TODO groupName!= "Default" && (service.get('serviceName') != this.get('selectedService.serviceName'))
+            //TODO&& (App.ServiceConfigGroup.find().filterProperty('serviceName', service.get('serviceName')).length > 1), //TODO
+            serviceDisplayName: service.get('displayName'),
+            recommendedValue: recommendedValue
+        };
+        this.get('recommendations').pushObject(recommendation);
+        return recommendation;
+    },
+
+    /**
+     * Remove recommendation
+     * based on unique identifiers
+     *
+     * @param {string} name
+     * @param {string} fileName
+     * @param {string} configGroupName
+     */
+    removeRecommendation: function(name, fileName, configGroupName) {
+        this.removeRecommendationObject(this.getRecommendation(name, fileName, configGroupName));
+    },
+
+    /**
+     * Remove recommended Object
+     *
+     * @param {recommendation} recommendation
+     */
+    removeRecommendationObject: function(recommendation) {
+        if (recommendation)
+            this.get('recommendations').removeObject(recommendation);
+    },
+
+    /**
+     * Update recommended object
+     *
+     * @param name
+     * @param fileName
+     * @param configGroupName
+     * @param recommendedValue
+     * @param parentPropertiesNames
+     * @returns {*|recommendation|null}
+     */
+    updateRecommendation: function(name, fileName, configGroupName, recommendedValue, parentPropertiesNames) {
+        var recommendation = this.getRecommendation(name, fileName, configGroupName);
+        if (recommendation) {
+            Em.set(recommendation, 'recommendedValue', recommendedValue);
+            if (parentPropertiesNames && parentPropertiesNames.length) {
+                var mergedProperties = parentPropertiesNames.concat(Em.get(recommendation, 'parentPropertiesNames'));
+                Em.set(recommendation, 'parentPropertiesNames', mergedProperties);
+            }
+        }
+        return recommendation;
+    },
+
+    /**
+     * Get single recommendation
+     *
+     * @param name
+     * @param fileName
+     * @param configGroupName
+     * @returns {recommendation|null}
+     */
+    getRecommendation: function(name, fileName, configGroupName) {
+        Em.assert('name and fileName should be defined', name && fileName);
+        return this.get('recommendations').find(function (dcv) {
+            return dcv.propertyName === name
+                && dcv.propertyFileName === App.config.getConfigTagFromFileName(fileName)
+                && dcv.configGroup === (configGroupName || "Default");
+        });
+    },
+
+    /**
+     * Clear recommendations that are
+     * same as initial value
+     *
+     * @method cleanUpRecommendations
+     */
+    cleanUpRecommendations: function() {
+        var cleanDependentList = this.get('recommendations').filter(function(d) {
+            return !((Em.isNone(d.initialValue) && Em.isNone(d.recommendedValue)) || d.initialValue == d.recommendedValue);
+        }, this);
+        this.set('recommendations', cleanDependentList);
+    },
+
+    /**
+     * Remove all recommendations
+     *
+     * @method clearAllRecommendations
+     */
+    clearAllRecommendations: function() {
+        this.set('recommendations', []);
+    },
+
+    /**
+     * Clear values for dependent configs for given services
+     *
+     * @method clearRecommendationsByServiceName
+     */
+    clearRecommendationsByServiceName: function(serviceNames) {
+        var filteredRecommendations = this.get('recommendations').reject(function(c) {
+            return serviceNames.contains(c.serviceName);
+        }, this);
+        this.set('recommendations', filteredRecommendations);
+    }
+
+});

+ 113 - 0
ambari-web/app/mixins/common/configs/config_with_override_recommendation_parser.js

@@ -0,0 +1,113 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.ConfigWithOverrideRecommendationParser = Em.Mixin.create(App.ConfigRecommendationParser, {
+
+	/**
+	 * Method that goes through all configs
+	 * and apply recommendations to overrides when it's needed
+	 *
+	 * @param {Object} recommendationObject
+	 * @param {Object[]} configs
+	 * @param {Object[]} parentProperties
+	 * @param {App.ServiceConfigGroup} configGroup
+	 */
+	updateOverridesByRecommendations: function (recommendationObject, configs, parentProperties, configGroup) {
+		Em.assert('Config groups should be defined and not default', configGroup && configGroup.get('name') && !configGroup.get('isDefault'));
+		this.parseRecommendations(recommendationObject, configs, parentProperties, configGroup,
+			this._updateOverride.bind(this), this._removeOverride.bind(this), this._updateOverrideBoundaries.bind(this));
+	},
+
+	/**
+	 * Update override by recommendations
+	 * includes add/update actions
+	 *
+	 * @param config
+	 * @param recommendedValue
+	 * @param parentProperties
+	 * @param configGroup
+	 * @protected
+	 */
+	_updateOverride: function(config, recommendedValue, parentProperties, configGroup) {
+		debugger;
+		var updateValue = this.allowUpdateProperty(parentProperties, Em.get(config, 'name'), Em.get(config, 'filename'));
+		var override = config.getOverride(configGroup.get('name'));
+		if (override) {
+			this._updateConfigByRecommendation(override, recommendedValue, parentProperties);
+		} else if (updateValue) {
+			this._addConfigOverrideRecommendation(config, recommendedValue, parentProperties, configGroup);
+		}
+	},
+
+	/**
+	 * Remove override by recommendations
+	 *
+	 * @param property
+	 * @param configs
+	 * @param parentProperties
+	 * @param configGroup
+ 	 * @protected
+	 */
+	_removeOverride: function(property, configs, parentProperties, configGroup) {
+		this._removeConfigByRecommendation(property.getOverride(configGroup.get('name')), property.get('overrides') || [], parentProperties);
+	},
+
+	/**
+	 * Add override by recommendations
+	 *
+	 * @param config
+	 * @param recommendedValue
+	 * @param configGroup
+	 * @param parentProperties
+	 * @protected
+	 */
+	_addConfigOverrideRecommendation: function (config, recommendedValue, parentProperties, configGroup) {
+		var popupProperty = this.getRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), configGroup.get('name')),
+			initialValue = popupProperty ? popupProperty.value : null;
+		var coreObject = {
+			"value": recommendedValue,
+			"recommendedValue": recommendedValue,
+			"initialValue": initialValue,
+			"savedValue": !this.useInitialValue(Em.get(config, 'serviceName')) && !Em.isNone(initialValue) ? initialValue : null,
+			"isEditable": true
+		};
+		var override = App.config.createOverride(config, coreObject, configGroup);
+		configGroup.get('properties').pushObject(override);
+
+		this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), configGroup.get('name'),
+			recommendedValue, this._getInitialValue(override), parentProperties);
+	},
+
+	/**
+	 * Update override valueAttributes by recommendations
+	 *
+	 * @param {Object} stackProperty
+	 * @param {string} attr
+	 * @param {Number|String|Boolean} value
+	 * @param {App.ServiceConfigGroup} configGroup
+	 * @protected
+	 */
+	_updateOverrideBoundaries: function(stackProperty, attr, value, configGroup) {
+		if (!stackProperty.valueAttributes[configGroup.get('name')]) {
+			stackProperty.valueAttributes[configGroup.get('name')] = {};
+		}
+		Em.set(stackProperty.valueAttributes[configGroup.get('name')], attr, value);
+	}
+});

+ 1 - 1
ambari-web/app/mixins/common/configs/configs_saver.js

@@ -620,7 +620,7 @@ App.ConfigsSaverMixin = Em.Mixin.create({
       App.QuickViewLinks.proto().loadTags();
     }
     this.showSaveConfigsPopup(header, flag, message, messageClass, value, status, urlParams);
-    this.clearDependentConfigs();
+    this.clearAllRecommendations();
   },
 
   /**

+ 48 - 416
ambari-web/app/mixins/common/configs/enhanced_configs.js

@@ -18,9 +18,8 @@
 
 var App = require('app');
 var blueprintUtils = require('utils/blueprint');
-var validator = require('utils/validator');
 
-App.EnhancedConfigsMixin = Em.Mixin.create({
+App.EnhancedConfigsMixin = Em.Mixin.create(App.ConfigWithOverrideRecommendationParser, {
 
   /**
    * this value is used for observing
@@ -49,17 +48,6 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
    */
   isControllerSupportsEnhancedConfigs: Em.computed.existsIn('name', ['wizardStep7Controller','mainServiceInfoConfigsController']),
 
-  /**
-   * defines if initialValue of config can be used on current controller
-   * if not savedValue is used instead
-   * @param {String} serviceName
-   * @return {boolean}
-   * @method useInitialValue
-   */
-  useInitialValue: function(serviceName) {
-    return ['wizardStep7Controller'].contains(this.get('name')) && !App.Service.find().findProperty('serviceName', serviceName);
-  },
-
   dependenciesGroupMessage: Em.I18n.t('popup.dependent.configs.dependencies.for.groups'),
   /**
    * message fro alert box for dependent configs
@@ -74,40 +62,16 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
       + Em.I18n.t('popup.dependent.configs.dependencies.service.' + sLenSuffix).format(changedServices.length);
   }.property('changedProperties.length'),
 
-  /**
-   * values for dependent configs
-   * @type {Object[]}
-   * ex:
-   * {
-   *   saveRecommended: {boolean}, //by default is true (checkbox binding)
-   *   saveRecommendedDefault: {boolean}, used for cancel operation to restore previous state
-   *   toDelete: {boolean} [true], // defines if property should be deleted
-   *   toAdd: {boolean} [false], // defines if property should be added
-   *   isDeleted: {boolean} [true], // defines if property was deleted, but was present in initial configs
-   *   fileName: {string}, //file name without '.xml'
-   *   propertyName: {string},
-   *   parentConfigs: {string[]} // name of the parent configs
-   *   configGroup: {string},
-   *   value: {string},
-   *   serviceName: {string},
-   *   allowChangeGroup: {boolean}, //used to disable group link for current service
-   *   serviceDisplayName: {string},
-   *   recommendedValue: {string}
-   * }
-   * @private
-   */
-  _dependentConfigValues: Em.A([]),
-
   /**
    * dependent properties that was changed by Ambari
    * @type {Object[]}
    */
   changedProperties: function() {
-    return this.get('_dependentConfigValues').filter(function(dp) {
+    return this.get('recommendations').filter(function(dp) {
       return (this.get('selectedConfigGroup.isDefault') && Em.get(dp, 'configGroup').contains('Default'))
         || [this.get('selectedConfigGroup.name'), this.get('selectedConfigGroup.dependentConfigGroups') && this.get('selectedConfigGroup.dependentConfigGroups')[Em.get(dp, 'serviceName')]].contains(Em.get(dp, 'configGroup'));
     }, this);
-  }.property('_dependentConfigValues.@each.saveRecommended', 'selectedConfigGroup'),
+  }.property('recommendations.@each.saveRecommended', 'recommendations.@each.recommendedValue', 'selectedConfigGroup'),
 
   /**
    * defines if change dependent group message should be shown
@@ -142,67 +106,6 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
 
   /******************************METHODS THAT WORKS WITH DEPENDENT CONFIGS *************************************/
 
-  /**
-   * clear values for dependent configs
-   * @method clearDependentConfigs
-   * @private
-   */
-  clearDependentConfigs: function() {
-    this.setProperties({
-      _dependentConfigValues: []
-    });
-  },
-
-  /**
-   * clear values for dependent configs for given services
-   * @method clearDependentConfigs
-   * @private
-   */
-  clearDependentConfigsByService: function(serviceNames) {
-    var cleanDependencies = this.get('_dependentConfigValues').reject(function(c) {
-      return serviceNames.contains(c.serviceName);
-    }, this);
-    this.get('stepConfigs').filter(function(s) {
-      return serviceNames.contains(s.get('serviceName'));
-    }).forEach(function(s) {
-      s.get('configs').setEach('isNotSaved', false);
-    });
-    this.set('_dependentConfigValues', cleanDependencies);
-  },
-
-  /**
-   * Remove configs from <code>_dependentConfigValues</code> which depends between installed services only.
-   *
-   * @param {String[]} installedServices
-   * @param {App.ServiceConfig[]} stepConfigs
-   */
-  clearDependenciesForInstalledServices: function(installedServices, stepConfigs) {
-    var allConfigs = stepConfigs.mapProperty('configs').filterProperty('length').reduce(function(p, c) {
-      return p && p.concat(c);
-    });
-    var cleanDependencies = this.get('_dependentConfigValues').reject(function(item) {
-      if (Em.get(item, 'propertyName').contains('hadoop.proxyuser')) return false;
-      if (installedServices.contains(Em.get(item, 'serviceName'))) {
-        var stackProperty = App.configsCollection.getConfigByName(item.propertyName, item.fileName);
-        var parentConfigs = stackProperty && stackProperty.propertyDependsOn;
-        if (!parentConfigs || !parentConfigs.length) {
-          return true;
-        }
-        // check that all parent properties from installed service
-        return !parentConfigs.reject(function(parentConfig) {
-          var property = allConfigs.filterProperty('filename', App.config.getOriginalFileName(parentConfig.type))
-                                   .findProperty('name', parentConfig.name);
-          if (!property) {
-            return false;
-          }
-          return installedServices.contains(Em.get(property, 'serviceName'));
-        }).length;
-      }
-      return false;
-    });
-    this.set('_dependentConfigValues', cleanDependencies);
-  },
-
   /**
    * get config group object for current service
    * @param serviceName
@@ -236,9 +139,7 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
    * @method removeCurrentFromDependentList
    */
   removeCurrentFromDependentList: function (config, saveRecommended) {
-    var current = this.get('_dependentConfigValues').find(function(dependentConfig) {
-      return Em.get(dependentConfig, 'propertyName') == config.get('name') && Em.get(dependentConfig, 'fileName') == App.config.getConfigTagFromFileName(config.get('filename'));
-    });
+    var current = this.getRecommendation(config.get('name'), config.get('filename'), config.get('group.name'));
     if (current) {
       Em.setProperties(current, {
           'saveRecommended': !!saveRecommended,
@@ -265,7 +166,8 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
         hosts: this.get('hostNames'),
         services: this.get('serviceNames')
       };
-      var clearConfigsOnAddService = this.isConfigHasInitialState();
+
+      var clearConfigsOnAddService = configGroup.get('isDefault') && this.isConfigHasInitialState();
       if (clearConfigsOnAddService) {
         recommendations.blueprint.configurations = this.get('initialConfigValues');
       } else {
@@ -287,7 +189,6 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
           stackVersionUrl: App.get('stackVersionURL'),
           dataToSend: dataToSend,
           notDefaultGroup: configGroup && !configGroup.get('isDefault'),
-          initial: initial,
           clearConfigsOnAddService: clearConfigsOnAddService
         },
         success: 'dependenciesSuccess',
@@ -313,7 +214,7 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
     return !this.get('stepConfigs').filter(function(stepConfig) {
       return stepConfig.get('changedConfigProperties').filter(function(c) {
         return !this.get('changedProperties').map(function(changed) {
-          return App.config.configId(changed.propertyName, changed.fileName);
+          return App.config.configId(changed.propertyName, changed.propertyFileName);
         }).contains(App.config.configId(c.get('name'), c.get('filename')));
       }, this).length;
     }, this).length;
@@ -326,18 +227,16 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
   clearConfigValues: function() {
     this.get('stepConfigs').forEach(function(stepConfig) {
       stepConfig.get('changedConfigProperties').forEach(function(c) {
-        var recommendedProperty = this.get('_dependentConfigValues').find(function(d) {
-          return App.config.configId(d.propertyName, d.fileName) == App.config.configId(c.get('name'), c.get('filename'));
-        });
+        var recommendedProperty = this.getRecommendation(c.get('name'), c.get('filename'), c.get('group.name'));
         if (recommendedProperty) {
-          var initialValue = recommendedProperty.value;
+          var initialValue = recommendedProperty.initialValue;
           if (Em.isNone(initialValue)) {
             stepConfig.get('configs').removeObject(c);
           } else {
+            c.set('initialValue', initialValue);
             c.set('value', initialValue);
-            c.set('recommendedValue', initialValue);
           }
-          this.get('_dependentConfigValues').removeObject(recommendedProperty);
+          this.removeRecommendationObject(recommendedProperty);
         }
       }, this)
     }, this);
@@ -379,13 +278,11 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
    * @method dependenciesSuccess
    */
   dependenciesSuccess: function (data, opt, params) {
-    this._saveRecommendedValues(data, params.initial, params.dataToSend.changed_configurations, params.notDefaultGroup);
+    this._saveRecommendedValues(data, params.dataToSend.changed_configurations, params.notDefaultGroup);
     this.set("recommendationsConfigs", Em.get(data.resources[0] , "recommendations.blueprint.configurations"));
     if (params.clearConfigsOnAddService) {
-      if (this.get('wizardController.name') == 'addServiceController') {
-        this.clearDependenciesForInstalledServices(this.get('installedServiceNames'), this.get('stepConfigs'));
-      }
       this.clearConfigValues();
+      this.clearAllRecommendations();
     }
     this.set('recommendationTimeStamp', (new Date).getTime());
   },
@@ -395,7 +292,7 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
    * @method showChangedDependentConfigs
    */
   showChangedDependentConfigs: function(event, callback, secondary) {
-    if (this.get('_dependentConfigValues.length') > 0) {
+    if (this.get('recommendations.length') > 0) {
       App.showDependentConfigsPopup(this.get('changedProperties'), this.onSaveRecommendedPopup.bind(this), secondary);
     } else {
       if (callback) {
@@ -413,23 +310,32 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
    */
   undoRedoRecommended: function(propertiesToUpdate, redo) {
     propertiesToUpdate.forEach(function(p) {
-      var initial = redo ? Em.get(p, 'value') : Em.get(p, 'recommendedValue');
-      var recommended = redo ? Em.get(p, 'recommendedValue') : Em.get(p, 'value');
+      var initial = redo ? Em.get(p, 'initialValue') : Em.get(p, 'recommendedValue');
+      var recommended = redo ? Em.get(p, 'recommendedValue') : Em.get(p, 'initialValue');
       var stepConfig = this.get('stepConfigs').findProperty('serviceName', Em.get(p, 'serviceName'));
       var config = stepConfig.get('configs').find(function(scp) {
-        return scp.get('name') == Em.get(p, 'propertyName') && scp.get('filename') == Em.get(p, 'fileName');
+        return scp.get('name') == Em.get(p, 'propertyName') && scp.get('filename') == App.config.getOriginalFileName(Em.get(p, 'propertyFileName'));
       });
       var selectedGroup = App.ServiceConfigGroup.find().filterProperty('serviceName', Em.get(p, 'serviceName')).findProperty('name', Em.get(p, 'configGroup'));
-      if (Em.isNone(recommended)) {
-        if (selectedGroup.get('isDefault')) {
+      if (selectedGroup.get('isDefault')) {
+        if (Em.isNone(recommended)) {
           stepConfig.get('configs').removeObject(config);
+        } else if (Em.isNone(initial)) {
+          this._addConfigByRecommendation(stepConfig.get('configs'), Em.get(p, 'propertyName'), Em.get(p, 'propertyFileName'), recommended);
         } else {
-          config.get('overrides').removeObject(this._getOverride(config, selectedGroup));
+          Em.set(config, 'value', recommended);
         }
-      } else if (Em.isNone(initial)) {
-        this._addConfigByRecommendation(stepConfig, selectedGroup, Em.get(p, 'propertyName'), Em.get(p, 'fileName'), Em.get(p, 'serviceName'), recommended, initial, config);
       } else {
-        Em.set(config, 'value', recommended);
+        if (Em.isNone(recommended)) {
+          config.get('overrides').removeObject(config.getOverride(selectedGroup.get('name')));
+        } else if (Em.isNone(initial)) {
+          this._addConfigOverrideRecommendation(config, recommended, null, selectedGroup);
+        } else {
+          var override = config.getOverride(selectedGroup.get('name'));
+          if (override) {
+            override.set('value', recommended);
+          }
+        }
       }
     }, this);
   },
@@ -451,7 +357,7 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
     }, this);
     App.showSelectGroupsPopup(this.get('selectedService.serviceName'),
       this.get('selectedService.configGroups').findProperty('name', this.get('selectedConfigGroup.name')),
-      dependentServices, this.get('_dependentConfigValues'))
+      dependentServices, this.get('recommendations'))
   },
 
   /**
@@ -467,72 +373,33 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
   },
 
   /**
-   * saves values from response for dependent config properties to <code>_dependentConfigValues<code>
+   * saves values from response for dependent config properties to <code>recommendations<code>
    * @param data
-   * @param [updateOnlyBoundaries=false]
    * @param [changedConfigs=null]
    * @param notDefaultGroup
-   * @param updateInitial
    * @method saveRecommendedValues
    * @private
    */
-  _saveRecommendedValues: function(data, updateOnlyBoundaries, changedConfigs, notDefaultGroup, updateInitial) {
+  _saveRecommendedValues: function(data, changedConfigs, notDefaultGroup) {
     Em.assert('invalid data - `data.resources[0].recommendations.blueprint.configurations` not defined ', data && data.resources[0] && Em.get(data.resources[0], 'recommendations.blueprint.configurations'));
     var configObject = data.resources[0].recommendations.blueprint.configurations;
     if (!notDefaultGroup) {
-      this.parseConfigsByTag(configObject, changedConfigs, updateInitial, updateOnlyBoundaries);
-    } else if (data.resources[0].recommendations['config-groups']){
+      this.get('stepConfigs').forEach(function(stepConfig) {
+        this.updateConfigsByRecommendations(configObject, stepConfig.get('configs'), changedConfigs);
+      }, this);
+      this.addByRecommendations(configObject, changedConfigs);
+    } else if (data.resources[0].recommendations['config-groups']) {
       var configFroGroup = data.resources[0].recommendations['config-groups'][0];
-      this.parseConfigsByTag(configFroGroup.configurations, changedConfigs, updateInitial, updateOnlyBoundaries);
-      this.parseConfigsByTag(configFroGroup.dependent_configurations, changedConfigs, updateInitial, updateOnlyBoundaries);
-    }
-    this._cleanUpPopupProperties();
-  },
-
-  /**
-   * saves values from response for dependent configs to <code>_dependentConfigValues<code>
-   * @param configObject - JSON response from `recommendations` endpoint
-   * @param {App.ServiceConfigProperty[]} parentConfigs - config properties for which recommendations were received
-   * @param updateInitial
-   * @param updateOnlyBoundaries
-   * @method saveRecommendedValues
-   * @private
-   */
-  parseConfigsByTag: function(configObject, parentConfigs, updateInitial, updateOnlyBoundaries) {
-    var parentPropertiesNames = parentConfigs ? parentConfigs.map(function(p) { return App.config.configId(Em.get(p, 'name'), Em.get(p, 'type'))}) : [];
-    /** get all configs by config group **/
-    for (var key in configObject) {
-
-      /**  defines main info for file name (service name, config group, config that belongs to filename) **/
-      var service = App.config.get('serviceByConfigTypeMap')[key];
-      var serviceName = service.get('serviceName');
-      var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
-      if (stepConfig) {
-        var configProperties = stepConfig ? stepConfig.get('configs').filterProperty('filename', App.config.getOriginalFileName(key)) : [];
-
-        var group = this.getGroupForService(serviceName);
-
-        for (var propertyName in configObject[key].properties) {
-
-          var cp = configProperties.findProperty('name', propertyName);
-
-          var configPropertyObject = (!group || group.get('isDefault')) ? cp : this._getOverride(cp, group);
-
-          var recommendedValue = this.getFormattedValue(configObject[key].properties[propertyName]);
-          var popupProperty = this.getPopupProperty(propertyName, key, Em.get(group || {}, 'name'));
-          var initialValue = this._getInitialValue(configObject, popupProperty, serviceName, recommendedValue, updateInitial);
-          if (configPropertyObject) {
-            this._updateConfigByRecommendation(configPropertyObject, recommendedValue, updateInitial, updateOnlyBoundaries);
-          } else if (!updateOnlyBoundaries) {
-            this._addConfigByRecommendation(stepConfig, group, propertyName, key, serviceName, recommendedValue, initialValue, cp);
-          }
-          if (!updateOnlyBoundaries) {
-            this._updatePopup(popupProperty, propertyName, key, recommendedValue, Em.get(configPropertyObject || {}, 'initialValue'), service, Em.get(group || {},'name') || "Default", parentPropertiesNames);
-          }
+      this.get('stepConfigs').forEach(function(stepConfig) {
+        var configGroup = this.getGroupForService(stepConfig.get('serviceName'));
+        if (configGroup) {
+          this.updateOverridesByRecommendations(configFroGroup.configurations, stepConfig.get('configs'), changedConfigs, configGroup);
+          this.updateOverridesByRecommendations(configFroGroup.dependent_configurations, stepConfig.get('configs'), changedConfigs, configGroup);
+          this.toggleProperty('forceUpdateBoundaries');
         }
-      }
+      }, this);
     }
-    this.parseConfigAttributes(configObject, parentPropertiesNames, updateOnlyBoundaries);
+    this.cleanUpRecommendations();
   },
 
   installedServices: function () {
@@ -541,241 +408,6 @@ App.EnhancedConfigsMixin = Em.Mixin.create({
     });
   }.property(),
 
-  /**
-   * Save property attributes received from recommendations. These attributes are minimum, maximum,
-   * increment_step. Attributes are stored in <code>App.StackConfigProperty</code> model.
-   *
-   * @param {Object[]} configs
-   * @param parentPropertiesNames
-   * @param updateOnlyBoundaries
-   * @private
-   */
-  parseConfigAttributes: function(configs, parentPropertiesNames, updateOnlyBoundaries) {
-    var self = this;
-    Em.keys(configs).forEach(function (siteName) {
-      var fileName = App.config.getOriginalFileName(siteName),
-        service = App.config.get('serviceByConfigTypeMap')[siteName];
-      var serviceName = service && service.get('serviceName'),
-        stepConfig = self.get('stepConfigs').findProperty('serviceName', serviceName);
-          if (stepConfig) {
-            var group = self.getGroupForService(serviceName),
-                configProperties = stepConfig ? stepConfig.get('configs').filterProperty('filename', App.config.getOriginalFileName(siteName)) : [],
-                properties = configs[siteName].property_attributes || {};
-            Em.keys(properties).forEach(function (propertyName) {
-              var cp = configProperties.findProperty('name', propertyName);
-              var stackProperty = App.configsCollection.getConfigByName(propertyName, siteName);
-              var configObject = (!group || group.get('isDefault')) ? cp : self._getOverride(cp, group);
-              var configsCollection = !group || group.get('isDefault') ? stepConfig.get('configs') : Em.getWithDefault(cp, 'overrides', []);
-              var dependentProperty = self.getPopupProperty(propertyName, fileName, Em.get(group || {},'name'));
-              var attributes = properties[propertyName] || {};
-              Em.keys(attributes).forEach(function (attributeName) {
-                if (attributeName == 'delete' && configObject) {
-                  if (!updateOnlyBoundaries) {
-                    self._removeConfigByRecommendation(configObject, configsCollection);
-                    self._updatePopup(dependentProperty, propertyName, siteName, null, Em.get(configObject, 'initialValue'), service, Em.get(group || {},'name') || "Default", parentPropertiesNames);
-                  }
-                } else if (stackProperty) {
-                  var selectedConfigGroup = group && !group.get('isDefault') ? group.get('name') : null;
-                  if (selectedConfigGroup) {
-                    if (!stackProperty.valueAttributes[selectedConfigGroup]) {
-                      /** create not default group object for updating such values as min/max **/
-                      Em.set(stackProperty.valueAttributes, selectedConfigGroup, {});
-                    }
-                    if (stackProperty.valueAttributes[selectedConfigGroup][attributeName] != attributes[attributeName]) {
-                      Em.set(stackProperty.valueAttributes[selectedConfigGroup], attributeName, attributes[attributeName]);
-                      self.toggleProperty('forceUpdateBoundaries');
-                    }
-                  } else {
-                    Em.set(stackProperty.valueAttributes, attributeName, attributes[attributeName]);
-                  }
-                }
-              });
-            });
-      }
-    });
-  },
-
-  /**
-   * update config based on recommendations
-   * @param config
-   * @param recommendedValue
-   * @param updateInitial
-   * @param updateOnlyBoundaries
-   * @private
-   */
-  _updateConfigByRecommendation: function(config, recommendedValue, updateInitial, updateOnlyBoundaries) {
-    Em.assert('config should be defined', config);
-    Em.set(config, 'recommendedValue', recommendedValue);
-    if (!updateOnlyBoundaries) Em.set(config, 'value', recommendedValue);
-    if (updateInitial && Em.isNone(Em.get(config, 'savedValue'))) Em.set(config, 'initialValue', recommendedValue);
-  },
-
-  /**
-   * remove config based on recommendations
-   * @param config
-   * @param configsCollection
-   * @private
-   */
-  _removeConfigByRecommendation: function(config, configsCollection) {
-    Em.assert('config and configsCollection should be defined', config && configsCollection);
-    configsCollection.removeObject(config);
-    /**
-     * need to update wizard info when removing configs for installed services;
-     */
-    var installedServices = this.get('installedServices'), wizardController = this.get('wizardController'),
-        fileNamesToUpdate = wizardController ? wizardController.getDBProperty('fileNamesToUpdate') || [] : [],
-        fileName = Em.get(config, 'filename'), serviceName = Em.get(config, 'serviceName');
-    var modifiedFileNames = this.get('modifiedFileNames');
-    if (modifiedFileNames && !modifiedFileNames.contains(fileName)) {
-      modifiedFileNames.push(fileName);
-    } else if (wizardController && installedServices[serviceName]) {
-      if (!fileNamesToUpdate.contains(fileName)) {
-        fileNamesToUpdate.push(fileName);
-      }
-    }
-    if (wizardController) {
-      wizardController.setDBProperty('fileNamesToUpdate', fileNamesToUpdate.uniq());
-    }
-  },
-
-  /**
-   * add config based on recommendations
-   * @param stepConfigs
-   * @param selectedGroup
-   * @param name
-   * @param fileName
-   * @param serviceName
-   * @param recommendedValue
-   * @param initialValue
-   * @param cp
-   * @private
-   */
-  _addConfigByRecommendation: function(stepConfigs, selectedGroup, name, fileName, serviceName, recommendedValue, initialValue, cp) {
-    fileName = App.config.getOriginalFileName(fileName);
-    var coreObject = {
-      "value": recommendedValue,
-      "recommendedValue": recommendedValue,
-      "initialValue": initialValue,
-      "savedValue": !this.useInitialValue(serviceName) && !Em.isNone(initialValue) ? initialValue : null,
-      "isEditable": true
-    };
-    if (!selectedGroup || selectedGroup.get('isDefault')) {
-      var addedProperty = App.configsCollection.getConfigByName(name, fileName) || App.config.createDefaultConfig(name, serviceName, fileName, false, coreObject);
-      var addedPropertyObject = App.ServiceConfigProperty.create(addedProperty);
-      stepConfigs.get('configs').pushObject(addedPropertyObject);
-      addedPropertyObject.validate();
-    } else {
-      if (cp) {
-        var newOverride = App.config.createOverride(cp, coreObject, selectedGroup);
-        selectedGroup.get('properties').pushObject(newOverride);
-      } else {
-        stepConfigs.get('configs').push(App.config.createCustomGroupConfig(name, fileName, recommendedValue, selectedGroup, true, true));
-      }
-    }
-  },
-
-  /**
-   * @param configProperty
-   * @param popupProperty
-   * @param serviceName
-   * @param recommendedValue
-   * @param updateInitial
-   * @returns {*}
-   * @private
-   */
-  _getInitialValue: function(configProperty, popupProperty, serviceName, recommendedValue, updateInitial) {
-    if (!this.useInitialValue(serviceName)) {
-      return configProperty ? Em.get(configProperty, 'savedValue') : null;
-    } else if (updateInitial) {
-      return recommendedValue;
-    } else {
-      return popupProperty ? popupProperty.value : configProperty ? Em.get(configProperty, 'initialValue') : null;
-    }
-  },
-
-  /**
-   * format value for float values
-   * @param value
-   * @returns {*}
-   */
-  getFormattedValue: function(value) {
-    return validator.isValidFloat(value) ? parseFloat(value).toString() : value;
-  },
-
-  /**
-   * just get config override
-   * @param cp
-   * @param selectedGroup
-   * @returns {*|Object}
-   * @private
-   */
-  _getOverride: function(cp, selectedGroup) {
-    return Em.get(cp, 'overrides.length') && Em.get(cp, 'overrides').findProperty('group.name', Em.get(selectedGroup, 'name'));
-  },
-
-  /**
-   * get property form popup
-   * @param name
-   * @param fileName
-   * @param groupName
-   * @returns {Object}
-   */
-  getPopupProperty: function(name, fileName, groupName) {
-    return this.get('_dependentConfigValues').find(function (dcv) {
-      return dcv.propertyName === name && dcv.fileName === App.config.getOriginalFileName(fileName) && dcv.configGroup === (groupName || "Default");
-    });
-  },
-
-  /**
-   * add or update proeprty in popup
-   * @param popupProperty
-   * @param name
-   * @param fileName
-   * @param recommendedValue
-   * @param initialValue
-   * @param service
-   * @param groupName
-   * @param parentPropertiesNames
-   * @private
-   */
-  _updatePopup: function(popupProperty, name, fileName, recommendedValue, initialValue, service, groupName, parentPropertiesNames) {
-    if (popupProperty) {
-      Em.set(popupProperty, 'recommendedValue', recommendedValue);
-      Em.set(popupProperty, 'isDeleted', Em.isNone(recommendedValue));
-    } else {
-      var popupPropertyObject = {
-        saveRecommended: true,
-        saveRecommendedDefault: true,
-        fileName: App.config.getOriginalFileName(fileName),
-        propertyName: name,
-
-        isDeleted: Em.isNone(recommendedValue),
-        notDefined: Em.isNone(initialValue),
-
-        configGroup: groupName,
-        value: initialValue,
-        parentConfigs: parentPropertiesNames,
-        serviceName: service.get('serviceName'),
-        allowChangeGroup: groupName!= "Default" && (service.get('serviceName') != this.get('selectedService.serviceName'))
-          && (App.ServiceConfigGroup.find().filterProperty('serviceName', service.get('serviceName')).length > 1),
-        serviceDisplayName: service.get('displayName'),
-        recommendedValue: recommendedValue
-      };
-      this.get('_dependentConfigValues').pushObject(popupPropertyObject);
-    }
-  },
-
-  /**
-   * clean properties that have same current and recommended values
-   * @private
-   */
-  _cleanUpPopupProperties: function() {
-    var cleanDependentList = this.get('_dependentConfigValues').filter(function(d) {
-      return !((Em.isNone(d.value) && Em.isNone(d.recommendedValue)) || d.value == d.recommendedValue);
-    }, this);
-    this.set('_dependentConfigValues', cleanDependentList);
-  },
-
   /**
    * Helper method to get property from the <code>stepConfigs</code>
    *

+ 1 - 1
ambari-web/app/mixins/common/serverValidator.js

@@ -174,7 +174,7 @@ App.ServerValidatorMixin = Em.Mixin.create({
    * @param data
    */
   loadRecommendationsSuccess: function(data) {
-    this._saveRecommendedValues(data, false, null, false, true);
+    this._saveRecommendedValues(data, null, false);
     this.set("recommendationsConfigs", Em.get(data.resources[0] , "recommendations.blueprint.configurations"));
     this.set('recommendationTimeStamp', (new Date).getTime());
   },

+ 4 - 0
ambari-web/app/models/configs/objects/service_config.js

@@ -59,6 +59,10 @@ App.ServiceConfig = Ember.Object.extend({
     }, this);
   }.observes('configsWithErrors'),
 
+  configTypes: function() {
+    return App.StackService.find(this.get('serviceName')).get('configTypeList') || [];
+  }.property('serviceName'),
+
   observeForeignKeys: function() {
     //TODO refactor or move this logic to other place
     var configs = this.get('configs');

+ 14 - 0
ambari-web/app/models/configs/objects/service_config_property.js

@@ -505,6 +505,20 @@ App.ServiceConfigProperty = Em.Object.extend({
     }
   }.property('displayType', 'name', 'App.isHadoop22Stack'),
 
+  /**
+   * Get override for selected group
+   *
+   * @param {String} groupName
+   * @returns {App.ServiceConfigProperty|null}
+   */
+  getOverride: function(groupName) {
+    Em.assert('Group name should be defined string', (typeof groupName === 'string') && groupName);
+    if (this.get('overrides.length')) {
+      return this.get('overrides').findProperty('group.name', groupName);
+    }
+    return null;
+  },
+
   /**
    * Update description for `password`-config
    * Add extra-message about their comparison

+ 1 - 1
ambari-web/app/routes/add_service_routes.js

@@ -199,7 +199,7 @@ module.exports = App.WizardRoute.extend({
             recommendationsConfigs: null
           });
           router.get('wizardStep7Controller').set('recommendationsConfigs', null);
-          router.get('wizardStep7Controller').clearDependentConfigs();
+          router.get('wizardStep7Controller').clearAllRecommendations();
           router.transitionTo('step4');
         });
       });

+ 2 - 2
ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs

@@ -44,12 +44,12 @@
             {{config.configGroup}}
           </a></span>
         </td>
-        <td class="config-dependency-filename">{{config.fileName}}</td>
+        <td class="config-dependency-filename">{{config.propertyFileName}}</td>
         <td class="config-dependency-value">
           {{#if config.notDefined}}
             <i>{{t popup.dependent.configs.table.not.defined}}</i>
           {{else}}
-            {{config.value}}
+            {{config.initialValue}}
           {{/if}}
         </td>
         <td class="config-dependency-recommended-value">

+ 25 - 1
ambari-web/app/utils/config.js

@@ -19,6 +19,7 @@
 var App = require('app');
 require('utils/configs_collection');
 var stringUtils = require('utils/string_utils');
+var validator = require('utils/validator');
 
 var configTagFromFileNameMap = {};
 
@@ -212,7 +213,7 @@ App.config = Em.Object.create({
    */
   getServiceByConfigType: function(configType) {
     return App.StackService.find().find(function(s) {
-      return Object.keys(s.get('configTypes')).contains(configType);
+      return s.get('configTypeList').contains(configType);
     });
   },
 
@@ -494,6 +495,29 @@ App.config = Em.Object.create({
     return value;
   },
 
+  /**
+   * Format float value
+   *
+   * @param {*} value
+   * @returns {string|*}
+   */
+  formatValue: function(value) {
+    return validator.isValidFloat(value) ? parseFloat(value).toString() : value;
+  },
+
+  /**
+   * Get step config by file name
+   *
+   * @param stepConfigs
+   * @param fileName
+   * @returns {Object|null}
+   */
+  getStepConfigForProperty: function (stepConfigs, fileName) {
+    return stepConfigs.find(function (s) {
+      return s.get('configTypes').contains(App.config.getConfigTagFromFileName(fileName));
+    });
+  },
+
   /**
    *
    * @param configs

+ 2 - 2
ambari-web/app/views/common/controls_view.js

@@ -113,9 +113,9 @@ App.SupportsDependentConfigs = Ember.Mixin.create({
    */
   restoreDependentConfigs: function(parentConfig) {
     var controller = this.get('controller');
-    var dependentConfigs = controller.get('_dependentConfigValues');
+    var dependentConfigs = controller.get('recommendations');
     try {
-      controller.set('_dependentConfigValues', dependentConfigs.reject(function(item) {
+      controller.set('recommendations', dependentConfigs.reject(function(item) {
         if (item.parentConfigs.contains(parentConfig.get('name'))) {
           if (item.parentConfigs.length > 1) {
             item.parentConfigs.removeObject(parentConfig.get('name'));

+ 1 - 1
ambari-web/app/views/common/modal_popups/select_groups_popup.js

@@ -64,7 +64,7 @@ App.showSelectGroupsPopup = function (selectedServiceName, selectedConfigGroup,
         var currentGroupName = this.get('selectedGroups')[serviceName] || "";
         var configGroup = this.get('dependentStepConfigs').findProperty('serviceName', serviceName).get('configGroups').findProperty('name', selectedGroupName);
         if (selectedGroupName != currentGroupName) {
-          /** changing config group for _dependentConfigValues **/
+          /** changing config group for recommendations **/
           configs.filterProperty('serviceName', serviceName).filterProperty('configGroup', selectedGroupName).forEach(function (c) {
             if (configs.filterProperty('serviceName', serviceName).filterProperty('configGroup', currentGroupName)) {
               configs.removeObject(c);

+ 2 - 2
ambari-web/test/controllers/main/service/info/config_test.js

@@ -376,9 +376,9 @@ describe("App.MainServiceInfoConfigsController", function () {
 
     it("should clear dependent configs", function() {
       mainServiceInfoConfigsController.set('groupsToSave', { HDFS: 'my cool group'});
-      mainServiceInfoConfigsController.set('_dependentConfigValues', Em.A([{name: 'prop_1'}]));
+      mainServiceInfoConfigsController.set('recommendations', Em.A([{name: 'prop_1'}]));
       mainServiceInfoConfigsController.doCancel();
-      expect(App.isEmptyObject(mainServiceInfoConfigsController.get('_dependentConfigValues'))).to.be.true;
+      expect(App.isEmptyObject(mainServiceInfoConfigsController.get('recommendations'))).to.be.true;
     });
   });
 

+ 6 - 4
ambari-web/test/mixins/common/configs/enhanced_configs_test.js

@@ -24,19 +24,21 @@ describe('App.EnhancedConfigsMixin', function() {
   var instanceObject = mixinObject.create({});
   describe('#removeCurrentFromDependentList()', function() {
     it('update some fields', function() {
-      instanceObject.get('_dependentConfigValues').pushObject({
+      instanceObject.get('recommendations').pushObject({
           saveRecommended: true,
           saveRecommendedDefault: true,
+          configGroup: "Default",
           propertyName: 'p1',
-          fileName: 'f1',
+          propertyFileName: 'f1',
           value: 'v1'
         });
       instanceObject.removeCurrentFromDependentList(Em.Object.create({name: 'p1', filename: 'f1.xml', value: 'v2'}));
-      expect(instanceObject.get('_dependentConfigValues')[0]).to.eql({
+      expect(instanceObject.get('recommendations')[0]).to.eql({
         saveRecommended: false,
         saveRecommendedDefault: false,
+        configGroup: "Default",
         propertyName: 'p1',
-        fileName: 'f1',
+        propertyFileName: 'f1',
         value: 'v1'
       });
     });

+ 7 - 7
ambari-web/test/views/common/configs/widgets/config_widget_view_test.js

@@ -209,22 +209,22 @@ describe('App.ConfigWidgetView', function () {
 
     tests.forEach(function(test) {
       it(test.m, function() {
-        view.set('controller._dependentConfigValues', test.dependentConfigs);
+        view.set('controller.recommendations', test.dependentConfigs);
         view.restoreDependentConfigs(view.get('config'));
-        expect(view.get('controller._dependentConfigValues').mapProperty('name')).to.be.eql(test.e);
+        expect(view.get('controller.recommendations').mapProperty('name')).to.be.eql(test.e);
       });
     });
 
     it('when dependent configs has multiple parents appropriate parent config should be removed', function() {
-      view.set('controller._dependentConfigValues', [
+      view.set('controller.recommendations', [
         {name: 'dependent1', parentConfigs: ['config1', 'config2']},
         {name: 'dependent2', parentConfigs: ['config2', 'config1']},
         {name: 'dependent3', parentConfigs: ['config1']}
       ]);
       view.restoreDependentConfigs(view.get('config'));
-      expect(view.get('controller._dependentConfigValues').findProperty('name', 'dependent1').parentConfigs.toArray()).to.be.eql(["config2"]);
-      expect(view.get('controller._dependentConfigValues').findProperty('name', 'dependent2').parentConfigs.toArray()).to.be.eql(["config2"]);
-      expect(view.get('controller._dependentConfigValues.length')).to.be.eql(2);
+      expect(view.get('controller.recommendations').findProperty('name', 'dependent1').parentConfigs.toArray()).to.be.eql(["config2"]);
+      expect(view.get('controller.recommendations').findProperty('name', 'dependent2').parentConfigs.toArray()).to.be.eql(["config2"]);
+      expect(view.get('controller.recommendations.length')).to.be.eql(2);
     });
 
     it('dependent config value should be set with inital or saved when it has one parent', function() {
@@ -237,7 +237,7 @@ describe('App.ConfigWidgetView', function () {
           ])
         })
       ]);
-      view.set('controller._dependentConfigValues', [
+      view.set('controller.recommendations', [
         {propertyName: 'dependent1', parentConfigs: ['config1', 'config2'], fileName: 'some-file' },
         {propertyName: 'dependent2', parentConfigs: ['config2', 'config1'], fileName: 'some-file'},
         {propertyName: 'dependent3', parentConfigs: ['config1'], fileName: 'some-file' }