You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2017/02/01 17:30:57 UTC

ambari git commit: AMBARI-19821 Recommendations for non-editable properties should be listed as 'Required Changes'. (ababiichuk)

Repository: ambari
Updated Branches:
  refs/heads/branch-2.5 98baf6c04 -> 8423a3e55


AMBARI-19821 Recommendations for non-editable properties should be listed as 'Required Changes'. (ababiichuk)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8423a3e5
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8423a3e5
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8423a3e5

Branch: refs/heads/branch-2.5
Commit: 8423a3e550f1b4d6ec5a49e0b894e645f74b76a5
Parents: 98baf6c
Author: ababiichuk <ab...@hortonworks.com>
Authored: Wed Feb 1 18:35:18 2017 +0200
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Wed Feb 1 19:40:23 2017 +0200

----------------------------------------------------------------------
 ambari-web/app/messages.js                      |   7 +-
 .../configs/config_recommendation_parser.js     |   6 +-
 .../common/configs/config_recommendations.js    |  11 +-
 ...onfig_with_override_recommendation_parser.js |   4 +-
 .../mixins/common/configs/enhanced_configs.js   |   6 +-
 .../modal_popups/dependent_configs_list.hbs     |  49 +-
 .../modal_popups/dependent_configs_table.hbs    |  70 +++
 .../dependent_configs_list_popup.js             |  78 ++-
 .../configs/config_recommendations_test.js      | 600 ++++++++++---------
 .../dependent_configs_list_popup_test.js        |   4 +-
 10 files changed, 464 insertions(+), 371 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 9a41a63..33a8289 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -453,11 +453,16 @@ Em.I18n.translations = {
   'popup.invalid.KDC.admin.password': 'Admin password',
 
   'popup.dependent.configs.header': 'Dependent Configurations',
-  'popup.dependent.configs.title': 'Based on your configuration changes, Ambari is recommending the following dependent configuration changes. <br/> Ambari will update all checked configuration changes to the <b>Recommended Value</b>. Uncheck any configuration to retain the <b>Current Value</b>.',
+  'popup.dependent.configs.title.recommendation': 'Based on your configuration changes, Ambari is recommending the following dependent configuration changes.',
+  'popup.dependent.configs.title.values': 'Ambari will update all checked configuration changes to the <b>Recommended Value</b>. Uncheck any configuration to retain the <b>Current Value</b>.',
+  'popup.dependent.configs.title.required': 'The following configuration changes are required and will be applied automatically.',
+  'popup.dependent.configs.table.recommended': 'Recommended Changes',
+  'popup.dependent.configs.table.required': 'Required Changes',
   'popup.dependent.configs.table.saveProperty': 'Save property',
   'popup.dependent.configs.table.initValue': 'Initial value',
   'popup.dependent.configs.table.currentValue': 'Current Value',
   'popup.dependent.configs.table.recommendedValue': 'Recommended Value',
+  'popup.dependent.configs.table.newValue': 'New Value',
   'popup.dependent.configs.table.not.defined': 'Not Defined',
 
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/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 b014ede..b8845a0 100644
--- a/ambari-web/app/mixins/common/configs/config_recommendation_parser.js
+++ b/ambari-web/app/mixins/common/configs/config_recommendation_parser.js
@@ -173,7 +173,7 @@ App.ConfigRecommendationParser = Em.Mixin.create(App.ConfigRecommendations, {
       if (!Em.isNone(recommendedValue) && !Em.get(config, 'hiddenBySection')) {
         Em.set(config, 'isVisible', true);
       }
-      this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), Em.get(config, 'group.name'), recommendedValue, this._getInitialValue(config), parentProperties);
+      this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), Em.get(config, 'group.name'), recommendedValue, this._getInitialValue(config), parentProperties, Em.get(config, 'isEditable'));
     }
     if (this.updateInitialOnRecommendations(Em.get(config, 'serviceName'))) {
       Em.set(config, 'initialValue', recommendedValue);
@@ -202,7 +202,7 @@ App.ConfigRecommendationParser = Em.Mixin.create(App.ConfigRecommendations, {
       addedPropertyObject = App.ServiceConfigProperty.create(newConfig);
 
     this.applyRecommendation(name, fileName, "Default",
-      recommendedValue, null, parentProperties);
+      recommendedValue, null, parentProperties, true);
 
     return addedPropertyObject;
   },
@@ -245,7 +245,7 @@ App.ConfigRecommendationParser = Em.Mixin.create(App.ConfigRecommendations, {
     configsCollection.removeObject(config);
 
     this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), Em.get(config, 'group.name'),
-      null, this._getInitialValue(config), parentProperties);
+      null, this._getInitialValue(config), parentProperties, Em.get(config, 'isEditable'));
   },
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/mixins/common/configs/config_recommendations.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/configs/config_recommendations.js b/ambari-web/app/mixins/common/configs/config_recommendations.js
index 7427a54..bccf3b7 100644
--- a/ambari-web/app/mixins/common/configs/config_recommendations.js
+++ b/ambari-web/app/mixins/common/configs/config_recommendations.js
@@ -54,16 +54,17 @@ App.ConfigRecommendations = Em.Mixin.create({
    * @param {string} recommendedValue
    * @param {string} initialValue
    * @param {Object[]} parentProperties
+   * @param {boolean} isEditable
    * @returns {recommendation}
    */
-  applyRecommendation: function (name, fileName, configGroupName, recommendedValue, initialValue, parentProperties) {
+  applyRecommendation: function (name, fileName, configGroupName, recommendedValue, initialValue, parentProperties, isEditable) {
     try {
       var parentPropertyIds = this.formatParentProperties(parentProperties);
       var recommendation = this.getRecommendation(name, fileName, configGroupName);
       if (recommendation) {
         return this.updateRecommendation(recommendation, recommendedValue, parentPropertyIds);
       }
-      return this.addRecommendation(name, fileName, configGroupName, recommendedValue, initialValue, parentPropertyIds);
+      return this.addRecommendation(name, fileName, configGroupName, recommendedValue, initialValue, parentPropertyIds, isEditable);
     } catch(e) {
       console.error(e.message);
     }
@@ -90,9 +91,10 @@ App.ConfigRecommendations = Em.Mixin.create({
    * @param {string} recommendedValue
    * @param {string} initialValue
    * @param {string[]} parentPropertyIds
+   * @param {boolean} isEditable
    * @returns {recommendation}
    */
-  addRecommendation: function (name, fileName, configGroupName, recommendedValue, initialValue, parentPropertyIds) {
+  addRecommendation: function (name, fileName, configGroupName, recommendedValue, initialValue, parentPropertyIds, isEditable) {
     Em.assert('name and fileName should be defined', name && fileName);
     var site = App.config.getConfigTagFromFileName(fileName);
     var service = App.config.get('serviceByConfigTypeMap')[site];
@@ -113,7 +115,8 @@ App.ConfigRecommendations = Em.Mixin.create({
       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
+      recommendedValue: recommendedValue,
+      isEditable: isEditable !== false
     };
     this.get('recommendations').pushObject(recommendation);
     return recommendation;

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/mixins/common/configs/config_with_override_recommendation_parser.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/configs/config_with_override_recommendation_parser.js b/ambari-web/app/mixins/common/configs/config_with_override_recommendation_parser.js
index bb39451..4312170 100644
--- a/ambari-web/app/mixins/common/configs/config_with_override_recommendation_parser.js
+++ b/ambari-web/app/mixins/common/configs/config_with_override_recommendation_parser.js
@@ -94,7 +94,7 @@ App.ConfigWithOverrideRecommendationParser = Em.Mixin.create(App.ConfigRecommend
     var override = App.config.createOverride(config, coreObject, configGroup);
 
     this.applyRecommendation(Em.get(config, 'name'), Em.get(config, 'filename'), configGroup.get('name'),
-      recommendedValue, this._getInitialValue(override), parentProperties);
+      recommendedValue, this._getInitialValue(override), parentProperties, Em.get(config, 'isEditable'));
   },
 
   /**
@@ -114,4 +114,4 @@ App.ConfigWithOverrideRecommendationParser = Em.Mixin.create(App.ConfigRecommend
     }
     Em.set(stackProperty.valueAttributes[configGroup.get('name')], attr, value);
   }
-});
\ No newline at end of file
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/mixins/common/configs/enhanced_configs.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/configs/enhanced_configs.js b/ambari-web/app/mixins/common/configs/enhanced_configs.js
index 882f69d..f7cc4cf 100644
--- a/ambari-web/app/mixins/common/configs/enhanced_configs.js
+++ b/ambari-web/app/mixins/common/configs/enhanced_configs.js
@@ -385,9 +385,11 @@ App.EnhancedConfigsMixin = Em.Mixin.create(App.ConfigWithOverrideRecommendationP
    */
   showChangedDependentConfigs: function(event, callback, secondary) {
     var self = this;
-    var recommendations = event ? this.get('changedProperties') : this.get('recommendations');
+    var recommendations = event ? this.get('changedProperties') : this.get('recommendations'),
+      recommendedChanges = recommendations.filterProperty('isEditable'),
+      requiredChanges = recommendations.filterProperty('isEditable', false);
     if (recommendations.length > 0) {
-      App.showDependentConfigsPopup(recommendations, function() {
+      App.showDependentConfigsPopup(recommendedChanges, requiredChanges, function() {
         self.onSaveRecommendedPopup(recommendations);
         if (callback) callback();
       }, secondary);

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
index 6a2d93b..3a03c07 100644
--- a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
+++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
@@ -16,48 +16,11 @@
 * limitations under the License.
 }}
 
-<div class="alert alert-warning">
-  {{t popup.dependent.configs.title}}
-</div>
 <span id="config-dependencies" class="limited-height-2">
-  <table class="table table-striped">
-    <thead>
-    <tr>
-      <th class="check-box-col">{{view view.toggleAll}}</th>
-      <th>{{t common.property}}</th>
-      <th>{{t common.service}}</th>
-      <th>{{t common.configGroup}}</th>
-      <th>{{t common.fileName}}</th>
-      <th class="row-fliud">
-          <div class="span6">
-            {{t popup.dependent.configs.table.currentValue}}
-          </div>
-          <div class="span6">
-            {{t popup.dependent.configs.table.recommendedValue}}
-          </div>
-      </th>
-    </tr>
-    </thead>
-    <tbody>
-    {{#each recommendation in view.parentView.recommendations}}
-      <tr>
-        <td class="check-box-col">{{view Em.Checkbox checkedBinding="recommendation.saveRecommended"}}</td>
-        <td class="config-dependency-name">{{recommendation.propertyName}}</td>
-        <td class="config-dependency-service">{{recommendation.serviceDisplayName}}</td>
-        <td class="config-dependency-group">
-          <span {{bindAttr class="recommendation.allowChangeGroup::not-active-link"}} ><a href="javascript:void(null);" class="black"
-            {{action showSelectGroupPopup recommendation.serviceName target="App.router.mainServiceInfoConfigsController"}}>
-            {{recommendation.configGroup}}
-          </a></span>
-        </td>
-        <td class="config-dependency-filename">{{recommendation.propertyFileName}}</td>
-        <td>
-          <div>
-            {{view App.ConfigDiffView configBinding="recommendation"}}
-          </div>
-        </td>
-      </tr>
-    {{/each}}
-    </tbody>
-  </table>
+  {{#if view.recommendations.length}}
+    {{view App.DependentConfigsTableView recommendationsBinding="view.recommendations"}}
+  {{/if}}
+  {{#if view.requiredChanges.length}}
+    {{view App.DependentConfigsTableView recommendationsBinding="view.requiredChanges" isEditable=false}}
+  {{/if}}
 </span>

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs b/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs
new file mode 100644
index 0000000..b6fff84
--- /dev/null
+++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs
@@ -0,0 +1,70 @@
+{{!
+* 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.
+}}
+
+<h4>{{view.title}}</h4>
+<div class="alert alert-warning">{{{view.message}}}</div>
+<table class="table table-striped">
+  <thead>
+    <tr>
+      {{#if view.isEditable}}
+        <th class="check-box-col">{{view view.parentView.toggleAll}}</th>
+      {{/if}}
+      <th>{{t common.property}}</th>
+      <th>{{t common.service}}</th>
+      <th>{{t common.configGroup}}</th>
+      <th>{{t common.fileName}}</th>
+      <th>
+        <div class="span6">
+          {{t popup.dependent.configs.table.currentValue}}
+        </div>
+        <div class="span6">
+          {{#if view.isEditable}}
+            {{t popup.dependent.configs.table.recommendedValue}}
+          {{else}}
+            {{t popup.dependent.configs.table.newValue}}
+          {{/if}}
+        </div>
+      </th>
+    </tr>
+  </thead>
+  <tbody>
+    {{#each recommendation in view.recommendations}}
+      <tr>
+        {{#if view.isEditable}}
+          <td class="check-box-col">{{view Em.Checkbox checkedBinding="recommendation.saveRecommended"}}</td>
+        {{/if}}
+        <td class="config-dependency-name">{{recommendation.propertyName}}</td>
+        <td class="config-dependency-service">{{recommendation.serviceDisplayName}}</td>
+        <td class="config-dependency-group">
+          <span {{bindAttr class="recommendation.allowChangeGroup::not-active-link"}} >
+            <a href="javascript:void(null);" class="black"
+              {{action showSelectGroupPopup recommendation.serviceName target="App.router.mainServiceInfoConfigsController"}}>
+              {{recommendation.configGroup}}
+            </a>
+          </span>
+        </td>
+        <td class="config-dependency-filename">{{recommendation.propertyFileName}}</td>
+        <td>
+          <div>
+            {{view App.ConfigDiffView configBinding="recommendation"}}
+          </div>
+        </td>
+      </tr>
+    {{/each}}
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js b/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
index bcd8b86..0b6a09d 100644
--- a/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
+++ b/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
@@ -18,50 +18,76 @@
 
 var App = require('app');
 
+App.DependentConfigsTableView = Em.View.extend({
+  templateName: require('templates/common/modal_popups/dependent_configs_table'),
+  recommendations: [],
+  isEditable: true,
+  title: Em.computed.ifThenElse('isEditable', Em.I18n.t('popup.dependent.configs.table.recommended'), Em.I18n.t('popup.dependent.configs.table.required')),
+  message: function () {
+    var message = '';
+    if (this.get('isEditable')) {
+      if (this.get('parentView.isAfterRecommendation')) {
+        message += Em.I18n.t('popup.dependent.configs.title.recommendation') + '<br>';
+      }
+      message += Em.I18n.t('popup.dependent.configs.title.values');
+    } else {
+      message += Em.I18n.t('popup.dependent.configs.title.required');
+    }
+    return message;
+  }.property('isEditable')
+});
+
+App.DependentConfigsListView = Em.View.extend({
+  templateName: require('templates/common/modal_popups/dependent_configs_list'),
+  isAfterRecommendation: true,
+  recommendations: [],
+  requiredChanges: [],
+  toggleAll: Em.Checkbox.extend({
+    didInsertElement: function () {
+      this.updateCheckbox();
+    },
+    click: function () {
+      Em.run.next(this, 'updateSaveRecommended');
+    },
+    updateCheckboxObserver: function () {
+      Em.run.once(this, 'updateCheckbox');
+    }.observes('parentView.recommendations.@each.saveRecommended'),
+
+    updateCheckbox: function() {
+      this.set('checked', !(this.get('parentView.recommendations') || []).someProperty('saveRecommended', false));
+    },
+    updateSaveRecommended: function() {
+      this.get('parentView.recommendations').setEach('saveRecommended', this.get('checked'));
+    }
+  })
+});
+
 /**
  * Show confirmation popup
- * @param {[Object]} recommendations
+ * @param {[Object]} recommendedChanges
+ * @param {[Object]} requiredChanges
  * @param {function} [primary=null]
  * @param {function} [secondary=null]
  * we use this parameter to defer saving configs before we make some decisions.
  * @return {App.ModalPopup}
  */
-App.showDependentConfigsPopup = function (recommendations, primary, secondary) {
+App.showDependentConfigsPopup = function (recommendedChanges, requiredChanges, primary, secondary) {
   return App.ModalPopup.show({
     encodeBody: false,
     header: Em.I18n.t('popup.dependent.configs.header'),
     classNames: ['sixty-percent-width-modal','modal-full-width'],
-    recommendations: recommendations,
     secondaryClass: 'cancel-button',
-    bodyClass: Em.View.extend({
-      templateName: require('templates/common/modal_popups/dependent_configs_list'),
-
-      toggleAll: Em.Checkbox.extend({
-        didInsertElement: function () {
-          this.updateCheckbox();
-        },
-        click: function () {
-          Em.run.next(this, 'updateSaveRecommended');
-        },
-        updateCheckboxObserver: function () {
-          Em.run.once(this, 'updateCheckbox');
-        }.observes('parentView.parentView.recommendations.@each.saveRecommended'),
-
-        updateCheckbox: function() {
-          this.set('checked', !(this.get('parentView.parentView.recommendations') || []).someProperty('saveRecommended', false));
-        },
-        updateSaveRecommended: function() {
-          this.get('parentView.parentView.recommendations').setEach('saveRecommended', this.get('checked'));
-        }
-      })
+    bodyClass: App.DependentConfigsListView.extend({
+      recommendations: recommendedChanges,
+      requiredChanges: requiredChanges
     }),
     saveChanges: function() {
-      this.get('recommendations').forEach(function (c) {
+      recommendedChanges.forEach(function (c) {
         Em.set(c, 'saveRecommendedDefault', Em.get(c, 'saveRecommended'));
       })
     },
     discardChanges: function() {
-      this.get('recommendations').forEach(function(c) {
+      recommendedChanges.forEach(function(c) {
         Em.set(c, 'saveRecommended', Em.get(c, 'saveRecommendedDefault'));
       });
     },

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/test/mixins/common/configs/config_recommendations_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/configs/config_recommendations_test.js b/ambari-web/test/mixins/common/configs/config_recommendations_test.js
index baa8ed6..a72f6d3 100644
--- a/ambari-web/test/mixins/common/configs/config_recommendations_test.js
+++ b/ambari-web/test/mixins/common/configs/config_recommendations_test.js
@@ -19,29 +19,29 @@
 var App = require('app');
 
 describe('App.ConfigRecommendations', function() {
-	var mixinObject = Em.Controller.extend(App.ConfigRecommendations, {});
-	var instanceObject = mixinObject.create({});
+  var mixinObject = Em.Controller.extend(App.ConfigRecommendations, {});
+  var instanceObject = mixinObject.create({});
 
-	beforeEach(function() {
-		instanceObject.set('recommendations', []);
-	});
+  beforeEach(function() {
+    instanceObject.set('recommendations', []);
+  });
 
-	describe('#applyRecommendation', function() {
+  describe('#applyRecommendation', function() {
     beforeEach(function() {
       sinon.stub(instanceObject, 'formatParentProperties', function(parentProperties) { return parentProperties} );
       sinon.stub(App.config, 'get').withArgs('serviceByConfigTypeMap').returns({
         'pFile': Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'})
       });
-	  sinon.stub(Handlebars, 'SafeString');
+    sinon.stub(Handlebars, 'SafeString');
     });
     afterEach(function() {
       instanceObject.formatParentProperties.restore();
       App.config.get.restore();
-	  Handlebars.SafeString.restore();
+    Handlebars.SafeString.restore();
     });
 
     it('adds new recommendation', function() {
-      var res = instanceObject.applyRecommendation('pName', 'pFile', 'pGroup', 'pRecommended', 'pInitial', ['p_id']);
+      var res = instanceObject.applyRecommendation('pName', 'pFile', 'pGroup', 'pRecommended', 'pInitial', ['p_id'], true);
       expect(res).to.eql({
         saveRecommended: true,
         saveRecommendedDefault: true,
@@ -56,26 +56,27 @@ describe('App.ConfigRecommendations', function() {
         allowChangeGroup: false,
         serviceDisplayName: 'sDisplayName',
         recommendedValue: 'pRecommended',
+        isEditable: true
       });
       expect(instanceObject.getRecommendation('pName', 'pFile', 'pGroup')).to.eql(res);
     });
 
     it('updates recommendation', function() {
-			instanceObject.set('recommendations', [{
-				saveRecommended: true,
-				saveRecommendedDefault: true,
-				propertyFileName: 'pFile',
-				propertyName: 'pName',
-				isDeleted: false,
-				notDefined: false,
-				configGroup: 'pGroup',
-				initialValue: 'pInitial',
-				parentConfigs: ['p_id'],
-				serviceName: 'sName',
-				allowChangeGroup: false,
-				serviceDisplayName: 'sDisplayName',
-				recommendedValue: 'pRecommended'
-			}]);
+      instanceObject.set('recommendations', [{
+        saveRecommended: true,
+        saveRecommendedDefault: true,
+        propertyFileName: 'pFile',
+        propertyName: 'pName',
+        isDeleted: false,
+        notDefined: false,
+        configGroup: 'pGroup',
+        initialValue: 'pInitial',
+        parentConfigs: ['p_id'],
+        serviceName: 'sName',
+        allowChangeGroup: false,
+        serviceDisplayName: 'sDisplayName',
+        recommendedValue: 'pRecommended'
+      }]);
       expect(instanceObject.applyRecommendation('pName', 'pFile', 'pGroup', 'pRecommended1', 'pInitial', ['p_id1'])).to.eql({
         saveRecommended: true,
         saveRecommendedDefault: true,
@@ -92,7 +93,7 @@ describe('App.ConfigRecommendations', function() {
         recommendedValue: 'pRecommended1'
       });
     });
-	});
+  });
 
   describe('#formatParentProperties', function() {
     beforeEach(function() {
@@ -111,152 +112,175 @@ describe('App.ConfigRecommendations', function() {
     });
   });
 
-	describe('#addRecommendation', function() {
-		var cases = [
-			{
-				title: 'add recommendation with full info',
-				name: 'pName', file: 'pFile.xml', group: 'pGroup', recommended: 'pRecommended', initial: 'pInitial', parent: ['p_id'],
-				service: Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}),
-				result: {
-					saveRecommended: true,
-					saveRecommendedDefault: true,
-					propertyFileName: 'pFile',
-					propertyName: 'pName',
-					isDeleted: false,
-					notDefined: false,
-					configGroup: 'pGroup',
-					initialValue: 'pInitial',
-					parentConfigs: ['p_id'],
-					serviceName: 'sName',
-					allowChangeGroup: false,
-					serviceDisplayName: 'sDisplayName',
-					recommendedValue: 'pRecommended'
-				}
-			},
-			{
-				title: 'add recommendation with min info',
-				name: 'pName', file: 'pFile.xml',
-				service: Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}),
-				result: {
-					saveRecommended: true,
-					saveRecommendedDefault: true,
-					propertyFileName: 'pFile',
-					propertyName: 'pName',
-					isDeleted: true,
-					notDefined: true,
-					configGroup: 'Default',
-					initialValue: undefined,
-					parentConfigs: [],
-					serviceName: 'sName',
-					allowChangeGroup: false,
-					serviceDisplayName: 'sDisplayName',
-					recommendedValue: undefined
-				}
-			}
-		];
-		cases.forEach(function(c) {
-			describe('successful add recommendation', function() {
-				var recommendation;
-				beforeEach(function() {
-					instanceObject.set('recommendations', []);
-					sinon.stub(App.config, 'get').withArgs('serviceByConfigTypeMap').returns({
-						'pFile': c.service
-					});
-					sinon.stub(Handlebars, 'SafeString');
-					recommendation = instanceObject.addRecommendation(c.name, c.file, c.group, c.recommended, c.initial, c.parent);
-				});
-
-				afterEach(function() {
-					App.config.get.restore();
-					Handlebars.SafeString.restore();
-				});
-
-				it(c.title, function() {
-					expect(recommendation).to.eql(c.result);
-				});
-
-				it(c.title + ' check recommendations collection', function() {
-					expect(instanceObject.get('recommendations.0')).to.eql(c.result);
-				});
-			})
-		});
-
-		it('throw exception when name, fileName', function() {
-			expect(instanceObject.addRecommendation.bind()).to.throw(Error, 'name and fileName should be defined');
-			expect(instanceObject.addRecommendation.bind(null, 'fname')).to.throw(Error, 'name and fileName should be defined');
-			expect(instanceObject.addRecommendation.bind('name', null)).to.throw(Error, 'name and fileName should be defined');
-		});
-	});
-
-	describe('#removeRecommendationObject', function () {
-		var recommendations = [
-			{
-				propertyName: 'p1',
-				propertyFileName: 'f1'
-			},
-			{
-				propertyName: 'p2',
-				propertyFileName: 'f2'
-			}
-		];
-
-		beforeEach(function () {
-			instanceObject.set('recommendations', recommendations);
-		});
-
-		it('remove recommendation', function () {
-			instanceObject.removeRecommendationObject(recommendations[1]);
-
-			expect(instanceObject.get('recommendations.length')).to.equal(1);
-			expect(instanceObject.get('recommendations.0')).to.eql({
-				propertyName: 'p1',
-				propertyFileName: 'f1'
-			});
-		});
-
-		it('remove recommendation that is not exist (don\'t do anything)', function () {
-			instanceObject.removeRecommendationObject({propertyName: 'any', 'propertyFileName': 'aby'});
-			expect(instanceObject.get('recommendations')).to.eql(recommendations);
-		});
-
-		it('throw error if recommendation is undefined ', function () {
-			expect(instanceObject.removeRecommendationObject.bind()).to.throw(Error, 'recommendation should be defined object');
-			expect(instanceObject.removeRecommendationObject.bind(null)).to.throw(Error, 'recommendation should be defined object');
-		});
-
-		it('throw error if recommendation is not an object ', function () {
-			expect(instanceObject.removeRecommendationObject.bind('recommendation')).to.throw(Error, 'recommendation should be defined object');
-			expect(instanceObject.removeRecommendationObject.bind(['recommendation'])).to.throw(Error, 'recommendation should be defined object');
-		});
-	});
-
-	describe('#updateRecommendation', function () {
-		it('update recommended value and parent properties', function () {
-			expect(instanceObject.updateRecommendation({'recommendedValue': 'v2', parentConfigs: ['id1']}, 'v1', ['id2']))
-				.to.eql({'recommendedValue': 'v1', parentConfigs: ['id2', 'id1']});
-		});
-
-		it('update recommended value and add parent properties', function () {
-			expect(instanceObject.updateRecommendation({}, 'v1', ['id1'])).to.eql({'recommendedValue': 'v1', parentConfigs: ['id1']});
-		});
-
-		it('update recommended value', function () {
-			expect(instanceObject.updateRecommendation({}, 'v1')).to.eql({'recommendedValue': 'v1'});
-			expect(instanceObject.updateRecommendation({'recommendedValue': 'v1'}, 'v2')).to.eql({'recommendedValue': 'v2'});
-		});
-
-		it('throw error if recommendation is undefined ', function () {
-			expect(instanceObject.updateRecommendation.bind()).to.throw(Error, 'recommendation should be defined object');
-			expect(instanceObject.updateRecommendation.bind(null)).to.throw(Error, 'recommendation should be defined object');
-		});
-
-		it('throw error if recommendation is not an object ', function () {
-			expect(instanceObject.updateRecommendation.bind('recommendation')).to.throw(Error, 'recommendation should be defined object');
-			expect(instanceObject.updateRecommendation.bind(['recommendation'])).to.throw(Error, 'recommendation should be defined object');
-		});
-	});
-
-	describe('#saveRecommendation', function() {
+  describe('#addRecommendation', function() {
+    var cases = [
+      {
+        title: 'add recommendation for editable property with full info',
+        name: 'pName', file: 'pFile.xml', group: 'pGroup', recommended: 'pRecommended', initial: 'pInitial', parent: ['p_id'], isEditable: true,
+        service: Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}),
+        result: {
+          saveRecommended: true,
+          saveRecommendedDefault: true,
+          propertyFileName: 'pFile',
+          propertyName: 'pName',
+          isDeleted: false,
+          notDefined: false,
+          configGroup: 'pGroup',
+          initialValue: 'pInitial',
+          parentConfigs: ['p_id'],
+          serviceName: 'sName',
+          allowChangeGroup: false,
+          serviceDisplayName: 'sDisplayName',
+          recommendedValue: 'pRecommended',
+          isEditable: true
+        }
+      },
+      {
+        title: 'add recommendation for read-only property with full info',
+        name: 'pName', file: 'pFile.xml', group: 'pGroup', recommended: 'pRecommended', initial: 'pInitial', parent: ['p_id'], isEditable: false,
+        service: Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}),
+        result: {
+          saveRecommended: true,
+          saveRecommendedDefault: true,
+          propertyFileName: 'pFile',
+          propertyName: 'pName',
+          isDeleted: false,
+          notDefined: false,
+          configGroup: 'pGroup',
+          initialValue: 'pInitial',
+          parentConfigs: ['p_id'],
+          serviceName: 'sName',
+          allowChangeGroup: false,
+          serviceDisplayName: 'sDisplayName',
+          recommendedValue: 'pRecommended',
+          isEditable: false
+        }
+      },
+      {
+        title: 'add recommendation with min info',
+        name: 'pName', file: 'pFile.xml',
+        service: Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}),
+        result: {
+          saveRecommended: true,
+          saveRecommendedDefault: true,
+          propertyFileName: 'pFile',
+          propertyName: 'pName',
+          isDeleted: true,
+          notDefined: true,
+          configGroup: 'Default',
+          initialValue: undefined,
+          parentConfigs: [],
+          serviceName: 'sName',
+          allowChangeGroup: false,
+          serviceDisplayName: 'sDisplayName',
+          recommendedValue: undefined,
+          isEditable: true
+        }
+      }
+    ];
+    cases.forEach(function(c) {
+      describe('successful add recommendation', function() {
+        var recommendation;
+        beforeEach(function() {
+          instanceObject.set('recommendations', []);
+          sinon.stub(App.config, 'get').withArgs('serviceByConfigTypeMap').returns({
+            'pFile': c.service
+          });
+          sinon.stub(Handlebars, 'SafeString');
+          recommendation = instanceObject.addRecommendation(c.name, c.file, c.group, c.recommended, c.initial, c.parent, c.isEditable);
+        });
+
+        afterEach(function() {
+          App.config.get.restore();
+          Handlebars.SafeString.restore();
+        });
+
+        it(c.title, function() {
+          expect(recommendation).to.eql(c.result);
+        });
+
+        it(c.title + ' check recommendations collection', function() {
+          expect(instanceObject.get('recommendations.0')).to.eql(c.result);
+        });
+      })
+    });
+
+    it('throw exception when name, fileName', function() {
+      expect(instanceObject.addRecommendation.bind()).to.throw(Error, 'name and fileName should be defined');
+      expect(instanceObject.addRecommendation.bind(null, 'fname')).to.throw(Error, 'name and fileName should be defined');
+      expect(instanceObject.addRecommendation.bind('name', null)).to.throw(Error, 'name and fileName should be defined');
+    });
+  });
+
+  describe('#removeRecommendationObject', function () {
+    var recommendations = [
+      {
+        propertyName: 'p1',
+        propertyFileName: 'f1'
+      },
+      {
+        propertyName: 'p2',
+        propertyFileName: 'f2'
+      }
+    ];
+
+    beforeEach(function () {
+      instanceObject.set('recommendations', recommendations);
+    });
+
+    it('remove recommendation', function () {
+      instanceObject.removeRecommendationObject(recommendations[1]);
+
+      expect(instanceObject.get('recommendations.length')).to.equal(1);
+      expect(instanceObject.get('recommendations.0')).to.eql({
+        propertyName: 'p1',
+        propertyFileName: 'f1'
+      });
+    });
+
+    it('remove recommendation that is not exist (don\'t do anything)', function () {
+      instanceObject.removeRecommendationObject({propertyName: 'any', 'propertyFileName': 'aby'});
+      expect(instanceObject.get('recommendations')).to.eql(recommendations);
+    });
+
+    it('throw error if recommendation is undefined ', function () {
+      expect(instanceObject.removeRecommendationObject.bind()).to.throw(Error, 'recommendation should be defined object');
+      expect(instanceObject.removeRecommendationObject.bind(null)).to.throw(Error, 'recommendation should be defined object');
+    });
+
+    it('throw error if recommendation is not an object ', function () {
+      expect(instanceObject.removeRecommendationObject.bind('recommendation')).to.throw(Error, 'recommendation should be defined object');
+      expect(instanceObject.removeRecommendationObject.bind(['recommendation'])).to.throw(Error, 'recommendation should be defined object');
+    });
+  });
+
+  describe('#updateRecommendation', function () {
+    it('update recommended value and parent properties', function () {
+      expect(instanceObject.updateRecommendation({'recommendedValue': 'v2', parentConfigs: ['id1']}, 'v1', ['id2']))
+        .to.eql({'recommendedValue': 'v1', parentConfigs: ['id2', 'id1']});
+    });
+
+    it('update recommended value and add parent properties', function () {
+      expect(instanceObject.updateRecommendation({}, 'v1', ['id1'])).to.eql({'recommendedValue': 'v1', parentConfigs: ['id1']});
+    });
+
+    it('update recommended value', function () {
+      expect(instanceObject.updateRecommendation({}, 'v1')).to.eql({'recommendedValue': 'v1'});
+      expect(instanceObject.updateRecommendation({'recommendedValue': 'v1'}, 'v2')).to.eql({'recommendedValue': 'v2'});
+    });
+
+    it('throw error if recommendation is undefined ', function () {
+      expect(instanceObject.updateRecommendation.bind()).to.throw(Error, 'recommendation should be defined object');
+      expect(instanceObject.updateRecommendation.bind(null)).to.throw(Error, 'recommendation should be defined object');
+    });
+
+    it('throw error if recommendation is not an object ', function () {
+      expect(instanceObject.updateRecommendation.bind('recommendation')).to.throw(Error, 'recommendation should be defined object');
+      expect(instanceObject.updateRecommendation.bind(['recommendation'])).to.throw(Error, 'recommendation should be defined object');
+    });
+  });
+
+  describe('#saveRecommendation', function() {
 
     it('skip update since values are same', function() {
       expect(instanceObject.saveRecommendation({saveRecommended: false, saveRecommendedDefault: false}, false)).to.be.false;
@@ -282,122 +306,122 @@ describe('App.ConfigRecommendations', function() {
       expect(instanceObject.updateRecommendation.bind('recommendation')).to.throw(Error, 'recommendation should be defined object');
       expect(instanceObject.updateRecommendation.bind(['recommendation'])).to.throw(Error, 'recommendation should be defined object');
     });
-	});
-
-	describe('#getRecommendation', function () {
-		var recommendations = [
-			{
-				propertyName: 'p1',
-				propertyFileName: 'f1',
-				configGroup: 'Default'
-			},
-			{
-				propertyName: 'p2',
-				propertyFileName: 'f2',
-				configGroup: 'group1'
-			},
-			{
-				propertyName: 'p1',
-				propertyFileName: 'f1',
-				configGroup: 'group1'
-			}
-		];
-
-		beforeEach(function () {
-			instanceObject.set('recommendations', recommendations);
-		});
-
-		it('get recommendation for default group', function () {
-			expect(instanceObject.getRecommendation('p1', 'f1')).to.eql(recommendations[0]);
-		});
-
-		it('get recommendation for default group (2)', function () {
-			expect(instanceObject.getRecommendation('p1', 'f1', 'group1')).to.eql(recommendations[2]);
-		});
-
-		it('get recommendation for wrong group', function () {
-			expect(instanceObject.getRecommendation('p2', 'f2', 'group2')).to.equal(null);
-		});
-
-		it('get undefined recommendation', function () {
-			expect(instanceObject.getRecommendation('some', 'amy')).to.equal(null);
-		});
-
-		it('get throw error if undefined name or fileName passed', function () {
-			expect(instanceObject.getRecommendation.bind()).to.throw(Error, 'name and fileName should be defined');
-			expect(instanceObject.getRecommendation.bind('name')).to.throw(Error, 'name and fileName should be defined');
-			expect(instanceObject.getRecommendation.bind(null, 'fileName')).to.throw(Error, 'name and fileName should be defined');
-		});
-	});
-
-	describe('#cleanUpRecommendations', function() {
-		var cases = [
-			{
-				title: 'remove recommendations with same init and recommended values',
-				recommendations: [{
-					initialValue: 'v1', recommendedValue: 'v1'
-				}, {
-						initialValue: 'v1', recommendedValue: 'v2'
-				}],
-				cleanUpRecommendations: [{
-					initialValue: 'v1', recommendedValue: 'v2'
-				}]
-			},
-			{
-				title: 'remove recommendations with null init and recommended values',
-				recommendations: [{
-					initialValue: null, recommendedValue: null
-				}, {
-					recommendedValue: null
-				}, {
-					initialValue: null
-				},{
-					initialValue: null, recommendedValue: 'v1'
-				}, {
-					initialValue: 'v1', recommendedValue: null
-				}],
-				cleanUpRecommendations: [{
-					initialValue: null, recommendedValue: 'v1'
-				}, {
-					initialValue: 'v1', recommendedValue: null
-				}
-				]
-			}
-		];
-
-		cases.forEach(function(c) {
-			describe(c.title, function() {
-				beforeEach(function() {
-					instanceObject.set('recommendations', c.recommendations);
-					instanceObject.cleanUpRecommendations()
-				});
-				it('do clean up', function() {
-					expect(instanceObject.get('recommendations')).to.eql(c.cleanUpRecommendations);
-				});
-			});
-		});
-	});
-
-	describe('#clearRecommendationsByServiceName', function () {
-		beforeEach(function () {
-			instanceObject.set('recommendations', [{serviceName: 's1'}, {serviceName: 's2'}, {serviceName: 's3'}]);
-		});
-
-		it('remove with specific service names ', function () {
-			instanceObject.clearRecommendationsByServiceName(['s2','s3']);
-			expect(instanceObject.get('recommendations')).to.eql([{serviceName: 's1'}]);
-		});
-	});
-
-	describe('#clearAllRecommendations', function () {
-		beforeEach(function () {
-			instanceObject.set('recommendations', [{anyObject: 'o1'}, {anyObject: 'o2'}]);
-		});
-
-		it('remove all recommendations', function () {
-			instanceObject.clearAllRecommendations();
-			expect(instanceObject.get('recommendations.length')).to.equal(0);
-		});
-	});
+  });
+
+  describe('#getRecommendation', function () {
+    var recommendations = [
+      {
+        propertyName: 'p1',
+        propertyFileName: 'f1',
+        configGroup: 'Default'
+      },
+      {
+        propertyName: 'p2',
+        propertyFileName: 'f2',
+        configGroup: 'group1'
+      },
+      {
+        propertyName: 'p1',
+        propertyFileName: 'f1',
+        configGroup: 'group1'
+      }
+    ];
+
+    beforeEach(function () {
+      instanceObject.set('recommendations', recommendations);
+    });
+
+    it('get recommendation for default group', function () {
+      expect(instanceObject.getRecommendation('p1', 'f1')).to.eql(recommendations[0]);
+    });
+
+    it('get recommendation for default group (2)', function () {
+      expect(instanceObject.getRecommendation('p1', 'f1', 'group1')).to.eql(recommendations[2]);
+    });
+
+    it('get recommendation for wrong group', function () {
+      expect(instanceObject.getRecommendation('p2', 'f2', 'group2')).to.equal(null);
+    });
+
+    it('get undefined recommendation', function () {
+      expect(instanceObject.getRecommendation('some', 'amy')).to.equal(null);
+    });
+
+    it('get throw error if undefined name or fileName passed', function () {
+      expect(instanceObject.getRecommendation.bind()).to.throw(Error, 'name and fileName should be defined');
+      expect(instanceObject.getRecommendation.bind('name')).to.throw(Error, 'name and fileName should be defined');
+      expect(instanceObject.getRecommendation.bind(null, 'fileName')).to.throw(Error, 'name and fileName should be defined');
+    });
+  });
+
+  describe('#cleanUpRecommendations', function() {
+    var cases = [
+      {
+        title: 'remove recommendations with same init and recommended values',
+        recommendations: [{
+          initialValue: 'v1', recommendedValue: 'v1'
+        }, {
+            initialValue: 'v1', recommendedValue: 'v2'
+        }],
+        cleanUpRecommendations: [{
+          initialValue: 'v1', recommendedValue: 'v2'
+        }]
+      },
+      {
+        title: 'remove recommendations with null init and recommended values',
+        recommendations: [{
+          initialValue: null, recommendedValue: null
+        }, {
+          recommendedValue: null
+        }, {
+          initialValue: null
+        },{
+          initialValue: null, recommendedValue: 'v1'
+        }, {
+          initialValue: 'v1', recommendedValue: null
+        }],
+        cleanUpRecommendations: [{
+          initialValue: null, recommendedValue: 'v1'
+        }, {
+          initialValue: 'v1', recommendedValue: null
+        }
+        ]
+      }
+    ];
+
+    cases.forEach(function(c) {
+      describe(c.title, function() {
+        beforeEach(function() {
+          instanceObject.set('recommendations', c.recommendations);
+          instanceObject.cleanUpRecommendations()
+        });
+        it('do clean up', function() {
+          expect(instanceObject.get('recommendations')).to.eql(c.cleanUpRecommendations);
+        });
+      });
+    });
+  });
+
+  describe('#clearRecommendationsByServiceName', function () {
+    beforeEach(function () {
+      instanceObject.set('recommendations', [{serviceName: 's1'}, {serviceName: 's2'}, {serviceName: 's3'}]);
+    });
+
+    it('remove with specific service names ', function () {
+      instanceObject.clearRecommendationsByServiceName(['s2','s3']);
+      expect(instanceObject.get('recommendations')).to.eql([{serviceName: 's1'}]);
+    });
+  });
+
+  describe('#clearAllRecommendations', function () {
+    beforeEach(function () {
+      instanceObject.set('recommendations', [{anyObject: 'o1'}, {anyObject: 'o2'}]);
+    });
+
+    it('remove all recommendations', function () {
+      instanceObject.clearAllRecommendations();
+      expect(instanceObject.get('recommendations.length')).to.equal(0);
+    });
+  });
 });
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/8423a3e5/ambari-web/test/views/common/modal_popups/dependent_configs_list_popup_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/modal_popups/dependent_configs_list_popup_test.js b/ambari-web/test/views/common/modal_popups/dependent_configs_list_popup_test.js
index 9dc4bf6..44c38b3 100644
--- a/ambari-web/test/views/common/modal_popups/dependent_configs_list_popup_test.js
+++ b/ambari-web/test/views/common/modal_popups/dependent_configs_list_popup_test.js
@@ -40,7 +40,7 @@ describe('App.showDependentConfigsPopup', function () {
     beforeEach(function () {
       this.ff = function () {};
       sinon.spy(this, 'ff');
-      view = App.showDependentConfigsPopup([], Em.K, this.ff);
+      view = App.showDependentConfigsPopup([], [], Em.K, this.ff);
     });
 
     afterEach(function () {
@@ -54,4 +54,4 @@ describe('App.showDependentConfigsPopup', function () {
 
   });
 
-});
\ No newline at end of file
+});