瀏覽代碼

AMBARI-13877 RU/EU: host failure summary dialog usability changes. (atkach)

Andrii Tkach 9 年之前
父節點
當前提交
18e595aeb9

+ 1 - 0
ambari-web/app/assets/test/tests.js

@@ -222,6 +222,7 @@ var files = [
   'test/views/main/admin/stack_upgrade/version_view_test',
   'test/views/main/admin/stack_upgrade/services_view_test',
   'test/views/main/admin/stack_upgrade/menu_view_test',
+  'test/views/main/admin/stack_upgrade/failed_hosts_modal_view_test',
   'test/views/main/dashboard/config_history_view_test',
   'test/views/main/dashboard/widget_test',
   'test/views/main/dashboard/widgets_test',

+ 56 - 2
ambari-web/app/controllers/main/admin/stack_and_upgrade_controller.js

@@ -88,6 +88,17 @@ App.MainAdminStackAndUpgradeController = Em.Controller.extend(App.LocalStorage,
    */
   targetVersions: [],
 
+  /**
+   * @type {object}
+   * @default null
+   */
+  slaveComponentStructuredInfo: null,
+
+  /**
+   * @type {Array}
+   */
+  serviceCheckFailuresServicenames: [],
+
   /**
    * methods through which cluster could be upgraded, "allowed" indicated if the method is allowed
    * by stack upgrade path
@@ -147,6 +158,18 @@ App.MainAdminStackAndUpgradeController = Em.Controller.extend(App.LocalStorage,
    */
   finalizeContext: 'Confirm Finalize',
 
+  /**
+   * Context for Slave component failures manual item
+   * @type {string}
+   */
+  slaveFailuresContext: "Check Component Versions",
+
+  /**
+   * Context for Service check (may include slave component) failures manual item
+   * @type {string}
+   */
+  serviceCheckFailuresContext: "Verifying Skipped Failures",
+
   /**
    * Check if current item is Finalize
    * @type {boolean}
@@ -391,9 +414,11 @@ App.MainAdminStackAndUpgradeController = Em.Controller.extend(App.LocalStorage,
 
   /**
    * request Upgrade Item and its tasks from server
+   * @param {Em.Object} item
+   * @param {Function} customCallback
    * @return {$.ajax}
    */
-  getUpgradeItem: function (item) {
+  getUpgradeItem: function (item, customCallback) {
     return App.ajax.send({
       name: 'admin.upgrade.upgrade_item',
       sender: this,
@@ -402,7 +427,7 @@ App.MainAdminStackAndUpgradeController = Em.Controller.extend(App.LocalStorage,
         groupId: item.get('group_id'),
         stageId: item.get('stage_id')
       },
-      success: 'getUpgradeItemSuccessCallback'
+      success: customCallback || 'getUpgradeItemSuccessCallback'
     });
   },
 
@@ -437,6 +462,35 @@ App.MainAdminStackAndUpgradeController = Em.Controller.extend(App.LocalStorage,
     }, this);
   },
 
+  /**
+   * Failures info may includes service_check and host_component failures. These two types should be displayed separately.
+   */
+  getServiceCheckItemSuccessCallback: function(data) {
+    var task = data.tasks[0];
+    var info = {
+      hosts: [],
+      host_detail: {}
+    };
+
+    if (task && task.Tasks && task.Tasks.structured_out && task.Tasks.structured_out.failures) {
+      this.set('serviceCheckFailuresServicenames', task.Tasks.structured_out.failures.service_check || []);
+      if (task.Tasks.structured_out.failures.host_component) {
+        for (var hostname in task.Tasks.structured_out.failures.host_component){
+          info.hosts.push(hostname);
+        }
+        info.host_detail = task.Tasks.structured_out.failures.host_component;
+      }
+      this.set('slaveComponentStructuredInfo', info);
+    }
+  },
+
+  getSlaveComponentItemSuccessCallback: function(data) {
+    var info = data.tasks[0];
+    if (info && info.Tasks && info.Tasks.structured_out) {
+      this.set('slaveComponentStructuredInfo', info.Tasks.structured_out);
+    }
+  },
+
   /**
    * downgrade confirmation popup
    * @param {object} event

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

@@ -1507,6 +1507,9 @@ Em.I18n.translations = {
   'admin.stackUpgrade.failedHosts.options': "Your options:",
   'admin.stackUpgrade.failedHosts.options.first': "<b>Pause Upgrade</b>, delete the unhealthy hosts and return to the Upgrade Wizard to Proceed.",
   'admin.stackUpgrade.failedHosts.options.second': "Perform a <b>Downgrade</b>, which will revert all hosts to the previous stack version.",
+  'admin.stackUpgrade.failedHosts.header': "Failed Hosts",
+  'admin.stackUpgrade.failedHosts.subHeader': "Upgrade failed on {0} hosts",
+  'admin.stackUpgrade.failedHosts.details': "Open Details",
   'admin.stackUpgrade.doThisLater': "Do This Later",
   'admin.stackUpgrade.pauseUpgrade': "Pause Upgrade",
   'admin.stackUpgrade.pauseDowngrade': "Pause Downgrade",  

+ 27 - 0
ambari-web/app/styles/stack_versions.less

@@ -537,3 +537,30 @@
     }
   }
 }
+
+#upgrade-failed-hosts {
+  .host-list-container {
+    overflow-y: auto;
+    max-height: 320px;
+    margin-right: -15px;
+    .accordion {
+      margin-bottom: 0;
+      .accordion-group {
+        border: none;
+        .accordion-toggle {
+          line-height: 20px;
+          padding: 5px 0;
+          .label {
+            margin-right: 10px;
+          }
+        }
+      }
+    }
+  }
+  .sub-header {
+    padding: 0 15px;
+    width: 530px;
+    margin-left: -15px;
+    border-bottom: 1px solid #eee;
+  }
+}

+ 56 - 0
ambari-web/app/templates/main/admin/stack_upgrade/failed_hosts_modal.hbs

@@ -0,0 +1,56 @@
+{{!
+* 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.
+}}
+
+
+<div id="upgrade-failed-hosts">
+  <div class="row-fluid sub-header">
+    <div class="span6">{{view.subHeader}}</div>
+    <div class="pull-right">
+      <a href="#" {{action openDetails target="view"}}>
+        {{t admin.stackUpgrade.failedHosts.details}}
+      </a>
+    </div>
+  </div>
+  <div class="host-list-container">
+    {{#each host in view.hosts}}
+      <div class="accordion">
+        <div class="accordion-group">
+          <div class="accordion-heading">
+            <i class="pull-left accordion-toggle icon-caret-right"></i>
+            <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="{{unbound host.collapseHref}}">
+              <span rel="UsageTooltip" data-original-title="{{unbound host.hostName}}">{{host.displayName}}</span>
+              <span class="pull-right label label-info">{{host.hostComponents.length}}</span>
+            </a>
+          </div>
+          <div id="{{unbound host.collapseId}}" class="accordion-body collapse">
+            <div class="accordion-inner">
+              {{#each hostComponent in host.hostComponents}}
+                <div class="row-fluid">
+                  <div class="span4">{{hostComponent.serviceName}}</div>
+                  <div class="span4">{{hostComponent.componentName}}</div>
+                </div>
+              {{/each}}
+            </div>
+          </div>
+        </div>
+      </div>
+    {{/each}}
+
+  </div>
+
+</div>

+ 1 - 1
ambari-web/app/templates/main/admin/stack_upgrade/stack_upgrade_wizard.hbs

@@ -151,7 +151,7 @@
                       <div>{{t admin.stackUpgrade.dialog.manual.serviceCheckFailures.msg2}}</div>
                     </div>
                   {{/if}}
-                  {{#if slaveComponentFailuresHosts.length}}
+                  {{#if slaveComponentStructuredInfo.hosts.length}}
                     <div class="slave-failures-info">
                       <p class="slave-failures-title"><strong>{{t admin.stackUpgrade.dialog.manual.slaveComponentFailures.title}}</strong></p>
                       <p>

+ 1 - 0
ambari-web/app/views.js

@@ -165,6 +165,7 @@ require('views/main/admin/stack_upgrade/upgrade_task_view');
 require('views/main/admin/stack_upgrade/services_view');
 require('views/main/admin/stack_upgrade/versions_view');
 require('views/main/admin/stack_upgrade/menu_view');
+require('views/main/admin/stack_upgrade/failed_hosts_modal_view');
 require('views/main/admin/stack_and_upgrade_view');
 require('views/main/admin/advanced');
 require('views/main/admin/advanced/password');

+ 83 - 0
ambari-web/app/views/main/admin/stack_upgrade/failed_hosts_modal_view.js

@@ -0,0 +1,83 @@
+/**
+ * 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.FailedHostsPopupBodyView = Em.View.extend({
+
+  templateName: require('templates/main/admin/stack_upgrade/failed_hosts_modal'),
+
+  /**
+   * @type {number}
+   * @const
+   */
+  MAX_HOSTNAME_LENGTH: 50,
+
+  /**
+   * @type {string}
+   */
+  subHeader: function () {
+    return Em.I18n.t('admin.stackUpgrade.failedHosts.subHeader').format(this.get('parentView.content.hosts.length'));
+  }.property('parentView.content.hosts.length'),
+
+  didInsertElement: function () {
+    App.tooltip(this.$("[rel='UsageTooltip']"));
+    this.$(".accordion").on("show hide", function (e) {
+      $(e.target).siblings(".accordion-heading").find("i.accordion-toggle").toggleClass('icon-caret-right icon-caret-down')
+    });
+  },
+
+  /**
+   * @type {Array.<Em.Object>}
+   */
+  hosts: function () {
+    var content = this.get('parentView.content');
+    var result = [];
+
+    content.hosts.forEach(function (hostName, index) {
+      var hostComponents = [];
+
+      if (content.host_detail[hostName]) {
+        content.host_detail[hostName].forEach(function (details) {
+          hostComponents.push(Em.Object.create({
+            componentName: App.format.role(details.component),
+            serviceName: App.format.role(details.service)
+          }))
+        }, this);
+      }
+      result.push(Em.Object.create({
+        hostName: hostName,
+        displayName: hostName.length > this.MAX_HOSTNAME_LENGTH ? hostName.substr(0, this.MAX_HOSTNAME_LENGTH) + '...' : hostName,
+        collapseId: 'collapse' + index,
+        collapseHref: '#collapse' + index,
+        hostComponents: hostComponents
+      }))
+    }, this);
+    return result;
+  }.property('parentView.content'),
+
+  /**
+   * open hosts info in new window in JSON format
+   */
+  openDetails: function () {
+    var newDocument = window.open().document;
+    newDocument.write(JSON.stringify(this.get('parentView.content')));
+    newDocument.close();
+  }
+});

+ 27 - 100
ambari-web/app/views/main/admin/stack_upgrade/upgrade_wizard_view.js

@@ -169,24 +169,12 @@ App.upgradeWizardView = Em.View.extend({
     return this.get('activeGroup.upgradeItems') && this.get('activeGroup.upgradeItems').findProperty('status', 'HOLDING');
   }.property('activeGroup.upgradeItems.@each.status'),
 
-  /**
-   * Context for Slave component failures manual item
-   * @type {string}
-   */
-  slaveFailuresContext: "Check Component Versions",
-
-  /**
-   * Context for Service check (may include slave component) failures manual item
-   * @type {string}
-   */
-  serviceCheckFailuresContext: "Verifying Skipped Failures",
-
   /**
    * manualItem: indicate whether the step is "Slave component failures", a dialog with instructions will show up for manual steps
    * @type {boolean}
    */
   isSlaveComponentFailuresItem: function () {
-    return this.get('manualItem.context') === this.get("slaveFailuresContext");
+    return this.get('manualItem.context') === this.get("controller.slaveFailuresContext");
   }.property('manualItem.context'),
 
   /**
@@ -194,7 +182,7 @@ App.upgradeWizardView = Em.View.extend({
    * @type {boolean}
    */
   isServiceCheckFailuresItem: function () {
-    return this.get('manualItem.context') === this.get("serviceCheckFailuresContext");
+    return this.get('manualItem.context') === this.get("controller.serviceCheckFailuresContext");
   }.property('manualItem.context'),
 
   /**
@@ -313,86 +301,38 @@ App.upgradeWizardView = Em.View.extend({
     }
   },
 
-  getSlaveComponentFailureHosts: function() {
+  /**
+   * get slave-component failure hosts
+   */
+  getSlaveComponentItem: function() {
+    var controller = this.get('controller');
     if (this.get('isSlaveComponentFailuresItem')) {
       if (!this.get('controller.areSlaveComponentFailuresHostsLoaded')) {
-        var self = this;
-        var item = this.get('manualItem');
-        App.ajax.send({
-          name: 'admin.upgrade.upgrade_item',
-          sender: this,
-          data: {
-            upgradeId: item.get('request_id'),
-            groupId: item.get('group_id'),
-            stageId: item.get('stage_id')
-          },
-          success: 'getSlaveComponentFailureHostsSuccessCallback'
-        }).complete(function () {
-            self.set('controller.areSlaveComponentFailuresHostsLoaded', true);
-          });
+        controller.getUpgradeItem(this.get('manualItem'), 'getSlaveComponentItemSuccessCallback').complete(function () {
+          controller.set('areSlaveComponentFailuresHostsLoaded', true);
+        });
       }
     } else {
-      this.set('controller.areSlaveComponentFailuresHostsLoaded', false);
+      controller.set('areSlaveComponentFailuresHostsLoaded', false);
     }
   }.observes('isSlaveComponentFailuresItem'),
 
-  getSlaveComponentFailureHostsSuccessCallback: function(data) {
-    var hostsNames = [];
-    if (data.tasks && data.tasks.length) {
-      data.tasks.forEach(function(task) {
-        if(task.Tasks && task.Tasks.structured_out) {
-          hostsNames = task.Tasks.structured_out.hosts;
-        }
-      });
-    }
-    this.set('controller.slaveComponentFailuresHosts', hostsNames.uniq());
-  },
-
-  getServiceCheckFailureServicenames: function() {
+  /**
+   * get service names of Service Check failures
+   */
+  getServiceCheckItem: function() {
+    var controller = this.get('controller');
     if (this.get('isServiceCheckFailuresItem')) {
       if (!this.get('controller.areServiceCheckFailuresServicenamesLoaded')) {
-        var self = this;
-        var item = this.get('manualItem');
-        App.ajax.send({
-          name: 'admin.upgrade.upgrade_item',
-          sender: this,
-          data: {
-            upgradeId: item.get('request_id'),
-            groupId: item.get('group_id'),
-            stageId: item.get('stage_id')
-          },
-          success: 'getServiceCheckFailureServicenamesSuccessCallback'
-        }).complete(function () {
-            self.set('controller.areServiceCheckFailuresServicenamesLoaded', true);
+        controller.getUpgradeItem(this.get('manualItem'), 'getServiceCheckItemSuccessCallback').complete(function () {
+            controller.set('areServiceCheckFailuresServicenamesLoaded', true);
           });
       }
     } else {
-      this.set('controller.areServiceCheckFailuresServicenamesLoaded', false);
+      controller.set('areServiceCheckFailuresServicenamesLoaded', false);
     }
   }.observes('isServiceCheckFailuresItem'),
 
-
-  /**
-   * Failures info may includes service_check and host_component failures. These two types should be displayed separately.
-   */
-  getServiceCheckFailureServicenamesSuccessCallback: function(data) {
-    var hostsNames = [], serviceNames = [];
-    if (data.tasks && data.tasks.length) {
-      data.tasks.forEach(function(task) {
-        if(task.Tasks && task.Tasks.structured_out && task.Tasks.structured_out.failures) {
-          serviceNames = task.Tasks.structured_out.failures.service_check || [];
-          if (task.Tasks.structured_out.failures.host_component) {
-            for (var hostname in task.Tasks.structured_out.failures.host_component){
-              hostsNames.push(hostname);
-            }
-          }
-        }
-      });
-    }
-    this.set('controller.serviceCheckFailuresServicenames', serviceNames.uniq());
-    this.set('controller.slaveComponentFailuresHosts', hostsNames.uniq());
-  },
-
   /**
    * start polling upgrade data
    */
@@ -472,34 +412,21 @@ App.upgradeWizardView = Em.View.extend({
     this.get('parentView').closeWizard();
   },
 
-  /**
-   * hosts failed to be upgraded
-   * @type {Array}
-   */
-  failedHosts: function() {
-    if (this.get('isSlaveComponentFailuresItem') && this.get('controller.areSlaveComponentFailuresHostsLoaded')) {
-      return this.get('controller.slaveComponentFailuresHosts');
-    }
-    if (this.get('isServiceCheckFailuresItem') && this.get('controller.areServiceCheckFailuresServicenamesLoaded')) {
-      return this.get('controller.slaveComponentFailuresHosts');
-    }
-    return [];
-  }.property('controller.areSlaveComponentFailuresHostsLoaded', 'isSlaveComponentFailuresItem',
-    'isServiceCheckFailuresItem', 'controller.areServiceCheckFailuresServicenamesLoaded'),
-
   /**
    * @type {string}
    */
   failedHostsMessage: function() {
-    return Em.I18n.t('admin.stackUpgrade.failedHosts.showHosts').format(this.get('failedHosts.length'));
-  }.property('failedHosts'),
+    var count = this.get('controller.slaveComponentStructuredInfo.hosts.length') || 0;
+    return Em.I18n.t('admin.stackUpgrade.failedHosts.showHosts').format(count);
+  }.property('controller.slaveComponentStructuredInfo.hosts'),
 
   showFailedHosts: function() {
     return App.ModalPopup.show({
-      content: this.get('failedHosts').join(", "),
-      header: Em.I18n.t('common.hosts'),
-      bodyClass: App.SelectablePopupBodyView,
-      secondary: null
+      header: Em.I18n.t('admin.stackUpgrade.failedHosts.header'),
+      bodyClass: App.FailedHostsPopupBodyView,
+      secondary: null,
+      primary: Em.I18n.t('common.close'),
+      content: this.get('controller.slaveComponentStructuredInfo')
     });
   }
 });

+ 210 - 1
ambari-web/test/controllers/main/admin/stack_and_upgrade_controller_test.js

@@ -256,7 +256,7 @@ describe('App.MainAdminStackAndUpgradeController', function() {
     afterEach(function () {
       App.ajax.send.restore();
     });
-    it("", function() {
+    it("default callback", function() {
       var item = Em.Object.create({
         request_id: 1,
         group_id: 2,
@@ -274,6 +274,24 @@ describe('App.MainAdminStackAndUpgradeController', function() {
         success: 'getUpgradeItemSuccessCallback'
       });
     });
+    it("custom callback", function() {
+      var item = Em.Object.create({
+        request_id: 1,
+        group_id: 2,
+        stage_id: 3
+      });
+      controller.getUpgradeItem(item, 'customCallback');
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'admin.upgrade.upgrade_item',
+        sender: controller,
+        data: {
+          upgradeId: 1,
+          groupId: 2,
+          stageId: 3
+        },
+        success: 'customCallback'
+      });
+    });
   });
 
   describe("#openUpgradeDialog()", function () {
@@ -1435,4 +1453,195 @@ describe('App.MainAdminStackAndUpgradeController', function() {
       expect(controller.loadUpgradeData.calledWith(true)).to.be.true;
     });
   });
+
+  describe("#getServiceCheckItemSuccessCallback()", function() {
+    var testCases = [
+      {
+        title: 'no tasks',
+        data: {
+          tasks: []
+        },
+        expected: {
+          slaveComponentStructuredInfo: null,
+          serviceCheckFailuresServicenames: []
+        }
+      },
+      {
+        title: 'no structured_out property',
+        data: {
+          tasks: [
+            {
+              Tasks: {}
+            }
+          ]
+        },
+        expected: {
+          slaveComponentStructuredInfo: null,
+          serviceCheckFailuresServicenames: []
+        }
+      },
+      {
+        title: 'no failures',
+        data: {
+          tasks: [
+            {
+              Tasks: {
+                structured_out: {}
+              }
+            }
+          ]
+        },
+        expected: {
+          slaveComponentStructuredInfo: null,
+          serviceCheckFailuresServicenames: []
+        }
+      },
+      {
+        title: 'service check failures',
+        data: {
+          tasks: [
+            {
+              Tasks: {
+                structured_out: {
+                  failures: {
+                    service_check: ['HDSF', 'YARN']
+                  }
+                }
+              }
+            }
+          ]
+        },
+        expected: {
+          slaveComponentStructuredInfo: {
+            hosts: [],
+            host_detail: {}
+          },
+          serviceCheckFailuresServicenames: ['HDSF', 'YARN']
+        }
+      },
+      {
+        title: 'host-component failures',
+        data: {
+          tasks: [
+            {
+              Tasks: {
+                structured_out: {
+                  failures: {
+                    service_check: ['HDSF'],
+                    host_component: {
+                      "host1": [
+                        {
+                          component: "DATANODE",
+                          service: 'HDFS'
+                        }
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          ]
+        },
+        expected: {
+          slaveComponentStructuredInfo: {
+            hosts: ['host1'],
+            host_detail: {
+              "host1": [
+                {
+                  component: "DATANODE",
+                  service: 'HDFS'
+                }
+              ]
+            }
+          },
+          serviceCheckFailuresServicenames: ['HDSF']
+        }
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it(test.title, function() {
+        controller.set('slaveComponentStructuredInfo', null);
+        controller.set('serviceCheckFailuresServicenames', []);
+        controller.getServiceCheckItemSuccessCallback(test.data);
+        expect(controller.get('serviceCheckFailuresServicenames')).eql(test.expected.serviceCheckFailuresServicenames);
+        expect(controller.get('slaveComponentStructuredInfo')).eql(test.expected.slaveComponentStructuredInfo);
+      });
+    });
+  });
+
+  describe("#getSlaveComponentItemSuccessCallback()", function () {
+    var testCases = [
+      {
+        title: 'no tasks',
+        data: {
+          tasks: []
+        },
+        expected: {
+          slaveComponentStructuredInfo: null
+        }
+      },
+      {
+        title: 'structured_out property absent',
+        data: {
+          tasks: [
+            {
+              Tasks: {}
+            }
+          ]
+        },
+        expected: {
+          slaveComponentStructuredInfo: null
+        }
+      },
+      {
+        title: 'structured_out property present',
+        data: {
+          tasks: [
+            {
+              Tasks: {
+                "structured_out" : {
+                  "hosts" : [
+                    "host1"
+                  ],
+                  "host_detail" : {
+                    "host1" : [
+                      {
+                        "service" : "FLUME",
+                        "component" : "FLUME_HANDLER"
+                      }
+                    ]
+                  }
+                }
+              }
+            }
+          ]
+        },
+        expected: {
+          slaveComponentStructuredInfo: {
+            "hosts" : [
+              "host1"
+            ],
+            "host_detail" : {
+              "host1" : [
+                {
+                  "service" : "FLUME",
+                  "component" : "FLUME_HANDLER"
+                }
+              ]
+            }
+          }
+        }
+      }
+    ];
+
+    testCases.forEach(function (test) {
+      it(test.title, function () {
+        controller.set('slaveComponentStructuredInfo', null);
+        controller.getSlaveComponentItemSuccessCallback(test.data);
+        expect(controller.get('slaveComponentStructuredInfo')).eql(test.expected.slaveComponentStructuredInfo);
+      });
+    });
+  });
+
 });

+ 106 - 0
ambari-web/test/views/main/admin/stack_upgrade/failed_hosts_modal_view_test.js

@@ -0,0 +1,106 @@
+/**
+ * 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/main/admin/stack_upgrade/failed_hosts_modal_view');
+
+describe('App.FailedHostsPopupBodyView', function () {
+  var view = App.FailedHostsPopupBodyView.create({
+    parentView: Em.Object.create({
+      content: {}
+    })
+  });
+
+
+  describe("#subHeader", function() {
+    it("", function() {
+      view.set('parentView.content', {
+        hosts: ['host1', 'host2', 'host3']
+      });
+      view.propertyDidChange('subHeader');
+      expect(view.get('subHeader')).to.equal(Em.I18n.t('admin.stackUpgrade.failedHosts.subHeader').format(3));
+    });
+  });
+
+  describe("#hosts", function() {
+    beforeEach(function(){
+      sinon.stub(App.format, 'role', function(name){
+        return name;
+      })
+    });
+    afterEach(function(){
+      App.format.role.restore();
+    });
+
+    it("", function() {
+      view.set('parentView.content', {
+        hosts: ['host1', 'long.host.50.chars.commmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'],
+        host_detail: {
+          "host1": [
+            {
+              component: 'DATANODE',
+              service: 'HDFS'
+            },
+            {
+              component: 'HBASE_REGIONSERVER',
+              service: 'HBASE'
+            }
+          ],
+          "long.host.50.chars.commmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm": [
+            {
+              service: 'FLUME',
+              component: 'FLUME_AGENT'
+            }
+          ]
+        }
+      });
+      view.propertyDidChange('hosts');
+      expect(view.get('hosts')).to.eql([
+        Em.Object.create({
+          hostName: 'host1',
+          displayName: 'host1',
+          collapseId: 'collapse0',
+          collapseHref: '#collapse0',
+          hostComponents: [
+            Em.Object.create({
+              componentName: 'DATANODE',
+              serviceName: 'HDFS'
+            }),
+            Em.Object.create({
+              componentName: 'HBASE_REGIONSERVER',
+              serviceName: 'HBASE'
+            })
+          ]
+        }),
+        Em.Object.create({
+          hostName: 'long.host.50.chars.commmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
+          displayName: 'long.host.50.chars.commmmmmmmmmmmmmmmmmmmmmmmmmmmm...',
+          collapseId: 'collapse1',
+          collapseHref: '#collapse1',
+          hostComponents: [
+            Em.Object.create({
+              componentName: 'FLUME_AGENT',
+              serviceName: 'FLUME'
+            })
+          ]
+        })
+      ]);
+    });
+  });
+});

+ 77 - 1
ambari-web/test/views/main/admin/stack_upgrade/upgrade_wizard_view_test.js

@@ -30,7 +30,9 @@ describe('App.upgradeWizardView', function () {
       upgradeData: Em.Object.create(),
       loadUpgradeData: Em.K,
       setUpgradeItemStatus: Em.K,
-      getUpgradeItem: Em.K
+      getUpgradeItem: function () {
+        return {complete: Em.K};
+      }
     })
   });
   view.removeObserver('App.clusterName', view, 'startPolling');
@@ -809,4 +811,78 @@ describe('App.upgradeWizardView', function () {
 
   });
 
+  describe("#failedHostsMessage", function() {
+    it("", function() {
+      view.set('controller.slaveComponentStructuredInfo', {
+        hosts: ['host1']
+      });
+      view.propertyDidChange('failedHostsMessage');
+      expect(view.get('failedHostsMessage')).to.equal(Em.I18n.t('admin.stackUpgrade.failedHosts.showHosts').format(1));
+    });
+  });
+
+  describe("#getSlaveComponentItem()", function() {
+    beforeEach(function () {
+      sinon.stub(view.get('controller'), 'getUpgradeItem', function () {
+        return {
+          complete: function (callback) {
+            callback();
+          }
+        }
+      });
+      view.set('controller.areSlaveComponentFailuresHostsLoaded', false);
+    });
+    afterEach(function () {
+      view.get('controller').getUpgradeItem.restore();
+    });
+
+    it("isSlaveComponentFailuresItem is false", function() {
+      view.reopen({
+        isSlaveComponentFailuresItem: false
+      });
+      view.getSlaveComponentItem();
+      expect(view.get('controller.areSlaveComponentFailuresHostsLoaded')).to.be.false;
+    });
+    it("isSlaveComponentFailuresItem is true", function() {
+      view.reopen({
+        isSlaveComponentFailuresItem: true
+      });
+      view.getSlaveComponentItem();
+      expect(view.get('controller').getUpgradeItem.calledOnce).to.be.true;
+      expect(view.get('controller.areSlaveComponentFailuresHostsLoaded')).to.be.true;
+    });
+  });
+
+  describe("#getServiceCheckItem()", function() {
+    beforeEach(function () {
+      sinon.stub(view.get('controller'), 'getUpgradeItem', function () {
+        return {
+          complete: function (callback) {
+            callback();
+          }
+        }
+      });
+      view.set('controller.areServiceCheckFailuresServicenamesLoaded', false);
+    });
+    afterEach(function () {
+      view.get('controller').getUpgradeItem.restore();
+    });
+
+    it("isServiceCheckFailuresItem is false", function() {
+      view.reopen({
+        isServiceCheckFailuresItem: false
+      });
+      view.getServiceCheckItem();
+      expect(view.get('controller.areServiceCheckFailuresServicenamesLoaded')).to.be.false;
+    });
+    it("isServiceCheckFailuresItem is true", function() {
+      view.reopen({
+        isServiceCheckFailuresItem: true
+      });
+      view.getServiceCheckItem();
+      expect(view.get('controller').getUpgradeItem.calledOnce).to.be.true;
+      expect(view.get('controller.areServiceCheckFailuresServicenamesLoaded')).to.be.true;
+    });
+  });
+
 });