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 2015/03/30 13:15:54 UTC

ambari git commit: AMBARI-10268 Draw Gauge widget from the relevant retrieved widget data from the API. (atkach)

Repository: ambari
Updated Branches:
  refs/heads/trunk 5cd8c291a -> d2e2a7914


AMBARI-10268 Draw Gauge widget from the relevant retrieved widget data from the API. (atkach)


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

Branch: refs/heads/trunk
Commit: d2e2a79141afe440f713533f99ab98722c3084dd
Parents: 5cd8c29
Author: Andrii Tkach <at...@hortonworks.com>
Authored: Mon Mar 30 13:52:35 2015 +0300
Committer: Andrii Tkach <at...@hortonworks.com>
Committed: Mon Mar 30 13:52:35 2015 +0300

----------------------------------------------------------------------
 .../HBASE/Append_num_ops_&_Delete_num_ops.json  |   6 +-
 .../data/widget_layouts/HBASE/stack_layout.json |  32 +++++
 .../controllers/main/service/info/summary.js    |   3 +-
 ambari-web/app/mixins/common/widget_mixin.js    |  58 ++++++++-
 ambari-web/app/models/widget.js                 |   2 +
 ambari-web/app/styles/widget_layout.less        |  29 +++--
 .../templates/common/widget/gauge_widget.hbs    |  22 ++++
 ambari-web/app/views.js                         |   3 +-
 .../views/common/widget/gauge_widget_view.js    | 120 +++++++++++++++++++
 .../views/common/widget/graph_widget_view.js    |   4 +-
 .../views/common/widget/template_widget_view.js |  51 +-------
 .../test/mixins/common/widget_mixin_test.js     |  80 +++++++++++--
 12 files changed, 328 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json b/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json
index 2db12a0..87e0294 100644
--- a/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json
+++ b/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json
@@ -6,6 +6,10 @@
     "service_name" : "HBASE"
   },
   "metrics" : {
+    "jvm": {
+      "HeapMemoryUsed": 61,
+      "HeapMemoryMax": 100
+    },
     "hbase" : {
       "ipc" : {
         "IPC" : {
@@ -13,8 +17,8 @@
         }
       },
       "regionserver" : {
+        "percentFilesLocal" : 99,
         "Server" : {
-          "percentFilesLocal" : 99,
           "Append_num_ops" : [
             [
               2.0,

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json b/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json
index 4c8d3ee..6285138 100644
--- a/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json
+++ b/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json
@@ -72,6 +72,38 @@
             "properties": {
               "display_unit": "%"
             }
+          },
+          {
+            "widget_name": "NAMENODE_HEAP",
+            "display_name": "NameNode Heap",
+            "widget_type": "GAUGE",
+            "description": "",
+            "metrics":[
+              {
+                "name": "java.lang:type=Memory.HeapMemoryUsage[used]",
+                "widget_id": "metrics/jvm/HeapMemoryUsed",
+                "category": "",
+                "service_name": "HBASE",
+                "component_name": "HBASE_REGIONSERVER"
+              },
+              {
+                "name": "java.lang:type=Memory.HeapMemoryUsage[max]",
+                "widget_id": "metrics/jvm/HeapMemoryMax",
+                "category": "",
+                "service_name": "HBASE",
+                "component_name": "HBASE_REGIONSERVER"
+              }
+            ],
+            "values": [
+              {
+                "name": "NameNode heap",
+                "value": "${java.lang:type=Memory.HeapMemoryUsage[used]/java.lang:type=Memory.HeapMemoryUsage[max]}"
+              }
+            ],
+            "properties": {
+              "warning_threshold": 0.9,
+              "error_threshold": 0.7
+            }
           }
         ]
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/controllers/main/service/info/summary.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/service/info/summary.js b/ambari-web/app/controllers/main/service/info/summary.js
index ed5dbb9..d1d1be8 100644
--- a/ambari-web/app/controllers/main/service/info/summary.js
+++ b/ambari-web/app/controllers/main/service/info/summary.js
@@ -346,8 +346,7 @@ App.MainServiceInfoSummaryController = Em.Controller.extend({
         stackVersionURL: App.get('stackVersionURL'),
         serviceName: this.get('content.serviceName')
       },
-      success: 'loadStackWidgetsLayoutSuccessCallback',
-      error: 'loadStackWidgetsLayoutErrorCallback'
+      success: 'loadStackWidgetsLayoutSuccessCallback'
     });
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/mixins/common/widget_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/widget_mixin.js b/ambari-web/app/mixins/common/widget_mixin.js
index 1f90500..7d1abfe 100644
--- a/ambari-web/app/mixins/common/widget_mixin.js
+++ b/ambari-web/app/mixins/common/widget_mixin.js
@@ -24,7 +24,7 @@ App.WidgetMixin = Ember.Mixin.create({
    * @type {RegExp}
    * @const
    */
-  EXPRESSION_REGEX: /\$\{([\w\.\+\-\*\/\(\)]*)\}/g,
+  EXPRESSION_REGEX: /\$\{([\w\.\+\-\*\/\(\)\:\=\[\]]*)\}/g,
 
   /**
    * @type {RegExp}
@@ -36,7 +36,7 @@ App.WidgetMixin = Ember.Mixin.create({
    * @type {RegExp}
    * @const
    */
-  VALUE_NAME_REGEX: /[\w\.]+/g,
+  VALUE_NAME_REGEX: /[\w\.\:\=\[\]]+/g,
 
   /**
    * common metrics container
@@ -102,6 +102,55 @@ App.WidgetMixin = Ember.Mixin.create({
   },
 
   /**
+   * calculate series datasets for graph widgets
+   */
+  calculateValues: function () {
+    var metrics = this.get('metrics');
+    var displayUnit = this.get('content.properties.display_unit');
+
+    this.get('content.values').forEach(function (value) {
+      var computeExpression = this.computeExpression(this.extractExpressions(value), metrics);
+      value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) {
+        return (computeExpression[match]) ? computeExpression[match] + (displayUnit || "") : Em.I18n.t('common.na');
+      });
+    }, this);
+  },
+
+  /**
+   * compute expression
+   * @param expressions
+   * @param metrics
+   * @returns {object}
+   */
+  computeExpression: function (expressions, metrics) {
+    var result = {};
+
+    expressions.forEach(function (_expression) {
+      var validExpression = true;
+      var value = "";
+
+      //replace values with metrics data
+      var beforeCompute = _expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
+        if (metrics.someProperty('name', match)) {
+          return metrics.findProperty('name', match).data;
+        } else {
+          validExpression = false;
+          console.warn('Metrics not found to compute expression');
+        }
+      });
+
+      if (validExpression) {
+        //check for correct math expression
+        validExpression = this.get('MATH_EXPRESSION_REGEX').test(beforeCompute);
+        !validExpression && console.warn('Value is not correct mathematical expression');
+      }
+
+      result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value;
+    }, this);
+    return result;
+  },
+
+  /**
    * get data formatted for request
    * @param {Array} metrics
    */
@@ -143,11 +192,10 @@ App.WidgetMixin = Ember.Mixin.create({
 
   getServiceComponentMetricsSuccessCallback: function (data, opt, params) {
     var metrics = [];
-    var metricsData = data.metrics[params.serviceName.toLowerCase()];
 
     this.get('content.metrics').forEach(function (_metric) {
-      if (Em.get(metricsData, _metric.name)) {
-        _metric.data = Em.get(metricsData, _metric.name);
+      if (Em.get(data, _metric.widget_id.replace(/\//g, '.'))) {
+        _metric.data = Em.get(data, _metric.widget_id.replace(/\//g, '.'));
         this.get('metrics').pushObject(_metric);
       }
     }, this);

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/models/widget.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/widget.js b/ambari-web/app/models/widget.js
index bdbb527..b093a53 100644
--- a/ambari-web/app/models/widget.js
+++ b/ambari-web/app/models/widget.js
@@ -59,6 +59,8 @@ App.Widget = DS.Model.extend({
         return App.GraphWidgetView;
       case 'NUMBER':
         return App.TemplateWidgetView;
+      case 'GAUGE':
+        return App.GaugeWidgetView;
       default:
         return Em.View;
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/styles/widget_layout.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/widget_layout.less b/ambari-web/app/styles/widget_layout.less
index 265e666..8b4b94c 100644
--- a/ambari-web/app/styles/widget_layout.less
+++ b/ambari-web/app/styles/widget_layout.less
@@ -18,21 +18,28 @@
 
 #widget_layout {
   .widget {
+    .title {
+      padding: 5px 0 0 5px;
+      height: 25px;
+      font-weight: bold;
+      text-align: left;
+    }
+    .content {
+      text-align: center;
+      color: #5ab400;
+      padding-top: 35px;
+      font-weight: bold;
+      font-size: 35px;
+    }
     .template-widget {
       height: 150px;
       width: 90%;
-      .title {
-        padding: 5px 0 0 5px;
-        height: 25px;
-        font-weight: bold;
-        text-align: left;
-      }
+    }
+    .gauge-widget {
+      height: 150px;
+      width: 90%;
       .content {
-        text-align: center;
-        color: #5ab400;
-        padding-top: 35px;
-        font-weight: bold;
-        font-size: 35px;
+        padding-top: 5px;
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/templates/common/widget/gauge_widget.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/widget/gauge_widget.hbs b/ambari-web/app/templates/common/widget/gauge_widget.hbs
new file mode 100644
index 0000000..03a975f
--- /dev/null
+++ b/ambari-web/app/templates/common/widget/gauge_widget.hbs
@@ -0,0 +1,22 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div class="gauge-widget thumbnail">
+  <div class="caption title">{{view.title}}</div>
+  <div class="content"> {{view view.chartView}}</div>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 472d2a5..b074010 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -65,13 +65,14 @@ require('views/common/configs/widgets/time_interval_spinner_view');
 require('views/common/configs/widgets/toggle_config_widget_view');
 require('views/common/configs/widgets/overrides/slider_config_widget_override_view');
 require('views/common/configs/service_config_layout_tab_view');
-require('views/common/widget/template_widget_view');
 require('views/common/filter_combobox');
 require('views/common/filter_combo_cleanable');
 require('views/common/table_view');
 require('views/common/progress_bar_view');
 require('views/common/controls_view');
 require('views/common/widget/graph_widget_view');
+require('views/common/widget/template_widget_view');
+require('views/common/widget/gauge_widget_view');
 require('views/login');
 require('views/main');
 require('views/main/menu');

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/views/common/widget/gauge_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/widget/gauge_widget_view.js b/ambari-web/app/views/common/widget/gauge_widget_view.js
new file mode 100644
index 0000000..0964e11
--- /dev/null
+++ b/ambari-web/app/views/common/widget/gauge_widget_view.js
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.GaugeWidgetView = Em.View.extend(App.WidgetMixin, {
+  templateName: require('templates/common/widget/gauge_widget'),
+
+  /**
+   * @type {string}
+   */
+  title: '',
+
+  /**
+   * @type {string}
+   */
+  value: '',
+
+  /**
+   * common metrics container
+   * @type {Array}
+   */
+  metrics: [],
+
+  drawWidget: function () {
+    if (this.get('isLoaded')) {
+      this.calculateValues();
+      this.set('title', this.get('content.values')[0].name);
+      this.set('value', this.get('content.values')[0].computedValue);
+    }
+  }.observes('isLoaded'),
+
+  chartView: App.ChartPieView.extend({
+    stroke: '#D6DDDF', //light grey
+    innerR: 25,
+
+    FACTOR: 100,
+
+    MAX_VALUE: 100,
+
+    warningThreshold: function(){
+      return this.get('parentView.content.properties.warning_threshold');
+    }.property('parentView.content.properties.warning_threshold'),
+
+    errorThreshold: function(){
+      return this.get('parentView.content.properties.error_threshold');
+    }.property('parentView.content.properties.error_threshold'),
+
+    id: function() {
+      return this.get('parentView.content.widgetName');
+    }.property('parentView.content.widgetName'),
+
+    existCenterText: true,
+    centerTextColor: function () {
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var data = parseFloat(this.get('parentView.value')) * this.get('FACTOR');
+      if (isNaN(data)) return [this.get('MAX_VALUE'), 0];
+      return [data, this.get('MAX_VALUE') - data];
+    }.property('parentView.value'),
+
+    contentColor: function () {
+      var used = parseFloat(this.get('parentView.value')) * this.get('FACTOR');
+      var thresh1 = parseFloat(this.get('warningThreshold')) * this.get('FACTOR');
+      var thresh2 = parseFloat(this.get('errorThreshold')) * this.get('FACTOR');
+      var color_green = App.healthStatusGreen;
+      var color_red = App.healthStatusRed;
+      var color_orange = App.healthStatusOrange;
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }));
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }));
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }));
+        return color_red;
+      }
+    }.property('parentView.value', 'warningThreshold', 'errorThreshold'),
+
+    // refresh text and color when data in model changed
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.get('id'));
+      if(old_svg){
+        old_svg.remove();
+      }
+
+      // draw new svg
+      this.appendSvg();
+    }.observes('parentView.value', 'warningThreshold', 'errorThreshold')
+  })
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/views/common/widget/graph_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/widget/graph_widget_view.js b/ambari-web/app/views/common/widget/graph_widget_view.js
index c245622..2e4f0d9 100644
--- a/ambari-web/app/views/common/widget/graph_widget_view.js
+++ b/ambari-web/app/views/common/widget/graph_widget_view.js
@@ -67,14 +67,14 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend(App.WidgetMixin, {
 
   drawWidget: function () {
     if (this.get('isLoaded')) {
-      this._refreshGraph(this.calculateSeries())
+      this._refreshGraph(this.calculateValues())
     }
   }.observes('isLoaded'),
 
   /**
    * calculate series datasets for graph widgets
    */
-  calculateSeries: function () {
+  calculateValues: function () {
     var metrics = this.get('metrics');
     var seriesData = [];
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/app/views/common/widget/template_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/widget/template_widget_view.js b/ambari-web/app/views/common/widget/template_widget_view.js
index 580b92a..9b9d844 100644
--- a/ambari-web/app/views/common/widget/template_widget_view.js
+++ b/ambari-web/app/views/common/widget/template_widget_view.js
@@ -43,54 +43,5 @@ App.TemplateWidgetView = Em.View.extend(App.WidgetMixin, {
       this.set('value', this.get('content.values')[0].computedValue);
       this.set('title', this.get('content.values')[0].name);
     }
-  }.observes('isLoaded'),
-
-  /**
-   * calculate series datasets for graph widgets
-   */
-  calculateValues: function () {
-    var metrics = this.get('metrics');
-    var displayUnit = this.get('content.properties.display_unit');
-
-    this.get('content.values').forEach(function (value) {
-      var computeExpression = this.computeExpression(this.extractExpressions(value), metrics);
-      value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) {
-        return (computeExpression[match]) ? computeExpression[match] + displayUnit : Em.I18n.t('common.na');
-      });
-    }, this);
-  },
-
-  /**
-   * compute expression
-   * @param expressions
-   * @param metrics
-   * @returns {object}
-   */
-  computeExpression: function (expressions, metrics) {
-    var result = {};
-
-    expressions.forEach(function (_expression) {
-      var validExpression = true;
-      var value = "";
-
-      //replace values with metrics data
-      var beforeCompute = _expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
-        if (metrics.someProperty('name', match)) {
-          return metrics.findProperty('name', match).data;
-        } else {
-          validExpression = false;
-          console.warn('Metrics not found to compute expression');
-        }
-      });
-
-      if (validExpression) {
-        //check for correct math expression
-        validExpression = this.get('MATH_EXPRESSION_REGEX').test(beforeCompute);
-        !validExpression && console.warn('Value is not correct mathematical expression');
-      }
-
-      result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value;
-    }, this);
-    return result;
-  }
+  }.observes('isLoaded')
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d2e2a791/ambari-web/test/mixins/common/widget_mixin_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/widget_mixin_test.js b/ambari-web/test/mixins/common/widget_mixin_test.js
index 6b78769..2ee9edd 100644
--- a/ambari-web/test/mixins/common/widget_mixin_test.js
+++ b/ambari-web/test/mixins/common/widget_mixin_test.js
@@ -179,16 +179,15 @@ describe('App.WidgetMixin', function() {
     });
   });
 
-  describe("#getServiceComponentMetricsSuccessCallback()", function() {
+  describe("#getServiceComponentMetricsSuccessCallback()", function () {
     var mixinObject = mixinClass.create();
-
-    it("", function() {
+    it("", function () {
       var data = {
         metrics: {
-          "hbase" : {
-            "ipc" : {
-              "IPC" : {
-                "numOpenConnections" : 11.5
+          "hbase": {
+            "ipc": {
+              "IPC": {
+                "numOpenConnections": 11.5
               }
             }
           }
@@ -196,11 +195,11 @@ describe('App.WidgetMixin', function() {
       };
       mixinObject.set('content.metrics', [
         {
-          name: 'ipc.IPC.numOpenConnections'
+          widget_id: 'metrics/hbase/ipc/IPC/numOpenConnections'
         }
       ]);
-      mixinObject.getServiceComponentMetricsSuccessCallback(data, {}, {serviceName: 'hbase'});
-      expect(mixinObject.get('metrics').findProperty('name', 'ipc.IPC.numOpenConnections').data).to.equal(11.5);
+      mixinObject.getServiceComponentMetricsSuccessCallback(data);
+      expect(mixinObject.get('metrics').findProperty('widget_id', 'metrics/hbase/ipc/IPC/numOpenConnections').data).to.equal(11.5);
     });
   });
 
@@ -234,5 +233,66 @@ describe('App.WidgetMixin', function() {
     });
   });
 
+  describe("#calculateValues()", function() {
+    var mixinObject = mixinClass.create();
+
+    beforeEach(function () {
+      sinon.stub(mixinObject, 'extractExpressions');
+      this.mock = sinon.stub(mixinObject, 'computeExpression');
+    });
+    afterEach(function () {
+      mixinObject.extractExpressions.restore();
+      this.mock.restore();
+    });
+    it("value compute correctly", function() {
+      this.mock.returns({'${a}': 1});
+      mixinObject.set('content.values', [{
+        value: '${a}'
+      }]);
+      mixinObject.calculateValues();
+      expect(mixinObject.get('content.values')[0].computedValue).to.equal('1');
+    });
+    it("value not available", function() {
+      this.mock.returns({});
+      mixinObject.set('content.values', [{
+        value: '${a}'
+      }]);
+      mixinObject.calculateValues();
+      expect(mixinObject.get('content.values')[0].computedValue).to.equal(Em.I18n.t('common.na'));
+    });
+  });
+
+  describe("#computeExpression()", function() {
+    var mixinObject = mixinClass.create();
+
+    it("expression missing metrics", function() {
+      var expressions = ['e.m1'];
+      var metrics = [];
+      expect(mixinObject.computeExpression(expressions, metrics)).to.eql({
+        "${e.m1}": ""
+      });
+    });
+    it("Value is not correct mathematical expression", function() {
+      var expressions = ['e.m1'];
+      var metrics = [{
+        name: 'e.m1',
+        data: 'a+1'
+      }];
+      expect(mixinObject.computeExpression(expressions, metrics)).to.eql({
+        "${e.m1}": ""
+      });
+    });
+    it("correct expression", function() {
+      var expressions = ['e.m1+e.m1'];
+      var metrics = [{
+        name: 'e.m1',
+        data: 1
+      }];
+      expect(mixinObject.computeExpression(expressions, metrics)).to.eql({
+        "${e.m1+e.m1}": "2"
+      });
+    });
+  });
+
 });