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/27 17:11:43 UTC

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

Repository: ambari
Updated Branches:
  refs/heads/trunk 391cc7a6f -> bfb586d85


AMBARI-10223 Draw Number 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/bfb586d8
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/bfb586d8
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/bfb586d8

Branch: refs/heads/trunk
Commit: bfb586d8562c7ffe54e9f313f0eac22389e075f5
Parents: 391cc7a
Author: Andrii Tkach <at...@hortonworks.com>
Authored: Fri Mar 27 17:19:26 2015 +0200
Committer: Andrii Tkach <at...@hortonworks.com>
Committed: Fri Mar 27 18:11:33 2015 +0200

----------------------------------------------------------------------
 .../HBASE/Append_num_ops_&_Delete_num_ops.json  |   7 +-
 .../data/widget_layouts/HBASE/stack_layout.json | 116 ---------
 ambari-web/app/assets/test/tests.js             |   1 +
 ambari-web/app/mixins.js                        |   1 +
 ambari-web/app/mixins/common/widget_mixin.js    | 179 ++++++++++++++
 ambari-web/app/models/widget.js                 |   2 +
 ambari-web/app/styles/widget_layout.less        |  39 +++
 .../templates/common/widget/template_widget.hbs |  22 ++
 .../app/templates/main/service/info/summary.hbs |   6 +-
 ambari-web/app/views.js                         |   1 +
 .../views/common/widget/graph_widget_view.js    | 153 +-----------
 .../views/common/widget/template_widget_view.js |  96 ++++++++
 .../test/mixins/common/widget_mixin_test.js     | 238 +++++++++++++++++++
 13 files changed, 594 insertions(+), 267 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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 97d1bee..2db12a0 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
@@ -1,4 +1,3 @@
-
 {
   "href" : "http://c6401.ambari.apache.org:8080/api/v1/clusters/c1/services/HBASE/components/HBASE_REGIONSERVER?fields=metrics/hbase/regionserver/Server/Append_num_ops[1426795139,1426798739,15]",
   "ServiceComponentInfo" : {
@@ -8,8 +7,14 @@
   },
   "metrics" : {
     "hbase" : {
+      "ipc" : {
+        "IPC" : {
+          "numOpenConnections" : 11.5
+        }
+      },
       "regionserver" : {
         "Server" : {
+          "percentFilesLocal" : 99,
           "Append_num_ops" : [
             [
               2.0,

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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 f921ad7..4c8d3ee 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
@@ -19,21 +19,6 @@
             "widget_type": "GRAPH",
             "metrics":[
               {
-                "name": "regionserver.Server.Get_num_ops",
-                "widget_id": "metrics/hbase/regionserver/Server/Get_num_ops",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER",
-                "host_component_criteria" : "isActive=true"
-              },
-              {
-                "name": "regionserver.Server.Scan_num_ops",
-                "widget_id": "metrics/hbase/regionserver/Server/Scan_num_ops",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
-              },
-              {
                 "name": "regionserver.Server.Append_num_ops",
                 "widget_id": "metrics/hbase/regionserver/Server/Append_num_ops",
                 "category": "",
@@ -46,20 +31,6 @@
                 "category": "",
                 "service_name": "HBASE",
                 "component_name": "HBASE_REGIONSERVER"
-              },
-              {
-                "name": "regionserver.Server.Increment_num_ops",
-                "widget_id": "metrics/hbase/regionserver/Server/Increment_num_ops",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
-              },
-              {
-                "name": "regionserver.Server.Mutate_num_ops",
-                "widget_id": "metrics/hbase/regionserver/Server/Mutate_num_ops",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
               }
             ],
             "values": [
@@ -79,67 +50,6 @@
             }
           },
           {
-            "widget_name": "OPEN_CONNECTIONS",
-            "display_name": "Open Connections",
-            "description": "This widget shows number of current open connections",
-            "widget_type": "GRAPH",
-            "metrics":[
-              {
-                "name": "ipc.IPC.numOpenConnections",
-                "widget_id": "metrics/hbase/ipc/IPC/numOpenConnections",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
-              }
-            ],
-            "values": [
-              {
-                "name": "Open Connections",
-                "value": "${ipc.IPC.numOpenConnections}"
-              }
-            ],
-            "properties": {
-              "display_unit": "Connections",
-              "graph_type": "STACK",
-              "time_range": "1 hour"
-            }
-          },
-          {
-            "widget_name": "ACTIVE_HANDLER",
-            "display_name": "Active Handlers vs Calls in General Queue",
-            "widget_type": "GRAPH",
-            "metrics":[
-              {
-                "name": "ipc.IPC.numOpenConnections",
-                "widget_id": "metrics/hbase/ipc/IPC/numOpenConnections",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
-              },
-              {
-                "name": "ipc.IPC.numCallsInGeneralQueue",
-                "widget_id": "metrics/hbase/ipc/IPC/numOpenConnections",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
-              }
-            ],
-            "values": [
-              {
-                "name": "Active Handlers",
-                "value": "${ipc.IPC.numActiveHandler}"
-              },
-              {
-                "name": "Calls in General Queue",
-                "value": "${ipc.IPC.numCallsInGeneralQueue}"
-              }
-            ],
-            "properties": {
-              "graph_type": "LINE",
-              "time_range": "1 hour"
-            }
-          },
-          {
             "widget_name": "FILES_LOCAL",
             "display_name": "Files Local",
             "description": "This widget shows percentage of files local.",
@@ -162,32 +72,6 @@
             "properties": {
               "display_unit": "%"
             }
-          },
-          {
-            "widget_name": "UPDATED_BLOCKED_TIME",
-            "display_name": "Updated Blocked Time",
-            "description": "",
-            "widget_type": "GRAPH",
-            "metrics":[
-              {
-                "name": "regionserver.Server.updatesBlockedTime",
-                "widget_id": "metrics/hbase/regionserver/Server/updatesBlockedTime",
-                "category": "",
-                "service_name": "HBASE",
-                "component_name": "HBASE_REGIONSERVER"
-              }
-            ],
-            "values": [
-              {
-                "name": "Updated Blocked Time",
-                "value": "${regionserver.Server.updatesBlockedTime}"
-              }
-            ],
-            "properties": {
-              "display_unit": "seconds",
-              "graph_type": "LINE",
-              "time_range": "1 day"
-            }
           }
         ]
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index c668a9f..01dbdb3 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -141,6 +141,7 @@ var files = ['test/init_model_test',
   'test/mixins/common/reload_popup_test',
   'test/mixins/common/serverValidator_test',
   'test/mixins/common/table_server_view_mixin_test',
+  'test/mixins/common/widget_mixin_test',
   'test/mixins/main/host/details/host_components/decommissionable_test',
   'test/mixins/routers/redirections_test',
   'test/mixins/wizard/addSeccurityConfigs_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/mixins.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js
index 52091c1..045333f 100644
--- a/ambari-web/app/mixins.js
+++ b/ambari-web/app/mixins.js
@@ -38,3 +38,4 @@ require('mixins/wizard/selectHost');
 require('mixins/wizard/addSecurityConfigs');
 require('mixins/wizard/wizard_menu_view');
 require('mixins/common/configs/enhanced_configs');
+require('mixins/common/widget_mixin');

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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
new file mode 100644
index 0000000..1f90500
--- /dev/null
+++ b/ambari-web/app/mixins/common/widget_mixin.js
@@ -0,0 +1,179 @@
+/**
+ * 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.WidgetMixin = Ember.Mixin.create({
+
+  /**
+   * @type {RegExp}
+   * @const
+   */
+  EXPRESSION_REGEX: /\$\{([\w\.\+\-\*\/\(\)]*)\}/g,
+
+  /**
+   * @type {RegExp}
+   * @const
+   */
+  MATH_EXPRESSION_REGEX: /^[\d\+\-\*\/\(\)\.]+$/,
+
+  /**
+   * @type {RegExp}
+   * @const
+   */
+  VALUE_NAME_REGEX: /[\w\.]+/g,
+
+  /**
+   * common metrics container
+   * @type {Array}
+   */
+  metrics: [],
+
+  /**
+   * @type {boolean}
+   */
+  isLoaded: false,
+
+  /**
+   * @type {App.Widget}
+   * @default null
+   */
+  content: null,
+
+  /**
+   * load metrics
+   */
+  beforeRender: function () {
+    var requestData = this.getRequestData(this.get('content.metrics')),
+        request,
+        requestCounter = 0,
+        self = this;
+
+    for (var i in requestData) {
+      request = requestData[i];
+      requestCounter++;
+      if (request.host_component_criteria) {
+        this.getHostComponentMetrics(request).complete(function () {
+          requestCounter--;
+          if (requestCounter === 0) self.set('isLoaded', true);
+        });
+      } else {
+        this.getServiceComponentMetrics(request).complete(function () {
+          requestCounter--;
+          if (requestCounter === 0) self.set('isLoaded', true);
+        });
+      }
+    }
+  },
+
+  /**
+   * extract expressions
+   * Example:
+   *  input: "${a/b} equal ${b+a}"
+   *  expressions: ['a/b', 'b+a']
+   *
+   * @param {object} input
+   * @returns {Array}
+   */
+  extractExpressions: function (input) {
+    var pattern = this.get('EXPRESSION_REGEX'),
+      expressions = [],
+      match;
+
+    while (match = pattern.exec(input.value)) {
+      expressions.push(match[1]);
+    }
+    return expressions;
+  },
+
+  /**
+   * get data formatted for request
+   * @param {Array} metrics
+   */
+  getRequestData: function (metrics) {
+    var requestsData = {};
+
+    metrics.forEach(function (metric) {
+      var key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria;
+      var requestMetric = $.extend({}, metric);
+
+      if (requestsData[key]) {
+        requestsData[key]["widget_ids"].push(requestMetric["widget_id"]);
+      } else {
+        requestMetric["widget_ids"] = [requestMetric["widget_id"]];
+        delete requestMetric["widget_id"];
+        requestsData[key] = requestMetric;
+      }
+    }, this);
+    return requestsData;
+  },
+
+  /**
+   * make GET call to server in order to fetch service-component metrics
+   * @param {object} request
+   * @returns {$.ajax}
+   */
+  getServiceComponentMetrics: function (request) {
+    return App.ajax.send({
+      name: 'widgets.serviceComponent.metrics.get',
+      sender: this,
+      data: {
+        serviceName: request.service_name,
+        componentName: request.component_name,
+        widgetIds: request.widget_ids.join(',')
+      },
+      success: 'getServiceComponentMetricsSuccessCallback'
+    });
+  },
+
+  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);
+        this.get('metrics').pushObject(_metric);
+      }
+    }, this);
+  },
+
+  /**
+   * make GET call to server in order to fetch host-component metrics
+   * @param {object} request
+   * @returns {$.ajax}
+   */
+  getHostComponentMetrics: function (request) {
+    return App.ajax.send({
+      name: 'widgets.hostComponent.metrics.get',
+      sender: this,
+      data: {
+        serviceName: request.service_name,
+        componentName: request.component_name,
+        widgetIds: request.widget_ids.join(','),
+        hostComponentCriteria: 'host_components/HostRoles/' + request.host_component_criteria
+      },
+      success: 'getHostComponentMetricsSuccessCallback'
+    });
+  },
+
+  getHostComponentMetricsSuccessCallback: function () {
+    //TODO push data to metrics after response structure approved
+  }
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/models/widget.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/widget.js b/ambari-web/app/models/widget.js
index 3d0ddc7..bdbb527 100644
--- a/ambari-web/app/models/widget.js
+++ b/ambari-web/app/models/widget.js
@@ -57,6 +57,8 @@ App.Widget = DS.Model.extend({
     switch (this.get('widgetType')) {
       case 'GRAPH':
         return App.GraphWidgetView;
+      case 'NUMBER':
+        return App.TemplateWidgetView;
       default:
         return Em.View;
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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
new file mode 100644
index 0000000..265e666
--- /dev/null
+++ b/ambari-web/app/styles/widget_layout.less
@@ -0,0 +1,39 @@
+/**
+ * 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.
+ */
+
+#widget_layout {
+  .widget {
+    .template-widget {
+      height: 150px;
+      width: 90%;
+      .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;
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/templates/common/widget/template_widget.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/widget/template_widget.hbs b/ambari-web/app/templates/common/widget/template_widget.hbs
new file mode 100644
index 0000000..fda71ea
--- /dev/null
+++ b/ambari-web/app/templates/common/widget/template_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="template-widget thumbnail">
+  <div class="caption title">{{view.title}}</div>
+  <div class="content"> {{view.value}}</div>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/templates/main/service/info/summary.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/service/info/summary.hbs b/ambari-web/app/templates/main/service/info/summary.hbs
index b6a5e1d..e87ca6b 100644
--- a/ambari-web/app/templates/main/service/info/summary.hbs
+++ b/ambari-web/app/templates/main/service/info/summary.hbs
@@ -99,10 +99,12 @@
           <div class="">
             <table class="graphs">
               {{#if App.supports.customizedWidgets}}
-                <tr>
+                <tr id="widget_layout">
                   {{#each widget in controller.widgets}}
                     <td>
-                      {{view widget.viewClass contentBinding="widget"}}
+                      <div class="widget">
+                        {{view widget.viewClass contentBinding="widget"}}
+                      </div>
                     </td>
                   {{/each}}
                 </tr>

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index a7719fc..cb121ec 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -63,6 +63,7 @@ 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');

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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 a6cd420..c245622 100644
--- a/ambari-web/app/views/common/widget/graph_widget_view.js
+++ b/ambari-web/app/views/common/widget/graph_widget_view.js
@@ -18,7 +18,7 @@
 
 var App = require('app');
 
-App.GraphWidgetView = App.ChartLinearTimeView.extend({
+App.GraphWidgetView = App.ChartLinearTimeView.extend(App.WidgetMixin, {
 
   /**
    * @type {string}
@@ -42,22 +42,10 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
   }.property('properties.graph_type'),
 
   /**
-   * @type {RegExp}
-   * @const
-   */
-  EXPRESSION_REGEX: /\$\{([\w\.\+\-\*\/\(\)]*)\}/g,
-
-  /**
-   * @type {RegExp}
-   * @const
-   */
-  MATH_EXPRESSION_REGEX: /^[\d\+\-\*\/\(\)\.]+$/,
-
-  /**
-   * @type {RegExp}
-   * @const
+   * common metrics container
+   * @type {Array}
    */
-  VALUE_NAME_REGEX: /[\w\.]+/g,
+  metrics: [],
 
   /**
    * value in ms
@@ -71,49 +59,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
    */
   timeStep: 15,
 
-  /**
-   * common metrics container
-   * @type {Array}
-   */
-  metrics: [],
-
-  /**
-   * @type {boolean}
-   */
-  isLoaded: false,
-
-  /**
-   * @type {App.Widget}
-   * @default null
-   */
-  content: null,
-
-  /**
-   * load metrics
-   */
-  beforeRender: function () {
-    var requestData = this.getRequestData(this.get('content.metrics')),
-      request,
-      requestCounter = 0,
-      self = this;
-
-    for (var i in requestData) {
-      request = requestData[i];
-      requestCounter++;
-      if (request.host_component_criteria) {
-        this.getHostComponentMetrics(request).complete(function () {
-          requestCounter--;
-          if (requestCounter === 0) self.set('isLoaded', true);
-        });
-      } else {
-        this.getServiceComponentMetrics(request).complete(function () {
-          requestCounter--;
-          if (requestCounter === 0) self.set('isLoaded', true);
-        });
-      }
-    }
-  },
-
   didInsertElement: Em.K,
 
   transformToSeries: function (seriesData) {
@@ -131,7 +76,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
    */
   calculateSeries: function () {
     var metrics = this.get('metrics');
-    var widgetType = this.get('content.widgetType');
     var seriesData = [];
 
     this.get('content.values').forEach(function (value) {
@@ -183,56 +127,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
   },
 
   /**
-   * TODO should be used for simple type of widgets
-   * @param expressions
-   * @param metrics
-   * @returns {object}
-   *//*
-   computeExpressions: 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;
-   },*/
-
-  /**
-   * extract expressions
-   * @param {object} value
-   * @returns {Array}
-   */
-  extractExpressions: function (value) {
-    var pattern = this.get('EXPRESSION_REGEX'),
-      expressions = [],
-      match;
-
-    while (match = pattern.exec(value.value)) {
-      expressions.push(match[1]);
-    }
-    return expressions;
-  },
-
-  /**
    * make GET call to server in order to fetch service-component metrics
    * @param {object} request
    * @returns {$.ajax}
@@ -250,18 +144,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
     });
   },
 
-  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);
-        this.get('metrics').pushObject(_metric);
-      }
-    }, this);
-  },
-
   /**
    * make GET call to server in order to fetch host-component metrics
    * @param {object} request
@@ -281,31 +163,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
     });
   },
 
-  getHostComponentMetricsSuccessCallback: function () {
-    //TODO push data to metrics after response structure approved
-  },
-
-  /**
-   * get data formatted for request
-   * @param {Array} metrics
-   */
-  getRequestData: function (metrics) {
-    var requestsData = {};
-
-    metrics.forEach(function (metric) {
-      var key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria;
-
-      if (requestsData[key]) {
-        requestsData[key]["widget_ids"].push(metric["widget_id"]);
-      } else {
-        metric["widget_ids"] = [metric["widget_id"]];
-        delete metric["widget_id"];
-        requestsData[key] = metric;
-      }
-    }, this);
-    return requestsData;
-  },
-
   /**
    * add time properties
    * @param {Array} widgetIds
@@ -323,4 +180,4 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({
 
     return result;
   }
-});
\ No newline at end of file
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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
new file mode 100644
index 0000000..580b92a
--- /dev/null
+++ b/ambari-web/app/views/common/widget/template_widget_view.js
@@ -0,0 +1,96 @@
+/**
+ * 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.TemplateWidgetView = Em.View.extend(App.WidgetMixin, {
+  templateName: require('templates/common/widget/template_widget'),
+
+  /**
+   * @type {string}
+   */
+  title: '',
+
+  /**
+   * @type {string}
+   */
+  value: '',
+
+  /**
+   * common metrics container
+   * @type {Array}
+   */
+  metrics: [],
+
+  drawWidget: function () {
+    if (this.get('isLoaded')) {
+      this.calculateValues();
+      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;
+  }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/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
new file mode 100644
index 0000000..6b78769
--- /dev/null
+++ b/ambari-web/test/mixins/common/widget_mixin_test.js
@@ -0,0 +1,238 @@
+/**
+ * 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');
+
+describe('App.WidgetMixin', function() {
+  var mixinClass = Em.Object.extend(App.WidgetMixin, {metrics: [], content: {}});
+
+  describe('#beforeRender()', function () {
+    var mixinObject = mixinClass.create();
+    beforeEach(function () {
+      this.mock = sinon.stub(mixinObject, 'getRequestData');
+      sinon.stub(mixinObject, 'getHostComponentMetrics').returns({complete: function(callback){
+        callback();
+      }});
+      sinon.stub(mixinObject, 'getServiceComponentMetrics').returns({complete: function(callback){
+        callback();
+      }});
+    });
+    afterEach(function () {
+      this.mock.restore();
+      mixinObject.getHostComponentMetrics.restore();
+      mixinObject.getServiceComponentMetrics.restore();
+    });
+    it('has host_component_criteria', function () {
+      this.mock.returns({'key1': {host_component_criteria: 'criteria'}});
+      mixinObject.set('isLoaded', false);
+      mixinObject.beforeRender();
+
+      expect(mixinObject.getHostComponentMetrics.calledWith({host_component_criteria: 'criteria'})).to.be.true;
+      expect(mixinObject.get('isLoaded')).to.be.true;
+    });
+    it('host_component_criteria is absent', function () {
+      this.mock.returns({'key1': {}});
+      mixinObject.set('isLoaded', false);
+      mixinObject.beforeRender();
+
+      expect(mixinObject.getServiceComponentMetrics.calledWith({})).to.be.true;
+      expect(mixinObject.get('isLoaded')).to.be.true;
+    });
+  });
+
+  describe("#extractExpressions()", function() {
+    var mixinObject = mixinClass.create();
+    var testCases = [
+      {
+        data: '',
+        result: []
+      },
+      {
+        data: 'text',
+        result: []
+      },
+      {
+        data: 'text${a}',
+        result: ['a']
+      },
+      {
+        data: 'text${a} - ${a.b}',
+        result: ['a', 'a.b']
+      },
+      {
+        data: '${o.a-(b+4)/cc*tt}',
+        result: ['o.a-(b+4)/cc*tt']
+      }
+    ];
+    testCases.forEach(function (test) {
+      it('input: ' + test.data, function () {
+        var input = {value: test.data};
+        expect(mixinObject.extractExpressions(input)).to.eql(test.result);
+      });
+    });
+  });
+
+  describe("#getRequestData()", function() {
+    var mixinObject = mixinClass.create();
+    it("", function() {
+      var data = [
+        {
+          "name": "regionserver.Server.percentFilesLocal",
+          "widget_id": "metrics/hbase/regionserver/percentFilesLocal",
+          "service_name": "HBASE",
+          "component_name": "HBASE_REGIONSERVER"
+        },
+        {
+          "name": "regionserver.Server.percentFilesLocal2",
+          "widget_id": "w2",
+          "service_name": "HBASE",
+          "component_name": "HBASE_REGIONSERVER"
+        },
+        {
+          "name": "regionserver.Server.percentFilesLocal",
+          "widget_id": "metrics/hbase/regionserver/percentFilesLocal",
+          "service_name": "HBASE",
+          "component_name": "HBASE_REGIONSERVER",
+          "host_component_criteria": 'c1'
+        },
+        {
+          "name": "regionserver.Server.percentFilesLocal",
+          "widget_id": "metrics/hbase/regionserver/percentFilesLocal",
+          "service_name": "HDFS",
+          "component_name": "DATANODE",
+          "host_component_criteria": 'c1'
+        }
+      ];
+
+      expect(mixinObject.getRequestData(data)).to.eql({
+        "HBASE_HBASE_REGIONSERVER_undefined": {
+          "name": "regionserver.Server.percentFilesLocal",
+          "service_name": "HBASE",
+          "component_name": "HBASE_REGIONSERVER",
+          "widget_ids": [
+            "metrics/hbase/regionserver/percentFilesLocal",
+            "w2"
+          ]
+        },
+        "HBASE_HBASE_REGIONSERVER_c1": {
+          "name": "regionserver.Server.percentFilesLocal",
+          "service_name": "HBASE",
+          "component_name": "HBASE_REGIONSERVER",
+          "host_component_criteria": "c1",
+          "widget_ids": [
+            "metrics/hbase/regionserver/percentFilesLocal"
+          ]
+        },
+        "HDFS_DATANODE_c1": {
+          "name": "regionserver.Server.percentFilesLocal",
+          "service_name": "HDFS",
+          "component_name": "DATANODE",
+          "host_component_criteria": "c1",
+          "widget_ids": [
+            "metrics/hbase/regionserver/percentFilesLocal"
+          ]
+        }
+      });
+    });
+  });
+
+  describe("#getServiceComponentMetrics()", function () {
+    var mixinObject = mixinClass.create();
+    before(function () {
+      sinon.stub(App.ajax, 'send');
+    });
+    after(function () {
+      App.ajax.send.restore();
+    });
+    it("", function () {
+      var request = {
+        service_name: 'S1',
+        component_name: 'C1',
+        widget_ids: ['w1', 'w2']
+      };
+      mixinObject.getServiceComponentMetrics(request);
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'widgets.serviceComponent.metrics.get',
+        sender: mixinObject,
+        data: {
+          serviceName: 'S1',
+          componentName: 'C1',
+          widgetIds: 'w1,w2'
+        },
+        success: 'getServiceComponentMetricsSuccessCallback'
+      })
+    });
+  });
+
+  describe("#getServiceComponentMetricsSuccessCallback()", function() {
+    var mixinObject = mixinClass.create();
+
+    it("", function() {
+      var data = {
+        metrics: {
+          "hbase" : {
+            "ipc" : {
+              "IPC" : {
+                "numOpenConnections" : 11.5
+              }
+            }
+          }
+        }
+      };
+      mixinObject.set('content.metrics', [
+        {
+          name: 'ipc.IPC.numOpenConnections'
+        }
+      ]);
+      mixinObject.getServiceComponentMetricsSuccessCallback(data, {}, {serviceName: 'hbase'});
+      expect(mixinObject.get('metrics').findProperty('name', 'ipc.IPC.numOpenConnections').data).to.equal(11.5);
+    });
+  });
+
+  describe("#getHostComponentMetrics()", function () {
+    var mixinObject = mixinClass.create();
+    before(function () {
+      sinon.stub(App.ajax, 'send');
+    });
+    after(function () {
+      App.ajax.send.restore();
+    });
+    it("", function () {
+      var request = {
+        service_name: 'S1',
+        component_name: 'C1',
+        widget_ids: ['w1', 'w2'],
+        host_component_criteria: 'c1'
+      };
+      mixinObject.getHostComponentMetrics(request);
+      expect(App.ajax.send.getCall(0).args[0]).to.eql({
+        name: 'widgets.hostComponent.metrics.get',
+        sender: mixinObject,
+        data: {
+          serviceName: 'S1',
+          componentName: 'C1',
+          widgetIds: 'w1,w2',
+          hostComponentCriteria: 'host_components/HostRoles/c1'
+        },
+        success: 'getHostComponentMetricsSuccessCallback'
+      })
+    });
+  });
+
+});
+