You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sr...@apache.org on 2014/02/08 02:55:27 UTC

git commit: AMBARI-4566. Jobs: implement the TEZ DAG page/panel. (srimanth)

Updated Branches:
  refs/heads/trunk 9d9e70486 -> 1f6bf6582


AMBARI-4566. Jobs: implement the TEZ DAG page/panel. (srimanth)


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

Branch: refs/heads/trunk
Commit: 1f6bf6582f536e3bc10641b47ad578c2ff00d8eb
Parents: 9d9e704
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Fri Feb 7 17:28:54 2014 -0800
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Fri Feb 7 17:40:12 2014 -0800

----------------------------------------------------------------------
 ambari-web/app/app.js                           |  19 +-
 .../main/jobs/hive_job_details_controller.js    |  12 +
 ambari-web/app/mappers/jobs/hive_job_mapper.js  |  12 +-
 ambari-web/app/messages.js                      |  12 +
 ambari-web/app/models/jobs/tez_dag.js           |   2 +-
 ambari-web/app/routes/main.js                   |   3 +-
 ambari-web/app/styles/application.less          |  90 +++-
 .../templates/main/jobs/hive_job_details.hbs    | 176 ++++---
 .../main/jobs/hive_job_details_tez_dag.hbs      |  39 ++
 ambari-web/app/utils/jobs.js                    |  40 +-
 ambari-web/app/utils/number_utils.js            |   2 +-
 ambari-web/app/views.js                         |   1 +
 .../main/jobs/hive_job_details_tez_dag_view.js  | 488 +++++++++++++++++++
 .../views/main/jobs/hive_job_details_view.js    |  48 +-
 14 files changed, 845 insertions(+), 99 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/app.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/app.js b/ambari-web/app/app.js
index 252edd5..451cd1a 100644
--- a/ambari-web/app/app.js
+++ b/ambari-web/app/app.js
@@ -165,4 +165,21 @@ DS.attr.transforms.object = {
   }
 };
 
-
+/**
+ * Allows EmberData models to have array properties.
+ *
+ * Declare the property as <code>
+ *  operations: DS.attr('array'),
+ * </code> and
+ * during load provide a JSON array for value.
+ *
+ * This transform simply assigns the same array in both directions.
+ */
+DS.attr.transforms.array = {
+  from : function(serialized) {
+    return serialized;
+  },
+  to : function(deserialized) {
+    return deserialized;
+  }
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/controllers/main/jobs/hive_job_details_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/jobs/hive_job_details_controller.js b/ambari-web/app/controllers/main/jobs/hive_job_details_controller.js
index 3714f18..891d8c2 100644
--- a/ambari-web/app/controllers/main/jobs/hive_job_details_controller.js
+++ b/ambari-web/app/controllers/main/jobs/hive_job_details_controller.js
@@ -16,10 +16,22 @@
  */
 
 var App = require('app');
+var jobsUtils = require('utils/jobs');
 
 App.MainHiveJobDetailsController = Em.Controller.extend({
   name : 'mainHiveJobDetailsController',
   content : null,
+  loaded : false,
+  loadJobDetails : function() {
+    var self = this;
+    this.set('loaded', false);
+    var content = this.get('content');
+    if (content != null) {
+      jobsUtils.refreshJobDetails(content, function() {
+        self.set('loaded', true);
+      });
+    }
+  },
 
   /**
    * path to page visited before

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/mappers/jobs/hive_job_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/jobs/hive_job_mapper.js b/ambari-web/app/mappers/jobs/hive_job_mapper.js
index 5f2b2f1..c764fcc 100644
--- a/ambari-web/app/mappers/jobs/hive_job_mapper.js
+++ b/ambari-web/app/mappers/jobs/hive_job_mapper.js
@@ -58,11 +58,14 @@ App.hiveJobMapper = App.QuickDataMapper.create({
           // Vertices
           var vertices = [];
           var vertexIds = [];
+          var vertexIdMap = {};
           for ( var vertexName in stageValue.Tez["Vertices:"]) {
             var vertex = stageValue.Tez["Vertices:"][vertexName];
             var vertexObj = {
               id : dagName + "/" + vertexName,
-              name : vertexName
+              name : vertexName,
+              incoming_edges : [],
+              outgoing_edges : []
             };
             vertexIds.push(vertexObj.id);
             var operatorExtractor = function(obj) {
@@ -90,6 +93,7 @@ App.hiveJobMapper = App.QuickDataMapper.create({
               vertexObj.operations = operatorExtractor(vertex["Reduce Operator Tree:"]);
               vertexObj.operation_plan = JSON.stringify(vertex["Reduce Operator Tree:"], undefined, "  ");
             }
+            vertexIdMap[vertexObj.id] = vertexObj;
             vertices.push(vertexObj);
           }
           // Edges
@@ -105,9 +109,11 @@ App.hiveJobMapper = App.QuickDataMapper.create({
               var parentVertex = e.parent;
               var edgeObj = {
                 id : dagName + "/" + parentVertex + "-" + childVertex,
-                from_vertex : dagName + "/" + parentVertex,
-                to_vertex : dagName + "/" + childVertex
+                from_vertex_id : dagName + "/" + parentVertex,
+                to_vertex_id : dagName + "/" + childVertex
               };
+              vertexIdMap[edgeObj.from_vertex_id].outgoing_edges.push(edgeObj.id);
+              vertexIdMap[edgeObj.to_vertex_id].incoming_edges.push(edgeObj.id);
               edgeIds.push(edgeObj.id);
               switch (e.type) {
               case "BROADCAST_EDGE":

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 162dde4..b2756f8 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -1858,6 +1858,18 @@ Em.I18n.translations = {
   'jobs.hive.tez.writes':'{0} writes',
   'jobs.hive.tez.records.count':'{0} Records',
   'jobs.hive.tez.operatorPlan':'Operator Plan',
+  'jobs.hive.tez.dag.summary.metric':'Summary Metric',
+  'jobs.hive.tez.dag.error.noDag.title':'No Tez DAG',
+  'jobs.hive.tez.dag.error.noDag.message':'Tez DAG not found Hive job {0}',
+  'jobs.hive.tez.dag.error.noDagId.title':'No Tez DAG ID',
+  'jobs.hive.tez.dag.error.noDagId.message':'Tez DAG has no ID associated with name {0}',
+  'jobs.hive.tez.dag.error.noDagForId.title':'No Tez DAG',
+  'jobs.hive.tez.dag.error.noDagForId.message':'No Tez DAG found for ID {0}',
+  'jobs.hive.tez.metric.input':'Input',
+  'jobs.hive.tez.metric.output':'Output',
+  'jobs.hive.tez.metric.recordsRead':'Records Read',
+  'jobs.hive.tez.metric.recordsWrite':'Records Written',
+  'jobs.hive.tez.metric.tezTasks':'Tez Tasks',
 
   'number.validate.empty': 'cannot be empty',
   'number.validate.notValidNumber': 'not a valid number',

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/models/jobs/tez_dag.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/jobs/tez_dag.js b/ambari-web/app/models/jobs/tez_dag.js
index a3ddb82..250ae71 100644
--- a/ambari-web/app/models/jobs/tez_dag.js
+++ b/ambari-web/app/models/jobs/tez_dag.js
@@ -92,7 +92,7 @@ App.TezDagVertex = DS.Model.extend({
    *
    * Array of strings. [{string}]
    */
-  operations : [],
+  operations : DS.attr('array'),
 
   /**
    * Provides additional information about the 'operations' performed in this

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/routes/main.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/routes/main.js b/ambari-web/app/routes/main.js
index b13512e..d9c92c9 100644
--- a/ambari-web/app/routes/main.js
+++ b/ambari-web/app/routes/main.js
@@ -18,7 +18,6 @@
 
 var App = require('app');
 var stringUtils = require('utils/string_utils');
-var jobsUtils = require('utils/jobs');
 
 module.exports = Em.Route.extend({
   route: '/main',
@@ -138,9 +137,9 @@ module.exports = Em.Route.extend({
       route : '/:job_id',
       connectOutlets : function(router, job) {
         if (job) {
-          jobsUtils.refreshJobDetails(job);
           if (job.get('jobType') === App.JobType.HIVE) {
             router.get('mainController').connectOutlet('mainHiveJobDetails', job);
+            router.get('mainHiveJobDetailsController').loadJobDetails();
           }
         }
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index 3443af4..623155f 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -5431,4 +5431,92 @@ i.icon-asterisks {
   .prompt-input {
     width: 520px;
   }
-}
\ No newline at end of file
+}
+
+/***************
+ * Tez DAG section
+ ***************/
+#tez-dag-section {
+  padding-left: 7px;
+  padding-top: 5px;
+}
+#tez-dag-section-body {
+  display: block;
+}
+#tez-dag-section-body-dag {
+  max-height: 800px;
+  overflow-y: auto;
+
+  .heat-0-20 {
+    fill: #ffffff;
+  }
+  .heat-20-40 {
+    fill: #aaaaaa;
+  }
+  .heat-40-60 {
+    fill: #ffff00;
+  }
+  .heat-60-80 {
+    fill: #FFC000;
+  }
+  .heat-80-100 {
+    fill: #ff0000;
+  }
+  .heat-none {
+    fill: #999999;
+  }
+  #tez-dag-svg {
+    .link line {
+      stroke: #696969;
+    }
+    .link line.separator {
+      stroke: #fff;
+      stroke-width: 2px;
+    }
+    text {
+      font-weight: lighter;
+    }
+    .node {
+      .background.map {
+        fill: #fde0dd;
+        stroke: #bbbbbb;
+      }
+      .background.reduce {
+        fill: #DBD4EB;
+        stroke: #bbbbbb;
+      }
+      .background.selected {
+        stroke: #3a87ad;
+      }
+      .background {
+        stroke-width: 6px;
+        fill: #c0c0c0;
+        stroke: #999999;
+        cursor: pointer;
+      }
+      .metric {
+        rect {
+          opacity: 0.6;
+        }
+        text {
+          font-size: 90%;
+        }
+      }
+      .operation {
+        fill: #3F66E8;
+        opacity: 0.5;
+        text {
+          font-size: 80%;
+        }
+      }
+    }
+    .node text {
+      pointer-events: none;
+    }
+    path.link {
+      fill: none;
+      stroke: #666;
+      stroke-width: 1.5px;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/templates/main/jobs/hive_job_details.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/jobs/hive_job_details.hbs b/ambari-web/app/templates/main/jobs/hive_job_details.hbs
index bd5d6ce..dd72926 100644
--- a/ambari-web/app/templates/main/jobs/hive_job_details.hbs
+++ b/ambari-web/app/templates/main/jobs/hive_job_details.hbs
@@ -23,85 +23,115 @@
     <div class="pull-right">Job Type: <span class="label label-info">{{view.content.jobType}}</span></div>
   </div>
 
-  <!-- Sections -->
-  <div class="row-fluid">
-    <div class="span12 sections">
-      <!-- Section LHS -->
-      <div class="span6 sections-lhs">
-      </div>
-
-      <!-- Section RHS -->
-      <div class="span6 sections-rhs">
-
-        <!-- Section RHS Vertices -->
-        <div id="tez-vertices-table-section">
-          <div id="tez-vertices-table-container" class="section">
-            <table class="table table-hover table-bordered table-striped">
-              <thead>
-                <tr>
-                  <th>{{t common.name}}</th>
-                  <th>{{t common.tasks}}</th>
-                  <th>{{t apps.item.dag.input}}</th>
-                  <th>{{t apps.item.dag.output}}</th>
-                  <th>{{t apps.item.dag.duration}}</th>
-                </tr>
-              </thead>
-              <tbody>
-                {{#each vertex in view.content.tezDag.vertices}}
-                  <tr {{bindAttr class="vertex.isSelected:info"}}>
-                    <td>
-                      <a title="{{vertex.name}}" href="#" {{action "doSelectVertex" vertex target="view"}}>{{vertex.name}}</a>
-                    </td>
-                    <td>{{vertex.tasksCount}}</td>
-                    <td>{{vertex.totalReadBytesDisplay}}</td>
-                    <td>{{vertex.totalWriteBytesDisplay}}</td>
-                    <td>{{vertex.durationDisplay}}</td>
-                  </tr>
-                {{/each}}
-              </tbody>
-            </table>
+  {{#if controller.loaded}}
+    <!-- Sections -->
+    <div class="row-fluid">
+      <div class="span12 sections">
+        <!-- Section LHS -->
+        <div id="tez-dag-lhs" class="span6 sections-lhs">
+          <div id="tez-dag-section" class="box">
+            <div id="tez-dag-section-top-bar"> &nbsp;
+              {{t jobs.hive.tez.dag.summary.metric}}
+              <div class="btn-group  display-inline-block">
+                <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+                  {{view.summaryMetricTypeDisplay}}
+                  <span class="caret"></span>
+                </a>
+                <ul class="dropdown-menu">
+                  <!-- dropdown menu links -->
+                  {{#each type in view.summaryMetricTypesDisplay}}
+                    <li>
+                      <a title="{{type}}" href="#" {{action "doSelectSummaryMetricType" type target="view"}}>{{type}}</a>
+                    </li>
+                  {{/each}}
+                </ul>
+              </div>
+            <div id="tez-dag-section-top-bar-actions" class="pull-right">
+            </div>
+            </div>
+            <div id="tez-dag-section-body">
+              {{view App.MainHiveJobDetailsTezDagView controllerBinding="controller" selectedVertexBinding="view.selectedVertex" summaryMetricTypeBinding="view.summaryMetricType"}}
+            </div>
           </div>
         </div>
 
-        <!-- Section RHS Vertex -->
-        {{#if view.selectedVertex}}
-          <div id="section tez-vertex-details-section">
-            <div class="box">
-            <div class="box-header">
-              <h4>{{view.selectedVertex.name}}</h4>
-            </div>
-            <div id="tez-vertex-details-section-body">
-              <table class="table">
-                <tr>
-                  <td>{{t jobs.hive.tez.tasks}}</td>
-                  <td>{{view.selectedVertex.tasksCount}}</td>
-                  <td></td>
-                </tr>
-                <tr>
-                  <td>{{t jobs.hive.tez.hdfs}}</td>
-                  <td>{{view.selectedVertexIODisplay.hdfs.read.ops}} / {{view.selectedVertexIODisplay.hdfs.read.bytes}}</td>
-                  <td>{{view.selectedVertexIODisplay.hdfs.write.ops}} / {{view.selectedVertexIODisplay.hdfs.write.bytes}}</td>
-                </tr>
-                <tr>
-                  <td>{{t jobs.hive.tez.localFiles}}</td>
-                  <td>{{view.selectedVertexIODisplay.file.read.ops}} / {{view.selectedVertexIODisplay.file.read.bytes}}</td>
-                  <td>{{view.selectedVertexIODisplay.file.write.ops}} / {{view.selectedVertexIODisplay.file.write.bytes}}</td>
-                </tr>
-                <tr>
-                  <td>{{t jobs.hive.tez.records}}</td>
-                  <td>{{view.selectedVertexIODisplay.records.read}}</td>
-                  <td>{{view.selectedVertexIODisplay.records.write}}</td>
-                </tr>
-                  <td>{{t jobs.hive.tez.operatorPlan}}</td>
-                  <td></td>
-                  <td></td>
-                </tr>
+        <!-- Section RHS -->
+        <div id="tez-vertices-rhs" class="span6 sections-rhs">
+
+          <!-- Section RHS Vertices -->
+          <div id="tez-vertices-table-section">
+            <div id="tez-vertices-table-container" class="section">
+              <table class="table table-hover table-bordered table-striped">
+                <thead>
+                  <tr>
+                    <th>{{t common.name}}</th>
+                    <th>{{t common.tasks}}</th>
+                    <th>{{t apps.item.dag.input}}</th>
+                    <th>{{t apps.item.dag.output}}</th>
+                    <th>{{t apps.item.dag.duration}}</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {{#each vertex in view.content.tezDag.vertices}}
+                    <tr {{bindAttr class="vertex.isSelected:info"}}>
+                      <td>
+                        <a title="{{vertex.name}}" href="#" {{action "doSelectVertex" vertex target="view"}}>{{vertex.name}}</a>
+                      </td>
+                      <td>{{vertex.tasksCount}}</td>
+                      <td>{{vertex.totalReadBytesDisplay}}</td>
+                      <td>{{vertex.totalWriteBytesDisplay}}</td>
+                      <td>{{vertex.durationDisplay}}</td>
+                    </tr>
+                  {{/each}}
+                </tbody>
               </table>
-              {{view Ember.TextArea valueBinding="view.selectedVertex.operationPlan" rows="15"}}
             </div>
           </div>
-        {{/if}}
+
+          <!-- Section RHS Vertex -->
+          {{#if view.selectedVertex}}
+            <div id="section tez-vertex-details-section">
+              <div class="box">
+              <div class="box-header">
+                <h4>{{view.selectedVertex.name}}</h4>
+              </div>
+              <div id="tez-vertex-details-section-body">
+                <table class="table">
+                  <tr>
+                    <td>{{t jobs.hive.tez.tasks}}</td>
+                    <td>{{view.selectedVertex.tasksCount}}</td>
+                    <td></td>
+                  </tr>
+                  <tr>
+                    <td>{{t jobs.hive.tez.hdfs}}</td>
+                    <td>{{view.selectedVertexIODisplay.hdfs.read.ops}} / {{view.selectedVertexIODisplay.hdfs.read.bytes}}</td>
+                    <td>{{view.selectedVertexIODisplay.hdfs.write.ops}} / {{view.selectedVertexIODisplay.hdfs.write.bytes}}</td>
+                  </tr>
+                  <tr>
+                    <td>{{t jobs.hive.tez.localFiles}}</td>
+                    <td>{{view.selectedVertexIODisplay.file.read.ops}} / {{view.selectedVertexIODisplay.file.read.bytes}}</td>
+                    <td>{{view.selectedVertexIODisplay.file.write.ops}} / {{view.selectedVertexIODisplay.file.write.bytes}}</td>
+                  </tr>
+                  <tr>
+                    <td>{{t jobs.hive.tez.records}}</td>
+                    <td>{{view.selectedVertexIODisplay.records.read}}</td>
+                    <td>{{view.selectedVertexIODisplay.records.write}}</td>
+                  </tr>
+                    <td>{{t jobs.hive.tez.operatorPlan}}</td>
+                    <td></td>
+                    <td></td>
+                  </tr>
+                </table>
+                {{view Ember.TextArea valueBinding="view.selectedVertex.operationPlan" rows="15"}}
+              </div>
+            </div>
+          {{/if}}
+        </div>
       </div>
     </div>
-  </div>
+  {{else}}
+    <div class="alert alert-info">
+      <h4>{{t app.loadingPlaceholder}}</h4>
+    </div>
+  {{/if}}
 </div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/templates/main/jobs/hive_job_details_tez_dag.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/jobs/hive_job_details_tez_dag.hbs b/ambari-web/app/templates/main/jobs/hive_job_details_tez_dag.hbs
new file mode 100644
index 0000000..28787ee
--- /dev/null
+++ b/ambari-web/app/templates/main/jobs/hive_job_details_tez_dag.hbs
@@ -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.
+}}
+
+<div id="tez-dag-section-body-dag">
+  <svg id="tez-dag-svg" stroke="black" strokeWidth="2">
+    <defs>
+      <marker id="arrow" viewBox="0 -5 10 10" refX="6" markerWidth="10" markerHeight="10" orient="auto">
+        <path d="M0,-5L10,0L0,5"></path>
+      </marker>
+      <filter id="shadow" width="200%" height="200%">
+        <feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
+        <feOffset dx="2" dy="2" result="offsetblur"/>
+        <feMerge> 
+          <feMergeNode/>
+          <feMergeNode in="SourceGraphic"/>
+        </feMerge>
+      </filter>
+      <clipPath id="operatorClipPath">
+        <rect x="0" y="0" width="44" height="16">
+        </rect>
+      </clipPath>
+    </defs>
+  </svg>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/utils/jobs.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/jobs.js b/ambari-web/app/utils/jobs.js
index c63975f..b56db19 100644
--- a/ambari-web/app/utils/jobs.js
+++ b/ambari-web/app/utils/jobs.js
@@ -23,12 +23,14 @@ module.exports = {
    *
    * @param {App.AbstractJob}
    *          job
+   * @param {Function}
+   *          successCallback
    */
-  refreshJobDetails : function(job) {
+  refreshJobDetails : function(job, successCallback) {
     if (job) {
       switch (job.get('jobType')) {
       case App.JobType.HIVE:
-        this.refreshHiveJobDetails(job);
+        this.refreshHiveJobDetails(job, successCallback);
         break;
       default:
         break;
@@ -41,15 +43,17 @@ module.exports = {
    *
    * @param {App.HiveJob}
    *          hiveJob
+   * @param {Function}
+   *          successCallback
    */
-  refreshHiveJobDetails : function(hiveJob) {
+  refreshHiveJobDetails : function(hiveJob, successCallback) {
     var self = this;
     // TODO - to be changed to history server when implemented in stack.
     var historyServerHostName = App.YARNService.find().objectAt(0).get('resourceManagerNode.hostName')
     var hiveJobId = hiveJob.get('id');
     // First refresh query
-    var hiveQueriesUrl = App.testMode ? "/data/jobs/hive-query-2.json" :
-    App.apiPrefix + "/proxy?url=http://"+historyServerHostName+":8188/ws/v1/apptimeline/HIVE_QUERY_ID/" + hiveJob.get('id')+"?fields=otherinfo";
+    var hiveQueriesUrl = App.testMode ? "/data/jobs/hive-query-2.json" : App.apiPrefix + "/proxy?url=http://" + historyServerHostName
+        + ":8188/ws/v1/apptimeline/HIVE_QUERY_ID/" + hiveJob.get('id') + "?fields=otherinfo";
     App.HttpClient.get(hiveQueriesUrl, App.hiveJobMapper, {
       complete : function(jqXHR, textStatus) {
         // Now get the Tez DAG ID from the DAG name
@@ -61,7 +65,9 @@ module.exports = {
               if (data && data.entities && data.entities.length > 0) {
                 var dagId = data.entities[0].entity;
                 App.TezDag.find(tezDagName).set('instanceId', dagId);
-                self.refreshTezDagDetails(tezDagName);
+                self.refreshTezDagDetails(tezDagName, successCallback);
+              }else{
+                App.showAlertPopup(Em.I18n.t('jobs.hive.tez.dag.error.noDagId.title'), Em.I18n.t('jobs.hive.tez.dag.error.noDagId.message').format(hiveJobId));
               }
             },
             dagNameToIdError : function(jqXHR, url, method, showStatus) {
@@ -79,9 +85,8 @@ module.exports = {
             error : 'dagNameToIdError'
           });
         } else {
-          // TODO
+          App.showAlertPopup(Em.I18n.t('jobs.hive.tez.dag.error.noDag.title'), Em.I18n.t('jobs.hive.tez.dag.error.noDag.message').format(hiveJobId));
         }
-
       }
     });
   },
@@ -92,8 +97,10 @@ module.exports = {
    *
    * @param {string}
    *          tezDagId ID of the Tez DAG. Example: 'HIVE-Q2:1'
+   * @param {Function}
+   *          successCallback
    */
-  refreshTezDagDetails : function(tezDagId) {
+  refreshTezDagDetails : function(tezDagId, successCallback) {
     var self = this;
     var historyServerHostName = App.YARNService.find().objectAt(0).get('resourceManagerNode.hostName');
     var tezDag = App.TezDag.find(tezDagId);
@@ -102,8 +109,14 @@ module.exports = {
       var sender = {
         loadTezDagSuccess : function(data) {
           if (data && data.relatedentities && data.relatedentities.TEZ_VERTEX_ID != null) {
+            var count = data.relatedentities.TEZ_VERTEX_ID.length;
             data.relatedentities.TEZ_VERTEX_ID.forEach(function(v) {
-              self.refreshTezDagVertex(tezDagId, v);
+              self.refreshTezDagVertex(tezDagId, v, function() {
+                if (--count <= 0) {
+                  // all vertices succeeded
+                  successCallback();
+                }
+              });
             });
           }
         },
@@ -121,6 +134,8 @@ module.exports = {
         success : 'loadTezDagSuccess',
         error : 'loadTezDagError'
       });
+    }else{
+      App.showAlertPopup(Em.I18n.t('jobs.hive.tez.dag.error.noDagForId.title'), Em.I18n.t('jobs.hive.tez.dag.error.noDagForId.message').format(tezDagId));
     }
   },
 
@@ -132,8 +147,10 @@ module.exports = {
    * @param {string}
    *          tezVertexInstanceID Instance ID of the vertex to refresh. Example
    *          'vertex_1390516007863_0001_1_00'
+   * @param {Function}
+   *          successCallback
    */
-  refreshTezDagVertex : function(tezDagId, tezVertexInstanceId) {
+  refreshTezDagVertex : function(tezDagId, tezVertexInstanceId, successCallback) {
     var historyServerHostName = App.YARNService.find().objectAt(0).get('resourceManagerNode.hostName');
     var sender = {
       loadTezDagVertexSuccess : function(data) {
@@ -155,6 +172,7 @@ module.exports = {
             vertexRecord.set('hdfsWriteBytes', 0);
             vertexRecord.set('recordReadCount', 0);
             vertexRecord.set('recordWriteCount', 0);
+            successCallback();
           }
         }
       },

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/utils/number_utils.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/number_utils.js b/ambari-web/app/utils/number_utils.js
index a83aa8e..bc6e577 100644
--- a/ambari-web/app/utils/number_utils.js
+++ b/ambari-web/app/utils/number_utils.js
@@ -59,7 +59,7 @@ module.exports = {
    * checks are ignored if their valid is NaN.
    */
   validateInteger : function(str, min, max) {
-    if (!str || (str + "").trim().length < 1) {
+    if (str==null || str==undefined || (str + "").trim().length < 1) {
       return Em.I18n.t('number.validate.empty');
     } else {
       str = (str + "").trim();

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 8a6fa9f..d3b4ae9 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -215,6 +215,7 @@ require('views/main/charts/heatmap/heatmap_host_detail');
 require('views/main/apps_view');
 require('views/main/jobs_view');
 require('views/main/jobs/hive_job_details_view');
+require('views/main/jobs/hive_job_details_tez_dag_view');
 require('views/main/apps/item_view');
 require('views/main/apps/item/bar_view');
 require('views/main/apps/item/dag_view');

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/views/main/jobs/hive_job_details_tez_dag_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/jobs/hive_job_details_tez_dag_view.js b/ambari-web/app/views/main/jobs/hive_job_details_tez_dag_view.js
new file mode 100644
index 0000000..21c3472
--- /dev/null
+++ b/ambari-web/app/views/main/jobs/hive_job_details_tez_dag_view.js
@@ -0,0 +1,488 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+var App = require('app');
+var date = require('utils/date');
+var numberUtils = require('utils/number_utils');
+
+App.MainHiveJobDetailsTezDagView = Em.View.extend({
+  templateName : require('templates/main/jobs/hive_job_details_tez_dag'),
+  selectedVertex : null,
+  summaryMetricType: null,
+  /**
+   * The contents of the <svg> element.
+   */
+  svgVerticesLayer : null,
+  svgWidth : -1,
+  svgHeight : -1,
+
+  content : null,
+
+  /**
+   * Populated by #drawTezDag() 
+   * 
+   * {
+   *   "nodes": [
+   *     {
+   *       "id": "Map2",
+   *       "name": "Map 2",
+   *       "isMap": true,
+   *       "operations": [
+   *         "TableScan",
+   *         "File Output"
+   *       ],
+   *       "depth": 1,
+   *       "parents": [],
+   *       "children": [],
+   *       "x": 0,
+   *       "y": 0,
+   *       "metricDisplay": "100MB",
+   *       "metricPercent": 64,
+   *       "metricType": "Input",
+   *       "selected": true,
+   *       "fixed": true,
+   *       "metrics": {
+   *         "input": 40022,
+   *         "output": 224344,
+   *         "recordsRead": 200,
+   *         "recordsWrite": 122,
+   *         "tezTasks": 2
+   *       }
+   *     }
+   *   ],
+   *   "links": [
+   *     {
+   *       "source": {},
+   *       "target": {},
+   *       "edgeType": "BROADCAST"
+   *     }
+   *   ]
+   * }
+   */
+  dagVisualModel : {
+    nodes : [],
+    links : [],
+    maxMetrics : {},
+    minMetrics: {}
+  },
+
+  didInsertElement : function() {
+    this._super();
+    this.createSvg();
+  },
+
+  createSvg : function() {
+    var dagVisualModel = this.get('dagVisualModel');
+    dagVisualModel.nodes.clear();
+    dagVisualModel.links.clear();
+    dagVisualModel.maxMetrics = {};
+    dagVisualModel.minMetrics = {};
+
+    this.set('content', this.get('controller.content'));
+    var svg = d3.select("#tez-dag-svg");
+    d3.selectAll(".tez-dag-canvas").remove();
+    this.set('svgVerticesLayer', svg.append("svg:g").attr("class", "tez-dag-canvas"));
+    this.adjustGraphHeight();
+    this.drawTezDag();
+  },
+
+  /**
+   * We have to make the height of the DAG section match the height of the
+   * Summary section.
+   */
+  adjustGraphHeight : function() {
+    var rhsDiv = document.getElementById('tez-vertices-rhs');
+    var lhsDiv = document.getElementById('tez-dag-section');
+    if (lhsDiv && rhsDiv) {
+      var rhsHeight = rhsDiv.clientHeight - 26; // box boundary
+      var currentWidth = lhsDiv.clientWidth;
+      var currentHeight = lhsDiv.clientHeight;
+      $(lhsDiv).attr('style', "height:" + rhsHeight + "px;");
+      var svgHeight = rhsHeight - 20;
+      d3.select("#tez-dag-svg").attr('height', svgHeight).attr('width', currentWidth);
+      this.set('svgWidth', currentWidth);
+      this.set('svgHeight', svgHeight);
+      console.log("SWT SVG Width=", currentWidth, ", Height=", svgHeight);
+      // this.get('svgVerticesLayer').attr('transform', 'translate(' +
+      // (currentWidth / 2) + ',' + (currentHeight / 2) + ')');
+    }
+  },
+
+  vertexSelectionUpdated : function() {
+    var vertexId = this.get('selectedVertex.id');
+    console.log("vertexSelectionUpdated(): Selected ",vertexId);
+    var dagVisualModel = this.get('dagVisualModel');
+    if (dagVisualModel && dagVisualModel.nodes && dagVisualModel.nodes.length > 0) {
+      dagVisualModel.nodes.forEach(function(node) {
+        node.selected = node.id == vertexId;
+        console.log("vertexSelectionUpdated(): Updated  ",node.id," to ",node.selected);
+      })
+    }
+    this.refreshGraphUI();
+  }.observes('selectedVertex'),
+
+  summaryMetricTypeUpdated : function() {
+    var summaryMetricType = this.get('summaryMetricType');
+    var dagVisualModel = this.get('dagVisualModel');
+    var min = dagVisualModel.minMetrics[summaryMetricType];
+    var max = dagVisualModel.maxMetrics[summaryMetricType];
+    dagVisualModel.nodes.forEach(function(node) {
+      var value = node.metrics[summaryMetricType];
+      var percent = -1;
+      if (numberUtils.validateInteger(value)==null && value >= 0) {
+        if (numberUtils.validateInteger(min) == null && numberUtils.validateInteger(max) == null) {
+          if (max > min && value >= 0) {
+            percent = Math.round((value - min) * 100 / (max - min));
+          }
+        }
+      } else {
+        value = '';
+      }
+      switch (summaryMetricType) {
+      case "Input":
+      case "Output":
+        value = numberUtils.bytesToSize(value);
+        break;
+      default:
+        break;
+      }
+      node.metricType = Em.I18n.t('jobs.hive.tez.metric.' + summaryMetricType);
+      node.metricDisplay = value;
+      node.metricPercent = percent;
+    });
+    this.refreshGraphUI();
+  }.observes('summaryMetricType'),
+
+  /**
+   * Observes metrics of all vertices.
+   */
+  vertexMetricsUpdated : function() {
+    var dagVisualModel = this.get('dagVisualModel');
+    dagVisualModel.minMetrics = {
+      input : 0,
+      output : 0,
+      recordsRead : 0,
+      recordsWrite : 0,
+      tezTasks : 0
+    };
+    dagVisualModel.maxMetrics = {
+      input : 0,
+      output : 0,
+      recordsRead : 0,
+      recordsWrite : 0,
+      tezTasks : 0
+    };
+    if (dagVisualModel.nodes) {
+      dagVisualModel.nodes.forEach(function(node) {
+        var vertex = App.TezDagVertex.find(node.id);
+        if (vertex) {
+          node.metrics['input'] = vertex.get('fileReadBytes') + vertex.get('hdfsReadBytes');
+          node.metrics['output'] = vertex.get('fileWriteBytes') + vertex.get('hdfsWriteBytes');
+          node.metrics['recordsRead'] = vertex.get('recordReadCount');
+          node.metrics['recordsWrite'] = vertex.get('recordWriteCount');
+          node.metrics['tezTasks'] = vertex.get('tasksCount');
+          // Min metrics
+          dagVisualModel.minMetrics.input = Math.min(dagVisualModel.minMetrics.input, node.metrics.input);
+          dagVisualModel.minMetrics.output = Math.min(dagVisualModel.minMetrics.output, node.metrics.output);
+          dagVisualModel.minMetrics.recordsRead = Math.min(dagVisualModel.minMetrics.recordsRead, node.metrics.recordsRead);
+          dagVisualModel.minMetrics.recordsWrite = Math.min(dagVisualModel.minMetrics.recordsWrite, node.metrics.recordsWrite);
+          dagVisualModel.minMetrics.tezTasks = Math.min(dagVisualModel.minMetrics.tezTasks, node.metrics.tezTasks);
+          // Max metrics
+          dagVisualModel.maxMetrics.input = Math.max(dagVisualModel.maxMetrics.input, node.metrics.input);
+          dagVisualModel.maxMetrics.output = Math.max(dagVisualModel.maxMetrics.output, node.metrics.output);
+          dagVisualModel.maxMetrics.recordsRead = Math.max(dagVisualModel.maxMetrics.recordsRead, node.metrics.recordsRead);
+          dagVisualModel.maxMetrics.recordsWrite = Math.max(dagVisualModel.maxMetrics.recordsWrite, node.metrics.recordsWrite);
+          dagVisualModel.maxMetrics.tezTasks = Math.max(dagVisualModel.maxMetrics.tezTasks, node.metrics.tezTasks);
+        }
+      });
+    }
+    Ember.run.once(this, 'summaryMetricTypeUpdated');
+  }.observes('content.tezDag.vertices.@each.fileReadBytes', 'content.tezDag.vertices.@each.fileWriteBytes', 
+      'content.tezDag.vertices.@each.hdfsReadBytes', 'content.tezDag.vertices.@each.hdfsWriteBytes', 
+      'content.tezDag.vertices.@each.recordReadCount', 'content.tezDag.vertices.@each.recordWriteCount'),
+
+  /**
+   * Determines layout and creates Tez graph. In the process it populates the
+   * visual model into 'dagVisualModel' field.
+   * 
+   * Terminology: 'vertices' and 'edges' are Tez terms. 'nodes' and 'links' are
+   * visual (d3) terms.
+   */
+  drawTezDag : function() {
+    var width = this.get('svgWidth');
+    var height = this.get('svgHeight');
+    var svgLayer = this.get('svgVerticesLayer');
+    var vertices = this.get('content.tezDag.vertices');
+    var edges = this.get('content.tezDag.edges');
+    var constants = this.get('constants');
+    var vertexIdToNode = {};
+    var depthToNodes = []; // Array of id arrays
+    var dagVisualModel = this.get('dagVisualModel');
+    var selectedVertex = this.get('selectedVertex');
+
+    //
+    // CALCULATE DEPTH - BFS to get correct graph depth
+    //
+    var visitVertices = [];
+    vertices.forEach(function(v) {
+      if (v.get('incomingEdges.length') < 1) {
+        visitVertices.push({
+          depth : 0,
+          parent : null,
+          vertex : v
+        });
+      }
+    });
+    function getNodeFromVertex(vertexObj) {
+      var vertex = vertexObj.vertex;
+      var node = vertexIdToNode[vertex.get('id')];
+      for ( var k = depthToNodes.length; k <= vertexObj.depth; k++) {
+        depthToNodes.push([]);
+      }
+      if (!node) {
+        // New node
+        node = {
+          id : vertex.get('id'),
+          name : vertex.get('name'),
+          isMap : vertex.get('isMap'),
+          operations : vertex.get('operations'),
+          depth : vertexObj.depth,
+          parents : [],
+          children : [],
+          x : 0,
+          y : 0,
+          metricType : null,
+          metricDisplay : null,
+          metricPercent : -1,
+          selected : selectedVertex != null ? selectedVertex.get('id') == vertex.get('id') : false,
+          fixed : true,
+          metrics : {
+            input: -1,
+            output: -1,
+            recordsRead: -1,
+            recordsWrite: -1,
+            tezTasks: -1
+          }
+        }
+        vertexIdToNode[vertex.get('id')] = node;
+        depthToNodes[node.depth].push(node);
+      } else {
+        // Existing node
+        if (vertexObj.depth > node.depth) {
+          var oldIndex = depthToNodes[node.depth].indexOf(node);
+          depthToNodes[node.depth] = depthToNodes[node.depth].splice(oldIndex, 1);
+          node.depth = vertex.depth;
+          depthToNodes[node.depth].push(node);
+        }
+      }
+      if (vertexObj.parent != null) {
+        node.parents.push(vertexObj.parent);
+        vertexObj.parent.children.push(node);
+      }
+      return node;
+    }
+    var vertexObj;
+    while (vertexObj = visitVertices.shift()) {
+      var node = getNodeFromVertex(vertexObj);
+      var outEdges = vertexObj.vertex.get('outgoingEdges');
+      outEdges.forEach(function(oe) {
+        var childVertex = oe.get('toVertex');
+        visitVertices.push({
+          depth : node.depth + 1,
+          parent : node,
+          vertex : childVertex
+        });
+      })
+    }
+    edges.forEach(function(e) {
+      dagVisualModel.links.push({
+        source : vertexIdToNode[e.get('fromVertex.id')],
+        target : vertexIdToNode[e.get('toVertex.id')],
+        edgeType : e.get('edgeType')
+      });
+    });
+
+    //
+    // LAYOUT - Now with correct depth, we calculate layouts
+    //
+    var deltaY = 150;
+    var currentY = 80;
+    for ( var depth = 0; depth < depthToNodes.length; depth++) {
+      var nodes = depthToNodes[depth];
+      var deltaX = 1 / (nodes.length + 1);
+      var startX = deltaX;
+      for ( var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
+        var node = nodes[nodeIndex];
+        if (depth == 0) {
+          // Top nodes - position uniformly
+          node.x = startX;
+          startX += deltaX;
+        } else {
+          node.x = (node.x / node.parents.length); // Average across parents
+        }
+        // Layout children
+        node.children.forEach(function(child) {
+          child.x += node.x;
+        });
+        var nodeDim = this.getNodeCalculatedDimensions(node);
+        node.width = nodeDim.width;
+        node.height = nodeDim.height;
+        node.y = currentY;
+        node.x = (node.x * width) - Math.round(nodeDim.width / 2);
+        node.incomingX = node.x + Math.round(nodeDim.width / 2);
+        node.incomingY = node.y;
+        node.outgoingX = node.incomingX;
+        node.outgoingY = node.incomingY + node.height;
+        console.log("drawTezDag(). Layout Node: ", node);
+        dagVisualModel.nodes.push(node);
+      }
+      currentY += deltaY;
+    }
+
+    //
+    // Draw SVG
+    //
+    var self = this;
+    var force = d3.layout.force().nodes(dagVisualModel.nodes).links(dagVisualModel.links).start();
+    // Create Links
+    var diagonal = d3.svg.diagonal().source(function(d) {
+      return {
+        x : d.source.outgoingX,
+        y : d.source.outgoingY
+      };
+    }).target(function(d) {
+      return {
+        x : d.target.incomingX,
+        y : d.target.incomingY - 10
+      }
+    });
+    var link = svgLayer.selectAll(".link").data(dagVisualModel.links).enter().append("g").attr("class", "link").attr("marker-end", "url(#arrow)");
+    link.append("path").attr("class", "link").attr("d", diagonal);
+    // Create Nodes
+    var node = svgLayer.selectAll(".node").data(dagVisualModel.nodes).enter().append("g").attr("class", "node");
+    node.append("rect").attr("class", "background").attr("width", function(n) {
+      return n.width;
+    }).attr("height", function(n) {
+      return n.height;
+    }).attr("rx", "10").attr("filter", "url(#shadow)").on('mousedown', function(n) {
+      var vertex = App.TezDagVertex.find(n.id);
+      if (vertex != null) {
+        self.get('parentView').doSelectVertex({
+          context : vertex
+        });
+      }
+    });
+    node.each(function(n, nodeIndex) {
+      var ops = n.operations;
+      var opGroups = d3.select(this).selectAll(".operation").data(ops).enter().append("g").attr("class", "operation").attr("transform", function(op, opIndex) {
+        var row = Math.floor(opIndex / 3);
+        var column = opIndex % 3;
+        return "translate(" + (10 + column * 50) + "," + (37 + row * 20) + ")";
+      }).attr("clip-path", "url(#operatorClipPath)");
+      opGroups.append("rect").attr("class", "operation").attr("width", "44").attr("height", "16").append("title").text(function(op) {
+        return op;
+      });
+      opGroups.append("text").attr("x", "2").attr("dy", "1em").text(function(op) {
+        return op != null ? op.split(' ')[0] : '';
+      })
+    })
+    var metricNodes = node.append("g").attr("class", "metric").attr("transform", "translate(92,7)");
+    metricNodes.append("rect").attr("width", 60).attr("height", 18).attr("rx", "3");
+    metricNodes.append("title").attr("class", "metric-title");
+    metricNodes.append("text").attr("class", "metric-text").attr("x", "2").attr("dy", "1em");
+    node.append("text").attr("x", "1em").attr("dy", "1.5em").text(function(d) {
+      return d.name;
+    });
+    node.attr("transform", function(d) {
+      return "translate(" + d.x + "," + d.y + ")";
+    });
+    this.vertexMetricsUpdated();
+  },
+
+  /**
+   * Refreshes UI of the Tez graph with latest values
+   */
+  refreshGraphUI: function () {
+    var svgLayer = this.get('svgVerticesLayer');
+    if (svgLayer!=null) {
+      var metricNodes = svgLayer.selectAll(".metric");
+      var metricNodeTexts = svgLayer.selectAll(".metric-text");
+      var metricNodeTitles = svgLayer.selectAll(".metric-title");
+      var nodeBackgrounds =svgLayer.selectAll(".background");
+      metricNodes.attr("class", function(node) {
+        var classes = "metric ";
+        var percent = node.metricPercent;
+        if (numberUtils.validateInteger(percent) == null && percent >= 0) {
+          if (percent <= 20) {
+            classes += "heat-0-20 ";
+          } else if (percent <= 40) {
+            classes += "heat-20-40 ";
+          } else if (percent <= 60) {
+            classes += "heat-40-60 ";
+          } else if (percent <= 80) {
+            classes += "heat-60-80 ";
+          } else if (percent <= 100) {
+            classes += "heat-80-100 ";
+          } else {
+            classes += "heat-none";
+          }
+        } else {
+          classes += "heat-none";
+        }
+        return classes;
+      });
+      metricNodeTexts.text(function(node){
+        return node.metricDisplay;
+      });
+      metricNodeTitles.text(function(node){
+        return node.metricType;
+      });
+      nodeBackgrounds.attr("class", function(n) {
+        var classes = "background ";
+        if (n.isMap) {
+          classes += "map ";
+        } else {
+          classes += "reduce ";
+        }
+        if (n.selected) {
+          classes += "selected ";
+        }
+        return classes;
+      });
+    }
+  },
+
+  /**
+   * Determines the node width and height in pixels.
+   *
+   * Takes into account the various contents of the a node. { width: 200,
+   * height: 60, margin: 15 }
+   */
+  getNodeCalculatedDimensions : function(node) {
+    var size = {
+      width : 160,
+      height : 40,
+      margin : 15
+    };
+    if (node.operations.length > 0) {
+      var opsHeight = Math.ceil(node.operations.length / 3);
+      size.height += (opsHeight * 20);
+    }
+    return size;
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/1f6bf658/ambari-web/app/views/main/jobs/hive_job_details_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/jobs/hive_job_details_view.js b/ambari-web/app/views/main/jobs/hive_job_details_view.js
index 7e2f280..0bd7765 100644
--- a/ambari-web/app/views/main/jobs/hive_job_details_view.js
+++ b/ambari-web/app/views/main/jobs/hive_job_details_view.js
@@ -5,9 +5,9 @@
  * 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
@@ -23,10 +23,20 @@ App.MainHiveJobDetailsView = Em.View.extend({
   templateName : require('templates/main/jobs/hive_job_details'),
 
   selectedVertex : null,
+  content : null,
+  summaryMetricType: 'input',
+  summaryMetricTypesDisplay : [ Em.I18n.t('jobs.hive.tez.metric.input'), Em.I18n.t('jobs.hive.tez.metric.output'), Em.I18n.t('jobs.hive.tez.metric.recordsRead'),
+                                Em.I18n.t('jobs.hive.tez.metric.recordsWrite'), Em.I18n.t('jobs.hive.tez.metric.tezTasks') ],
+  summaryMetricTypeDisplay: function(){
+    return Em.I18n.t('jobs.hive.tez.metric.'+this.get('summaryMetricType'));
+  }.property('summaryMetricType'),
 
-  content : function() {
-    return this.get('controller.content');
-  }.property('controller.content'),
+  initialDataLoaded : function() {
+    var loaded = this.get('controller.loaded');
+    if (loaded) {
+      this.set('content', this.get('controller.content'));
+    }
+  }.observes('controller.loaded'),
 
   jobObserver : function() {
     var content = this.get('content');
@@ -35,7 +45,9 @@ App.MainHiveJobDetailsView = Em.View.extend({
       var vertices = content.get('tezDag.vertices');
       if (vertices) {
         vertices.setEach('isSelected', false);
-        this.doSelectVertex({context:vertices.objectAt(0)});
+        this.doSelectVertex({
+          context : vertices.objectAt(0)
+        });
       }
     }
   }.observes('selectedVertex', 'content.tezDag.vertices.@each.id'),
@@ -50,6 +62,30 @@ App.MainHiveJobDetailsView = Em.View.extend({
     this.set('selectedVertex', newVertex);
   },
 
+  doSelectSummaryMetricType: function(event) {
+    var summaryType = event.context;
+    switch (summaryType) {
+    case Em.I18n.t('jobs.hive.tez.metric.input'):
+      summaryType = 'input';
+      break;
+    case Em.I18n.t('jobs.hive.tez.metric.output'):
+      summaryType = 'output';
+      break;
+    case Em.I18n.t('jobs.hive.tez.metric.recordsRead'):
+      summaryType = 'recordsRead';
+      break;
+    case Em.I18n.t('jobs.hive.tez.metric.recordsWrite'):
+      summaryType = 'recordsWrite';
+      break;
+    case Em.I18n.t('jobs.hive.tez.metric.tezTasks'):
+      summaryType = 'tezTasks';
+      break;
+    default:
+      break;
+    }
+    this.set('summaryMetricType', summaryType);
+  },
+
   /**
    * Provides display information for vertex I/O.
    *