Repository: ambari
Updated Branches:
  refs/heads/trunk 7acc90952 -> 07b423677


AMBARI-17813 Cover configurations mixin with unit tests. (atkach)


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

Branch: refs/heads/trunk
Commit: 07b423677ab3ab87debffdb732b5d95a7f9ae13c
Parents: 7acc909
Author: Andrii Tkach <[email protected]>
Authored: Wed Jul 20 18:23:18 2016 +0300
Committer: Andrii Tkach <[email protected]>
Committed: Thu Jul 21 14:09:11 2016 +0300

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   1 +
 .../configs/component_actions_by_configs.js     | 149 +++---
 .../main/service/configs/config_overridable.js  | 143 +++--
 .../service/configs/widget_popover_support.js   |   2 +-
 .../component_actions_by_configs_test.js        | 519 +++++++++++++++++++
 .../service/configs/config_overridable_test.js  | 357 ++++++++++++-
 .../configs/widget_popover_support_test.js      | 165 +++++-
 7 files changed, 1212 insertions(+), 124 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js 
b/ambari-web/app/assets/test/tests.js
index 2b73b39..0bd8c2d 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -168,6 +168,7 @@ var files = [
   'test/mixins/main/host/details/host_components/install_component_test',
   'test/mixins/main/service/configs/widget_popover_support_test',
   'test/mixins/main/service/configs/config_overridable_test',
+  'test/mixins/main/service/configs/component_actions_by_configs_test',
   'test/mixins/routers/redirections_test',
   'test/mixins/wizard/addSeccurityConfigs_test',
   'test/mixins/wizard/assign_master_components_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/app/mixins/main/service/configs/component_actions_by_configs.js
----------------------------------------------------------------------
diff --git 
a/ambari-web/app/mixins/main/service/configs/component_actions_by_configs.js 
b/ambari-web/app/mixins/main/service/configs/component_actions_by_configs.js
index 9271675..c7bb047 100644
--- a/ambari-web/app/mixins/main/service/configs/component_actions_by_configs.js
+++ b/ambari-web/app/mixins/main/service/configs/component_actions_by_configs.js
@@ -31,7 +31,7 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @public
    * @method doConfigActions
    */
-  doConfigActions: function() {
+  doConfigActions: function () {
     var serviceConfigs = this.get('stepConfigs').findProperty('serviceName', 
this.get('content.serviceName')).get('configs');
     var configActionComponents = 
serviceConfigs.filterProperty('configActionComponent');
     this.doComponentDeleteActions(configActionComponents);
@@ -44,7 +44,7 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @public
    * @method isComponentActionsPresent
    */
-  isComponentActionsPresent: function() {
+  isComponentActionsPresent: function () {
     var serviceConfigs = this.get('stepConfigs').findProperty('serviceName', 
this.get('content.serviceName')).get('configs');
     var configActionComponents = 
serviceConfigs.filterProperty('configActionComponent');
     return !!(this.getComponentsToDelete(configActionComponents).length + 
this.getComponentsToAdd(configActionComponents).length);
@@ -57,11 +57,13 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @private
    * @method getComponentsToDelete
    */
-  getComponentsToDelete: function(configActionComponents) {
-    return 
configActionComponents.filterProperty('configActionComponent.action', 
'delete').map(function(item){
+  getComponentsToDelete: function (configActionComponents) {
+    var hostComponents = App.HostComponent.find();
+    return 
configActionComponents.filterProperty('configActionComponent.action', 
'delete').map(function (item) {
       return item.configActionComponent;
-    }).filter(function(_componentToDelete){
-      return  
App.HostComponent.find().filterProperty('componentName',_componentToDelete.componentName).someProperty('hostName',
 _componentToDelete.hostName);
+    }).filter(function (_componentToDelete) {
+      return hostComponents.filterProperty('componentName', 
_componentToDelete.componentName)
+                           .someProperty('hostName', 
_componentToDelete.hostName);
     }, this);
   },
 
@@ -72,15 +74,17 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @private
    * @method getComponentsToDelete
    */
-  getComponentsToAdd: function(configActionComponents) {
-    return 
configActionComponents.filterProperty('configActionComponent.action', 
'add').map(function(item){
+  getComponentsToAdd: function (configActionComponents) {
+    var ssc = App.StackServiceComponent.find(),
+        services = App.Service.find();
+    return 
configActionComponents.filterProperty('configActionComponent.action', 
'add').map(function (item) {
       return item.configActionComponent;
-    }).filter(function(_componentToAdd) {
-      var serviceNameForcomponent = 
App.StackServiceComponent.find().findProperty('componentName',_componentToAdd.componentName).get('serviceName');
+    }).filter(function (_componentToAdd) {
+      var serviceNameForcomponent = ssc.findProperty('componentName', 
_componentToAdd.componentName).get('serviceName');
       // List of host components to be added should not include ones that are 
already present in the cluster.
       // Need to do below check from App.Service model as it keeps getting 
polled and updated on service page.
-      return  !App.Service.find().findProperty('serviceName', 
serviceNameForcomponent).get('hostComponents').
-               
filterProperty('componentName',_componentToAdd.componentName).someProperty('hostName',
 _componentToAdd.hostName);
+      return !services.findProperty('serviceName', 
serviceNameForcomponent).get('hostComponents').
+        filterProperty('componentName', 
_componentToAdd.componentName).someProperty('hostName', 
_componentToAdd.hostName);
     }, this);
   },
 
@@ -90,16 +94,16 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @private
    * @method {configActionComponents}
    */
-  doComponentDeleteActions: function(configActionComponents) {
+  doComponentDeleteActions: function (configActionComponents) {
     var componentsToDelete = 
this.getComponentsToDelete(configActionComponents);
     if (componentsToDelete.length) {
       // There is always only one item to delete when doing config actions.
-      var componentToDelete  =  componentsToDelete[0];
+      var componentToDelete = componentsToDelete[0];
       var componentName = componentToDelete.componentName;
       var hostName = componentToDelete.hostName;
-      var displayName = 
App.StackServiceComponent.find().findProperty('componentName',  
componentToDelete.componentName).get('displayName');
+      var displayName = 
App.StackServiceComponent.find().findProperty('componentName', 
componentToDelete.componentName).get('displayName');
       var context = Em.I18n.t('requestInfo.stop').format(displayName);
-      var batches =[];
+      var batches = [];
 
       this.setRefreshYarnQueueRequest(batches);
       batches.push(this.getInstallHostComponentsRequest(hostName, 
componentName, context));
@@ -124,37 +128,23 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @private
    * @method {doComponentAddActions}
    */
-  doComponentAddActions: function(configActionComponents) {
-    var self = this;
+  doComponentAddActions: function (configActionComponents) {
     var componentsToAdd = this.getComponentsToAdd(configActionComponents);
-    var dependentComponents = [];
+
     if (componentsToAdd.length) {
-      componentsToAdd.forEach(function(_component) {
-        var dependencies = 
App.StackServiceComponent.find(_component.componentName).get('dependencies').filterProperty('scope',
 'host').map(function(_dependency){
-          return {
-            componentName: _dependency.componentName,
-            hostName:  _component.hostName,
-            isClient: 
App.StackServiceComponent.find(_dependency.componentName).get('isClient')
-          }
-        }, this);
-        var dependenciesToInstall =  dependencies.filter(function 
(_dependencyToAdd) {
-          var isInstalled = 
App.HostComponent.find().filterProperty('componentName', 
_dependencyToAdd.componentName).someProperty('hostName', 
_dependencyToAdd.hostName);
-          var isAddedToInstall =  
dependentComponents.filterProperty('componentName',_dependencyToAdd.componentName).someProperty('hostName',
 _dependencyToAdd.hostName);
-          return !(isInstalled || isAddedToInstall);
-        }, this);
-        dependentComponents = 
dependentComponents.concat(dependenciesToInstall);
-      }, this);
-      var allComponentsToAdd = componentsToAdd.concat(dependentComponents);
+      var allComponentsToAdd = 
componentsToAdd.concat(this.getDependentComponents(componentsToAdd));
       var allComponentsToAddHosts = 
allComponentsToAdd.mapProperty('hostName').uniq();
-      allComponentsToAddHosts.forEach(function(_hostName){
+
+      allComponentsToAddHosts.forEach(function (_hostName) {
         var hostComponents = allComponentsToAdd.filterProperty('hostName', 
_hostName).mapProperty('componentName').uniq();
-        var masterHostComponents =  
allComponentsToAdd.filterProperty('hostName', 
_hostName).filterProperty('isClient', 
false).mapProperty('componentName').uniq();
-        var displayNames = masterHostComponents.map(function(item) {
+        var masterHostComponents = 
allComponentsToAdd.filterProperty('hostName', 
_hostName).filterProperty('isClient', 
false).mapProperty('componentName').uniq();
+        var displayNames = masterHostComponents.map(function (item) {
           return 
App.StackServiceComponent.find().findProperty('componentName', 
item).get('displayName');
         });
-        var displayStr =  
stringUtils.getFormattedStringFromArray(displayNames);
+
+        var displayStr = stringUtils.getFormattedStringFromArray(displayNames);
         var context = Em.I18n.t('requestInfo.start').format(displayStr);
-        var batches =[];
+        var batches = [];
         this.setCreateComponentRequest(batches, hostComponents);
         batches.push(this.getCreateHostComponentsRequest(_hostName, 
hostComponents));
         batches.push(this.getInstallHostComponentsRequest(_hostName, 
hostComponents));
@@ -176,13 +166,39 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
   },
 
   /**
+   * @method getDependentComponents
+   * @param {Array} componentsToAdd
+   * @returns {Array}
+   */
+  getDependentComponents: function(componentsToAdd) {
+    var dependentComponents = [];
+
+    componentsToAdd.forEach(function (_component) {
+      var dependencies = 
App.StackServiceComponent.find(_component.componentName).get('dependencies').filterProperty('scope',
 'host').map(function (_dependency) {
+        return {
+          componentName: _dependency.componentName,
+          hostName: _component.hostName,
+          isClient: 
App.StackServiceComponent.find(_dependency.componentName).get('isClient')
+        }
+      }, this);
+      var dependenciesToInstall = dependencies.filter(function 
(_dependencyToAdd) {
+        var isInstalled = 
App.HostComponent.find().filterProperty('componentName', 
_dependencyToAdd.componentName).someProperty('hostName', 
_dependencyToAdd.hostName);
+        var isAddedToInstall = 
dependentComponents.filterProperty('componentName', 
_dependencyToAdd.componentName).someProperty('hostName', 
_dependencyToAdd.hostName);
+        return !(isInstalled || isAddedToInstall);
+      }, this);
+      dependentComponents = dependentComponents.concat(dependenciesToInstall);
+    }, this);
+    return dependentComponents;
+  },
+
+  /**
    * Sets order_id for each batch request in the `batches` array
    * @param batches {Array}
    * @private
    * @method {setOrderIdForBatches}
    */
-  setOrderIdForBatches: function(batches) {
-    batches.forEach(function(_batch, index){
+  setOrderIdForBatches: function (batches) {
+    batches.forEach(function (_batch, index) {
       _batch.order_id = index + 1;
     }, this);
   },
@@ -193,18 +209,18 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @param components {String[]}|{String}
    * @return {Object} Deferred promise
    */
-  getCreateHostComponentsRequest: function(hostName, components) {
-    var query =  "Hosts/host_name.in(" + hostName + ")";
+  getCreateHostComponentsRequest: function (hostName, components) {
+    var query = "Hosts/host_name.in(" + hostName + ")";
     components = (Array.isArray(components)) ? components : [components];
-    var hostComponent = components.map(function(_componentName){
+    var hostComponent = components.map(function (_componentName) {
       return {
-        "HostRoles":{
-          "component_name":_componentName
+        "HostRoles": {
+          "component_name": _componentName
         }
       }
     }, this);
 
-    return  {
+    return {
       "type": 'POST',
       "uri": App.get('apiPrefix') + "/clusters/" + App.get('clusterName') + 
"/hosts",
       "RequestBodyInfo": {
@@ -225,7 +241,7 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @param context {String} Optional
    * @return {Object}
    */
-  getInstallHostComponentsRequest: function(hostName, components, context) {
+  getInstallHostComponentsRequest: function (hostName, components, context) {
     context = context || Em.I18n.t('requestInfo.installComponents');
     return this.getUpdateHostComponentsRequest(hostName, components, 
App.HostComponentStatus.stopped, context);
   },
@@ -237,7 +253,7 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @param context {String} Optional
    * @return {Object}
    */
-  getStartHostComponentsRequest: function(hostName, components, context) {
+  getStartHostComponentsRequest: function (hostName, components, context) {
     context = context || Em.I18n.t('requestInfo.startHostComponents');
     return this.getUpdateHostComponentsRequest(hostName, components, 
App.HostComponentStatus.started, context);
   },
@@ -253,11 +269,11 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @method {getUpdateHostComponentsRequest}
    * @return {Object}
    */
-  getUpdateHostComponentsRequest: function(hostName, components, desiredState, 
context) {
+  getUpdateHostComponentsRequest: function (hostName, components, 
desiredState, context) {
     components = (Array.isArray(components)) ? components : [components];
     var query = "HostRoles/component_name.in(" + components.join(',') + ")";
 
-    return  {
+    return {
       "type": 'PUT',
       "uri": App.get('apiPrefix') + "/clusters/" + App.get('clusterName') + 
"/hosts/" + hostName + "/host_components",
       "RequestBodyInfo": {
@@ -287,7 +303,7 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @method {getDeleteHostComponentRequest}
    * @return {Object}
    */
-  getDeleteHostComponentRequest: function(hostName, component) {
+  getDeleteHostComponentRequest: function (hostName, component) {
     return {
       "type": 'DELETE',
       "uri": App.get('apiPrefix') + "/clusters/" + App.get('clusterName') + 
"/hosts/" + hostName + "/host_components/" + component
@@ -301,10 +317,13 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @private
    * @method {setCreateComponentRequest}
    */
-  setCreateComponentRequest: function(batches, hostComponents) {
-    hostComponents.forEach(function(_componentName){
-      var serviceName = 
App.StackServiceComponent.find().findProperty('componentName',  
_componentName).get('serviceName');
-      var serviceComponents = App.Service.find().findProperty('serviceName', 
serviceName).get('serviceComponents');
+  setCreateComponentRequest: function (batches, hostComponents) {
+    var stackServices = App.StackServiceComponent.find(),
+        services = App.Service.find();
+
+    hostComponents.forEach(function (_componentName) {
+      var serviceName = stackServices.findProperty('componentName', 
_componentName).get('serviceName');
+      var serviceComponents = services.findProperty('serviceName', 
serviceName).get('serviceComponents');
       if (!serviceComponents.contains(_componentName)) {
         batches.push({
           "type": 'POST',
@@ -320,25 +339,27 @@ App.ComponentActionsByConfigs = Em.Mixin.create({
    * @private
    * @method {setRefreshYarnQueueRequest}
    */
-  setRefreshYarnQueueRequest: function(batches) {
-    var capacitySchedulerConfigs = 
this.get('allConfigs').filterProperty('filename', 
'capacity-scheduler.xml').filter(function(item){
+  setRefreshYarnQueueRequest: function (batches) {
+    var capacitySchedulerConfigs = this.get('allConfigs')
+                                  .filterProperty('filename', 
'capacity-scheduler.xml')
+                                  .filter(function (item) {
       return item.get('value') !== item.get('initialValue');
     });
 
     if (capacitySchedulerConfigs.length) {
       var serviceName = 'YARN';
       var componentName = 'RESOURCEMANAGER';
-      var commandName = 'REFRESHQUEUES';
-      var tag = 'capacity-scheduler';
-      var hostNames = 
App.Service.find(serviceName).get('hostComponents').filterProperty('componentName',
 componentName).mapProperty('hostName');
+      var hostNames = App.Service.find(serviceName).get('hostComponents')
+                                                   
.filterProperty('componentName', componentName)
+                                                   .mapProperty('hostName');
       batches.push({
         "type": 'POST',
         "uri": App.get('apiPrefix') + "/clusters/" + App.get('clusterName') + 
"/requests",
         "RequestBodyInfo": {
           "RequestInfo": {
             "context": 
Em.I18n.t('services.service.actions.run.yarnRefreshQueues.context'),
-            "command": commandName,
-            "parameters/forceRefreshConfigTags": tag
+            "command": "REFRESHQUEUES",
+            "parameters/forceRefreshConfigTags": "capacity-scheduler"
           },
           "Requests/resource_filters": [
             {

http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/app/mixins/main/service/configs/config_overridable.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/main/service/configs/config_overridable.js 
b/ambari-web/app/mixins/main/service/configs/config_overridable.js
index 920e5c4..a8bd273 100644
--- a/ambari-web/app/mixins/main/service/configs/config_overridable.js
+++ b/ambari-web/app/mixins/main/service/configs/config_overridable.js
@@ -63,7 +63,10 @@ App.ConfigOverridable = Em.Mixin.create({
     }
     else {
       var valueForOverride = (serviceConfigProperty.get('widget') || 
serviceConfigProperty.get('displayType') == 'checkbox') ? 
serviceConfigProperty.get('value') : '';
-      App.config.createOverride(serviceConfigProperty, { "value": 
valueForOverride, "isEditable": true }, selectedConfigGroup);
+      App.config.createOverride(serviceConfigProperty, {
+        "value": valueForOverride,
+        "isEditable": true
+      }, selectedConfigGroup);
     }
     Em.$('body>.tooltip').remove();
   },
@@ -253,35 +256,45 @@ App.ConfigOverridable = Em.Mixin.create({
         })
       }
     };
-    var sendData = {
+    return App.ajax.send({
       name: 'config_groups.create',
+      sender: this,
       data: {
         data: [newGroupData],
         modelData: newConfigGroupData
       },
-      success: 'successFunction',
-      error: 'errorFunction',
-      successFunction: function (response, opt, params) {
-        var modelData = params.modelData;
-        modelData.id = response.resources[0].ConfigGroup.id;
-        App.store.load(App.ServiceConfigGroup, modelData);
-        App.store.commit();
-        App.ServiceConfigGroup.deleteTemporaryRecords();
-        if (callback) {
-          callback();
-        }
-      },
-      errorFunction: function (xhr, text, errorThrown) {
-        if (callback) {
-          callback(xhr, text, errorThrown);
-        }
+      success: 'postNewConfigurationGroupSuccess',
+      error: 'postNewConfigurationGroupError'
+    }).always(function (xhr, text, errorThrown) {
+      if (callback) {
+        callback(xhr, text, errorThrown);
       }
-    };
-    sendData.sender = sendData;
-    return App.ajax.send(sendData);
+    });
   },
 
   /**
+   *
+   * @param {string} response
+   * @param {object} opt
+   * @param {object} params
+   */
+  postNewConfigurationGroupSuccess: function (response, opt, params) {
+    var modelData = params.modelData;
+    modelData.id = response.resources[0].ConfigGroup.id;
+    App.store.load(App.ServiceConfigGroup, modelData);
+    App.store.commit();
+    App.ServiceConfigGroup.deleteTemporaryRecords();
+  },
+
+  /**
+   *
+   * @param {object} xhr
+   * @param {string} text
+   * @param {Error} errorThrown
+   */
+  postNewConfigurationGroupError: Em.K,
+
+  /**
    * PUTs the new configuration-group on the server.
    * Changes possible here are the name, description and
    * host memberships of the configuration-group.
@@ -293,31 +306,11 @@ App.ConfigOverridable = Em.Mixin.create({
    * @method updateConfigurationGroup
    */
   updateConfigurationGroup: function (configGroup, successCallback, 
errorCallback) {
-    var desiredConfigs = configGroup.get('desiredConfigs') || [];
-    var putConfigGroup = {
-      ConfigGroup: {
-        group_name: configGroup.get('name'),
-        description: configGroup.get('description'),
-        tag: configGroup.get('service.id'),
-        hosts: configGroup.get('hosts').map(function (h) {
-          return {
-            host_name: h
-          };
-        }),
-        desired_configs: desiredConfigs.map(function (cst) {
-          return {
-            type: Em.get(cst, 'site') || Em.get(cst, 'type'),
-            tag: Em.get(cst, 'tag')
-          };
-        })
-      }
-    };
-
     var sendData = {
       name: 'config_groups.update',
       data: {
         id: configGroup.get('id'),
-        data: putConfigGroup
+        data: this.getConfigGroupData(configGroup)
       },
       success: 'successFunction',
       error: 'errorFunction',
@@ -337,6 +330,34 @@ App.ConfigOverridable = Em.Mixin.create({
   },
 
   /**
+   *
+   * @param {Em.Object} configGroup
+   * @returns {{ConfigGroup: {group_name: *, description: *, tag: *, hosts: *, 
desired_configs: (Array|*)}}}
+   */
+  getConfigGroupData: function (configGroup) {
+    var desiredConfigs = configGroup.get('desiredConfigs') || [];
+
+    return {
+      ConfigGroup: {
+        group_name: configGroup.get('name'),
+        description: configGroup.get('description'),
+        tag: configGroup.get('service.id'),
+        hosts: configGroup.get('hosts').map(function (h) {
+          return {
+            host_name: h
+          };
+        }),
+        desired_configs: desiredConfigs.map(function (cst) {
+          return {
+            type: Em.get(cst, 'site') || Em.get(cst, 'type'),
+            tag: Em.get(cst, 'tag')
+          };
+        })
+      }
+    };
+  },
+
+  /**
    * launch dialog where can be assigned another group to host
    * @param {App.ConfigGroup} selectedGroup
    * @param {App.ConfigGroup[]} configGroups
@@ -391,6 +412,7 @@ App.ConfigOverridable = Em.Mixin.create({
    * @method deleteConfigurationGroup
    */
   deleteConfigurationGroup: function (configGroup, successCallback, 
errorCallback) {
+    var self = this;
     var sendData = {
       name: 'common.delete.config_group',
       sender: this,
@@ -400,13 +422,7 @@ App.ConfigOverridable = Em.Mixin.create({
       success: 'successFunction',
       error: 'errorFunction',
       successFunction: function (data, xhr, params) {
-        var groupFromModel = App.ServiceConfigGroup.find().findProperty('id', 
params.id);
-        if (groupFromModel) {
-          if (groupFromModel.get('stateManager.currentState.name') !== 
'saved') {
-            groupFromModel.get('stateManager').transitionTo('loaded');
-          }
-          App.configGroupsMapper.deleteRecord(groupFromModel);
-        }
+        self.deleteConfigurationGroupSuccess(data, xhr, params);
         if (successCallback) {
           successCallback();
         }
@@ -421,6 +437,23 @@ App.ConfigOverridable = Em.Mixin.create({
     return App.ajax.send(sendData);
   },
 
+
+  /**
+   *
+   * @param {object} data
+   * @param {object} xhr
+   * @param {object} params
+   */
+  deleteConfigurationGroupSuccess: function (data, xhr, params) {
+    var groupFromModel = App.ServiceConfigGroup.find().findProperty('id', 
params.id);
+    if (groupFromModel) {
+      if (groupFromModel.get('stateManager.currentState.name') !== 'saved') {
+        groupFromModel.get('stateManager').transitionTo('loaded');
+      }
+      App.configGroupsMapper.deleteRecord(groupFromModel);
+    }
+  },
+
   /**
    * Launches a dialog where an existing config-group can be selected, or a new
    * one can be created. This is different than the config-group management
@@ -444,11 +477,11 @@ App.ConfigOverridable = Em.Mixin.create({
       bodyClass: Em.View.extend({
         templateName: require('templates/common/configs/saveConfigGroup')
       }),
-      onPrimary:function() {
-        if (self.get('controller.name') == 'mainServiceInfoConfigsController') 
{
-          
self.get('controller').loadConfigGroups([self.get('controller.content.serviceName')]).done(function()
 {
-            var group = App.ServiceConfigGroup.find().find(function(g) {
-              return g.get('serviceName') == 
self.get('controller.content.serviceName') && g.get('name') == groupName;
+      onPrimary: function () {
+        if (self.get('controller.name') === 
'mainServiceInfoConfigsController') {
+          
self.get('controller').loadConfigGroups([self.get('controller.content.serviceName')]).done(function
 () {
+            var group = App.ServiceConfigGroup.find().find(function (g) {
+              return g.get('serviceName') === 
self.get('controller.content.serviceName') && g.get('name') === groupName;
             });
             self.get('controller').doSelectConfigGroup({context: group});
           });
@@ -469,7 +502,7 @@ App.ConfigOverridable = Em.Mixin.create({
   persistConfigGroups: function () {
     var installerController = App.router.get('installerController');
     var step7Controller = App.router.get('wizardStep7Controller');
-    installerController.saveServiceConfigGroups(step7Controller, 
step7Controller.get('content.controllerName') == 'addServiceController');
+    installerController.saveServiceConfigGroups(step7Controller, 
step7Controller.get('content.controllerName') === 'addServiceController');
     App.clusterStatus.setClusterStatus({
       localdb: App.db.data
     });

http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/app/mixins/main/service/configs/widget_popover_support.js
----------------------------------------------------------------------
diff --git 
a/ambari-web/app/mixins/main/service/configs/widget_popover_support.js 
b/ambari-web/app/mixins/main/service/configs/widget_popover_support.js
index ded8dd9..dc9f7d3 100644
--- a/ambari-web/app/mixins/main/service/configs/widget_popover_support.js
+++ b/ambari-web/app/mixins/main/service/configs/widget_popover_support.js
@@ -44,7 +44,7 @@ App.WidgetPopoverSupport = Em.Mixin.create({
    * @type {string}
    */
   popoverPlacement: function () {
-    return this.get('section.isLastColumn') && 
this.get('subSection.isLastColumn')? 'left' : 'right';
+    return this.get('section.isLastColumn') && 
this.get('subSection.isLastColumn') ? 'left' : 'right';
   }.property('section.isLastColumn', 'subSection.isLastColumn'),
 
   initPopover: function () {

http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/test/mixins/main/service/configs/component_actions_by_configs_test.js
----------------------------------------------------------------------
diff --git 
a/ambari-web/test/mixins/main/service/configs/component_actions_by_configs_test.js
 
b/ambari-web/test/mixins/main/service/configs/component_actions_by_configs_test.js
new file mode 100644
index 0000000..a6877fe
--- /dev/null
+++ 
b/ambari-web/test/mixins/main/service/configs/component_actions_by_configs_test.js
@@ -0,0 +1,519 @@
+/**
+ * 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.
+ */
+
+require('mixins/main/service/configs/component_actions_by_configs');
+var testHelpers = require('test/helpers');
+var stringUtils = require('utils/string_utils');
+
+
+var mixin;
+
+describe('App.ComponentActionsByConfigs', function () {
+
+  beforeEach(function() {
+    mixin = Em.Object.create(App.ComponentActionsByConfigs, {
+      stepConfigs: [],
+      content: Em.Object.create()
+    });
+  });
+
+  describe("#doConfigActions()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'doComponentDeleteActions');
+      sinon.stub(mixin, 'doComponentAddActions');
+      mixin.set('stepConfigs', [Em.Object.create({
+        serviceName: 'S1',
+        configs: [{
+          configActionComponent: {}
+        }]
+      })]);
+      mixin.set('content.serviceName', 'S1');
+      mixin.doConfigActions();
+    });
+
+    afterEach(function() {
+      mixin.doComponentDeleteActions.restore();
+      mixin.doComponentAddActions.restore();
+    });
+
+    it("doComponentDeleteActions should be called", function() {
+      expect(mixin.doComponentDeleteActions.calledWith([{
+        configActionComponent: {}
+      }])).to.be.true;
+    });
+
+    it("doComponentAddActions should be called", function() {
+      expect(mixin.doComponentAddActions.calledWith([{
+        configActionComponent: {}
+      }])).to.be.true;
+    });
+  });
+
+  describe("#isComponentActionsPresent()", function () {
+
+    beforeEach(function() {
+      this.mockDelete = sinon.stub(mixin, 'getComponentsToDelete').returns(1);
+      this.mockAdd = sinon.stub(mixin, 'getComponentsToAdd').returns(1);
+      mixin.set('stepConfigs', [Em.Object.create({
+        serviceName: 'S1',
+        configs: [{
+          configActionComponent: {}
+        }]
+      })]);
+      mixin.set('content.serviceName', 'S1');
+    });
+
+    afterEach(function() {
+      this.mockAdd.restore();
+      this.mockDelete.restore();
+    });
+
+    it("no delete or add components", function() {
+      this.mockAdd.returns([]);
+      this.mockDelete.returns([]);
+      expect(mixin.isComponentActionsPresent()).to.be.false;
+    });
+
+    it("has delete and no add components", function() {
+      this.mockAdd.returns([]);
+      this.mockDelete.returns([{}]);
+      expect(mixin.isComponentActionsPresent()).to.be.true;
+    });
+
+    it("no delete and has add components", function() {
+      this.mockAdd.returns([{}]);
+      this.mockDelete.returns([]);
+      expect(mixin.isComponentActionsPresent()).to.be.true;
+    });
+  });
+
+  describe("#getComponentsToDelete()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.HostComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'C1',
+          hostName: 'host1'
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.HostComponent.find.restore();
+    });
+
+    it("should return array of components to delete", function() {
+      var configActionComponents = [
+        {
+          configActionComponent: {
+            action: 'delete',
+            componentName: 'C1',
+            hostName: 'host1'
+          }
+        }
+      ];
+      expect(mixin.getComponentsToDelete(configActionComponents)).to.be.eql([{
+        action: 'delete',
+        componentName: 'C1',
+        hostName: 'host1'
+      }]);
+    });
+  });
+
+  describe("#getComponentsToAdd()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.StackServiceComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'C1',
+          serviceName: 'S1'
+        })
+      ]);
+      sinon.stub(App.Service, 'find').returns([
+        Em.Object.create({
+          serviceName: 'S1',
+          hostComponents: [
+            Em.Object.create({
+              componentName: 'C1',
+              hostName: 'host2'
+            })
+          ]
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.StackServiceComponent.find.restore();
+      App.Service.find.restore();
+    });
+
+    it("should return array of components to add", function() {
+      var configActionComponents = [
+        {
+          configActionComponent: {
+            action: 'add',
+            componentName: 'C1',
+            hostName: 'host1'
+          }
+        }
+      ];
+      expect(mixin.getComponentsToAdd(configActionComponents)).to.be.eql([{
+        action: 'add',
+        componentName: 'C1',
+        hostName: 'host1'
+      }]);
+    });
+  });
+
+  describe("#doComponentDeleteActions()", function () {
+
+    beforeEach(function() {
+      this.mockDelete = sinon.stub(mixin, 'getComponentsToDelete');
+      sinon.stub(App.StackServiceComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'C1',
+          displayName: 'c1'
+        })
+      ]);
+      sinon.stub(mixin, 'setRefreshYarnQueueRequest');
+      sinon.stub(mixin, 'getInstallHostComponentsRequest').returns({});
+      sinon.stub(mixin, 'getDeleteHostComponentRequest').returns({});
+      sinon.stub(mixin, 'setOrderIdForBatches');
+    });
+
+    afterEach(function() {
+      this.mockDelete.restore();
+      App.StackServiceComponent.find.restore();
+      mixin.setRefreshYarnQueueRequest.restore();
+      mixin.getDeleteHostComponentRequest.restore();
+      mixin.setOrderIdForBatches.restore();
+      mixin.getInstallHostComponentsRequest.restore();
+    });
+
+    it("App.ajax.send should not be called", function() {
+      this.mockDelete.returns([]);
+      mixin.doComponentDeleteActions();
+      expect(testHelpers.findAjaxRequest('name', 
'common.batch.request_schedules')).to.not.exists;
+    });
+
+    it("App.ajax.send should be called", function() {
+      this.mockDelete.returns([{
+        componentName: 'C1',
+        hostName: 'host1'
+      }]);
+      mixin.doComponentDeleteActions();
+      var args = testHelpers.findAjaxRequest('name', 
'common.batch.request_schedules');
+      expect(args[0]).to.be.eql({
+        name: 'common.batch.request_schedules',
+        sender: mixin,
+        data: {
+          intervalTimeSeconds: 1,
+          tolerateSize: 0,
+          batches: [{}, {}]
+        }
+      });
+    });
+  });
+
+  describe("#doComponentAddActions()", function () {
+
+    beforeEach(function() {
+      this.mockAdd = sinon.stub(mixin, 'getComponentsToAdd');
+      sinon.stub(mixin, 'getDependentComponents').returns([]);
+      sinon.stub(App.StackServiceComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'C1',
+          displayName: 'c1'
+        })
+      ]);
+      sinon.stub(mixin, 'setCreateComponentRequest');
+      sinon.stub(mixin, 'getCreateHostComponentsRequest').returns({});
+      sinon.stub(mixin, 'getInstallHostComponentsRequest').returns({});
+      sinon.stub(mixin, 'setRefreshYarnQueueRequest');
+      sinon.stub(mixin, 'getStartHostComponentsRequest').returns({});
+      sinon.stub(mixin, 'setOrderIdForBatches');
+      sinon.stub(stringUtils, 'getFormattedStringFromArray').returns('c1');
+    });
+
+    afterEach(function() {
+      this.mockAdd.restore();
+      mixin.getDependentComponents.restore();
+      App.StackServiceComponent.find.restore();
+      mixin.setCreateComponentRequest.restore();
+      mixin.getCreateHostComponentsRequest.restore();
+      mixin.getInstallHostComponentsRequest.restore();
+      mixin.setRefreshYarnQueueRequest.restore();
+      mixin.getStartHostComponentsRequest.restore();
+      mixin.setOrderIdForBatches.restore();
+      stringUtils.getFormattedStringFromArray.restore();
+    });
+
+    it("App.ajax.send should not be called", function() {
+      this.mockAdd.returns([]);
+      mixin.doComponentAddActions();
+      expect(testHelpers.findAjaxRequest('name', 
'common.batch.request_schedules')).to.not.exists;
+    });
+
+    it("App.ajax.send should be called", function() {
+      this.mockAdd.returns([{
+        componentName: 'C1',
+        hostName: 'host1',
+        isClient: false
+      }]);
+      mixin.doComponentAddActions();
+      var args = testHelpers.findAjaxRequest('name', 
'common.batch.request_schedules');
+      expect(args[0]).to.be.eql({
+        name: 'common.batch.request_schedules',
+        sender: mixin,
+        data: {
+          intervalTimeSeconds: 1,
+          tolerateSize: 0,
+          batches: [{}, {}, {}]
+        }
+      });
+    });
+  });
+
+  describe("#getDependentComponents()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.StackServiceComponent, 'find').returns(Em.Object.create({
+        dependencies: [{
+          scope: 'host',
+          componentName: 'C2'
+        }],
+        isClient: false
+      }));
+      sinon.stub(App.HostComponent, 'find').returns([]);
+    });
+
+    afterEach(function() {
+      App.StackServiceComponent.find.restore();
+      App.HostComponent.find.restore();
+    });
+
+    it("should return dependent components", function() {
+      var componentsToAdd = [{
+        componentName: 'C1',
+        hostName: 'host1'
+      }];
+      expect(mixin.getDependentComponents(componentsToAdd)).to.be.eql([
+        {
+          componentName: 'C2',
+          hostName: 'host1',
+          isClient: false
+        }
+      ]);
+    });
+  });
+
+  describe("#setOrderIdForBatches()", function () {
+
+    it("should set order_id", function() {
+      var batches = [{}, {}, {}];
+      mixin.setOrderIdForBatches(batches);
+      expect(batches).to.be.eql([
+        {order_id: 1},
+        {order_id: 2},
+        {order_id: 3}
+      ]);
+    });
+  });
+
+  describe("#getCreateHostComponentsRequest()", function () {
+
+    it("App.ajax.send should be called", function() {
+      expect(mixin.getCreateHostComponentsRequest('host1', ['C1'])).to.be.eql({
+        "type": 'POST',
+        "uri": "/api/v1/clusters/mycluster/hosts",
+        "RequestBodyInfo": {
+          "RequestInfo": {
+            "query": "Hosts/host_name.in(host1)"
+          },
+          "Body": {
+            "host_components": [{
+              "HostRoles": {
+                "component_name": 'C1'
+              }
+            }]
+          }
+        }
+      });
+    });
+  });
+
+  describe("#getInstallHostComponentsRequest()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'getUpdateHostComponentsRequest').returns({});
+    });
+
+    afterEach(function() {
+      mixin.getUpdateHostComponentsRequest.restore();
+    });
+
+    it("should return request object", function() {
+      expect(mixin.getInstallHostComponentsRequest('host1', [{componentName: 
'C1'}])).to.be.eql({});
+      expect(mixin.getUpdateHostComponentsRequest.calledWith(
+        'host1',
+        [{componentName: 'C1'}],
+        App.HostComponentStatus.stopped,
+        Em.I18n.t('requestInfo.installComponents'))).to.be.true;
+    });
+  });
+
+  describe("#getStartHostComponentsRequest()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'getUpdateHostComponentsRequest').returns({});
+    });
+
+    afterEach(function() {
+      mixin.getUpdateHostComponentsRequest.restore();
+    });
+
+    it("should return request object", function() {
+      expect(mixin.getStartHostComponentsRequest('host1', [{componentName: 
'C1'}])).to.be.eql({});
+      expect(mixin.getUpdateHostComponentsRequest.calledWith(
+        'host1',
+        [{componentName: 'C1'}],
+        App.HostComponentStatus.started,
+        Em.I18n.t('requestInfo.startHostComponents'))).to.be.true;
+    });
+  });
+
+  describe("#getUpdateHostComponentsRequest()", function () {
+
+    it("should return request object", function() {
+      expect(mixin.getUpdateHostComponentsRequest('host1', ['C1', 'C2'], 
'INSTALLED', 'context')).to.be.eql({
+        "type": 'PUT',
+        "uri": "/api/v1/clusters/mycluster/hosts/host1/host_components",
+        "RequestBodyInfo": {
+          "RequestInfo": {
+            "context": 'context',
+            "operation_level": {
+              "level": "HOST",
+              "cluster_name": 'mycluster',
+              "host_names": 'host1'
+            },
+            "query": "HostRoles/component_name.in(C1,C2)"
+          },
+          "Body": {
+            "HostRoles": {
+              "state": 'INSTALLED'
+            }
+          }
+        }
+      });
+    });
+  });
+
+  describe("#getDeleteHostComponentRequest()", function () {
+
+    it("should return request object", function() {
+      expect(mixin.getDeleteHostComponentRequest('host1', 'C1')).to.be.eql({
+        "type": 'DELETE',
+        "uri": "/api/v1/clusters/mycluster/hosts/host1/host_components/C1"
+      });
+    });
+  });
+
+  describe("#setCreateComponentRequest()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.StackServiceComponent, 'find').returns([Em.Object.create({
+        componentName: 'C1',
+        serviceName: 'S1'
+      })]);
+      sinon.stub(App.Service, 'find').returns([Em.Object.create({
+        serviceName: 'S1',
+        serviceComponents: []
+      })]);
+    });
+
+    afterEach(function() {
+      App.StackServiceComponent.find.restore();
+      App.Service.find.restore();
+    });
+
+    it("should add batch", function() {
+      var batches = [];
+      mixin.setCreateComponentRequest(batches, ["C1"]);
+      expect(batches).to.be.eql([
+        {
+          "type": 'POST',
+          "uri": "/api/v1/clusters/mycluster/services/S1/components/C1"
+        }
+      ]);
+
+    });
+  });
+
+  describe("#setRefreshYarnQueueRequest()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.Service, 'find').returns(Em.Object.create({
+        hostComponents: [Em.Object.create({
+          componentName: 'RESOURCEMANAGER',
+          hostName: 'host1'
+        })]
+      }));
+    });
+
+    afterEach(function() {
+      App.Service.find.restore();
+    });
+
+    it("should not add a batch", function() {
+      var batches = [];
+      mixin.set('allConfigs', []);
+      mixin.setRefreshYarnQueueRequest(batches);
+      expect(batches).to.be.empty;
+    });
+
+    it("should add a batch", function() {
+      var batches = [];
+      mixin.set('allConfigs', [Em.Object.create({
+        filename: 'capacity-scheduler.xml',
+        value: 'val1',
+        initialValue: 'val2'
+      })]);
+      mixin.setRefreshYarnQueueRequest(batches);
+      expect(batches).to.be.eql([{
+        "type": 'POST',
+        "uri": "/api/v1/clusters/mycluster/requests",
+        "RequestBodyInfo": {
+          "RequestInfo": {
+            "context": 
Em.I18n.t('services.service.actions.run.yarnRefreshQueues.context'),
+            "command": "REFRESHQUEUES",
+            "parameters/forceRefreshConfigTags": "capacity-scheduler"
+          },
+          "Requests/resource_filters": [
+            {
+              service_name: "YARN",
+              component_name: "RESOURCEMANAGER",
+              hosts: "host1"
+            }
+          ]
+        }
+      }]);
+    });
+  });
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/test/mixins/main/service/configs/config_overridable_test.js
----------------------------------------------------------------------
diff --git 
a/ambari-web/test/mixins/main/service/configs/config_overridable_test.js 
b/ambari-web/test/mixins/main/service/configs/config_overridable_test.js
index 3c88819..b1eb099 100644
--- a/ambari-web/test/mixins/main/service/configs/config_overridable_test.js
+++ b/ambari-web/test/mixins/main/service/configs/config_overridable_test.js
@@ -19,6 +19,7 @@
 var App = require('app');
 
 require('mixins/main/service/configs/config_overridable');
+var testHelpers = require('test/helpers');
 
 var configOverridable;
 
@@ -28,13 +29,19 @@ var configGroups = [
   Em.Object.create({name: 'configGroup3'})
 ];
 
-describe('App.Decommissionable', function () {
+describe('App.ConfigOverridable', function () {
 
   beforeEach(function () {
-    configOverridable = Em.Object.create(App.ConfigOverridable);
+    configOverridable = Em.Object.create(App.ConfigOverridable, {
+      controller: Em.Object.create({
+        loadConfigGroups: Em.K,
+        doSelectConfigGroup: Em.K
+      }),
+      isView: true
+    });
   });
 
-  describe('#validate of Config Group Selection Creation Dialog', function () {
+  describe('#launchConfigGroupSelectionCreationDialog()', function () {
 
     var testCases = [
       {
@@ -92,4 +99,348 @@ describe('App.Decommissionable', function () {
     });
   });
 
+  describe("#createOverrideProperty()", function () {
+
+    beforeEach(function() {
+      sinon.stub(configOverridable, 
'launchConfigGroupSelectionCreationDialog');
+      sinon.stub(App.ModalPopup, 'show');
+      sinon.stub(App.config, 'createOverride');
+    });
+
+    afterEach(function() {
+      configOverridable.launchConfigGroupSelectionCreationDialog.restore();
+      App.ModalPopup.show.restore();
+      App.config.createOverride.restore();
+    });
+
+    it("App.ModalPopup.show should be called", function() {
+      var event = {
+        contexts: [
+          Em.Object.create({
+            isUserProperty: true,
+            isNotSaved: true
+          })
+        ]
+      };
+      configOverridable.set('controller.name', '');
+      configOverridable.createOverrideProperty(event);
+      expect(App.ModalPopup.show.calledOnce).to.be.true;
+    });
+
+    it("launchConfigGroupSelectionCreationDialogshould be called", function() {
+      var event = {
+        contexts: [
+          Em.Object.create()
+        ]
+      };
+      configOverridable.set('controller.selectedConfigGroup', 
Em.Object.create({
+        isDefault: true
+      }));
+      configOverridable.createOverrideProperty(event);
+      
expect(configOverridable.launchConfigGroupSelectionCreationDialog.calledOnce).to.be.true;
+    });
+
+    it("App.config.createOverride be called", function() {
+      var event = {
+        contexts: [
+          Em.Object.create({
+            widget: true,
+            value: 'val'
+          })
+        ]
+      };
+      configOverridable.set('controller.selectedConfigGroup', 
Em.Object.create({
+        isDefault: false
+      }));
+      configOverridable.createOverrideProperty(event);
+      expect(App.config.createOverride.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#postNewConfigurationGroup()", function () {
+
+    it("App.ajax.send should be called", function() {
+      var newConfigGroupData = {
+        properties: [],
+        name: 'cg1',
+        service_id: 'S1',
+        description: '',
+        desired_configs: [],
+        hosts: ['host1']
+      };
+      configOverridable.postNewConfigurationGroup(newConfigGroupData);
+
+      var args = testHelpers.findAjaxRequest('name', 'config_groups.create');
+      expect(args[0]).to.be.eql({
+        name: 'config_groups.create',
+        sender: configOverridable,
+        data: {
+          data: [{
+            "ConfigGroup": {
+              "group_name": 'cg1',
+              "tag": 'S1',
+              "description": '',
+              "desired_configs": [],
+              "hosts": [{host_name: 'host1'}]
+            }
+          }],
+          modelData: newConfigGroupData
+        },
+        success: 'postNewConfigurationGroupSuccess',
+        error: 'postNewConfigurationGroupError'
+      });
+    });
+  });
+
+  describe("#postNewConfigurationGroupSuccess()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.store, 'load');
+      sinon.stub(App.store, 'commit');
+      sinon.stub(App.ServiceConfigGroup, 'deleteTemporaryRecords');
+      configOverridable.postNewConfigurationGroupSuccess({
+        resources: [
+          {
+            ConfigGroup: {
+              id: 'cg1'
+            }
+          }
+        ]
+      }, {}, {modelData: {}});
+    });
+
+    afterEach(function() {
+      App.ServiceConfigGroup.deleteTemporaryRecords.restore();
+      App.store.commit.restore();
+      App.store.load.restore();
+    });
+
+    it("App.store.load should be called", function() {
+      expect(App.store.load.calledWith(App.ServiceConfigGroup, {id: 
'cg1'})).to.be.true;
+    });
+
+    it("App.store.commit should be called", function() {
+      expect(App.store.commit.calledOnce).to.be.true;
+    });
+
+    it("App.ServiceConfigGroup.deleteTemporaryRecords should be called", 
function() {
+      
expect(App.ServiceConfigGroup.deleteTemporaryRecords.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#updateConfigurationGroup()", function () {
+
+    beforeEach(function() {
+      sinon.stub(configOverridable, 'getConfigGroupData');
+    });
+
+    afterEach(function() {
+      configOverridable.getConfigGroupData.restore();
+    });
+
+    it("App.ajax.send should be called", function() {
+      configOverridable.updateConfigurationGroup(Em.Object.create());
+      expect(testHelpers.findAjaxRequest('name', 
'config_groups.update')).to.be.exist;
+    });
+  });
+
+  describe("#getConfigGroupData()", function () {
+
+    it("should return config group data", function() {
+      var configGroup = Em.Object.create({
+        name: 'cg1',
+        description: 'dsc',
+        service: {
+          id: 'S1'
+        },
+        hosts: ['host1'],
+        desiredConfigs: [{
+          site: 'type1',
+          tag: 'tag1'
+        }]
+      });
+      expect(configOverridable.getConfigGroupData(configGroup)).to.be.eql({
+        ConfigGroup: {
+          group_name: 'cg1',
+          description: 'dsc',
+          tag: 'S1',
+          hosts: [{
+            host_name: 'host1'
+          }],
+          desired_configs: [{
+            type: 'type1',
+            tag: 'tag1'
+          }]
+        }
+      });
+    });
+  });
+
+  describe("#launchSwitchConfigGroupOfHostDialog()", function () {
+    var mock = {
+      callback: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.spy(App.ModalPopup, 'show');
+      sinon.stub(configOverridable, 'updateConfigurationGroup');
+      sinon.spy(mock, 'callback');
+    });
+
+    afterEach(function() {
+      configOverridable.updateConfigurationGroup.restore();
+      App.ModalPopup.show.restore();
+      mock.callback.restore();
+    });
+
+    it("updateConfigurationGroup should be called", function() {
+      var popup = 
configOverridable.launchSwitchConfigGroupOfHostDialog(Em.Object.create({
+        name: 'cg1',
+        isDefault: false,
+        hosts: ['host1']
+      }),
+      [],'host1', Em.K);
+      popup.onPrimary();
+      
expect(configOverridable.updateConfigurationGroup.calledTwice).to.be.true;
+    });
+
+    it("callback should be called", function() {
+      var group = Em.Object.create({
+        name: 'cg1',
+        isDefault: false,
+        hosts: ['host1']
+      });
+      var popup = configOverridable.launchSwitchConfigGroupOfHostDialog(group, 
[], 'host1', mock.callback);
+      popup.onPrimary();
+      expect(mock.callback.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#deleteConfigurationGroup()", function () {
+
+    it("App.ajax.send should be called", function() {
+      configOverridable.deleteConfigurationGroup(Em.Object.create());
+      expect(testHelpers.findAjaxRequest('name', 
'common.delete.config_group')).to.be.exist;
+    });
+  });
+
+  describe("#deleteConfigurationGroupSuccess()", function () {
+    var group = Em.Object.create({
+      id: 'cg1',
+      stateManager: Em.Object.create({
+        transitionTo: Em.K
+      })
+    });
+
+    beforeEach(function() {
+      sinon.stub(App.ServiceConfigGroup, 'find').returns([
+        group
+      ]);
+      sinon.stub(App.configGroupsMapper, 'deleteRecord');
+    });
+
+    afterEach(function() {
+      App.ServiceConfigGroup.find.restore();
+      App.configGroupsMapper.deleteRecord.restore();
+    });
+
+    it("App.configGroupsMapper.deleteRecord should be called", function() {
+      configOverridable.deleteConfigurationGroupSuccess({}, {}, {id: 'cg1'});
+      expect(App.configGroupsMapper.deleteRecord.calledWith(group)).to.be.true;
+    });
+
+    it("App.configGroupsMapper.deleteRecord should not be called", function() {
+      configOverridable.deleteConfigurationGroupSuccess({}, {}, {id: 'cg2'});
+      expect(App.configGroupsMapper.deleteRecord.called).to.be.false;
+    });
+  });
+
+  describe("#saveGroupConfirmationPopup()", function () {
+    var group = Em.Object.create({
+      serviceName: 'S1',
+      name: 'cg1'
+    });
+    var mock = {
+      manageConfigurationGroups: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(configOverridable.get('controller'), 
'loadConfigGroups').returns({
+        done: function (callback) {
+          callback();
+        }
+      });
+      sinon.stub(configOverridable.get('controller'), 'doSelectConfigGroup');
+      sinon.stub(App.ServiceConfigGroup, 'find').returns([group]);
+      sinon.spy(App.ModalPopup, 'show');
+      sinon.stub(App.router, 'get').returns(mock);
+      sinon.spy(mock, 'manageConfigurationGroups');
+    });
+
+    afterEach(function() {
+      configOverridable.get('controller').loadConfigGroups.restore();
+      configOverridable.get('controller').doSelectConfigGroup.restore();
+      App.ServiceConfigGroup.find.restore();
+      App.ModalPopup.show.restore();
+      App.router.get.restore();
+      mock.manageConfigurationGroups.restore();
+    });
+
+    it("onPrimary", function() {
+      configOverridable.set('controller.name', 
'mainServiceInfoConfigsController');
+      configOverridable.set('controller.content', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      var popup = configOverridable.saveGroupConfirmationPopup('cg1');
+      popup.onPrimary();
+      
expect(configOverridable.get('controller').doSelectConfigGroup.calledWith({context:
 group})).to.be.true;
+    });
+
+    it("onSecondary", function() {
+      configOverridable.set('controller.content', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      var popup = configOverridable.saveGroupConfirmationPopup('cg1');
+      popup.onSecondary();
+      expect(mock.manageConfigurationGroups.calledWith(null, Em.Object.create({
+        serviceName: 'S1'
+      }))).to.be.true;
+    });
+  });
+
+  describe("#persistConfigGroups()", function () {
+    var mockInstaller = Em.Object.create({
+      saveServiceConfigGroups: Em.K
+    });
+    var mockStep7 = Em.Object.create({
+      content: Em.Object.create({
+        controllerName: 'addServiceController'
+      })
+    });
+
+    beforeEach(function() {
+      sinon.stub(mockInstaller, 'saveServiceConfigGroups');
+      this.mock = sinon.stub(App.router, 'get');
+      sinon.stub(App.clusterStatus, 'setClusterStatus');
+      this.mock.withArgs('installerController').returns(mockInstaller);
+      this.mock.withArgs('wizardStep7Controller').returns(mockStep7);
+      configOverridable.persistConfigGroups();
+    });
+
+    afterEach(function() {
+      App.clusterStatus.setClusterStatus.restore();
+      App.router.get.restore();
+      this.mock.restore();
+      mockInstaller.saveServiceConfigGroups.restore();
+    });
+
+    it("saveServiceConfigGroups should be called", function() {
+      expect(mockInstaller.saveServiceConfigGroups.calledWith(mockStep7, 
true)).to.be.true;
+    });
+
+    it("App.clusterStatus.setClusterStatus should be called", function() {
+      expect(App.clusterStatus.setClusterStatus.calledOnce).to.be.true;
+    });
+  });
+
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/07b42367/ambari-web/test/mixins/main/service/configs/widget_popover_support_test.js
----------------------------------------------------------------------
diff --git 
a/ambari-web/test/mixins/main/service/configs/widget_popover_support_test.js 
b/ambari-web/test/mixins/main/service/configs/widget_popover_support_test.js
index 449ff5d..d4057b8 100644
--- a/ambari-web/test/mixins/main/service/configs/widget_popover_support_test.js
+++ b/ambari-web/test/mixins/main/service/configs/widget_popover_support_test.js
@@ -16,4 +16,167 @@
  * limitations under the License.
  */
 
-describe('App.WidgetPopoverSupport', function () {});
\ No newline at end of file
+require('mixins/main/service/configs/widget_popover_support');
+
+var mixin;
+
+describe('App.WidgetPopoverSupport', function () {
+
+  beforeEach(function() {
+    mixin = Em.Object.create(App.WidgetPopoverSupport, {
+      stepConfigs: [],
+      content: Em.Object.create(),
+      on: Em.K,
+      $: Em.K
+    });
+  });
+
+  describe("#popoverPlacement", function () {
+
+    var testCases = [
+      {
+        sectionLastColumn: false,
+        subSectionLastColumn: false,
+        expected: 'right'
+      },
+      {
+        sectionLastColumn: true,
+        subSectionLastColumn: false,
+        expected: 'right'
+      },
+      {
+        sectionLastColumn: false,
+        subSectionLastColumn: true,
+        expected: 'right'
+      },
+      {
+        sectionLastColumn: true,
+        subSectionLastColumn: true,
+        expected: 'left'
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("subSection.isLastColumn = " + test.subSectionLastColumn +
+         "section.isLastColumn = " + test.sectionLastColumn, function() {
+        mixin.setProperties({
+          section: {
+            isLastColumn: test.sectionLastColumn
+          },
+          subSection: {
+            isLastColumn: test.subSectionLastColumn
+          }
+        });
+        mixin.propertyDidChange('popoverPlacement');
+        expect(mixin.get('popoverPlacement')).to.be.equal(test.expected);
+      });
+    });
+  });
+
+  describe("#initPopover()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'destroyPopover');
+      sinon.stub(App, 'popover');
+      sinon.stub(mixin, 'on');
+      sinon.stub(mixin, '$')
+    });
+
+    afterEach(function() {
+      mixin.on.restore();
+      App.popover.restore();
+      mixin.destroyPopover.restore();
+      mixin.$.restore();
+    });
+
+    it("destroyPopover should not be called", function() {
+      mixin.set('isPopoverEnabled', false);
+      mixin.initPopover();
+      expect(mixin.destroyPopover.called).to.be.false;
+    });
+
+    it("App.popover should not be called", function() {
+      mixin.set('isPopoverEnabled', false);
+      mixin.initPopover();
+      expect(App.popover.called).to.be.false;
+    });
+
+    it("on should not be called", function() {
+      mixin.set('isPopoverEnabled', false);
+      mixin.initPopover();
+      expect(mixin.on.called).to.be.false;
+    });
+
+    it("destroyPopover should be called", function() {
+      mixin.set('isPopoverEnabled', true);
+      mixin.initPopover();
+      expect(mixin.destroyPopover.calledOnce).to.be.true;
+    });
+
+    it("App.popover should be called", function() {
+      mixin.set('isPopoverEnabled', true);
+      mixin.initPopover();
+      expect(App.popover.calledOnce).to.be.true;
+    });
+
+    it("on should be called", function() {
+      mixin.set('isPopoverEnabled', true);
+      mixin.initPopover();
+      expect(mixin.on.calledWith('willDestroyElement', mixin, 
mixin.destroyPopover)).to.be.true;
+    });
+  });
+
+  describe("#destroyPopover()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'movePopover');
+    });
+
+    afterEach(function() {
+      mixin.movePopover.restore();
+    });
+
+    it("movePopover should be called", function() {
+      mixin.destroyPopover();
+      expect(mixin.movePopover.calledWith('destroy')).to.be.true;
+    });
+  });
+
+  describe("#hidePopover()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'movePopover');
+    });
+
+    afterEach(function() {
+      mixin.movePopover.restore();
+    });
+
+    it("movePopover should be called", function() {
+      mixin.hidePopover();
+      expect(mixin.movePopover.calledWith('hide')).to.be.true;
+    });
+  });
+
+  describe("#movePopover()", function () {
+    var mock = {
+      popover: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(mixin, '$').returns(mock);
+      sinon.stub(mock, 'popover');
+    });
+
+    afterEach(function() {
+      mock.popover.restore();
+      mixin.$.restore();
+    });
+
+    it("popover should be called", function() {
+      mixin.movePopover('action');
+      expect(mock.popover.calledWith('action')).to.be.true;
+    });
+  });
+
+});
\ No newline at end of file

Reply via email to