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">
+ {{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.
*