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

[ambari] branch trunk updated: AMBARI-22867 Add new state for Not Available data in Heatmap widget

This is an automated email from the ASF dual-hosted git repository.

atkach pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 8b5cf83  AMBARI-22867 Add new state for Not Available data in Heatmap widget
8b5cf83 is described below

commit 8b5cf83842f2f1297795b2ec2aa9e834b5d5ef0b
Author: Andrii Tkach <at...@apache.org>
AuthorDate: Mon Jan 29 17:42:47 2018 +0200

    AMBARI-22867 Add new state for Not Available data in Heatmap widget
---
 ambari-web/app/assets/test/tests.js                |   1 -
 .../main/charts/heatmap_metrics/heatmap_metric.js  | 158 ++++++++-----
 ambari-web/app/messages.js                         |   1 +
 .../app/mixins/common/widgets/widget_mixin.js      |  12 +-
 ambari-web/app/styles/application.less             |   8 +-
 ambari-web/app/utils/heatmap.js                    |  48 ----
 ambari-web/app/utils/number_utils.js               |   5 +
 .../app/views/common/widget/heatmap_widget_view.js |  24 +-
 .../charts/heatmap_metrics/heatmap_metric_test.js  | 260 ++++++++-------------
 ambari-web/test/utils/heatmap_test.js              | 141 -----------
 .../common/widget/heatmap_widget_view_test.js      |  27 ++-
 11 files changed, 252 insertions(+), 433 deletions(-)

diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index dbc4d55..dd64452 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -219,7 +219,6 @@ var files = [
   'test/utils/form_field_test',
   'test/utils/file_utils_test',
   'test/utils/handlebars_helpers_test',
-  'test/utils/heatmap_test',
   'test/utils/host_progress_popup_test',
   'test/utils/hosts_test',
   'test/utils/http_client_test',
diff --git a/ambari-web/app/controllers/main/charts/heatmap_metrics/heatmap_metric.js b/ambari-web/app/controllers/main/charts/heatmap_metrics/heatmap_metric.js
index 9cae1df..4ff1d59 100644
--- a/ambari-web/app/controllers/main/charts/heatmap_metrics/heatmap_metric.js
+++ b/ambari-web/app/controllers/main/charts/heatmap_metrics/heatmap_metric.js
@@ -18,7 +18,7 @@
 var App = require('app');
 var date = require('utils/date/date');
 
-var heatmap = require('utils/heatmap');
+const numberUtils = require('utils/number_utils');
 
 /**
  * Base class for any heatmap metric.
@@ -50,30 +50,33 @@ App.MainChartHeatmapMetric = Em.Object.extend({
    * 
    * @type {Array}
    */
-  slotColors: [ {
-    r: 0x00,
-    g: 0xcc,
-    b: 0x00
-  }, // Green
-  {
-    r: 0x9f,
-    g: 0xee,
-    b: 0x00
-  }, {
-    r: 0xff,
-    g: 0xff,
-    b: 0x00
-  }, // Yellow
-  {
-    r: 0xff,
-    g: 0xc0,
-    b: 0x00
-  }, // Orange
-  {
-    r: 0xff,
-    g: 0x00,
-    b: 0x00
-  } ],// Red
+  slotColors: [
+    { // Green
+      r: 0x00,
+      g: 0xcc,
+      b: 0x00
+    },
+    { //Light green
+      r: 0x9f,
+      g: 0xee,
+      b: 0x00
+    },
+    { // Yellow
+      r: 0xff,
+      g: 0xff,
+      b: 0x00
+    },
+    { // Orange
+      r: 0xff,
+      g: 0xc0,
+      b: 0x00
+    },
+    { // Red
+      r: 0xff,
+      g: 0x00,
+      b: 0x00
+    }
+  ],
 
   /**
    * Minimum value of this metric. Default is 0.
@@ -110,9 +113,9 @@ App.MainChartHeatmapMetric = Em.Object.extend({
    * 
    */
   slotDefinitions: function () {
-    var max = parseFloat(this.get('maximumValue'));
+    var max = this.get('maximumValue') ? parseFloat(this.get('maximumValue')) : 0;
     var slotCount = this.get('numberOfSlots');
-    var labelSuffix = this.get('slotDefinitionLabelSuffix');
+    var units = this.get('units');
     var delta = (max - this.get('minimumValue')) / slotCount;
     var defs = [];
     var slotColors = this.get('slotColors');
@@ -121,21 +124,27 @@ App.MainChartHeatmapMetric = Em.Object.extend({
       var slotColor = slotColors[slotColorIndex++];
       var start = (c * delta);
       var end = ((c + 1) * delta);
-      defs.push(this.generateSlot(start, end, labelSuffix, slotColor));
+      defs.push(this.generateSlot(start, end, units, slotColor));
     }
     slotColor = slotColors[slotColorIndex++];
     start = ((slotCount - 1) * delta);
-    defs.push(this.generateSlot(start, max, labelSuffix, slotColor));
+    defs.push(this.generateSlot(start, max, units, slotColor));
 
     defs.push(Em.Object.create({
-      from: NaN,
-      to: NaN,
+      invalidData: true,
+      index: defs.length,
       label: Em.I18n.t('charts.heatmap.label.invalidData'),
       cssStyle: this.getHatchStyle()
     }));
     defs.push(Em.Object.create({
-      from: -1,
-      to: -1,
+      notAvailable: true,
+      index: defs.length,
+      label: Em.I18n.t('charts.heatmap.label.notAvailable'),
+      cssStyle: "background-color: dimgray"
+    }));
+    defs.push(Em.Object.create({
+      notApplicable: true,
+      index: defs.length,
       label: Em.I18n.t('charts.heatmap.label.notApplicable'),
       cssStyle: "background-color:rgb(200, 200, 200)"
     }));
@@ -146,24 +155,22 @@ App.MainChartHeatmapMetric = Em.Object.extend({
    *
    * @param min
    * @param max
-   * @param labelSuffix
+   * @param units
    * @param slotColor
    * @return {object}
    * @method generateSlot
    */
-  generateSlot: function (min, max, labelSuffix, slotColor) {
-    var from = this.formatLegendNumber(min);
-    var to = this.formatLegendNumber(max);
-    var label;
-    if ($.trim(labelSuffix) == 'ms') {
-      label = date.timingFormat(from) + " - " + date.timingFormat(to);
-    } else {
-      label = from + labelSuffix + " - " + to + labelSuffix;
-    }
+  generateSlot: function (min, max, units, slotColor) {
+    const fromLabel = this.formatLegendLabel(min, units);
+    const toLabel = this.formatLegendLabel(max, units);
+    const from = this.convertNumber(min, units);
+    const to = this.convertNumber(max, units);
+
     return Em.Object.create({
+      hasBoundaries: true,
       from: from,
       to: to,
-      label: label,
+      label: fromLabel + " - " + toLabel,
       cssStyle: "background-color:rgb(" + slotColor.r + "," + slotColor.g + "," + slotColor.b + ")"
     });
   },
@@ -218,27 +225,29 @@ App.MainChartHeatmapMetric = Em.Object.extend({
    * @return {Number}
    */
   calculateSlot: function (hostToValueMap, hostName) {
-    var slot = -1;
-    var slotDefs = this.get('slotDefinitions');
+    const slotDefinitions = this.get('slotDefinitions');
+    const slotWithBoundaries = slotDefinitions.filterProperty('hasBoundaries');
+    const invalidDataSlot = slotDefinitions.findProperty('invalidData').get('index');
+    const notAvailableDataSlot = slotDefinitions.findProperty('notAvailable').get('index');
+    let slot = slotDefinitions.findProperty('notApplicable').get('index');
 
     if (hostName in hostToValueMap) {
-      var value = hostToValueMap[hostName];
-      if (isNaN(value)) {
-        slot = slotDefs.length - 2;
+      let value = hostToValueMap[hostName];
+      if (Em.isNone(value)) {
+        slot = notAvailableDataSlot;
+      } else if (isNaN(value) || !isFinite(value) || value < 0) {
+        slot = invalidDataSlot;
       } else {
-        for (var slotIndex = 0; slotIndex < slotDefs.length - 2; slotIndex++) {
-          var slotDef = slotDefs[slotIndex];
-          if (value >= slotDef.from && value <= slotDef.to) {
+        value = Number(value);
+        slotWithBoundaries.forEach((slotDef, slotIndex, array) => {
+
+          if ((value >= slotDef.from && value <= slotDef.to) ||
+            // If value exceeds maximum then it pushed to the last/maximal slot
+            (value > slotDef.to && slotIndex === array.length - 1)) {
             slot = slotIndex;
           }
-        }
-        if (slot < 0) {
-          // Assign it to the last legend
-          slot = slotDefs.length - 3;
-        }
+        });
       }
-    } else {
-      slot = slotDefs.length - 1;
     }
     return slot;
   },
@@ -249,11 +258,32 @@ App.MainChartHeatmapMetric = Em.Object.extend({
    * 
    * @private
    */
-  formatLegendNumber: function (num) {
-    var fraction = num % 1;
-    if (fraction > 0) {
-      return parseFloat(num.toFixed(1));
+  formatLegendLabel: function (num, units) {
+    const fraction = num % 1;
+    if (num >= 100) {
+      num = Math.round(num);
+    } else if (num >= 10 && fraction > 0) {
+      num = parseFloat(num.toFixed(1));
+    } else if (fraction > 0) {
+      num = parseFloat(num.toFixed(2));
     }
-    return num;
+    if (units === 'ms') {
+      return date.timingFormat(num);
+    }
+    return num + units;
+  },
+
+  /**
+   * return converted value
+   *
+   * @param {number} number
+   * @param {string} units
+   */
+  convertNumber: function(number, units) {
+    if (units === 'MB') {
+      return Math.round(number * numberUtils.BYTES_IN_MB);
+    }
+    return number;
   }
+
 });
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 391ac89..df09dfa 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -2862,6 +2862,7 @@ Em.I18n.translations = {
   'charts.heatmap.unknown': 'Unknown',
   'charts.heatmap.label.notApplicable' :'Not Applicable',
   'charts.heatmap.label.invalidData' :'Invalid data',
+  'charts.heatmap.label.notAvailable': 'Data Not Available',
   'metric.notFound':'no items found',
   'metric.default':'combined',
   'metric.cpu':'cpu',
diff --git a/ambari-web/app/mixins/common/widgets/widget_mixin.js b/ambari-web/app/mixins/common/widgets/widget_mixin.js
index cb10d8c..1b1de93 100644
--- a/ambari-web/app/mixins/common/widgets/widget_mixin.js
+++ b/ambari-web/app/mixins/common/widgets/widget_mixin.js
@@ -391,12 +391,12 @@ App.WidgetMixin = Ember.Mixin.create({
     var metrics = this.get('content.metrics');
     data.host_components.forEach(function (item) {
       metrics.forEach(function (_metric) {
+        const metric = $.extend({}, _metric, true);
+        metric.hostName = item.HostRoles.host_name;
         if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) {
-          var metric = $.extend({}, _metric, true);
           metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.'));
-          metric.hostName = item.HostRoles.host_name;
-          this.get('metrics').pushObject(metric);
         }
+        this.get('metrics').pushObject(metric);
       }, this);
     }, this);
   },
@@ -417,12 +417,12 @@ App.WidgetMixin = Ember.Mixin.create({
     var metrics = this.get('content.metrics');
     data.items.forEach(function (item) {
       metrics.forEach(function (_metric, index) {
+        const metric = $.extend({}, _metric, true);
+        metric.hostName = item.Hosts.host_name;
         if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) {
-          var metric = $.extend({}, _metric, true);
           metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.'));
-          metric.hostName = item.Hosts.host_name;
-          this.get('metrics').pushObject(metric);
         }
+        this.get('metrics').pushObject(metric);
       }, this);
     }, this);
   },
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index 20ff5b1..a6a756b 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -376,16 +376,12 @@ table.diff {
 }
 
 .row.row-no-pad {
+  padding-left: 15px;
+  padding-right: 15px;
   .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12,
   .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
     padding-left: 1px;
     padding-right: 1px;
-    &:first-child {
-      padding-left: 15px;
-    }
-    &:last-child {
-      padding-right: 15px;
-    }
   }
 }
 
diff --git a/ambari-web/app/utils/heatmap.js b/ambari-web/app/utils/heatmap.js
deleted file mode 100644
index e033005..0000000
--- a/ambari-web/app/utils/heatmap.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * 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.
- */
-
-module.exports = {
-  mappers: Em.Mixin.create({
-    metricMapperWithTransform: function (json, metricName, transformValueFunction) {
-      var hostToValueMap = {};
-      //var metricName = this.get('defaultMetric');
-      if (json.host_components) {
-        var props = metricName.split('.');
-        //var transformValueFunction = this.get('transformValue');
-        json.host_components.forEach(function (hc) {
-          var value = hc;
-          props.forEach(function (prop) {
-            if (value != null && prop in value) {
-              value = value[prop];
-            } else {
-              value = null;
-            }
-          });
-          if (value != null) {
-            if (transformValueFunction) {
-              value = transformValueFunction(value);
-            }
-            var hostName = hc.HostRoles.host_name;
-            hostToValueMap[hostName] = value.toFixed(1);
-          }
-        });
-      }
-      return hostToValueMap;
-    }
-  })
-};
diff --git a/ambari-web/app/utils/number_utils.js b/ambari-web/app/utils/number_utils.js
index 4331f85..f300d03 100644
--- a/ambari-web/app/utils/number_utils.js
+++ b/ambari-web/app/utils/number_utils.js
@@ -17,6 +17,11 @@
 module.exports = {
 
   /**
+   * @const
+   */
+  BYTES_IN_MB: 1024 * 1024,
+
+  /**
    * Convert byte size to other metrics.
    *
    * @param {Number} bytes to convert to string
diff --git a/ambari-web/app/views/common/widget/heatmap_widget_view.js b/ambari-web/app/views/common/widget/heatmap_widget_view.js
index a7a919b..6fbb7e7 100644
--- a/ambari-web/app/views/common/widget/heatmap_widget_view.js
+++ b/ambari-web/app/views/common/widget/heatmap_widget_view.js
@@ -16,7 +16,8 @@
  * limitations under the License.
  */
 
-var App = require('app');
+const App = require('app');
+const numberUtils = require('utils/number_utils');
 
 App.HeatmapWidgetView = Em.View.extend(App.WidgetMixin, {
   templateName: require('templates/common/widget/heatmap_widget'),
@@ -124,6 +125,7 @@ App.HeatmapWidgetView = Em.View.extend(App.WidgetMixin, {
     var metricsMap = {};
 
     metrics.forEach(function (_metric) {
+      this.convertDataWhenMB(_metric);
       metricsMap[_metric.name + "_" + _metric.hostName] = _metric;
     }, this);
 
@@ -148,11 +150,10 @@ App.HeatmapWidgetView = Em.View.extend(App.WidgetMixin, {
         });
 
         if (validExpression && this.get('MATH_EXPRESSION_REGEX').test(beforeCompute)) {
-          var value = Number(window.eval(beforeCompute)).toString();
-          if (value === "NaN")  {
-            value = 0
-          }
-          hostToValueMap[_hostName] = value;
+          hostToValueMap[_hostName] = Number(window.eval(beforeCompute)).toString();
+        } else if (beforeCompute === 'undefined') {
+          // Data not available
+          hostToValueMap[_hostName] = undefined;
         } else {
           console.error('Value for metric is not correct mathematical expression: ' + beforeCompute);
         }
@@ -160,5 +161,16 @@ App.HeatmapWidgetView = Em.View.extend(App.WidgetMixin, {
     }, this);
 
     return hostToValueMap;
+  },
+
+  /**
+   * If metric has value in MB convert it to bytes
+   * @param _metric
+   */
+  convertDataWhenMB: function(_metric) {
+    if (_metric.metric_path.endsWith('M') && !Em.isNone(_metric.data) && isFinite(_metric.data)) {
+      _metric.originalData = _metric.data;
+      _metric.data = Math.round(_metric.data * numberUtils.BYTES_IN_MB);
+    }
   }
 });
diff --git a/ambari-web/test/controllers/main/charts/heatmap_metrics/heatmap_metric_test.js b/ambari-web/test/controllers/main/charts/heatmap_metrics/heatmap_metric_test.js
index 0375a53..c2f7e85 100644
--- a/ambari-web/test/controllers/main/charts/heatmap_metrics/heatmap_metric_test.js
+++ b/ambari-web/test/controllers/main/charts/heatmap_metrics/heatmap_metric_test.js
@@ -26,24 +26,6 @@ describe('MainChartHeatmapMetric', function () {
     mainChartHeatmapMetric = App.MainChartHeatmapMetric.create({});
   });
 
-  describe('#formatLegendNumber', function () {
-    var tests = [
-      {m:'undefined to undefined',i:undefined,e:undefined},
-      {m:'0 to 0',i:0,e:0},
-      {m:'1 to 1',i:1,e:1},
-      {m:'1.23 to 1.2',i:1.23,e:1.2}
-    ];
-    tests.forEach(function(test) {
-      it(test.m + ' ', function () {
-        expect(mainChartHeatmapMetric.formatLegendNumber(test.i)).to.equal(test.e);
-      });
-    });
-    it('NaN to NaN', function () {
-      expect(isNaN(mainChartHeatmapMetric.formatLegendNumber(NaN))).to.equal(true);
-    });
-  });
-
-
   describe('#slotDefinitions', function () {
     beforeEach(function () {
       sinon.stub(mainChartHeatmapMetric, 'generateSlot', Em.K);
@@ -62,8 +44,8 @@ describe('MainChartHeatmapMetric', function () {
         this.slotDefinitions = mainChartHeatmapMetric.get('slotDefinitions');
       });
 
-      it('3 slotDefinitions', function () {
-        expect(this.slotDefinitions.length).to.equal(3);
+      it('4 slotDefinitions', function () {
+        expect(this.slotDefinitions.length).to.equal(4);
       });
       it('generateSlot is called 1 time', function () {
         expect(mainChartHeatmapMetric.generateSlot.callCount).to.be.equal(1);
@@ -83,7 +65,7 @@ describe('MainChartHeatmapMetric', function () {
       });
 
       it('4 slotDefinitions', function () {
-        expect(this.slotDefinitions.length).to.equal(4);
+        expect(this.slotDefinitions.length).to.equal(5);
       });
       it('generateSlot is called 2 times', function () {
         expect(mainChartHeatmapMetric.generateSlot.callCount).to.be.equal(2);
@@ -101,68 +83,24 @@ describe('MainChartHeatmapMetric', function () {
   describe('#generateSlot()', function () {
 
     beforeEach(function () {
-      sinon.stub(mainChartHeatmapMetric, 'formatLegendNumber').returns('val');
-      sinon.stub(date, 'timingFormat').returns('time');
+      sinon.stub(mainChartHeatmapMetric, 'formatLegendLabel').returns('label');
+      sinon.stub(mainChartHeatmapMetric, 'convertNumber').returns('number');
     });
 
     afterEach(function () {
-      mainChartHeatmapMetric.formatLegendNumber.restore();
-      date.timingFormat.restore();
-    });
-
-    describe('label suffix is empty', function () {
-
-      beforeEach(function () {
-        this.result = mainChartHeatmapMetric.generateSlot(0, 1, '', {r: 0, g: 0, b: 0});
-      });
-
-      it('generateSlot result is valid', function () {
-        expect(this.result).to.eql(Em.Object.create({
-          "from": "val",
-          "to": "val",
-          "label": "val - val",
-          "cssStyle": "background-color:rgb(0,0,0)"
-        }));
-      });
-
-      it('formatLegendNumber 1st call with valid arguments', function () {
-        expect(mainChartHeatmapMetric.formatLegendNumber.getCall(0).args).to.eql([0]);
-      });
-
-      it('formatLegendNumber 2nd call with valid arguments', function () {
-        expect(mainChartHeatmapMetric.formatLegendNumber.getCall(1).args).to.eql([1]);
-      });
+      mainChartHeatmapMetric.formatLegendLabel.restore();
+      mainChartHeatmapMetric.convertNumber.restore();
     });
 
-    describe('label suffix is "ms"', function () {
-
-      beforeEach(function () {
-        this.result = mainChartHeatmapMetric.generateSlot(0, 1, 'ms', {r: 0, g: 0, b: 0});
-      });
-
-      it('generateSlot result is valid', function () {
-        expect(this.result).to.eql(Em.Object.create({
-          "from": "val",
-          "to": "val",
-          "label": "time - time",
-          "cssStyle": "background-color:rgb(0,0,0)"
-        }));
-      });
-      it('formatLegendNumber 1st call with valid arguments', function () {
-        expect(mainChartHeatmapMetric.formatLegendNumber.getCall(0).args).to.eql([0]);
-      });
-      it('formatLegendNumber 2nd call with valid arguments', function () {
-        expect(mainChartHeatmapMetric.formatLegendNumber.getCall(1).args).to.eql([1]);
-      });
-      it('timingFormat 1st call with valid arguments', function () {
-        expect(date.timingFormat.getCall(0).args).to.eql(['val']);
-      });
-      it('timingFormat 2nd call with valid arguments', function () {
-        expect(date.timingFormat.getCall(1).args).to.eql(['val']);
-      });
-
+    it('generateSlot result is valid', function () {
+      expect(mainChartHeatmapMetric.generateSlot(0, 1, '', {r: 0, g: 0, b: 0})).to.eql(Em.Object.create({
+        hasBoundaries: true,
+        "from": "number",
+        "to": "number",
+        "label": "label - label",
+        "cssStyle": "background-color:rgb(0,0,0)"
+      }));
     });
-
   });
 
   describe('#getHatchStyle()', function () {
@@ -253,101 +191,107 @@ describe('MainChartHeatmapMetric', function () {
   });
 
   describe('#calculateSlot()', function () {
+
+    beforeEach(function() {
+      sinon.stub(mainChartHeatmapMetric, 'get').withArgs('slotDefinitions').returns([
+        Em.Object.create({
+          from: 0,
+          to: 2,
+          hasBoundaries: true
+        }),
+        Em.Object.create({
+          from: 2,
+          to: 10,
+          hasBoundaries: true
+        }),
+        Em.Object.create({
+          invalidData: true,
+          index: 5
+        }),
+        Em.Object.create({
+          notAvailable: true,
+          index: 6
+        }),
+        Em.Object.create({
+          notApplicable: true,
+          index: 7
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      mainChartHeatmapMetric.get.restore();
+    });
+
+    it('not applicable metric', function () {
+      expect(mainChartHeatmapMetric.calculateSlot({}, 'host1')).to.be.equal(7);
+    });
+
+    it('not available data metric', function () {
+      expect(mainChartHeatmapMetric.calculateSlot({'host1': undefined}, 'host1')).to.be.equal(6);
+    });
+
+    it('invalid data metric', function () {
+      expect(mainChartHeatmapMetric.calculateSlot({'host1': 'NaN'}, 'host1')).to.be.equal(5);
+    });
+
+    it('metric should be in a minimal slot', function () {
+      expect(mainChartHeatmapMetric.calculateSlot({'host1': '1'}, 'host1')).to.be.equal(0);
+    });
+
+    it('metric should be in a maximal slot', function () {
+      expect(mainChartHeatmapMetric.calculateSlot({'host1': '11'}, 'host1')).to.be.equal(1);
+    });
+  });
+
+  describe('#formatLegendLabel', function() {
+    beforeEach(function () {
+      sinon.stub(date, 'timingFormat').returns('30 secs');
+    });
+
+    afterEach(function () {
+      date.timingFormat.restore();
+    });
+
+
     var testCases = [
       {
-        title: 'hostToValueMap is empty',
-        data: {
-          hostToValueMap: {},
-          hostName: 'host1',
-          slotDefinitions: []
-        },
-        result: -1
+        num: 100.1,
+        units: '',
+        expected: '100'
       },
       {
-        title: 'host value is NaN',
-        data: {
-          hostToValueMap: {'host1': NaN},
-          hostName: 'host1',
-          slotDefinitions: []
-        },
-        result: -2
-      },
-      {
-        title: 'host value correct but slotDefinitions does not contain host value',
-        data: {
-          hostToValueMap: {'host1': 1},
-          hostName: 'host1',
-          slotDefinitions: [{}, {}]
-        },
-        result: -1
+        num: 98.94,
+        units: 'MB',
+        expected: '98.9MB'
       },
       {
-        title: 'host value -1',
-        data: {
-          hostToValueMap: {'host1': -1},
-          hostName: 'host1',
-          slotDefinitions: [
-            {
-              from: 0,
-              to: 10
-            },
-            {},
-            {}
-          ]
-        },
-        result: 0
+        num: 8.123,
+        units: '%',
+        expected: '8.12%'
       },
       {
-        title: 'host value 11',
-        data: {
-          hostToValueMap: {'host1': 11},
-          hostName: 'host1',
-          slotDefinitions: [
-            {
-              from: 0,
-              to: 10
-            },
-            {},
-            {}
-          ]
-        },
-        result: 0
-      },
-      {
-        title: 'host value 5',
-        data: {
-          hostToValueMap: {'host1': 5},
-          hostName: 'host1',
-          slotDefinitions: [
-            {},
-            {
-              from: 0,
-              to: 10
-            },
-            {},
-            {}
-          ]
-        },
-        result: 1
+        num: 30000,
+        units: 'ms',
+        expected: '30 secs'
       }
     ];
 
-    testCases.forEach(function (test) {
-      describe(test.title, function () {
-
-        beforeEach(function () {
-          sinon.stub(mainChartHeatmapMetric, 'get').withArgs('slotDefinitions').returns(test.data.slotDefinitions);
-        });
+    testCases.forEach(function(test) {
+      it('should return label = ' + test.expected, function() {
+        expect(mainChartHeatmapMetric.formatLegendLabel(test.num, test.units)).to.be.equal(test.expected);
+      });
+    });
+  });
 
-        afterEach(function () {
-          mainChartHeatmapMetric.get.restore();
-        });
+  describe('#convertNumber', function() {
 
-        it('calculateSlot result is valid', function () {
-          expect(mainChartHeatmapMetric.calculateSlot(test.data.hostToValueMap, test.data.hostName)).to.equal(test.result);
-        });
+    it('MB units', function() {
+      expect(mainChartHeatmapMetric.convertNumber(1, 'MB')).to.be.equal(1024 * 1024);
+    });
 
-      });
+    it('no units', function() {
+      expect(mainChartHeatmapMetric.convertNumber(1, '')).to.be.equal(1);
     });
   });
 
diff --git a/ambari-web/test/utils/heatmap_test.js b/ambari-web/test/utils/heatmap_test.js
deleted file mode 100644
index 0a63cbf..0000000
--- a/ambari-web/test/utils/heatmap_test.js
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * 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 heatmap = require('utils/heatmap');
-
-describe('heatmap utils', function () {
-
-  describe('mappers', function () {
-
-    var mappers;
-
-    beforeEach(function () {
-      mappers = Em.Object.create(heatmap.mappers);
-    });
-
-    describe('#metricMapperWithTransform', function () {
-
-      var cases = [
-        {
-          hostComponents: null,
-          hostToValueMap: {},
-          title: 'no host components data'
-        },
-        {
-          hostComponents: [null, null],
-          metricName: 'm0',
-          hostToValueMap: {},
-          title: 'host components data is absent'
-        },
-        {
-          hostComponents: [{}, {}],
-          metricName: 'm1',
-          hostToValueMap: {},
-          title: 'provided metric data is absent'
-        },
-        {
-          hostComponents: [{}, {}],
-          metricName: 'm2.m3',
-          hostToValueMap: {},
-          title: 'provided metrics data is absent'
-        },
-        {
-          hostComponents: [
-            null,
-            {},
-            {
-              m4: 1,
-              HostRoles: {
-                host_name: 'h0'
-              }
-            },
-            {
-              m4: 1.5,
-              HostRoles: {
-                host_name: 'h1'
-              }
-            },
-            {
-              m4: 1.60,
-              HostRoles: {
-                host_name: 'h2'
-              }
-            },
-            {
-              m4: 1.72,
-              HostRoles: {
-                host_name: 'h3'
-              }
-            },
-            {
-              m4: 1.85,
-              HostRoles: {
-                host_name: 'h4'
-              }
-            },
-            {
-              m4: 1.97,
-              HostRoles: {
-                host_name: 'h5'
-              }
-            }
-          ],
-          metricName: 'm4',
-          hostToValueMap: {
-            h0: '1.0',
-            h1: '1.5',
-            h2: '1.6',
-            h3: '1.7',
-            h4: '1.9',
-            h5: '2.0'
-          },
-          title: 'no transform function'
-        },
-        {
-          hostComponents: [
-            {
-              m5: 100,
-              HostRoles: {
-                host_name: 'h6'
-              }
-            }
-          ],
-          metricName: 'm5',
-          transformValueFunction: Math.sqrt,
-          hostToValueMap: {
-            h6: '10.0'
-          },
-          title: 'transform function provided'
-        }
-      ];
-
-      cases.forEach(function (item) {
-
-        it(item.title, function () {
-          expect(mappers.metricMapperWithTransform({
-            host_components: item.hostComponents
-          }, item.metricName, item.transformValueFunction)).to.eql(item.hostToValueMap);
-        });
-
-      });
-
-    });
-
-  });
-
-});
diff --git a/ambari-web/test/views/common/widget/heatmap_widget_view_test.js b/ambari-web/test/views/common/widget/heatmap_widget_view_test.js
index 137e664..7ed9256 100644
--- a/ambari-web/test/views/common/widget/heatmap_widget_view_test.js
+++ b/ambari-web/test/views/common/widget/heatmap_widget_view_test.js
@@ -190,6 +190,14 @@ describe('App.HeatmapWidgetView', function () {
 
   describe("#computeExpression()", function () {
 
+    beforeEach(function() {
+      sinon.stub(view, 'convertDataWhenMB');
+    });
+
+    afterEach(function() {
+      view.convertDataWhenMB.restore();
+    });
+
     var testCases = [
       {
         expressions: [],
@@ -202,7 +210,7 @@ describe('App.HeatmapWidgetView', function () {
           name: 'm1',
           hostName: 'host1'
         }],
-        expected: {}
+        expected: {'host1': undefined}
       },
       {
         expressions: ['1'],
@@ -244,7 +252,7 @@ describe('App.HeatmapWidgetView', function () {
           data: '0/0'
         }],
         expected: {
-          'host1': 0
+          'host1': 'NaN'
         }
       },
       {
@@ -254,7 +262,7 @@ describe('App.HeatmapWidgetView', function () {
           hostName: 'host1',
           data: '2'
         }],
-        expected: {}
+        expected: {'host1': undefined}
       }
     ];
 
@@ -265,4 +273,17 @@ describe('App.HeatmapWidgetView', function () {
       });
     });
   });
+
+  describe('#convertDataWhenMB', function() {
+
+    it('should convert MB to bytes', function() {
+      var metric = {
+        metric_path: 'readM',
+        data: 1
+      };
+      view.convertDataWhenMB(metric);
+      expect(metric.data).to.be.equal(1 * 1024 * 1024);
+      expect(metric.originalData).to.be.equal(1);
+    });
+  });
 });
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
atkach@apache.org.