You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2015/01/13 17:46:01 UTC

ambari git commit: AMBARI-9105. Ambari shows empty popup when underlying call fails (alexantonenko)

Repository: ambari
Updated Branches:
  refs/heads/trunk 275e8986b -> e67c5ea50


AMBARI-9105. Ambari shows empty popup when underlying call fails (alexantonenko)


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

Branch: refs/heads/trunk
Commit: e67c5ea50c0ac1a71855b1f0e3af8dbb39a9c793
Parents: 275e898
Author: Alex Antonenko <hi...@gmail.com>
Authored: Tue Jan 13 17:26:44 2015 +0200
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Tue Jan 13 18:45:50 2015 +0200

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   1 +
 .../app/controllers/wizard/step8_controller.js  | 143 ++++++++++---
 ambari-web/app/messages.js                      |   4 +-
 ambari-web/app/utils/ajax/ajax.js               |  14 +-
 ambari-web/app/views.js                         |   1 +
 .../common/ajax_default_error_popup_body.js     |  74 +++++++
 .../test/controllers/wizard/step8_test.js       | 213 ++++++++++++++++++-
 .../ajax_default_error_popup_body_test.js       |  85 ++++++++
 8 files changed, 497 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/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 218e71f..203b9c9 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -153,6 +153,7 @@ var files = ['test/init_model_test',
   'test/utils/ui_effects_test',
   'test/utils/updater_test',
   'test/views/common/chart/linear_time_test',
+  'test/views/common/ajax_default_error_popup_body_test',
   'test/views/common/filter_combo_cleanable_test',
   'test/views/common/filter_view_test',
   'test/views/common/table_view_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/app/controllers/wizard/step8_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/wizard/step8_controller.js b/ambari-web/app/controllers/wizard/step8_controller.js
index 33e8b4d..b582866 100644
--- a/ambari-web/app/controllers/wizard/step8_controller.js
+++ b/ambari-web/app/controllers/wizard/step8_controller.js
@@ -169,6 +169,26 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz
   clusterNames: [],
 
   /**
+   * Number of completed cluster delete requests
+   * @type {number}
+   */
+  clusterDeleteRequestsCompleted: 0,
+
+  /**
+   * Indicates if all cluster delete requests are completed
+   * @type {boolean}
+   */
+  isAllClusterDeleteRequestsCompleted: function () {
+    return this.get('clusterDeleteRequestsCompleted') == this.get('clusterNames.length');
+  }.property('clusterDeleteRequestsCompleted'),
+
+  /**
+   * Error popup body views for clusters that couldn't be deleted
+   * @type {App.AjaxDefaultErrorPopupBodyView[]}
+   */
+  clusterDeleteErrorViews: [],
+
+  /**
    * Clear current step data
    * @method clearStep
    */
@@ -181,6 +201,8 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz
     this.set('ajaxQueueLength', 0);
     this.set('ajaxRequestsQueue', App.ajaxQueue.create());
     this.set('ajaxRequestsQueue.finishedCallback', this.ajaxQueueFinished);
+    this.get('clusterDeleteErrorViews').clear();
+    this.set('clusterDeleteRequestsCompleted', 0);
   },
 
   /**
@@ -881,8 +903,12 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz
    */
   submitProceed: function () {
     var self = this;
-    this.set('isSubmitDisabled', true);
-    this.set('isBackBtnDisabled', true);
+    this.setProperties({
+      isSubmitDisabled: true,
+      isBackBtnDisabled: true,
+      clusterDeleteRequestsCompleted: 0
+    });
+    this.get('clusterDeleteErrorViews').clear();
     if (this.get('content.controllerName') == 'addHostController') {
       App.router.get('addHostController').setLowerStepsDisable(4);
     }
@@ -924,7 +950,7 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz
       if (self.get('content.controllerName') == 'installerController' && (!App.get('testMode')) && clusterNames.length) {
         self.deleteClusters(clusterNames);
       } else {
-        self.deleteClustersCallback(null, null, {isLast: true});
+        self.startDeploy();
       }
     });
   },
@@ -972,6 +998,7 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz
    * @method deleteClusters
    */
   deleteClusters: function (clusterNames) {
+    this.get('clusterDeleteErrorViews').clear();
     clusterNames.forEach(function (clusterName, index) {
       App.ajax.send({
         name: 'common.delete.cluster',
@@ -980,39 +1007,103 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz
           name: clusterName,
           isLast: index == clusterNames.length - 1
         },
-        success: 'deleteClustersCallback',
-        error: 'deleteClustersCallback'
+        success: 'deleteClusterSuccessCallback',
+        error: 'deleteClusterErrorCallback'
       });
     }, this);
 
   },
 
-  deleteClustersCallback: function (response, request, data) {
-    if (data.isLast) {
-      this.createCluster();
-      this.createSelectedServices();
-      if (this.get('content.controllerName') !== 'addHostController') {
-        if (this.get('wizardController').getDBProperty('fileNamesToUpdate') && this.get('wizardController').getDBProperty('fileNamesToUpdate').length) {
-          this.updateConfigurations(this.get('wizardController').getDBProperty('fileNamesToUpdate'));
-        }
-        this.createConfigurations();
-        this.applyConfigurationsToCluster(this.get('serviceConfigTags'));
-      }
-      this.createComponents();
-      this.registerHostsToCluster();
-      this.createConfigurationGroups();
-      this.createMasterHostComponents();
-      this.createSlaveAndClientsHostComponents();
-      if (this.get('content.controllerName') === 'addServiceController') {
-        this.createAdditionalClientComponents();
+  /**
+   * Method to execute after successful cluster deletion
+   * @method deleteClusterSuccessCallback
+   */
+  deleteClusterSuccessCallback: function () {
+    this.incrementProperty('clusterDeleteRequestsCompleted');
+    if (this.get('isAllClusterDeleteRequestsCompleted')) {
+      if (this.get('clusterDeleteErrorViews.length')) {
+        this.showDeleteClustersErrorPopup();
+      } else {
+        this.startDeploy();
       }
-      this.createAdditionalHostComponents();
+    }
+  },
 
-      this.set('ajaxQueueLength', this.get('ajaxRequestsQueue.queue.length'));
-      this.get('ajaxRequestsQueue').start();
+  /**
+   * Method to execute after failed cluster deletion
+   * @param {object} request
+   * @param {string} ajaxOptions
+   * @param {string} error
+   * @param {object} opt
+   * @method deleteClusterErrorCallback
+   */
+  deleteClusterErrorCallback: function (request, ajaxOptions, error, opt) {
+    this.incrementProperty('clusterDeleteRequestsCompleted');
+    try {
+      var json = $.parseJSON(request.responseText);
+      var message = json.message;
+    } catch (err) {
+    }
+    this.get('clusterDeleteErrorViews').pushObject(App.AjaxDefaultErrorPopupBodyView.create({
+      url: opt.url,
+      type: opt.type,
+      status: request.status,
+      message: message
+    }));
+    if (this.get('isAllClusterDeleteRequestsCompleted')) {
+      this.showDeleteClustersErrorPopup();
     }
   },
 
+  /**
+   * Show error popup if cluster deletion failed
+   * @method showDeleteClustersErrorPopup
+   */
+  showDeleteClustersErrorPopup: function () {
+    var self = this;
+    this.setProperties({
+      isSubmitDisabled: false,
+      isBackBtnDisabled: false
+    });
+    App.ModalPopup.show({
+      header: Em.I18n.t('common.error'),
+      secondary: false,
+      onPrimary: function () {
+        this.hide();
+      },
+      bodyClass: Em.ContainerView.extend({
+        childViews: self.get('clusterDeleteErrorViews')
+      })
+    });
+  },
+
+  /**
+   * Start deploy process
+   * @method startDeploy
+   */
+  startDeploy: function () {
+    this.createCluster();
+    this.createSelectedServices();
+    if (this.get('content.controllerName') !== 'addHostController') {
+      if (this.get('wizardController').getDBProperty('fileNamesToUpdate') && this.get('wizardController').getDBProperty('fileNamesToUpdate').length) {
+        this.updateConfigurations(this.get('wizardController').getDBProperty('fileNamesToUpdate'));
+      }
+      this.createConfigurations();
+      this.applyConfigurationsToCluster(this.get('serviceConfigTags'));
+    }
+    this.createComponents();
+    this.registerHostsToCluster();
+    this.createConfigurationGroups();
+    this.createMasterHostComponents();
+    this.createSlaveAndClientsHostComponents();
+    if (this.get('content.controllerName') === 'addServiceController') {
+      this.createAdditionalClientComponents();
+    }
+    this.createAdditionalHostComponents();
+
+    this.set('ajaxQueueLength', this.get('ajaxRequestsQueue.queue.length'));
+    this.get('ajaxRequestsQueue').start();
+  },
 
   /**
    * *******************************************************************

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 42384bc..b1e0ea8 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -2445,5 +2445,7 @@ Em.I18n.translations = {
   'config.group.save.confirmation.manage.button': 'Manage Hosts',
   'config.group.description.default': 'New configuration group created on {0}',
 
-  'utils.ajax.errorMessage': 'Error message'
+  'utils.ajax.errorMessage': 'Error message',
+  'utils.ajax.defaultErrorPopupBody.message': 'received on {0} method for API: {1}',
+  'utils.ajax.defaultErrorPopupBody.statusCode': '{0} status code'
 };

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/app/utils/ajax/ajax.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js
index 1756a99..e4a0960 100644
--- a/ambari-web/app/utils/ajax/ajax.js
+++ b/ambari-web/app/utils/ajax/ajax.js
@@ -2308,7 +2308,6 @@ var ajax = Em.Object.extend({
   defaultErrorHandler: function (jqXHR, url, method, showStatus) {
     method = method || 'GET';
     var self = this;
-    var api = " received on " + method + " method for API: " + url;
     try {
       var json = $.parseJSON(jqXHR.responseText);
       var message = json.message;
@@ -2317,7 +2316,6 @@ var ajax = Em.Object.extend({
     if (!showStatus) {
       showStatus = 500;
     }
-    var statusCode = jqXHR.status + " status code";
     if (jqXHR.status === showStatus && !this.get('modalPopup')) {
       this.set('modalPopup', App.ModalPopup.show({
         header: Em.I18n.t('common.error'),
@@ -2326,13 +2324,11 @@ var ajax = Em.Object.extend({
           this.hide();
           self.set('modalPopup', null);
         },
-        bodyClass: Ember.View.extend({
-          classNames: ['api-error'],
-          templateName: require('templates/utils/ajax'),
-          api: api,
-          statusCode: statusCode,
-          message: message,
-          showMessage: !!message
+        bodyClass: App.AjaxDefaultErrorPopupBodyView.extend({
+          type: method,
+          url: url,
+          status: jqXHR.status,
+          message: message
         })
       }));
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 1873b22..9971914 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -20,6 +20,7 @@
 // load all views here
 
 require('views/application');
+require('views/common/ajax_default_error_popup_body');
 require('views/common/chart');
 require('views/common/chart/pie');
 require('views/common/chart/linear');

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/app/views/common/ajax_default_error_popup_body.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/ajax_default_error_popup_body.js b/ambari-web/app/views/common/ajax_default_error_popup_body.js
new file mode 100644
index 0000000..616e69e
--- /dev/null
+++ b/ambari-web/app/views/common/ajax_default_error_popup_body.js
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.AjaxDefaultErrorPopupBodyView = Em.View.extend({
+
+  classNames: ['api-error'],
+  templateName: require('templates/utils/ajax'),
+
+  /**
+   * HTTP request URL
+   * @type {string}
+   */
+  url: '',
+
+  /**
+   * HTTP request type
+   * @type {string}
+   */
+  type: '',
+
+  /**
+   * HTTP response status code
+   * @type {number}
+   */
+  status: 0,
+
+  /**
+   * Received error message
+   * @type {string}
+   */
+  message: '',
+
+  /**
+   * Status code string
+   * @type {string}
+   */
+  statusCode: function () {
+    return Em.I18n.t('utils.ajax.defaultErrorPopupBody.statusCode').format(this.get('status'));
+  }.property('status'),
+
+  /**
+   * Indicates if error message should be displayed
+   * @type {boolean}
+   */
+  showMessage: function () {
+    return !!this.get('message');
+  }.property('message'),
+
+  /**
+   * HTTP response error description
+   * @type {string}
+   */
+  api: function () {
+    return Em.I18n.t('utils.ajax.defaultErrorPopupBody.message').format(this.get('type'), this.get('url'));
+  }.property('type', 'url')
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/test/controllers/wizard/step8_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/wizard/step8_test.js b/ambari-web/test/controllers/wizard/step8_test.js
index e6b022b..0f3923d 100644
--- a/ambari-web/test/controllers/wizard/step8_test.js
+++ b/ambari-web/test/controllers/wizard/step8_test.js
@@ -833,16 +833,29 @@ describe('App.WizardStep8Controller', function () {
   });
 
   describe('#deleteClusters', function() {
-    it('should call App.ajax.send for each provided clusterName', function() {
+
+    beforeEach(function () {
       sinon.stub(App.ajax, 'send', Em.K);
+    });
+
+    afterEach(function () {
+      App.ajax.send.restore();
+    });
+
+    it('should call App.ajax.send for each provided clusterName', function() {
       var clusterNames = ['h1', 'h2', 'h3'];
       installerStep8Controller.deleteClusters(clusterNames);
       expect(App.ajax.send.callCount).to.equal(clusterNames.length);
       clusterNames.forEach(function(n, i) {
         expect(App.ajax.send.getCall(i).args[0].data).to.eql({name: n, isLast: i == clusterNames.length - 1});
       });
-      App.ajax.send.restore();
     });
+
+    it('should clear cluster delete error popup body views', function () {
+      installerStep8Controller.deleteClusters([]);
+      expect(installerStep8Controller.get('clusterDeleteErrorViews')).to.eql([]);
+    });
+
   });
 
   describe('#createSelectedServicesData', function() {
@@ -1551,4 +1564,200 @@ describe('App.WizardStep8Controller', function () {
     });
   });
 
+  describe('#isAllClusterDeleteRequestsCompleted', function () {
+    it('should depend on completed cluster delete requests number', function () {
+      installerStep8Controller.setProperties({
+        clusterDeleteRequestsCompleted: 0,
+        clusterNames: ['c0']
+      });
+      expect(installerStep8Controller.get('isAllClusterDeleteRequestsCompleted')).to.be.false;
+      installerStep8Controller.incrementProperty('clusterDeleteRequestsCompleted');
+      expect(installerStep8Controller.get('isAllClusterDeleteRequestsCompleted')).to.be.true;
+    });
+  });
+
+  describe('#deleteClusterSuccessCallback', function () {
+
+    beforeEach(function () {
+      sinon.stub(installerStep8Controller, 'showDeleteClustersErrorPopup', Em.K);
+      sinon.stub(installerStep8Controller, 'startDeploy', Em.K);
+      installerStep8Controller.setProperties({
+        clusterDeleteRequestsCompleted: 0,
+        clusterNames: ['c0', 'c1'],
+        clusterDeleteErrorViews: []
+      });
+      installerStep8Controller.deleteClusterSuccessCallback();
+    });
+
+    afterEach(function () {
+      installerStep8Controller.showDeleteClustersErrorPopup.restore();
+      installerStep8Controller.startDeploy.restore();
+    });
+
+    it('no failed requests', function () {
+      expect(installerStep8Controller.get('clusterDeleteRequestsCompleted')).to.equal(1);
+      expect(installerStep8Controller.showDeleteClustersErrorPopup.called).to.be.false;
+      expect(installerStep8Controller.startDeploy.called).to.be.false;
+      installerStep8Controller.deleteClusterSuccessCallback();
+      expect(installerStep8Controller.get('clusterDeleteRequestsCompleted')).to.equal(2);
+      expect(installerStep8Controller.showDeleteClustersErrorPopup.called).to.be.false;
+      expect(installerStep8Controller.startDeploy.calledOnce).to.be.true;
+    });
+
+    it('one request failed', function () {
+      installerStep8Controller.deleteClusterErrorCallback({}, null, null, {});
+      expect(installerStep8Controller.get('clusterDeleteRequestsCompleted')).to.equal(2);
+      expect(installerStep8Controller.showDeleteClustersErrorPopup.calledOnce).to.be.true;
+      expect(installerStep8Controller.startDeploy.called).to.be.false;
+    });
+
+  });
+
+  describe('#deleteClusterErrorCallback', function () {
+
+    var request = {
+        status: 500,
+        responseText: '{"message":"Internal Server Error"}'
+      },
+      ajaxOptions = 'error',
+      error = 'Internal Server Error',
+      opt = {
+        url: 'api/v1/clusters/c0',
+        type: 'DELETE'
+      };
+
+    beforeEach(function () {
+      installerStep8Controller.setProperties({
+        clusterDeleteRequestsCompleted: 0,
+        clusterNames: ['c0', 'c1'],
+        clusterDeleteErrorViews: []
+      });
+      sinon.stub(installerStep8Controller, 'showDeleteClustersErrorPopup', Em.K);
+      installerStep8Controller.deleteClusterErrorCallback(request, ajaxOptions, error, opt);
+    });
+
+    afterEach(function () {
+      installerStep8Controller.showDeleteClustersErrorPopup.restore();
+    });
+
+    it('should show error popup only if all requests are completed', function () {
+      expect(installerStep8Controller.get('clusterDeleteRequestsCompleted')).to.equal(1);
+      expect(installerStep8Controller.showDeleteClustersErrorPopup.called).to.be.false;
+      installerStep8Controller.deleteClusterErrorCallback(request, ajaxOptions, error, opt);
+      expect(installerStep8Controller.get('clusterDeleteRequestsCompleted')).to.equal(2);
+      expect(installerStep8Controller.showDeleteClustersErrorPopup.calledOnce).to.be.true;
+    });
+
+    it('should create error popup body view', function () {
+      expect(installerStep8Controller.get('clusterDeleteErrorViews')).to.have.length(1);
+      expect(installerStep8Controller.get('clusterDeleteErrorViews.firstObject.url')).to.equal('api/v1/clusters/c0');
+      expect(installerStep8Controller.get('clusterDeleteErrorViews.firstObject.type')).to.equal('DELETE');
+      expect(installerStep8Controller.get('clusterDeleteErrorViews.firstObject.status')).to.equal(500);
+      expect(installerStep8Controller.get('clusterDeleteErrorViews.firstObject.message')).to.equal('Internal Server Error');
+    });
+
+  });
+
+  describe('#showDeleteClustersErrorPopup', function () {
+
+    beforeEach(function () {
+      installerStep8Controller.setProperties({
+        isSubmitDisabled: true,
+        isBackBtnDisabled: true
+      });
+      sinon.stub(App.ModalPopup, 'show', Em.K);
+      installerStep8Controller.showDeleteClustersErrorPopup();
+    });
+
+    afterEach(function () {
+      App.ModalPopup.show.restore();
+    });
+
+    it('should show error popup and unlock navigation', function () {
+      expect(installerStep8Controller.get('isSubmitDisabled')).to.be.false;
+      expect(installerStep8Controller.get('isBackBtnDisabled')).to.be.false;
+      expect(App.ModalPopup.show.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#startDeploy', function () {
+
+    var stubbedNames = ['createCluster', 'createSelectedServices', 'updateConfigurations', 'createConfigurations',
+        'applyConfigurationsToCluster', 'createComponents', 'registerHostsToCluster', 'createConfigurationGroups',
+        'createMasterHostComponents', 'createSlaveAndClientsHostComponents', 'createAdditionalClientComponents',
+        'createAdditionalHostComponents'],
+      cases = [
+        {
+          controllerName: 'installerController',
+          notExecuted: ['createAdditionalClientComponents', 'updateConfigurations'],
+          fileNamesToUpdate: [],
+          title: 'Installer, no configs to update'
+        },
+        {
+          controllerName: 'installerController',
+          notExecuted: ['createAdditionalClientComponents'],
+          fileNamesToUpdate: [''],
+          title: 'Installer, some configs to be updated'
+        },
+        {
+          controllerName: 'addHostController',
+          notExecuted: ['updateConfigurations', 'createConfigurations', 'applyConfigurationsToCluster', 'createAdditionalClientComponents'],
+          title: 'Add Host Wizard'
+        },
+        {
+          controllerName: 'addServiceController',
+          notExecuted: ['updateConfigurations'],
+          fileNamesToUpdate: [],
+          title: 'Add Service Wizard, no configs to update'
+        },
+        {
+          controllerName: 'addServiceController',
+          notExecuted: [],
+          fileNamesToUpdate: [''],
+          title: 'Add Service Wizard, some configs to be updated'
+        }
+      ];
+
+    beforeEach(function () {
+      stubbedNames.forEach(function (name) {
+        sinon.stub(installerStep8Controller, name, Em.K);
+      });
+      installerStep8Controller.setProperties({
+        serviceConfigTags: [],
+        content: {
+          controllerName: null
+        }
+      });
+    });
+
+    afterEach(function () {
+      stubbedNames.forEach(function (name) {
+        installerStep8Controller[name].restore();
+      });
+      installerStep8Controller.get.restore();
+    });
+
+    cases.forEach(function (item) {
+      it(item.title, function () {
+        sinon.stub(installerStep8Controller, 'get')
+          .withArgs('ajaxRequestsQueue').returns({
+            start: Em.K
+          })
+          .withArgs('ajaxRequestsQueue.queue.length').returns(1)
+          .withArgs('wizardController').returns({
+            getDBProperty: function () {
+              return item.fileNamesToUpdate;
+            }
+          })
+          .withArgs('content.controllerName').returns(item.controllerName);
+        installerStep8Controller.startDeploy();
+        stubbedNames.forEach(function (name) {
+          expect(installerStep8Controller[name].called).to.equal(!item.notExecuted.contains(name));
+        });
+      });
+    });
+
+  });
+
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/e67c5ea5/ambari-web/test/views/common/ajax_default_error_popup_body_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/ajax_default_error_popup_body_test.js b/ambari-web/test/views/common/ajax_default_error_popup_body_test.js
new file mode 100644
index 0000000..5575fe8
--- /dev/null
+++ b/ambari-web/test/views/common/ajax_default_error_popup_body_test.js
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+require('views/common/ajax_default_error_popup_body');
+
+describe('App.AjaxDefaultErrorPopupBodyView', function () {
+
+  describe('#statusCode', function () {
+
+    var view = App.AjaxDefaultErrorPopupBodyView.create();
+
+    it('should format status code', function () {
+      view.set('status', 404);
+      expect(view.get('statusCode')).to.equal(Em.I18n.t('utils.ajax.defaultErrorPopupBody.statusCode').format(404));
+    });
+
+  });
+
+  describe('#showMessage', function () {
+
+    var view = App.AjaxDefaultErrorPopupBodyView.create(),
+      title = 'should be {0}',
+      cases = [
+        {
+          message: 'error',
+          showMessage: true
+        },
+        {
+          message: '',
+          showMessage: false
+        },
+        {
+          message: null,
+          showMessage: false
+        },
+        {
+          message: undefined,
+          showMessage: false
+        },
+        {
+          message: 0,
+          showMessage: false
+        }
+      ];
+
+    cases.forEach(function (item) {
+      it(title.format(item.showMessage), function () {
+        view.set('message', item.message);
+        expect(view.get('showMessage')).to.equal(item.showMessage);
+      });
+    });
+
+  });
+
+  describe('#api', function () {
+
+    var view = App.AjaxDefaultErrorPopupBodyView.create();
+
+    it('should format string with request type and URL', function () {
+      view.setProperties({
+        type: 'GET',
+        url: 'api/v1/clusters'
+      });
+      expect(view.get('api')).to.equal(Em.I18n.t('utils.ajax.defaultErrorPopupBody.message').format('GET', 'api/v1/clusters'));
+    });
+
+  });
+
+});