Browse Source

AMBARI-14415 Cover Config models with unit tests. (atkach)

Andrii Tkach 9 years ago
parent
commit
a55c81267c

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

@@ -323,6 +323,8 @@ var files = [
   'test/models/configs/sub_section_test',
   'test/models/configs/section_test',
   'test/models/configs/service_config_version_test',
+  'test/models/configs/config_group_test',
+  'test/models/configs/stack_config_property_test',
   'test/models/configs/objects/service_config_test',
   'test/models/configs/objects/service_config_category_test',
   'test/models/configs/objects/service_config_property_test',

+ 2 - 10
ambari-web/app/controllers/main/alerts/manage_alert_groups_controller.js

@@ -254,11 +254,7 @@ App.ManageAlertGroupsController = Em.Controller.extend({
         name: group.get('name'),
         default: group.get('default'),
         displayName: function () {
-          var name = this.get('name');
-          if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
-            var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
-            name = name.substring(0, middle) + "..." + name.substring(name.length - middle);
-          }
+          var name = App.config.truncateGroupName(this.get('name'));
           return this.get('default') ? (name + ' Default') : name;
         }.property('name', 'default'),
         label: function () {
@@ -743,11 +739,7 @@ App.ManageAlertGroupsController = Em.Controller.extend({
           name: this.get('alertGroupName').trim(),
           default: false,
           displayName: function () {
-            var name = this.get('name');
-            if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
-              var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
-              name = name.substring(0, middle) + "..." + name.substring(name.length - middle);
-            }
+            var name = App.config.truncateGroupName(this.get('name'));
             return this.get('default') ? (name + ' Default') : name;
           }.property('name', 'default'),
           label: function () {

+ 22 - 8
ambari-web/app/controllers/main/charts/heatmap.js

@@ -41,6 +41,9 @@ App.MainChartsHeatmapController = Em.Controller.extend(App.WidgetSectionMixin, {
 
   loadRacksUrlParams: 'fields=Hosts/rack_info,Hosts/host_name,Hosts/public_host_name,Hosts/os_type,Hosts/ip,host_components,metrics/disk,metrics/cpu/cpu_system,metrics/cpu/cpu_user,metrics/memory/mem_total,metrics/memory/mem_free&minimal_response=true',
 
+  /**
+   * @type {string}
+   */
   loadHeatmapsUrlParams: function() {
     var serviceName = this.get('content.serviceName');
     if (serviceName) {
@@ -86,23 +89,24 @@ App.MainChartsHeatmapController = Em.Controller.extend(App.WidgetSectionMixin, {
    * @param {Array} allHeatmaps
    * @return {Array}
    */
-  categorizeByServiceName: function(allHeatmaps) {
+  categorizeByServiceName: function (allHeatmaps) {
     var categories = [];
-    allHeatmaps.forEach(function(_heatmap){
-    var serviceNames = JSON.parse(_heatmap.metrics).mapProperty('service_name').uniq();
-      serviceNames.forEach(function(_serviceName){
-        var category = categories.findProperty('serviceName',_serviceName);
+
+    allHeatmaps.forEach(function (_heatmap) {
+      var serviceNames = JSON.parse(_heatmap.metrics).mapProperty('service_name').uniq();
+      serviceNames.forEach(function (_serviceName) {
+        var category = categories.findProperty('serviceName', _serviceName);
         if (!category) {
           categories.pushObject(Em.Object.create({
             serviceName: _serviceName,
-            displayName: _serviceName === 'STACK' ? 'Host' : App.StackService.find().findProperty('serviceName',_serviceName).get('displayName'),
+            displayName: _serviceName === 'STACK' ? 'Host' : App.format.role(_serviceName),
             heatmaps: [_heatmap]
           }));
         } else {
           category.get('heatmaps').pushObject(_heatmap);
         }
-      },this);
-    },this);
+      }, this);
+    }, this);
     return categories;
   },
 
@@ -174,8 +178,13 @@ App.MainChartsHeatmapController = Em.Controller.extend(App.WidgetSectionMixin, {
     this.set('racks', racks);
   },
 
+  /**
+   * @param {Array} hosts
+   * @returns {Object} rackMap
+   */
   indexByRackId: function (hosts) {
     var rackMap = {};
+
     hosts.forEach(function (host) {
       var rackId = host.rack;
       if(!rackMap[rackId]) {
@@ -192,6 +201,11 @@ App.MainChartsHeatmapController = Em.Controller.extend(App.WidgetSectionMixin, {
     return rackMap;
   },
 
+  /**
+   *
+   * @param {Object} rackMap
+   * @returns {Array} racks
+   */
   toList: function (rackMap) {
     var racks = [];
     var i = 0;

+ 1 - 5
ambari-web/app/models/alerts/alert_group.js

@@ -55,11 +55,7 @@ App.AlertGroup = DS.Model.extend({
    * @type {string}
    */
   displayName: function () {
-    var name = this.get('name');
-    if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
-      var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
-      name = name.substring(0, middle) + "..." + name.substring(name.length - middle);
-    }
+    var name = App.config.truncateGroupName(this.get('name'));
     return this.get('default') ? (name + ' Default') : name;
   }.property('name', 'default'),
 

+ 10 - 9
ambari-web/app/models/configs/config_group.js

@@ -63,9 +63,7 @@ App.ServiceConfigGroup = DS.Model.extend({
    * defines if group is default
    * @type {boolean}
    */
-  isDefault: function() {
-    return this.get('configGroupId') == "-1";
-  }.property('configGroupId'),
+  isDefault: Em.computed.equal('configGroupId', '-1'),
 
   /**
    * list of group names that shows which config
@@ -91,17 +89,13 @@ App.ServiceConfigGroup = DS.Model.extend({
   childConfigGroups: DS.hasMany('App.ServiceConfigGroup'),
 
   hash: DS.attr('string'),
+
   /**
    * Provides a display friendly name. This includes trimming
    * names to a certain length.
    */
   displayName: function () {
-    var name = this.get('name');
-    if (name && name.length>App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
-      var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
-      name = name.substring(0, middle) + "..." + name.substring(name.length-middle);
-    }
-    return name;
+    return App.config.truncateGroupName(this.get('name'));
   }.property('name'),
 
   /**
@@ -112,6 +106,7 @@ App.ServiceConfigGroup = DS.Model.extend({
   /**
    * Provides hosts which are available for inclusion in
    * non-default configuration groups.
+   * @type {Array}
    */
   availableHosts: function () {
     if (this.get('isDefault')) return [];
@@ -131,6 +126,9 @@ App.ServiceConfigGroup = DS.Model.extend({
     return availableHosts;
   }.property('isDefault', 'parentConfigGroup', 'childConfigGroups', 'parentConfigGroup.hosts.@each', 'clusterHosts'),
 
+  /**
+   * @type {boolean}
+   */
   isAddHostsDisabled: Em.computed.or('isDefault', '!availableHosts.length'),
 
   /**
@@ -138,6 +136,9 @@ App.ServiceConfigGroup = DS.Model.extend({
    */
   properties: DS.attr('array', {defaultValue: []}),
 
+  /**
+   * @type {string}
+   */
   propertiesList: function () {
     var result = '';
 

+ 57 - 4
ambari-web/app/models/configs/service_config_version.js

@@ -22,6 +22,8 @@ var dateUtil = require('utils/date/date');
 
 
 App.ServiceConfigVersion = DS.Model.extend({
+  MAX_AUTHOR_LENGTH: 20,
+  MAX_NOTES_LENGTH: 80,
   serviceName: DS.attr('string'),
   displayName: Em.computed.formatRole('serviceName'),
   groupName: DS.attr('string'),
@@ -41,36 +43,79 @@ App.ServiceConfigVersion = DS.Model.extend({
   canBeMadeCurrent: Em.computed.and('isCompatible', '!isCurrent'),
   isDefault: Em.computed.equal('groupName', 'default'),
   currentTooltip: Em.computed.i18nFormat('dashboard.configHistory.table.current.tooltip', 'displayName', 'configGroupName'),
+
+  /**
+   * @type {string}
+   */
   configGroupName: function () {
     return this.get('isDefault') ? Em.I18n.t('common.default') : this.get('groupName');
   }.property('groupName','isDefault'),
+
+  /**
+   * @type {string}
+   */
   authorFormatted: function () {
     var author = this.get('author');
     if (author) {
-      return author.length > 20 ? author.slice(0, 20) + '...' : author;
+      return author.length > this.get('MAX_AUTHOR_LENGTH') ? author.slice(0, this.get('MAX_AUTHOR_LENGTH')) + '...' : author;
     }
   }.property('author'),
+
+  /**
+   * @type {string}
+   */
   fullNotes: function () {
-    return (typeof this.get('notes') === 'string') ? this.get('notes') || Em.I18n.t('dashboard.configHistory.table.notes.no') : Em.I18n.t('dashboard.configHistory.table.notes.no');
+    return (typeof this.get('notes') === 'string') ?
+           (this.get('notes') || Em.I18n.t('dashboard.configHistory.table.notes.no')) :
+           Em.I18n.t('dashboard.configHistory.table.notes.no');
   }.property('notes'),
+
+  /**
+   * @type {string}
+   */
   briefNotes: function () {
-    return this.get('fullNotes').slice(0, 81);
+    return this.get('fullNotes').slice(0, (this.get('MAX_NOTES_LENGTH') + 1));
   }.property('fullNotes'),
+
+  /**
+   * @type {boolean}
+   */
   moreNotesExists: function () {
-    return (typeof this.get('notes') === 'string') ?  this.get('notes').length > 80 : false;
+    return (typeof this.get('notes') === 'string') && this.get('notes').length > this.get('MAX_NOTES_LENGTH');
   }.property('notes'),
+
+  /**
+   * @type {string}
+   */
   versionText: Em.computed.i18nFormat('dashboard.configHistory.table.version.versionText', 'version'),
+
+  /**
+   * @type {string}
+   */
   makeCurrentButtonText: Em.computed.i18nFormat('dashboard.configHistory.info-bar.revert.versionButton', 'versionText'),
+
+  /**
+   * @type {string}
+   */
   createdDate: function () {
     return dateUtil.dateFormat(this.get('createTime'));
   }.property('createTime'),
+
+  /**
+   * @type {string}
+   */
   timeSinceCreated: function () {
     return $.timeago(this.get('rawCreateTime'));
   }.property('rawCreateTime'),
+
   /**
    * determine whether ServiceConfigVersion is requested from server
    */
   isRequested: DS.attr('boolean'),
+
+  /**
+   * @type {boolean}
+   */
   isRestartRequired: function () {
     if (this.get('service.isRestartRequired') && this.get('isCurrent')) {
       var hostNames = this.get('hosts');
@@ -83,6 +128,10 @@ App.ServiceConfigVersion = DS.Model.extend({
     }
     return false;
   }.property('service.isRestartRequired','isDefault', 'isCurrent', 'hosts', 'service.restartRequiredHostsAndComponents', 'router.mainServiceInfoConfigsController.configGroups'),
+
+  /**
+   * {{view: string, compare: string, revert: string}} disabledActionMessages
+   */
   disabledActionMessages: function () {
     return {
       view: (this.get('isDisplayed')) ? Em.I18n.t('dashboard.configHistory.info-bar.view.button.disabled') : '',
@@ -90,6 +139,10 @@ App.ServiceConfigVersion = DS.Model.extend({
       revert: (this.get('isCurrent')) ? Em.I18n.t('dashboard.configHistory.info-bar.revert.button.disabled') : ''
     }
   }.property('isDisplayed', 'isCurrent'),
+
+  /**
+   * {{view: (string|boolean), compare: (string|boolean), revert: (string|boolean)}} disabledActionAttr
+   */
   disabledActionAttr: function () {
     return {
       view: (this.get('isDisplayed')) ? 'disabled' : false,

+ 11 - 0
ambari-web/app/utils/config.js

@@ -50,6 +50,17 @@ App.config = Em.Object.create({
     return this.get('filenameExceptions').contains(fileName) ? fileName : fileName + '.xml';
   },
 
+  /**
+   * truncate Config Group name to <CONFIG_GROUP_NAME_MAX_LENGTH> length and paste "..." in the middle
+   */
+  truncateGroupName: function (name) {
+    if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
+      var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
+      name = name.substring(0, middle) + "..." + name.substring(name.length - middle);
+    }
+    return name;
+  },
+
   /**
    * Check if Hive installation with new MySQL database created via Ambari is allowed
    * @param osFamily

+ 11 - 14
ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js

@@ -45,7 +45,7 @@ App.NotificationsConfigsView = App.ServiceConfigsByCategoryView.extend({
    * Determines if notification configs should be disabled
    * @type {boolean}
    */
-  configsAreDisabled: true,
+  configsAreDisabled: Em.computed.equal('createNotification', 'no'),
 
   /**
    * Config with flag for user auth in the notification
@@ -63,8 +63,7 @@ App.NotificationsConfigsView = App.ServiceConfigsByCategoryView.extend({
     this.set('createNotification', this.get('categoryConfigsAll').findProperty('name', 'create_notification').get('value'));
     this.set('tlsOrSsl', this.get('categoryConfigsAll').findProperty('name', 'mail.smtp.starttls.enable').get('value') ? 'tls' : 'ssl');
     var smtp_use_auth = this.get('categoryConfigsAll').findProperty('name', 'smtp_use_auth');
-    var v = (smtp_use_auth.get('value') == 'true');
-    smtp_use_auth.set('value', v);
+    smtp_use_auth.set('value', Boolean(smtp_use_auth.get('value') === 'true'));
     this.updateCategoryConfigs();
   },
 
@@ -85,15 +84,15 @@ App.NotificationsConfigsView = App.ServiceConfigsByCategoryView.extend({
    */
   onUseAuthConfigChange: function () {
     var configsToUpdate = ['ambari.dispatch.credential.username', 'ambari.dispatch.credential.password'],
-      useAuthConfigValue = this.get('useAuthConfig.value'),
-      useAuthConfigIsEditable = this.get('useAuthConfig.isEditable'),
-      self = this;
+        useAuthConfigValue = this.get('useAuthConfig.value'),
+        useAuthConfigIsEditable = this.get('useAuthConfig.isEditable');
+
     this.getWithDefault('categoryConfigs', []).forEach(function (config) {
       if (configsToUpdate.contains(config.get('name'))) {
         var flag = useAuthConfigIsEditable ? useAuthConfigValue : false;
-        self.updateConfig(config, flag);
+        this.updateConfig(config, flag);
       }
-    });
+    }, this);
   }.observes('useAuthConfig.value'),
 
   /**
@@ -103,13 +102,11 @@ App.NotificationsConfigsView = App.ServiceConfigsByCategoryView.extend({
    * @method updateCategoryConfigs
    */
   updateCategoryConfigs: function () {
-    var createNotification = this.get('createNotification'),
-      self = this;
+    var createNotification = this.get('createNotification');
+
     this.getWithDefault('categoryConfigs', []).forEach(function (config) {
-      var flag = (createNotification == 'yes');
-      self.updateConfig(config, flag);
-    });
-    this.set('configsAreDisabled', this.get('createNotification') == 'no');
+      this.updateConfig(config, Boolean(createNotification === 'yes'));
+    }, this);
     this.onUseAuthConfigChange();
     this.get('categoryConfigsAll').findProperty('name', 'create_notification').set('value', createNotification);
   }.observes('createNotification'),

+ 56 - 37
ambari-web/app/views/main/charts/heatmap/heatmap_host.js

@@ -43,44 +43,79 @@ App.MainChartsHeatmapHostView = Em.View.extend({
    * @this App.MainChartsHeatmapHostView
    */
   mouseEnter: function (event) {
-    var host = this.get('content');
-    var view = App.MainChartsHeatmapHostDetailView.create();
-    var self = this;
-    var nonClientComponents = App.get('components.slaves').concat(App.get('components.masters'));
+    var host = this.get('content'),
+        view = App.MainChartsHeatmapHostDetailView.create();
 
-    $.each(view.get('details'), function (i) {
+    Object.keys(view.get('details')).forEach(function (i) {
       var val = host[i];
 
       switch (i) {
         case 'diskUsage':
-          val = self.getUsage(host, 'diskTotal', 'diskFree');
+          val = this.getUsage(host['diskTotal'], host['diskFree']);
           break;
         case 'cpuUsage':
-          val = 0;
-          if (Number.isFinite(host.cpuSystem) && Number.isFinite(host.cpuUser)) {
-            val = host.cpuSystem + host.cpuUser;
-          }
-          val = val.toFixed(1);
+          val = this.getCpuUsage(host['cpuSystem'], host['cpuUser']);
           break;
         case 'memoryUsage':
-          val = self.getUsage(host, 'memTotal', 'memFree');
+          val = this.getUsage(host['memTotal'], host['memFree']);
           break;
         case 'hostComponents':
-          val = [];
-          host[i].forEach(function (componentName) {
-            if (nonClientComponents.contains(componentName)) {
-              val.push(App.format.role(componentName));
-            }
-          }, this);
-          val = val.join(', ')
+          val = this.getHostComponents(host[i]);
       }
 
       view.set('details.' + i, val);
-    });
+    }, this);
     this.setMetric(view, host);
     this.openDetailsBlock(event);
   },
 
+  /**
+   * get relative usage of metric in percents
+   * @param {number} total
+   * @param {number} free
+   * @return {string}
+   */
+  getUsage: function (total, free) {
+    var result = 0;
+
+    if (Number.isFinite(total) && Number.isFinite(free) && total > 0) {
+      result = ((total - free) / total) * 100;
+    }
+    return result.toFixed(1);
+  },
+
+  /**
+   * get CPU usage
+   * @param {number} cpuSystem
+   * @param {number} cpuUser
+   * @returns {string}
+   */
+  getCpuUsage: function (cpuSystem, cpuUser) {
+    var result = 0;
+
+    if (Number.isFinite(cpuSystem) && Number.isFinite(cpuUser)) {
+      result = cpuSystem + cpuUser;
+    }
+    return result.toFixed(1);
+  },
+
+  /**
+   * get non-client host-components of host
+   * @param {Array} components
+   * @returns {string}
+   */
+  getHostComponents: function (components) {
+    var nonClientComponents = App.get('components.slaves').concat(App.get('components.masters'));
+    var result = [];
+
+    components.forEach(function (componentName) {
+      if (nonClientComponents.contains(componentName)) {
+        result.push(App.format.role(componentName));
+      }
+    }, this);
+    return result.join(', ')
+  },
+
   /**
    * show tooltip with host's details
    */
@@ -108,7 +143,7 @@ App.MainChartsHeatmapHostView = Em.View.extend({
         if (Em.isNone(value)) {
           value = this.t('charts.heatmap.unknown');
         } else {
-          if (metricName == 'Garbage Collection Time') {
+          if (metricName === 'Garbage Collection Time') {
             value = date.timingFormat(parseInt(value));
           } else {
             if (isNaN(value)) {
@@ -123,22 +158,6 @@ App.MainChartsHeatmapHostView = Em.View.extend({
       }
     }
   },
-  /**
-   * get relative usage of metric in percents
-   * @param item
-   * @param totalProperty
-   * @param freeProperty
-   * @return {String}
-   */
-  getUsage: function (item, totalProperty, freeProperty) {
-    var result = 0;
-    var total = item[totalProperty];
-
-    if (Number.isFinite(total) && Number.isFinite(item[freeProperty]) && total > 0) {
-      result = ((total - item[freeProperty]) / total) * 100;
-    }
-    return result.toFixed(1);
-  },
 
   /**
    * hide Host details block

+ 9 - 6
ambari-web/app/views/main/charts/heatmap/heatmap_host_detail.js

@@ -20,11 +20,14 @@ var App = require('app');
 
 App.MainChartsHeatmapHostDetailView = Em.View.extend({
   templateName: require('templates/main/charts/heatmap/heatmap_host_detail'),
-  /** @private */ classNames:['heatmap_host_details'],
-  /** @private */ elementId:'heatmapDetailsBlock',
-  /** @private */ details:{
-    hostName:'test node',
-    publicHostName:'test node',
+  /** @private */ classNames: ['heatmap_host_details'],
+  /** @private */ elementId: 'heatmapDetailsBlock',
+  /**
+   * @private
+   */
+  details: {
+    hostName: 'test node',
+    publicHostName: 'test node',
     osType: 'OS',
     ip: '192.168.0.0',
     rack: '/default_rack',
@@ -33,6 +36,6 @@ App.MainChartsHeatmapHostDetailView = Em.View.extend({
     diskUsage: '10',
     cpuUsage: '10',
     memoryUsage: '10',
-    hostComponents: 'host components'
+    hostComponents: []
   }
 });

+ 1 - 1
ambari-web/app/views/wizard/step3/hostWarningPopupBody_view.js

@@ -132,7 +132,7 @@ App.WizardStep3HostWarningPopupBody = Em.View.extend({
     var warningsByHost = this.get('warningsByHost');
     if (Em.isNone(warningsByHost)) return [];
     var category = warningsByHost.findProperty('name', this.get('category'));
-    return Em.isNone(category) ? []: category.warnings;
+    return Em.isNone(category) ? [] : category.warnings || [];
   }.property('warningsByHost', 'category'),
 
   /**

+ 341 - 18
ambari-web/test/controllers/main/charts/heatmap_test.js

@@ -18,6 +18,7 @@
 
 
 var App = require('app');
+var controller;
 require('models/rack');
 require('controllers/main/charts/heatmap');
 
@@ -27,26 +28,34 @@ function getController() {
 
 describe('MainChartsHeatmapController', function () {
 
+  before(function () {
+    controller = getController();
+  });
+
   App.TestAliases.testAsComputedAlias(getController(), 'activeWidget', 'widgets.firstObject', 'object');
 
   App.TestAliases.testAsComputedAlias(getController(), 'hostToSlotMap', 'selectedMetric.hostToSlotMap', 'object');
 
   describe('#validation()', function () {
-    var controller = App.MainChartsHeatmapController.create({
-      allMetrics: [],
-      selectedMetric: Ember.Object.create({maximumValue: 100})
+
+    beforeEach(function() {
+      controller.setProperties({
+        allMetrics: [],
+        selectedMetric: Ember.Object.create({maximumValue: 100})
+      });
     });
+
     it('should set maximumValue if inputMaximum consists only of digits', function () {
       controller.set("inputMaximum", 5);
       expect(controller.get('selectedMetric.maximumValue')).to.equal(5);
     });
     it('should not set maximumValue if inputMaximum consists not only of digits', function () {
       controller.set("inputMaximum", 'qwerty');
-      expect(controller.get('selectedMetric.maximumValue')).to.equal(5);
+      expect(controller.get('selectedMetric.maximumValue')).to.equal(100);
     });
     it('should not set maximumValue if inputMaximum consists not only of digits', function () {
       controller.set("inputMaximum", '100%');
-      expect(controller.get('selectedMetric.maximumValue')).to.equal(5);
+      expect(controller.get('selectedMetric.maximumValue')).to.equal(100);
     });
     it('should set maximumValue if inputMaximum consists only of digits', function () {
       controller.set("inputMaximum", 1000);
@@ -63,22 +72,21 @@ describe('MainChartsHeatmapController', function () {
           }
         }
       });
+      controller.setProperties({
+        activeWidgetLayout: Em.Object.create({
+          displayName: 'widget',
+          id: '1',
+          scope: 'CLUSTER',
+          layoutName: 'defualt_layout',
+          sectionName: 'default_section'
+        })
+      });
     });
 
     afterEach(function () {
       App.ajax.send.restore();
     });
 
-    var controller = App.MainChartsHeatmapController.create({
-      activeWidgetLayout: Em.Object.create({
-        displayName: 'widget',
-        id: '1',
-        scope: 'CLUSTER',
-        layoutName: 'defualt_layout',
-        sectionName: 'default_section'
-      })
-    });
-
     it('should call App.ajax', function () {
       controller.showHeatMapMetric({context:{id: 2}});
       expect(App.ajax.send.called).to.be.true;
@@ -86,10 +94,14 @@ describe('MainChartsHeatmapController', function () {
   });
 
   describe('#rackClass', function () {
-    var controller = App.MainChartsHeatmapController.create({
-      allMetrics: [],
-      racks: [1]
+
+    beforeEach(function () {
+      controller.setProperties({
+        allMetrics: [],
+        racks: [1]
+      });
     });
+
     it('should return "span12" for 1 cluster rack', function () {
       expect(controller.get('rackClass')).to.equal('span12');
     });
@@ -102,5 +114,316 @@ describe('MainChartsHeatmapController', function () {
       expect(controller.get('rackClass')).to.equal('span4');
     });
   });
+
+  describe("#loadHeatmapsUrlParams", function() {
+
+    it("content.serviceName is null", function() {
+      controller.set('content', Em.Object.create({serviceName: null}));
+      expect(controller.get('loadHeatmapsUrlParams')).to.equal('WidgetInfo/widget_type=HEATMAP&WidgetInfo/scope=CLUSTER&fields=WidgetInfo/metrics');
+    });
+
+    it("content.serviceName is correct", function() {
+      controller.set('content', Em.Object.create({serviceName: 'S1'}));
+      expect(controller.get('loadHeatmapsUrlParams')).to.equal('WidgetInfo/widget_type=HEATMAP&WidgetInfo/scope=CLUSTER&WidgetInfo/metrics.matches(.*\"service_name\":\"S1\".*)&fields=WidgetInfo/metrics');
+    });
+  });
+
+  describe("#loadPageData()", function() {
+    var allHeatmapData = {
+      items: [
+        {
+          WidgetInfo: 'info'
+        }
+      ]
+    };
+
+    beforeEach(function(){
+      sinon.stub(controller, 'loadRacks').returns({
+        always: function(callback) {
+          callback();
+        }
+      });
+      sinon.stub(controller, 'getAllHeatMaps').returns({
+        done: function(callback) {
+          callback(allHeatmapData);
+        }
+      });
+      sinon.stub(controller, 'resetPageData');
+      sinon.stub(controller, 'categorizeByServiceName').returns('categories');
+      sinon.stub(controller, 'getActiveWidgetLayout');
+      controller.get('allHeatmaps').clear();
+      controller.loadPageData();
+    });
+
+    afterEach(function() {
+      controller.loadRacks.restore();
+      controller.resetPageData.restore();
+      controller.getAllHeatMaps.restore();
+      controller.categorizeByServiceName.restore();
+      controller.getActiveWidgetLayout.restore();
+    });
+
+    it("loadRacks() should be called", function() {
+      expect(controller.loadRacks.calledOnce).to.be.true;
+      expect(controller.resetPageData.calledOnce).to.be.true;
+    });
+
+    it("getAllHeatMaps() should be called", function() {
+      expect(controller.getAllHeatMaps.calledOnce).to.be.true;
+      expect(controller.get('isLoaded')).to.be.true;
+      expect(controller.get('allHeatmaps')[0]).to.equal('info')
+    });
+
+    it("categorizeByServiceName() should be called", function() {
+      expect(controller.categorizeByServiceName.calledOnce).to.be.true;
+      expect(controller.get('heatmapCategories')).to.equal('categories');
+    });
+
+    it("getActiveWidgetLayout() should be called", function() {
+      expect(controller.getActiveWidgetLayout.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#categorizeByServiceName()", function() {
+
+    beforeEach(function() {
+      sinon.stub(App.format, 'role').returns('S1');
+    });
+
+    afterEach(function() {
+      App.format.role.restore();
+    });
+
+    it("single category", function() {
+      var allHeatmaps = [
+        {
+          metrics: JSON.stringify([{service_name: 'S1'}])
+        }
+      ];
+      var categories = controller.categorizeByServiceName(allHeatmaps);
+      expect(categories[0].get('serviceName')).to.equal('S1');
+      expect(categories[0].get('displayName')).to.equal('S1');
+      expect(categories[0].get('heatmaps')).to.eql(allHeatmaps);
+    });
+
+    it("two categories", function() {
+      var allHeatmaps = [
+        {
+          metrics: JSON.stringify([{service_name: 'S1'}])
+        },
+        {
+          metrics: JSON.stringify([{service_name: 'S1'}])
+        }
+      ];
+      var categories = controller.categorizeByServiceName(allHeatmaps);
+      expect(categories[0].get('serviceName')).to.equal('S1');
+      expect(categories[0].get('displayName')).to.equal('S1');
+      expect(categories[0].get('heatmaps')[0]).to.eql(allHeatmaps[0]);
+      expect(categories[0].get('heatmaps')[1]).to.eql(allHeatmaps[1]);
+    });
+  });
+
+  describe("#resetPageData()", function() {
+
+    it("should clean heatmapCategories and allHeatmaps", function() {
+      controller.set('heatmapCategories', [{}]);
+      controller.set('allHeatmaps', [{}]);
+      controller.resetPageData();
+      expect(controller.get('heatmapCategories')).to.be.empty;
+      expect(controller.get('allHeatmaps')).to.be.empty;
+    });
+  });
+
+  describe("#getAllHeatMaps()", function() {
+
+    beforeEach(function() {
+      sinon.stub(App.ajax, 'send');
+    });
+
+    afterEach(function() {
+      App.ajax.send.restore();
+    });
+
+    it("should call App.ajax.send", function() {
+      controller.reopen({
+        loadHeatmapsUrlParams: 'url',
+        sectionName: 's1'
+      });
+      controller.getAllHeatMaps();
+      expect(App.ajax.send.calledWith({
+        name: 'widgets.get',
+        sender: controller,
+        data: {
+          urlParams: 'url',
+          sectionName: 's1'
+        }
+      })).to.be.true;
+    });
+  });
+
+  describe("#loadRacks()", function() {
+
+    beforeEach(function() {
+      sinon.stub(App.ajax, 'send');
+    });
+
+    afterEach(function() {
+      App.ajax.send.restore();
+    });
+
+    it("should call App.ajax.send", function() {
+      controller.reopen({
+        loadRacksUrlParams: 'url'
+      });
+      controller.loadRacks();
+      expect(App.ajax.send.calledWith({
+        name: 'hosts.heatmaps',
+        sender: controller,
+        data: {
+          urlParams: 'url'
+        },
+        success: 'loadRacksSuccessCallback'
+      })).to.be.true;
+
+    });
+  });
+
+  describe("#loadRacksSuccessCallback()", function() {
+
+    var data = {
+      items: [
+        {
+          Hosts: {
+            host_name: 'host1',
+            public_host_name: 'host1',
+            os_type: 'os1',
+            ip: 'ip1',
+            rack_info: 'info'
+          },
+          host_components: [
+            {
+              HostRoles: {
+                component_name: 'c1'
+              }
+            }
+          ]
+        }
+      ]
+    };
+
+    beforeEach(function() {
+      sinon.stub(controller, 'indexByRackId').returns({rack: {}});
+      sinon.stub(controller, 'toList').returns(['rack']);
+      controller.loadRacksSuccessCallback(data);
+    });
+
+    afterEach(function(){
+      controller.indexByRackId.restore();
+      controller.toList.restore();
+    });
+
+    it("indexByRackId should be called", function() {
+      expect(controller.indexByRackId.calledWith([{
+        hostName: 'host1',
+        publicHostName: 'host1',
+        osType: 'os1',
+        ip: 'ip1',
+        rack: 'info',
+        diskTotal: 0,
+        diskFree: 0,
+        cpuSystem: 0,
+        cpuUser: 0,
+        memTotal: 0,
+        memFree: 0,
+        hostComponents: ['c1']
+      }])).to.be.true;
+    });
+
+    it("toList should be called", function() {
+      expect(controller.toList.calledWith({rack: {}})).to.be.true;
+      expect(controller.get('rackMap')).to.eql({rack: {}});
+      expect(controller.get('racks')).to.eql(['rack']);
+    });
+  });
+
+  describe("#indexByRackId()", function() {
+
+    it("should return rack map", function() {
+      var hosts = [
+        {rack: 'r1'},
+        {rack: 'r1'}
+      ];
+      var rackMap = controller.indexByRackId(hosts);
+      expect(rackMap['r1'].name).to.equal('r1');
+      expect(rackMap['r1'].rackId).to.equal('r1');
+      expect(rackMap['r1'].hosts).to.eql([{rack: 'r1'}, {rack: 'r1'}]);
+    });
+  });
+
+  describe("#toList()", function() {
+    it("", function() {
+      var rackMap = {'r1': {
+        name: 'r1',
+        rackId: 'r1',
+        hosts: [{rack: 'r1'}, {rack: 'r1'}]
+      }};
+      expect(controller.toList(rackMap)).to.eql([Em.Object.create({
+        name: 'r1',
+        rackId: 'r1',
+        hosts: [{rack: 'r1'}, {rack: 'r1'}],
+        isLoaded: false,
+        index: 0
+      })]);
+    });
+  });
+
+  describe("#addRackView()", function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'displayAllRacks');
+    });
+
+    afterEach(function() {
+      controller.displayAllRacks.restore();
+    });
+
+    it("displayAllRacks should be called", function() {
+      controller.set('racks', [{}]);
+      controller.set('rackViews', []);
+      controller.addRackView({});
+      expect(controller.displayAllRacks.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#displayAllRacks", function() {
+    var rackView = {
+      displayHosts: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.spy(controller, 'displayAllRacks');
+      sinon.spy(rackView, 'displayHosts');
+    });
+
+    afterEach(function() {
+      controller.displayAllRacks.restore();
+      rackView.displayHosts.restore();
+    });
+
+    it("displayAllRacks should be called again", function() {
+      controller.set('rackViews', [rackView]);
+      controller.displayAllRacks();
+      expect(controller.displayAllRacks.calledTwice).to.be.true;
+      expect(rackView.displayHosts.calledOnce).to.be.true;
+    });
+
+    it("displayAllRacks should not be called again", function() {
+      controller.set('rackViews', []);
+      controller.displayAllRacks();
+      expect(controller.displayAllRacks.calledOnce).to.be.true;
+    });
+  });
+
+
 });
 

+ 3 - 8
ambari-web/test/controllers/main/host/details_test.js

@@ -1816,20 +1816,15 @@ describe('App.MainHostDetailsController', function () {
     });
 
     it('serviceActiveComponents is correct', function () {
+      var components = [{}];
       controller.reopen({
-        serviceActiveComponents: [
-          {}
-        ]
+        serviceActiveComponents: components
       });
 
       var popup = controller.doRestartAllComponents();
       expect(App.showConfirmationPopup.calledOnce).to.be.true;
       popup.onPrimary();
-      expect(batchUtils.restartHostComponents.calledWith(
-          [
-            {}
-          ])
-      ).to.be.true;
+      expect(batchUtils.restartHostComponents.calledWith(components)).to.be.true;
     });
     it('serviceActiveComponents is correct, NAMENODE started', function () {
       controller.reopen({

+ 5 - 1
ambari-web/test/init_test.js

@@ -47,4 +47,8 @@ if (!Function.prototype.bind) {
 
     return fBound;
   };
-}
+}
+
+Number.isFinite = Number.isFinite || function(value) {
+  return typeof value === 'number' && isFinite(value);
+};

+ 138 - 0
ambari-web/test/models/configs/config_group_test.js

@@ -0,0 +1,138 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var model;
+
+function getModel() {
+  return App.ServiceConfigGroup.createRecord({
+    parentConfigGroup: Em.Object.create({
+      hosts: []
+    })
+  });
+}
+
+describe('App.ServiceConfigGroup', function () {
+
+  beforeEach(function () {
+    model = getModel();
+  });
+
+  App.TestAliases.testAsComputedEqual(getModel(), 'isDefault', 'configGroupId', '-1');
+
+  describe("#displayName", function() {
+
+    before(function () {
+      sinon.stub(App.config, 'truncateGroupName');
+    });
+    after(function () {
+      App.config.truncateGroupName.restore();
+    });
+
+    it("App.config.truncateGroupName should be called", function() {
+      model.set('name', 'group1');
+      model.get('displayName');
+      expect(App.config.truncateGroupName.called).to.be.true;
+    });
+  });
+
+  describe("#availableHosts", function() {
+
+    it("default group", function() {
+      model.reopen({
+        isDefault: true
+      });
+      expect(model.get('availableHosts')).to.be.empty;
+    });
+
+    it("no cluster hosts", function() {
+      model.reopen({
+        isDefault: false,
+        clusterHosts: []
+      });
+      expect(model.get('availableHosts')).to.be.empty;
+    });
+
+    it("cluster hosts used", function() {
+      model.reopen({
+        isDefault: false,
+        clusterHosts: [
+          Em.Object.create({id: 'g1'})
+        ]
+      });
+      expect(model.get('availableHosts')).to.be.empty;
+    });
+
+    it("cluster hosts not used", function() {
+      var host = Em.Object.create({
+        id: 'g1',
+        hostComponents: [{componentName: 'c1'}]
+      });
+
+      model.reopen({
+        isDefault: false,
+        clusterHosts: [host]
+      });
+      model.set('parentConfigGroup.hosts', ['g1']);
+      expect(model.get('availableHosts')).to.not.be.empty;
+      expect(model.get('availableHosts')[0].get('selected')).to.be.false;
+      expect(model.get('availableHosts')[0].get('hostComponentNames')).to.eql(['c1']);
+      expect(model.get('availableHosts')[0].get('host')).to.eql(host);
+    });
+  });
+
+  describe("#propertiesList", function() {
+
+    it("properties is null", function() {
+      model.set('properties', null);
+      expect(model.get('propertiesList')).to.be.empty;
+    });
+
+    it("properties is correct", function() {
+      model.set('properties', [
+        {
+          name: 'p1',
+          value: 'v1'
+        }
+      ]);
+      expect(model.get('propertiesList')).to.equal('p1 : v1<br/>');
+    });
+  });
+
+  describe("#getParentConfigGroupId()", function () {
+
+    before(function () {
+      sinon.stub(App.ServiceConfigGroup, 'groupId');
+    });
+    after(function () {
+      App.ServiceConfigGroup.groupId.restore();
+    });
+
+    it("App.ServiceConfigGroup.groupId should be called", function () {
+      App.ServiceConfigGroup.getParentConfigGroupId('S1');
+      expect(App.ServiceConfigGroup.groupId.calledWith('S1', 'Default')).to.be.true;
+    });
+  });
+
+  describe("#groupId()", function () {
+
+    it("should return group id", function () {
+      expect(App.ServiceConfigGroup.groupId('S1', 'g1')).to.be.equal('S1_g1');
+    });
+  });
+});

+ 287 - 1
ambari-web/test/models/configs/service_config_version_test.js

@@ -31,6 +31,8 @@ describe('App.ServiceConfigVersion', function () {
     model = getModel();
   });
 
+  App.TestAliases.testAsComputedAnd(getModel(), 'canBeMadeCurrent', ['isCompatible', '!isCurrent']);
+
   describe('#authorFormatted', function () {
 
     var cases = [
@@ -55,6 +57,290 @@ describe('App.ServiceConfigVersion', function () {
 
   });
 
-  App.TestAliases.testAsComputedAnd(getModel(), 'canBeMadeCurrent', ['isCompatible', '!isCurrent']);
+  describe("#configGroupName", function() {
+
+    it("default group", function() {
+      model.reopen({
+        groupName: 'g1',
+        isDefault: false
+      });
+      expect(model.get('configGroupName')).to.equal('g1');
+    });
+
+    it("default group", function() {
+      model.reopen({
+        isDefault: true
+      });
+      expect(model.get('configGroupName')).to.equal(Em.I18n.t('common.default'));
+    });
+
+  });
+
+  describe("#fullNotes", function() {
+
+    it("notes is null", function() {
+      model.set('notes', null);
+      expect(model.get('fullNotes')).to.equal(Em.I18n.t('dashboard.configHistory.table.notes.no'));
+    });
+
+    it("notes is empty", function() {
+      model.set('notes', "");
+      expect(model.get('fullNotes')).to.equal(Em.I18n.t('dashboard.configHistory.table.notes.no'));
+    });
+
+    it("notes has value", function() {
+      model.set('notes', "notes-value");
+      expect(model.get('fullNotes')).to.equal('notes-value');
+    });
+
+  });
+
+  describe("#briefNotes", function() {
+
+    it("notes shorter than MAX_NOTES_LENGTH", function() {
+      model.reopen({
+        fullNotes: 'short-notes'
+      });
+      expect(model.get('briefNotes')).to.equal('short-notes');
+    });
+
+    it("notes longer than MAX_NOTES_LENGTH", function() {
+      model.reopen({
+        fullNotes: 'long-notes-long-notes-long-notes-long-notes-long-notes-long-notes-long-notes-long-notes' +
+        '-long-notes-long-notes-long-notes-long-notes-long-notes'
+      });
+      expect(model.get('briefNotes')).to.equal('long-notes-long-notes-long-notes-long-notes-long-notes-long-notes-long-notes-long');
+    });
+
+  });
+
+  describe("#moreNotesExists", function() {
+
+    it("notes is null", function() {
+      model.set('notes', null);
+      expect(model.get('moreNotesExists')).to.be.false;
+    });
+
+    it("notes is shorter than MAX_NOTES_LENGTH", function() {
+      model.set('notes', 'short-notes');
+      expect(model.get('moreNotesExists')).to.be.false;
+    });
+
+    it("notes is longer than MAX_NOTES_LENGTH", function() {
+      model.set('notes', 'long-notes-long-notes-long-notes-long-notes-long-notes-long-notes-long-notes-long-notes' +
+      '-long-notes-long-notes-long-notes-long-notes-long-notes');
+      expect(model.get('moreNotesExists')).to.be.true;
+    });
+
+  });
+
+  describe("#createdDate", function() {
+
+    it("should return created date", function() {
+      model.set('createTime', 1450267588961);
+      expect(model.get('createdDate')).to.equal('Wed, Dec 16, 2015 14:06');
+    });
+
+  });
+
+  describe("#timeSinceCreated", function () {
+
+    before(function () {
+      sinon.stub($, 'timeago').returns('timeago');
+    });
+    after(function () {
+      $.timeago.restore()
+    });
+
+    it("should return time since created", function () {
+      model.set('rawCreateTime', 1450267588961);
+      expect(model.get('timeSinceCreated')).to.equal('timeago');
+    });
+
+  });
+
+  describe("#isRestartRequired", function() {
+
+    it("service.isRestartRequired is false", function() {
+      model.set('service', Em.Object.create({
+        isRestartRequired: false
+      }));
+      expect(model.get('isRestartRequired')).to.be.false;
+    });
+
+    it("non-current version", function() {
+      model.set('service', Em.Object.create({
+        isRestartRequired: true
+      }));
+      model.set('isCurrent', false);
+      expect(model.get('isRestartRequired')).to.be.false;
+    });
+
+    it("version has no hosts", function() {
+      model.setProperties({
+        service: Em.Object.create({
+          isRestartRequired: true
+        }),
+        isCurrent: true,
+        hosts: []
+      });
+      expect(model.get('isRestartRequired')).to.be.false;
+    });
+
+    it("version hosts don't need restart", function() {
+      model.setProperties({
+        service: Em.Object.create({
+          isRestartRequired: true,
+          restartRequiredHostsAndComponents: {}
+        }),
+        isCurrent: true,
+        hosts: ['host1']
+      });
+      expect(model.get('isRestartRequired')).to.be.false;
+    });
+
+    it("version hosts need restart", function() {
+      model.setProperties({
+        service: Em.Object.create({
+          isRestartRequired: true,
+          restartRequiredHostsAndComponents: {'host1': {}}
+        }),
+        isCurrent: true,
+        hosts: ['host1']
+      });
+      expect(model.get('isRestartRequired')).to.be.true;
+    });
+
+  });
+
+  describe("#disabledActionMessages", function() {
+    var testCases = [
+      {
+        input: {
+          isDisplayed: false,
+          isCurrent: false
+        },
+        expected: {
+          view: '',
+          compare: '',
+          revert: ''
+        }
+      },
+      {
+        input: {
+          isDisplayed: true,
+          isCurrent: false
+        },
+        expected: {
+          view: Em.I18n.t('dashboard.configHistory.info-bar.view.button.disabled'),
+          compare: Em.I18n.t('dashboard.configHistory.info-bar.compare.button.disabled'),
+          revert: ''
+        }
+      },
+      {
+        input: {
+          isDisplayed: false,
+          isCurrent: true
+        },
+        expected: {
+          view: '',
+          compare: '',
+          revert: Em.I18n.t('dashboard.configHistory.info-bar.revert.button.disabled')
+        }
+      },
+      {
+        input: {
+          isDisplayed: true,
+          isCurrent: true
+        },
+        expected: {
+          view: Em.I18n.t('dashboard.configHistory.info-bar.view.button.disabled'),
+          compare: Em.I18n.t('dashboard.configHistory.info-bar.compare.button.disabled'),
+          revert: Em.I18n.t('dashboard.configHistory.info-bar.revert.button.disabled')
+        }
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("isDisplayed = " + test.input.isDisplayed + ", isCurrent = " + test.input.isCurrent, function() {
+        model.setProperties(test.input);
+        expect(model.get('disabledActionMessages')).to.eql(test.expected);
+      });
+    });
+
+  });
+
+  describe("#disabledActionAttr", function() {
+    var testCases = [
+      {
+        input: {
+          isDisplayed: false,
+          isCurrent: false,
+          isDisabled: false
+        },
+        expected: {
+          view: false,
+          compare: false,
+          revert: false
+        }
+      },
+      {
+        input: {
+          isDisplayed: true,
+          isCurrent: false,
+          isDisabled: false
+        },
+        expected: {
+          view: 'disabled',
+          compare: 'disabled',
+          revert: false
+        }
+      },
+      {
+        input: {
+          isDisplayed: false,
+          isCurrent: false,
+          isDisabled: true
+        },
+        expected: {
+          view: false,
+          compare: 'disabled',
+          revert: 'disabled'
+        }
+      },
+      {
+        input: {
+          isDisplayed: false,
+          isCurrent: true,
+          isDisabled: false
+        },
+        expected: {
+          view: false,
+          compare: false,
+          revert: 'disabled'
+        }
+      },
+      {
+        input: {
+          isDisplayed: true,
+          isCurrent: true,
+          isDisabled: true
+        },
+        expected: {
+          view: 'disabled',
+          compare: 'disabled',
+          revert: 'disabled'
+        }
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("isDisplayed = " + test.input.isDisplayed + ", isCurrent = " + test.input.isCurrent + ", isDisabled = " + test.input.isDisabled, function() {
+        model.setProperties(test.input);
+        expect(model.get('disabledActionAttr')).to.eql(test.expected);
+      });
+    });
+
+  });
 
 });

+ 128 - 0
ambari-web/test/models/configs/stack_config_property_test.js

@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var model;
+
+function getModel() {
+  return App.StackConfigProperty.createRecord();
+}
+
+describe('App.StackConfigProperty', function () {
+
+  beforeEach(function () {
+    model = getModel();
+  });
+
+  describe("#Attributes", function() {
+    var testCases = [
+      {
+        propertyKey: 'type',
+        propertyName: 'displayType',
+        value: 't1',
+        expectedValue: 't1',
+        defaultValue: 'string'
+      },
+      {
+        propertyKey: 'overridable',
+        propertyName: 'isOverridable',
+        value: false,
+        expectedValue: false,
+        defaultValue: true
+      },
+      {
+        propertyKey: 'visible',
+        propertyName: 'isVisible',
+        value: false,
+        expectedValue: false,
+        defaultValue: true
+      },
+      {
+        propertyKey: 'empty_value_valid',
+        propertyName: 'isRequired',
+        value: true,
+        expectedValue: false,
+        defaultValue: true
+      },
+      {
+        propertyKey: 'editable_only_at_install',
+        propertyName: 'isReconfigurable',
+        value: true,
+        expectedValue: false,
+        defaultValue: true
+      },
+      {
+        propertyKey: 'show_property_name',
+        propertyName: 'showLabel',
+        value: false,
+        expectedValue: false,
+        defaultValue: true
+      },
+      {
+        propertyKey: 'read_only',
+        propertyName: 'isEditable',
+        value: false,
+        expectedValue: false,
+        defaultValue: true
+      },
+      {
+        propertyKey: 'unit',
+        propertyName: 'unit',
+        value: 'mb',
+        expectedValue: 'mb',
+        defaultValue: ''
+      }
+    ];
+
+    testCases.forEach(function(test) {
+
+      it("valueAttributes is null, " + test.propertyName + " should be " + test.defaultValue, function() {
+        model.set('valueAttributes', null);
+        expect(model.get(test.propertyName)).to.equal(test.defaultValue);
+      });
+
+      it("valueAttributes is object, " + test.propertyName + " should be " + test.expectedValue, function() {
+        var valueAttributes = {};
+        valueAttributes[test.propertyKey] = test.value;
+        model.set('valueAttributes', valueAttributes);
+        expect(model.get(test.propertyName)).to.equal(test.expectedValue);
+      });
+
+    });
+  });
+
+  describe("#getAttribute()", function() {
+
+    it("valueAttributes is null", function() {
+      model.set('valueAttributes', null);
+      expect(model.getAttribute('attr1', 'defVal')).to.equal('defVal');
+    });
+
+    it("valueAttributes is empty object", function() {
+      model.set('valueAttributes', {});
+      expect(model.getAttribute('attr1', 'defVal')).to.equal('defVal');
+    });
+
+    it("valueAttributes is correct object", function() {
+      model.set('valueAttributes', {attr1: 'val'});
+      expect(model.getAttribute('attr1', 'defVal')).to.equal('val');
+    });
+
+  });
+
+});

+ 15 - 0
ambari-web/test/utils/config_test.js

@@ -1068,4 +1068,19 @@ describe('App.config', function () {
       });
     });
   });
+
+  describe("#truncateGroupName()", function() {
+
+    it("name is empty", function() {
+      expect(App.config.truncateGroupName('')).to.be.empty;
+    });
+
+    it("name has less than max chars", function() {
+      expect(App.config.truncateGroupName('group1')).to.equal('group1');
+    });
+
+    it("name has more than max chars", function() {
+      expect(App.config.truncateGroupName('group_has_more_than_max_characters')).to.equal('group_has...haracters');
+    });
+  });
 });

+ 169 - 0
ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js

@@ -29,6 +29,8 @@ function getView() {
       name: 'name'
     },
     serviceConfigs: [],
+    categoryConfigs: [],
+    categoryConfigsAll: [],
     parentView: Em.View.create({
       filter: '',
       columns: []
@@ -48,10 +50,12 @@ describe('App.NotificationsConfigsView', function () {
 
     beforeEach(function () {
       sinon.stub(view, 'updateCategoryConfigs', Em.K);
+      sinon.stub(view, 'onTlsOrSslChanged');
     });
 
     afterEach(function () {
       view.updateCategoryConfigs.restore();
+      view.onTlsOrSslChanged.restore();
     });
 
     it('should not do nothing if no configs', function () {
@@ -62,6 +66,171 @@ describe('App.NotificationsConfigsView', function () {
 
     });
 
+    it('should update category configs', function () {
+      var configs = [
+        Em.Object.create({
+          name: "create_notification",
+          value: 'yes'
+        }),
+        Em.Object.create({
+          name: 'mail.smtp.starttls.enable',
+          value: false
+        }),
+        Em.Object.create({
+          name: 'smtp_use_auth',
+          value: 'true'
+        })
+      ];
+
+      view.set('categoryConfigsAll', configs);
+      view.didInsertElement();
+      expect(view.get('createNotification')).to.equal('yes');
+      expect(view.get('tlsOrSsl')).to.equal('ssl');
+      expect(configs.findProperty('name', 'smtp_use_auth').get('value')).to.be.true;
+      expect(view.updateCategoryConfigs.called).to.be.true;
+    });
+  });
+
+  describe("#onTlsOrSslChanged()", function () {
+
+    var configs = [
+      Em.Object.create({
+        name: "mail.smtp.starttls.enable",
+        value: 'yes'
+      }),
+      Em.Object.create({
+        name: 'mail.smtp.startssl.enable',
+        value: false
+      })
+    ];
+
+    it("tls", function () {
+      view.set('categoryConfigsAll', configs);
+      view.set('tlsOrSsl', 'tls');
+      view.onTlsOrSslChanged();
+      expect(configs.findProperty('name', 'mail.smtp.starttls.enable').get('value')).to.be.true;
+      expect(configs.findProperty('name', 'mail.smtp.startssl.enable').get('value')).to.be.false;
+    });
+
+    it("ssl", function () {
+      view.set('categoryConfigsAll', configs);
+      view.set('tlsOrSsl', 'ssl');
+      view.onTlsOrSslChanged();
+      expect(configs.findProperty('name', 'mail.smtp.starttls.enable').get('value')).to.be.false;
+      expect(configs.findProperty('name', 'mail.smtp.startssl.enable').get('value')).to.be.true;
+    });
+  });
+
+  describe("#onUseAuthConfigChange()", function () {
+
+    beforeEach(function () {
+      sinon.stub(view, 'updateConfig');
+      view.set('categoryConfigs', [
+        Em.Object.create({name: 'ambari.dispatch.credential.username'}),
+        Em.Object.create({name: 'smtp_use_auth'})
+      ]);
+    });
+
+    afterEach(function () {
+      view.updateConfig.restore();
+    });
+
+    it("auth config is not editable", function () {
+      view.get('categoryConfigs').findProperty('name', 'smtp_use_auth').setProperties({
+        value: true,
+        isEditable: false
+      });
+      view.onUseAuthConfigChange();
+      expect(view.updateConfig.calledWith(
+        Em.Object.create({name: 'ambari.dispatch.credential.username'}),
+        false
+      )).to.be.true;
+    });
+
+    it("auth config is editable", function () {
+      view.get('categoryConfigs').findProperty('name', 'smtp_use_auth').setProperties({
+        value: true,
+        isEditable: true
+      });
+      view.onUseAuthConfigChange();
+      expect(view.updateConfig.calledWith(
+        Em.Object.create({name: 'ambari.dispatch.credential.username'}),
+        true
+      )).to.be.true;
+    });
+  });
+
+  describe("#updateCategoryConfigs()", function () {
+
+    beforeEach(function () {
+      sinon.stub(view, 'updateConfig');
+      sinon.stub(view, 'onUseAuthConfigChange');
+      view.set('categoryConfigs', [
+        Em.Object.create({name: 'ambari.dispatch.credential.username'})
+      ]);
+      view.set('categoryConfigsAll', [
+        Em.Object.create({
+          name: 'create_notification'
+        })
+      ]);
+    });
+
+    afterEach(function () {
+      view.updateConfig.restore();
+      view.onUseAuthConfigChange.restore();
+    });
+
+    it("createNotification is 'yes'", function () {
+      view.set('createNotification', 'yes');
+      view.updateCategoryConfigs();
+      expect(view.onUseAuthConfigChange.called).to.be.true;
+      expect(view.get('categoryConfigsAll').findProperty('name', 'create_notification').get('value')).to.equal('yes');
+      expect(view.updateConfig.calledWith(
+        Em.Object.create({name: 'ambari.dispatch.credential.username'}),
+        true
+      )).to.be.true;
+    });
+
+    it("createNotification is 'no'", function () {
+      view.set('createNotification', 'no');
+      view.updateCategoryConfigs();
+      expect(view.onUseAuthConfigChange.called).to.be.true;
+      expect(view.get('categoryConfigsAll').findProperty('name', 'create_notification').get('value')).to.equal('no');
+      expect(view.updateConfig.calledWith(
+        Em.Object.create({name: 'ambari.dispatch.credential.username'}),
+        false
+      )).to.be.true;
+    });
   });
 
+  describe("#updateConfig()", function () {
+
+    var config;
+
+    beforeEach(function () {
+      config = Em.Object.create({
+        validate: Em.K
+      });
+      sinon.spy(config, 'validate');
+    });
+
+    afterEach(function () {
+      config.validate.restore();
+    });
+
+    it("flag is true", function () {
+      view.updateConfig(config, true);
+      expect(config.get('isRequired')).to.be.true;
+      expect(config.get('isEditable')).to.be.true;
+      expect(config.validate.calledOnce).to.be.true;
+    });
+
+    it("flag is false", function () {
+      view.updateConfig(config, false);
+      expect(config.get('isRequired')).to.be.false;
+      expect(config.get('isEditable')).to.be.false;
+      expect(config.get('errorMessage')).to.be.empty;
+      expect(config.validate.called).to.be.false;
+    });
+  });
 });

+ 7 - 6
ambari-web/test/views/common/filter_view_test.js

@@ -202,31 +202,30 @@ describe('filters.getFilterByType', function () {
   describe('date', function () {
 
     var filter = filters.getFilterByType('date');
-    var currentTime = new Date().getTime();
     var testData = [
       {
         condition: 'Past 1 Day',
-        value: currentTime - 86300000,
+        value: 86300000,
         result: true
       },
       {
         condition: 'Past 2 Days',
-        value: currentTime - 172700000,
+        value: 172700000,
         result: true
       },
       {
         condition: 'Past 7 Days',
-        value: currentTime - 604700000,
+        value: 604700000,
         result: true
       },
       {
         condition: 'Past 14 Days',
-        value: currentTime - 1209500000,
+        value: 1209500000,
         result: true
       },
       {
         condition: 'Past 30 Days',
-        value: currentTime - 2591900000,
+        value: 2591900000,
         result: true
       },
       {
@@ -238,6 +237,8 @@ describe('filters.getFilterByType', function () {
 
     testData.forEach(function(item){
       it('Condition: ' + item.condition + ' - match value: ' + item.value, function () {
+        var currentTime = App.dateTime();
+        item.value = currentTime - item.value;
         expect(filter(item.value, item.condition)).to.equal(item.result);
       })
     });

+ 317 - 1
ambari-web/test/views/main/charts/heatmap/heatmap_host_test.js

@@ -18,9 +18,11 @@
 
 
 var App = require('app');
+var date = require('utils/date/date');
+require('models/host');
 require('views/main/charts/heatmap/heatmap_host');
 
-describe('App.MainChartsHeatmapHostView', function() {
+describe('App.MainChartsHeatmapHostView', function () {
 
   var view = App.MainChartsHeatmapHostView.create({
     templateName: '',
@@ -117,4 +119,318 @@ describe('App.MainChartsHeatmapHostView', function() {
     });
   });
 
+  describe("#hostModelLink", function () {
+
+    before(function () {
+      sinon.stub(App.Host, 'find').returns(Em.Object.create({id: 'host1'}));
+    });
+
+    after(function () {
+      App.Host.find.restore();
+    });
+
+    it("should return hostname", function () {
+      view.set('content.hostName', 'host1');
+      view.propertyDidChange('hostModelLink');
+      expect(view.get('hostModelLink.id')).to.equal('host1');
+    });
+  });
+
+  describe("#mouseEnter()", function () {
+
+    beforeEach(function () {
+      sinon.stub(view, 'getUsage').returns('usage');
+      sinon.stub(view, 'getCpuUsage').returns('cpu_usage');
+      sinon.stub(view, 'getHostComponents').returns(['c1']);
+      sinon.stub(view, 'setMetric');
+      sinon.stub(view, 'openDetailsBlock');
+      this.mock = sinon.stub(App.MainChartsHeatmapHostDetailView, 'create');
+      view.set('details', {});
+    });
+
+    afterEach(function () {
+      view.getUsage.restore();
+      view.getCpuUsage.restore();
+      view.getHostComponents.restore();
+      view.setMetric.restore();
+      view.openDetailsBlock.restore();
+      this.mock.restore();
+    });
+
+    it("set diskUsage", function () {
+      var childView = Em.Object.create({
+        details: {
+          diskUsage: ''
+        }
+      });
+      this.mock.returns(childView);
+      view.set('content', {
+        diskTotal: 100,
+        diskFree: 50
+      });
+      view.mouseEnter();
+      expect(childView.get('details.diskUsage')).to.equal('usage');
+      expect(view.getUsage.calledWith(100, 50)).to.be.true;
+      expect(view.setMetric.calledOnce).to.be.true;
+      expect(view.openDetailsBlock.calledOnce).to.be.true;
+    });
+
+    it("set cpuUsage", function () {
+      var childView = Em.Object.create({
+        details: {
+          cpuUsage: ''
+        }
+      });
+      this.mock.returns(childView);
+      view.set('content', {
+        cpuSystem: 100,
+        cpuUser: 50
+      });
+      view.mouseEnter();
+      expect(childView.get('details.cpuUsage')).to.equal('cpu_usage');
+      expect(view.getCpuUsage.calledWith(100, 50)).to.be.true;
+      expect(view.setMetric.calledOnce).to.be.true;
+      expect(view.openDetailsBlock.calledOnce).to.be.true;
+    });
+
+    it("set memoryUsage", function () {
+      var childView = Em.Object.create({
+        details: {
+          memoryUsage: ''
+        }
+      });
+      this.mock.returns(childView);
+      view.set('content', {
+        memTotal: 100,
+        memFree: 50
+      });
+      view.mouseEnter();
+      expect(childView.get('details.memoryUsage')).to.equal('usage');
+      expect(view.getUsage.calledWith(100, 50)).to.be.true;
+      expect(view.setMetric.calledOnce).to.be.true;
+      expect(view.openDetailsBlock.calledOnce).to.be.true;
+    });
+
+    it("set hostComponents", function () {
+      var childView = Em.Object.create({
+        details: {
+          hostComponents: ''
+        }
+      });
+      this.mock.returns(childView);
+      view.set('content', {
+        hostComponents: ['host1']
+      });
+      view.mouseEnter();
+      expect(childView.get('details.hostComponents')).to.eql(['c1']);
+      expect(view.getHostComponents.calledWith(['host1'])).to.be.true;
+      expect(view.setMetric.calledOnce).to.be.true;
+      expect(view.openDetailsBlock.calledOnce).to.be.true;
+    });
+
+    it("set hostName", function () {
+      var childView = Em.Object.create({
+        details: {
+          hostName: ''
+        }
+      });
+      this.mock.returns(childView);
+      view.set('content', {
+        hostName: 'host1'
+      });
+      view.mouseEnter();
+      expect(childView.get('details.hostName')).to.equal('host1');
+      expect(view.setMetric.calledOnce).to.be.true;
+      expect(view.openDetailsBlock.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#getUsage()", function () {
+    var testCases = [
+      {
+        input: {
+          total: null,
+          free: null
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          total: 100,
+          free: null
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          total: null,
+          free: 50
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          total: 0,
+          free: 0
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          total: 100,
+          free: 50
+        },
+        expected: '50.0'
+      }
+    ];
+
+    testCases.forEach(function (test) {
+      it("total = " + test.input.total + "; free = " + test.input.free, function () {
+        expect(view.getUsage(test.input.total, test.input.free)).to.equal(test.expected);
+      });
+    });
+  });
+
+  describe("#getCpuUsage()", function () {
+    var testCases = [
+      {
+        input: {
+          cpuSystem: null,
+          cpuUser: null
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          cpuSystem: 1.0,
+          cpuUser: null
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          cpuSystem: null,
+          cpuUser: 1.0
+        },
+        expected: '0.0'
+      },
+      {
+        input: {
+          cpuSystem: 2.22,
+          cpuUser: 1.0
+        },
+        expected: '3.2'
+      }
+    ];
+
+    testCases.forEach(function (test) {
+      it("cpuSystem = " + test.input.cpuSystem + "; cpuUser = " + test.input.cpuUser, function () {
+        expect(view.getCpuUsage(test.input.cpuSystem, test.input.cpuUser)).to.equal(test.expected);
+      });
+    });
+  });
+
+  describe("#getHostComponents()", function () {
+
+    beforeEach(function () {
+      sinon.stub(App.format, 'role', function (name) {
+        return name;
+      });
+      sinon.stub(App, 'get').returns('non-client');
+    });
+
+    afterEach(function () {
+      App.format.role.restore();
+      App.get.restore();
+    });
+
+    it("should return host-components", function () {
+      expect(view.getHostComponents(['is-client', 'non-client', 'non-client'])).to.equal('non-client, non-client');
+    });
+  });
+
+  describe("#setMetric()", function () {
+    var viewObject;
+
+    beforeEach(function () {
+      sinon.stub(date, 'timingFormat').returns('time');
+      viewObject = Em.Object.create({
+        details: {}
+      });
+    });
+
+    afterEach(function () {
+      date.timingFormat.restore();
+    });
+
+    it("selected metric is null", function () {
+      view.set('controller.selectedMetric', null);
+      view.setMetric(viewObject, {});
+      expect(viewObject.get('details')).to.be.empty;
+    });
+
+    it("metric name is null", function () {
+      view.set('controller.selectedMetric', Em.Object.create({
+        name: null,
+        hostToValueMap: {}
+      }));
+      view.setMetric(viewObject, {});
+      expect(viewObject.get('details')).to.be.empty;
+    });
+
+    it("host value is undefined", function () {
+      view.set('controller.selectedMetric', Em.Object.create({
+        name: 'm1',
+        hostToValueMap: {}
+      }));
+      view.setMetric(viewObject, {hostName: 'host1'});
+      expect(viewObject.get('details')).to.eql({
+        metricName: 'm1',
+        metricValue: Em.I18n.t('charts.heatmap.unknown')
+      });
+    });
+
+    it("metric name is 'Garbage Collection Time'", function () {
+      view.set('controller.selectedMetric', Em.Object.create({
+        name: 'Garbage Collection Time',
+        hostToValueMap: {
+          host1: 'val'
+        }
+      }));
+      view.setMetric(viewObject, {hostName: 'host1'});
+      expect(viewObject.get('details')).to.eql({
+        metricName: 'Garbage Collection Time',
+        metricValue: 'time'
+      });
+    });
+
+    it("metric value is NaN", function () {
+      view.set('controller.selectedMetric', Em.Object.create({
+        name: 'm1',
+        hostToValueMap: {
+          host1: 'val'
+        }
+      }));
+      view.setMetric(viewObject, {hostName: 'host1'});
+      expect(viewObject.get('details')).to.eql({
+        metricName: 'm1',
+        metricValue: Em.I18n.t('charts.heatmap.unknown')
+      });
+    });
+
+    it("metric value is number", function () {
+      view.set('controller.selectedMetric', Em.Object.create({
+        name: 'm1',
+        hostToValueMap: {
+          host1: 10
+        },
+        units: 'mb'
+      }));
+      view.setMetric(viewObject, {hostName: 'host1'});
+      expect(viewObject.get('details')).to.eql({
+        metricName: 'm1',
+        metricValue: '10mb'
+      });
+    });
+  });
 });