You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by on...@apache.org on 2014/07/29 14:00:44 UTC
[1/2] AMBARI-6651. Create job-details page. (onechiporenko)
Repository: ambari
Updated Branches:
refs/heads/trunk 569acf801 -> 85362043f
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_view.js
new file mode 100644
index 0000000..122f236
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_view.js
@@ -0,0 +1,327 @@
+/**
+ * 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.
+ */
+
+App.JobView = Em.View.extend({
+
+ templateName: 'job/job',
+
+ selectedVertex: null,
+
+ content: null,
+
+ zoomScaleFrom: 1,
+
+ zoomScaleTo: 2,
+
+ zoomScale: 1,
+
+ showQuery: false,
+
+ willInsertElement: function () {
+ this.get('controller').loadJobDetails();
+ },
+
+ zoomStep: function () {
+ var zoomStep = 0.01;
+ var zoomFrom = this.get('zoomScaleFrom');
+ var zoomTo = this.get('zoomScaleTo');
+ if (zoomFrom < zoomTo) {
+ zoomStep = (zoomTo - zoomFrom) / 5;
+ }
+ return zoomStep;
+ }.property('zoomScaleFrom', 'zoomScaleTo'),
+
+ isGraphMaximized: false,
+
+ actions: {
+
+ doSelectSummaryMetricType: function (summaryType) {
+ 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;
+ case Em.I18n.t('jobs.hive.tez.metric.spilledRecords'):
+ summaryType = 'spilledRecords';
+ break;
+ default:
+ break;
+ }
+ this.set('summaryMetricType', summaryType);
+ },
+
+ toggleShowQuery: function () {
+ this.toggleProperty('showQuery');
+ var queryBlock = $('.query-info');
+ if (this.get('showQuery')) {
+ queryBlock.slideDown();
+ }
+ else {
+ queryBlock.slideUp();
+ }
+ },
+
+ actionDoSelectVertex: function (event, notTableClick) {
+ this.doSelectVertex(event, notTableClick);
+ },
+
+ doGraphZoomIn: function () {
+ var zoomTo = this.get('zoomScaleTo'),
+ zoomScale = this.get('zoomScale'),
+ zoomStep = this.get('zoomStep');
+ if (zoomScale < zoomTo) {
+ var step = Math.min(zoomStep, (zoomTo - zoomScale));
+ zoomScale += step;
+ this.set('zoomScale', zoomScale);
+ }
+ },
+
+ doGraphZoomOut: function () {
+ var zoomFrom = this.get('zoomScaleFrom'),
+ zoomScale = this.get('zoomScale'),
+ zoomStep = this.get('zoomStep');
+ if (zoomScale > zoomFrom) {
+ var step = Math.min(zoomStep, (zoomScale - zoomFrom));
+ zoomScale -= step;
+ this.set('zoomScale', zoomScale);
+ }
+ },
+
+ doGraphMaximize: function () {
+ this.set('isGraphMaximized', true);
+ },
+
+ doGraphMinimize: function () {
+ this.set('isGraphMaximized', false);
+ }
+
+ },
+
+ toggleShowQueryText: function () {
+ return this.get('showQuery') ? Em.I18n.t('jobs.hive.less') : Em.I18n.t('jobs.hive.more');
+ }.property('showQuery'),
+
+ 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'),
+ Em.I18n.t('jobs.hive.tez.metric.spilledRecords')
+ ],
+
+ summaryMetricTypeDisplay: function () {
+ return Em.I18n.t('jobs.hive.tez.metric.' + this.get('summaryMetricType'));
+ }.property('summaryMetricType'),
+
+ sortedVertices: function () {
+ var sortColumn = this.get('controller.sortingColumn');
+ if (sortColumn && sortColumn.get('status')) {
+ var sortColumnStatus = sortColumn.get('status');
+ var sorted = sortColumn.get('parentView').sort(sortColumn, sortColumnStatus === "sorting_desc", true);
+ sortColumn.set('status', sortColumnStatus);
+ return sorted;
+ }
+ var vertices = this.get('content.tezDag.vertices');
+ if (vertices != null) {
+ vertices = vertices.toArray();
+ }
+ return vertices;
+ }.property('content.tezDag.vertices', 'controller.sortingColumn'),
+
+ initialDataLoaded: function () {
+ if (this.get('controller.loaded')) {
+ this.set('content', this.get('controller.content'));
+ }
+ }.observes('controller.loaded'),
+
+ jobObserver: function () {
+ var content = this.get('content'),
+ selectedVertex = this.get('selectedVertex');
+ if (selectedVertex == null && content != null) {
+ var vertices = content.get('tezDag.vertices');
+ if (vertices) {
+ vertices.setEach('isSelected', false);
+ this.doSelectVertex(vertices.objectAt(0), false);
+ }
+ }
+ }.observes('selectedVertex', 'content.tezDag.vertices.@each.id'),
+
+ doSelectVertex: function (newVertex, notTableClick) {
+ var currentVertex = this.get('selectedVertex');
+ if (currentVertex != null) {
+ currentVertex.set('isSelected', false);
+ }
+ newVertex.set('notTableClick', !!notTableClick);
+ newVertex.set('isSelected', true);
+ this.set('selectedVertex', newVertex);
+ },
+
+ /**
+ * Provides display information for vertex I/O.
+ *
+ * {
+ * 'file': {
+ * 'read': {
+ * 'ops': '100 reads',
+ * 'bytes': '10 MB'
+ * }
+ * 'write: {
+ * 'ops': '200 writes',
+ * 'bytes': '20 MB'
+ * }
+ * },
+ * 'hdfs': {
+ * 'read': {
+ * 'ops': '100 reads',
+ * 'bytes': '10 MB'
+ * }
+ * 'write: {
+ * 'ops': '200 writes',
+ * 'bytes': '20 MB'
+ * }
+ * },
+ * 'records': {
+ * 'read': '100 records',
+ * 'write': '123 records'
+ * },
+ * 'started': 'Feb 12, 2014 10:30am',
+ * 'ended': 'Feb 12, 2014 10:35am',
+ * 'status': 'Running'
+ * }
+ */
+ selectedVertexIODisplay: {},
+
+ selectedVertexIODisplayObsOnce: function() {
+ Em.run.once(this, 'selectedVertexIODisplayObs');
+ }.observes('selectedVertex.fileReadOps', 'selectedVertex.fileWriteOps', 'selectedVertex.hdfsReadOps', 'selectedVertex.hdfdWriteOps',
+ 'selectedVertex.fileReadBytes', 'selectedVertex.fileWriteBytes', 'selectedVertex.hdfsReadBytes', 'selectedVertex.hdfdWriteBytes',
+ 'selectedVertex.recordReadCount', 'selectedVertex.recordWriteCount', 'selectedVertex.status'),
+
+ selectedVertexIODisplayObs: function () {
+ var v = this.get('selectedVertex'),
+ naString = Em.I18n.t('common.na'),
+ status = App.Helpers.string.getCamelCase(v.getWithDefault('state', '')),
+ fileReadOps = v.getWithDefault('fileReadOps', naString),
+ fileWriteOps = v.getWithDefault('fileWriteOps', naString),
+ hdfsReadOps = v.getWithDefault('hdfsReadOps', naString),
+ hdfsWriteOps = v.getWithDefault('hdfsWriteOps', naString),
+ r = {
+ file: {
+ read: {
+ ops: Em.I18n.t('jobs.hive.tez.reads').fmt(fileReadOps),
+ bytes: App.Helpers.number.bytesToSize(v.get('fileReadBytes'))
+ },
+ write: {
+ ops: Em.I18n.t('jobs.hive.tez.writes').fmt(fileWriteOps),
+ bytes: App.Helpers.number.bytesToSize(v.get('fileWriteBytes'))
+ }
+ },
+ hdfs: {
+ read: {
+ ops: Em.I18n.t('jobs.hive.tez.reads').fmt(hdfsReadOps),
+ bytes: App.Helpers.number.bytesToSize(v.get('hdfsReadBytes'))
+ },
+ write: {
+ ops: Em.I18n.t('jobs.hive.tez.writes').fmt(hdfsWriteOps),
+ bytes: App.Helpers.number.bytesToSize(v.get('hdfsWriteBytes'))
+ }
+ },
+ records: {
+ read: v.get('recordReadCount') == null ? null : Em.I18n.t('jobs.hive.tez.records.count').fmt(v.get('recordReadCount')),
+ write: v.get('recordWriteCount') == null ? null : Em.I18n.t('jobs.hive.tez.records.count').fmt(v.get('recordWriteCount'))
+ },
+ started: v.get('startTime') ? App.Helpers.date.dateFormat(v.get('startTime'), true, true) : '',
+ ended: v.get('endTime') ? App.Helpers.date.dateFormat(v.get('endTime'), true, true) : '',
+ status: status
+ };
+ this.set('selectedVertexIODisplay', r);
+ },
+
+ canGraphZoomIn: function () {
+ return this.get('zoomScale') < this.get('zoomScaleTo');
+ }.property('zoomScale', 'zoomScaleTo'),
+
+ canGraphZoomOut: function () {
+ return this.get('zoomScale') > this.get('zoomScaleFrom');
+ }.property('zoomScale', 'zoomScaleFrom')
+
+});
+
+App.MainHiveJobDetailsVerticesTableView = App.TableView.extend({
+
+ sortView: App.Sorts.wrapperView,
+
+ didInsertElement: function () {
+ if (!this.get('controller.sortingColumn')) {
+ var columns = this.get('childViews')[0].get('childViews');
+ if (columns && columns.findProperty('name', 'name')) {
+ columns.findProperty('name', 'name').set('status', 'sorting_asc');
+ this.get('controller').set('sortingColumn', columns.findProperty('name', 'name'))
+ }
+ }
+ },
+
+ nameSort: App.Sorts.fieldView.extend({
+ column: 0,
+ name: 'name',
+ displayName: Em.I18n.t('common.name'),
+ type: 'string'
+ }),
+
+ tasksSort: App.Sorts.fieldView.extend({
+ column: 1,
+ name: 'tasksNumber',
+ displayName: Em.I18n.t('common.tasks'),
+ type: 'number'
+ }),
+
+ inputSort: App.Sorts.fieldView.extend({
+ column: 2,
+ name: 'totalReadBytes',
+ displayName: Em.I18n.t('apps.item.dag.input'),
+ type: 'number'
+ }),
+
+ outputSort: App.Sorts.fieldView.extend({
+ column: 3,
+ name: 'totalWriteBytes',
+ displayName: Em.I18n.t('apps.item.dag.output'),
+ type: 'number'
+ }),
+
+ durationSort: App.Sorts.fieldView.extend({
+ column: 4,
+ name: 'duration',
+ displayName: Em.I18n.t('apps.item.dag.duration'),
+ type: 'number'
+ })
+
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js
deleted file mode 100644
index eae86c2..0000000
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-App.JobView = Ember.View.extend({});
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
index 9d814d6..279298b 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
@@ -219,9 +219,9 @@ App.JobsView = App.TableView.extend({
templateName: 'jobs/jobs_name',
click: function(event) {
- /*if (this.get('job.hasTezDag')) {
- App.router.transitionTo('main.jobs.jobDetails', this.get('job'));
- }*/
+ if (this.get('job.hasTezDag')) {
+ this.get('controller').transitionToRoute('job', this.get('job'));
+ }
return false;
},
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/styles/main.less b/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
index 16cfba7..dafb275 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
+++ b/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
@@ -16,8 +16,74 @@
* limitations under the License.
*/
-@import '../../app/bower_components/jquery-ui/themes/base/all.css';
+@import '../../app/bower_components/jquery-ui/themes/base/core.css';
+@import '../../app/bower_components/jquery-ui/themes/base/datepicker.css';
@import '../../app/bower_components/bootstrap/less/bootstrap';
+@import '../../app/bower_components/font-awesome/less/font-awesome';
+
+html {
+ overflow-y: scroll;
+}
+
+a {
+ cursor: pointer;
+}
+
+/*.tooltip {
+ z-index: 10000;
+}*/
+
+.icon-remove-sign {
+ color: #FF4B4B;
+}
+
+
+/*.tooltip-inner {
+ text-align: left;
+}*/
+
+/*.jobs-tooltip {
+ .tooltip-inner {
+ max-width: 400px;
+ }
+}*/
+
+.box {
+ border: 1px solid #D4D4D4;
+ margin-bottom: 20px;
+
+ .box-header {
+ border-bottom: 1px solid #D4D4D4;
+ }
+ .box-header,
+ .box-footer {
+ padding: 4px 4px 4px 10px;
+ background: #f5f5f5;
+ }
+ .box-header:after,
+ .box-footer:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+ .box-header {
+ .btn-group {
+ float: right;
+ }
+ h4 {
+ float: left;
+ margin: 5px;
+ font-size: 15px;
+ color: #777;
+ }
+ }
+ .box-footer {
+ hr {
+ margin: 0px;
+ }
+ }
+}
+
#jobs {
@@ -32,12 +98,12 @@
margin-top: -20px;
}
- #filtered-jobs{
+ #filtered-jobs {
float: left;
margin-top: 8px;
}
- .jobs_head{
+ .jobs_head {
height: 30px;
}
@@ -51,16 +117,16 @@
}
div {
display: inline-block;
- margin:0 10px;
+ margin: 0 10px;
}
.items-on-page {
label {
- display:inline;
+ display: inline;
}
select {
margin-bottom: 4px;
margin-top: 4px;
- width:70px;
+ width: 70px;
font-size: 12px;
height: 27px;
}
@@ -68,11 +134,11 @@
.paging_two_button {
a {
- padding:0 5px;
+ padding: 0 5px;
}
a.paginate_disabled_next, a.paginate_disabled_previous {
color: gray;
- &:hover i{
+ &:hover i {
color: gray;
text-decoration: none;
cursor: default;
@@ -90,7 +156,7 @@
#jobs-table {
- .is-not-link{
+ .is-not-link {
cursor: default;
color: #000000;
text-decoration: none;
@@ -104,7 +170,7 @@
line-height: 22px;
}
- .input-120{
+ .input-120 {
width: 120px;
}
@@ -167,7 +233,7 @@
th, td {
border-left-width: 0;
}
- .no-data{
+ .no-data {
text-align: center;
}
a.job-link {
@@ -190,17 +256,17 @@
width: 36%;
}
td:first-child + td + td,
- th:first-child + th + th{
+ th:first-child + th + th {
width: 20%;
}
td:first-child + td + td + td,
th:first-child + th + th + th,
td:first-child + td + td + td + td,
- th:first-child + th + th + th + th{
+ th:first-child + th + th + th + th {
width: 16%;
}
td:first-child + td + td + td + td + td,
- th:first-child + th + th + th + th + th{
+ th:first-child + th + th + th + th + th {
width: 12%;
}
}
@@ -276,14 +342,13 @@
}
.sorting_asc {
- background:
- url(data:image/png;base64,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) no-repeat right 50%;
+ background: url(data:image/png;base64,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) no-repeat right 50
%;
}
.sorting_desc {
background: url(data:image/png;base64,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) no-repeat right 50%;
}
.sorting {
- background: url( data:image/png;base64,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) no-repeat right 50%;
+ background: url(data:image/png;base64,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) no-repeat right 50%;
}
div.view-wrapper {
input[type="checkbox"], .btn-group {
@@ -318,6 +383,258 @@
}
}
-.help-inline{
+.help-inline {
color: #b94a48;
+}
+
+#hive-job-details {
+ #toggle-query {
+ margin-left: 20px;
+ }
+ .query-info {
+ display: none;
+ margin-top: 10px;
+ }
+ #job-more-details-table {
+ td {
+ vertical-align: top;
+ }
+ td:first-child {
+ font-weight: bold;
+ padding-right: 7px;
+ }
+ a {
+ cursor: pointer;
+ }
+ }
+ .sections {
+ margin-top: 10px;
+ }
+ #tez-vertices-table-section {
+ display: block;
+ }
+ #tez-vertices-table-container {
+ max-height: 300px;
+ overflow-y: auto;
+ margin-bottom: 7px;
+ border-bottom: 1px solid #dddddd;
+ border-top: 1px solid #dddddd;
+ border-radius: 4px;
+ table {
+ margin-bottom: 0px;
+ .sorting_asc {
+ background: url(data:image/png;base64,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) no-repeat right
50%;
+ }
+ .sorting_desc {
+ background: url(data:image/png;base64,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) no-repeat right 50%;
+ }
+ .sorting {
+ background: url(data:image/png;base64,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) no-repeat right 50%;
+ }
+ }
+ }
+ #tez-vertex-details-section-body {
+ td {
+ border-top: none;
+ }
+ tr td:first-child {
+ font-weight: bold;
+ width: 20%;
+ overflow: auto;
+ }
+ textarea {
+ width: 95%;
+ margin-left: 10px;
+ }
+ }
+}
+
+/***************
+ * Tez DAG section
+ ***************/
+#tez-dag-section {
+ padding-left: 7px;
+ padding-top: 5px;
+ overflow: hidden;
+ #tez-dag-section-top-bar-actions {
+ margin-right: 7px;
+ }
+}
+
+#tez-dag-section-body {
+ display: block;
+}
+
+#tez-dag-section-body-dag {
+ overflow-y: hidden;
+
+ .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;
+ }
+ .tez-root-rect {
+ fill: white;
+ stroke: white;
+ opacity: 0;
+ stroke-opacity: 0;
+ }
+ .debug {
+ fill: green;
+ opacity: 0.2;
+ }
+ .node {
+ .background.map {
+ fill: #fde0dd;
+ stroke: #bbbbbb;
+ }
+ .background.reduce {
+ fill: #DBD4EB;
+ stroke: #bbbbbb;
+ }
+ .background.union {
+ fill: #BCE6DD;
+ stroke: #bbbbbb;
+ }
+ .background.unknown-vertex-type {
+ fill: #EBE8CC;
+ 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;
+ cursor: pointer;
+ text {
+ font-size: 80%;
+ }
+ }
+ .vertex-icon-text {
+ stroke-width: 0;
+ opacity: 0.9;
+ }
+ .vertex-icon-rect {
+ opacity: 0;
+ }
+ .vertex-icon-text.running,
+ .vertex-icon-text.initializing,
+ .vertex-icon-text.succeeded,
+ .vertex-icon-text.inited {
+ fill: green;
+ stroke: green;
+ }
+ .vertex-icon-text.new {
+ fill: #fde0dd;
+ stroke: cornflowerblue;
+ stroke-width: 1;
+ }
+ .vertex-icon-text.failed,
+ .vertex-icon-text.killed,
+ .vertex-icon-text.error,
+ .vertex-icon-text.terminating {
+ fill: red;
+ stroke: red;
+ }
+ }
+ .node text {
+ pointer-events: none;
+ }
+ path.link {
+ fill: none;
+ stroke: #666;
+ stroke-width: 5px;
+ opacity: 0.6;
+ }
+ path.link.type-broadcast {
+ stroke: teal;
+ }
+ path.link.type-scatter_gather {
+ stroke: orange;
+ stroke-dasharray: 10, 3;
+ }
+ path.link.type-contains {
+ stroke: green;
+ stroke-dasharray: 10, 2, 5, 2;
+ }
+ path.link.type-unknown {
+ stroke: gray;
+ }
+ }
+}
+
+#hover-op-table {
+ margin-bottom: 2px;
+ td {
+ padding: 2px;
+ word-wrap: break-word;
+ }
+}
+
+#hive-job-details {
+ #operator-table {
+ td {
+ border-top: 1px solid #dddddd;
+ }
+ tr td:first-child {
+ width: 20%;
+ overflow: auto;
+ }
+ td:first-child + td,
+ th:first-child + th {
+ width: 80%;
+ }
+ }
+}
+
+.alert-info {
+ background-color: #E6F1F6;
+ border-color: #D2D9DD;
+ color: #4E575B;
+ text-shadow: none;
+ .link {
+ padding: 0 15px;
+ }
+ .link-left-pad {
+ padding-left:15px;
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/templates/job/error_popup.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/job/error_popup.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/job/error_popup.hbs
new file mode 100644
index 0000000..8947b6f
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/job/error_popup.hbs
@@ -0,0 +1,19 @@
+{{!
+* 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.
+}}
+
+{{controller.error_message}}
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/templates/job/hive_job_details_tez_dag.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/job/hive_job_details_tez_dag.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/job/hive_job_details_tez_dag.hbs
new file mode 100644
index 0000000..4d8d9e7
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/job/hive_job_details_tez_dag.hbs
@@ -0,0 +1,44 @@
+{{!
+* 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="4" markerWidth="3" markerHeight="3" 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="50" height="16">
+ </rect>
+ </clipPath>
+ <font-face xmlns="http://www.w3.org/2000/svg" font-family="FontAwesome" unicode-range="U+0-7F">
+ <font-face-src>
+ <font-face-uri xlink:href="font/fontawesome-webfont.svg" xmlns:xlink="http://www.w3.org/1999/xlink"/>
+ </font-face-src>
+ </font-face>
+ </defs>
+ </svg>
+</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/templates/job/hover_op_table.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/job/hover_op_table.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/job/hover_op_table.hbs
new file mode 100644
index 0000000..a2cf77d
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/job/hover_op_table.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.
+}}
+
+<p>{{content.operationName}}</p>
+<table id="hover-op-table" class="table table-bordered">
+ <thead>
+ <tr>
+ <th>{{t common.name}}</th>
+ <th>{{t common.value}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#each keys in content.operatorPlanObj}}
+ <tr>
+ <td>
+ {{keys.name}}
+ </td>
+ <td>
+ {{keys.value}}
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+</table>
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/templates/job/job.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/job/job.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/job/job.hbs
new file mode 100644
index 0000000..9e9b6ef
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/job/job.hbs
@@ -0,0 +1,228 @@
+{{!
+* 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="hive-job-details">
+
+ {{#if controller.loaded}}
+ <!-- Top Bar -->
+ <div class="top-bar">
+ <a {{action "actionRouteToJobs" target="controller"}} href="#">{{t menu.item.jobs}}</a> > {{view.content.name}}
+ <a {{action "toggleShowQuery" target="view"}} href="#" id="toggle-query">{{view.toggleShowQueryText}}</a>
+
+ <div class="pull-right">Job Type: <span class="label label-info">{{view.content.jobType}}</span></div>
+ <div class="alert alert-info query-info">
+ <table id="job-more-details-table">
+ <tr>
+ <td>
+ {{t jobs.hive.query}}
+ </td>
+ <td>
+ {{view.content.queryText}}
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {{t jobs.hive.yarnApplication}}
+ </td>
+ <td>
+ {{view.content.tezDag.yarnApplicationId}}
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {{t jobs.hive.stages}}
+ </td>
+ <td>
+ <ol>
+ {{#each stage in view.content.stages}}
+ <li>{{stage.id}}{{stage.description}}.</li>
+ {{/each}}
+ </ol>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- Sections -->
+ <div class="row-fluid">
+ <div class="span12 sections">
+ <!-- Section LHS -->
+ <div id="tez-dag-lhs" {{bind-attr class="view.isGraphMaximized:span12: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">
+ <button class="btn dropdown-toggle" data-toggle="dropdown">
+ {{view.summaryMetricTypeDisplay}}
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <!-- dropdown menu links -->
+ {{#each type in view.summaryMetricTypesDisplay}}
+ <li>
+ <a {{bind-attr 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 class="btn-group">
+ <a id="tez-dag-zoom-in-button" {{bind-attr class="view.canGraphZoomIn::disabled :btn"}} {{action "doGraphZoomIn" target="view"}}>
+ <i class="icon-zoom-in"></i>
+ </a>
+ <a id="tez-dag-zoom-out-button" {{bind-attr class="view.canGraphZoomOut::disabled :btn"}} {{action "doGraphZoomOut" target="view"}}>
+ <i class="icon-zoom-out"></i>
+ </a>
+ {{#if view.isGraphMaximized}}
+ <a id="tez-dag-resize-small-button" class="btn" {{action "doGraphMinimize" target="view"}}>
+ <i class="icon-resize-small"></i>
+ </a>
+ {{else}}
+ <a id="tez-dag-resize-full-button" class="btn" {{action "doGraphMaximize" target="view"}}>
+ <i class="icon-resize-full"></i>
+ </a>
+ {{/if}}
+ </div>
+ </div>
+ </div>
+ <div id="tez-dag-section-body">
+ {{#if controller.loaded}}
+ {{view App.MainHiveJobDetailsTezDagView
+ contentBinding="view.content"
+ tezDagBinding="view.content.tezDag"
+ selectedVertexBinding="view.selectedVertex"
+ summaryMetricTypeBinding="view.summaryMetricType"
+ zoomScaleBinding="view.zoomScale"
+ zoomScaleFromBinding="view.zoomScaleFrom"
+ zoomScaleToBinding="view.zoomScaleTo"
+ }}
+ {{/if}}
+ </div>
+ </div>
+ </div>
+
+ <!-- Section RHS -->
+ <div id="tez-vertices-rhs" {{bind-attr class="view.isGraphMaximized:hidden: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">
+ {{#view App.MainHiveJobDetailsVerticesTableView
+ contentBinding="view.sortedVertices"
+ doSelectVertexBinding="view.doSelectVertex"
+ actionDoSelectVertexBinding="view.actionDoSelectVertex"
+ selectedVertexBinding="view.selectedVertex"
+ }}
+ <thead>
+ {{#view view.sortView contentBinding="view.content"}}
+ {{view view.parentView.nameSort}}
+ {{view view.parentView.tasksSort}}
+ {{view view.parentView.inputSort}}
+ {{view view.parentView.outputSort}}
+ {{view view.parentView.durationSort}}
+ {{/view}}
+ </thead>
+ <tbody>
+ {{#each vertex in view.content}}
+ <tr {{bind-attr class="vertex.isSelected:info"}}>
+ <td>
+ <a {{bind-attr title="vertex.name"}} {{action "actionDoSelectVertex" vertex target="view.parentView"}}>{{vertex.name}}</a>
+ </td>
+ <td>{{vertex.tasksNumber}}</td>
+ <td>{{vertex.totalReadBytesDisplay}}</td>
+ <td>{{vertex.totalWriteBytesDisplay}}</td>
+ <td>{{vertex.durationDisplay}}</td>
+ </tr>
+ {{/each}}
+ </tbody>
+ {{/view}}
+ </table>
+ </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 no-borders table-condensed">
+ <tr>
+ <td>{{t common.status}}</td>
+ <td>{{view.selectedVertexIODisplay.status}}</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>{{t common.time.start}}</td>
+ <td>{{view.selectedVertexIODisplay.started}}</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>{{t common.time.end}}</td>
+ <td>{{view.selectedVertexIODisplay.ended}}</td>
+ <td></td>
+ </tr>
+ <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.spilledRecords}}</td>
+ <td>{{view.selectedVertex.spilledRecords}}</td>
+ </tr>
+ {{#if view.selectedVertexIODisplay.records.read}}
+ <tr>
+ <td>{{t jobs.hive.tez.records}}</td>
+ <td>{{view.selectedVertexIODisplay.records.read}}</td>
+ <td>{{view.selectedVertexIODisplay.records.write}}</td>
+ </tr>
+ {{/if}}
+ </table>
+ </div>
+ </div>
+ </div>
+ {{/if}}
+ </div>
+ </div>
+ </div>
+ {{else}}
+ <div class="alert alert-info">
+ <h4>{{t app.loadingPlaceholder}}</h4>
+ </div>
+ {{/if}}
+</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/bower.json
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/bower.json b/contrib/views/jobs/src/main/resources/ui/bower.json
index 6eda9d2..78377f9 100644
--- a/contrib/views/jobs/src/main/resources/ui/bower.json
+++ b/contrib/views/jobs/src/main/resources/ui/bower.json
@@ -10,7 +10,9 @@
"bootstrap": "2.3.*",
"ember-addons.bs_for_ember": ">=0.7",
"ember-json-mapper": "master",
- "jquery-ui": ">=1.11"
+ "jquery-ui": ">=1.11",
+ "d3": "2.10.2",
+ "font-awesome": "3.2.1"
},
"devDependencies": {
"ember-mocha-adapter": "0.1.2"
[2/2] git commit: AMBARI-6651. Create job-details page.
(onechiporenko)
Posted by on...@apache.org.
AMBARI-6651. Create job-details page. (onechiporenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/85362043
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/85362043
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/85362043
Branch: refs/heads/trunk
Commit: 85362043f7365fe59c2900284ef12057f00cf0b1
Parents: 569acf8
Author: Oleg Nechiporenko <on...@apache.org>
Authored: Tue Jul 29 14:58:06 2014 +0300
Committer: Oleg Nechiporenko <on...@apache.org>
Committed: Tue Jul 29 14:58:06 2014 +0300
----------------------------------------------------------------------
.../jobs/src/main/resources/ui/Gruntfile.js | 6 +
.../jobs/src/main/resources/ui/app/index.html | 4 +
.../src/main/resources/ui/app/scripts/app.js | 14 +-
.../app/scripts/controllers/job_controller.js | 87 +-
.../resources/ui/app/scripts/helpers/ajax.js | 24 +
.../resources/ui/app/scripts/helpers/jobs.js | 255 +++++
.../resources/ui/app/scripts/helpers/number.js | 36 +-
.../resources/ui/app/scripts/helpers/string.js | 59 ++
.../app/scripts/mappers/jobs/hive_job_mapper.js | 58 +-
.../scripts/mappers/jobs/hive_jobs_mapper.js | 4 +
.../ui/app/scripts/models/jobs/hive_job.js | 6 +-
.../ui/app/scripts/models/jobs/tez_dag.js | 105 ++-
.../src/main/resources/ui/app/scripts/router.js | 2 +-
.../ui/app/scripts/routes/application_route.js | 13 +-
.../resources/ui/app/scripts/translations.js | 37 +-
.../views/job/hive_job_details_tez_dag_view.js | 925 +++++++++++++++++++
.../scripts/views/job/hive_job_details_view.js | 327 +++++++
.../resources/ui/app/scripts/views/job_view.js | 19 -
.../resources/ui/app/scripts/views/jobs_view.js | 6 +-
.../src/main/resources/ui/app/styles/main.less | 353 ++++++-
.../ui/app/templates/job/error_popup.hbs | 19 +
.../templates/job/hive_job_details_tez_dag.hbs | 44 +
.../ui/app/templates/job/hover_op_table.hbs | 39 +
.../main/resources/ui/app/templates/job/job.hbs | 228 +++++
.../views/jobs/src/main/resources/ui/bower.json | 4 +-
25 files changed, 2537 insertions(+), 137 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/Gruntfile.js b/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
index 444acb1..d223202 100644
--- a/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
+++ b/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
@@ -267,6 +267,12 @@ module.exports = function (grunt) {
flatten: true,
src: '<%= yeoman.app %>/bower_components/jquery-ui/themes/base/images/*',
dest: '<%= yeoman.dist %>/styles/images/'
+ },
+ {
+ expand: true,
+ flatten: true,
+ src: '<%= yeoman.app %>/bower_components/font-awesome/font/*',
+ dest: '<%= yeoman.dist %>/font/'
}
]
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/index.html
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/index.html b/contrib/views/jobs/src/main/resources/ui/app/index.html
index f5ae0de..feb8bcb 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/index.html
+++ b/contrib/views/jobs/src/main/resources/ui/app/index.html
@@ -27,7 +27,10 @@
</head>
<body>
<!-- build:js(app) scripts/components.js -->
+ <script src="bower_components/cldr/plurals.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
+ <script src="bower_components/bootstrap/js/bootstrap-dropdown.js"></script>
+ <script src="bower_components/bootstrap/js/bootstrap-button.js"></script>
<script src="bower_components/bootstrap/js/bootstrap-tooltip.js"></script>
<script src="bower_components/jquery-ui/ui/datepicker.js"></script>
<script src="bower_components/moment/moment.js"></script>
@@ -39,6 +42,7 @@
<script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-core.min.js"></script>
<script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-button.min.js"></script>
<script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-modal.min.js"></script>
+ <script src="bower_components/d3/d3.v2.js"></script>
<!-- endbuild -->
<!-- build:js(.tmp) scripts/templates.js -->
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
index b7c6d2b..1067704 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
@@ -16,7 +16,14 @@
* limitations under the License.
*/
-var App = window.App = Ember.Application.create();
+var App = window.App = Ember.Application.createWithMixins(Bootstrap, {
+ LOG_TRANSITIONS: false,
+ LOG_TRANSITIONS_INTERNAL: false
+});
+
+require('scripts/router');
+require('scripts/routes/*');
+require('scripts/store');
App.Helpers = Ember.Namespace.create();
@@ -56,15 +63,12 @@ App.initializer({
/* Order and include as you please. */
require('scripts/translations');
-require('scripts/router');
-require('scripts/store');
require('scripts/mixins/*');
require('scripts/helpers/*');
require('scripts/models/**/*');
require('scripts/mappers/server_data_mapper.js');
require('scripts/mappers/**/*');
-require('scripts/controllers/*');
-require('scripts/routes/*');
+require('scripts/controllers/**/*');
require('scripts/components/*');
require('scripts/views/sort_view');
require('scripts/views/filter_view');
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
index e300323..bce4526 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
@@ -16,4 +16,89 @@
* limitations under the License.
*/
-App.JobController = Ember.Controller.extend({});
+App.JobController = Ember.ObjectController.extend({
+
+ name: 'jobController',
+
+ loaded: false,
+
+ loadTimeout: null,
+
+ job: null,
+
+ sortingColumn: null,
+
+ showPopupButtons: [
+ Ember.Object.create({title: Em.I18n.t('ok'), dismiss: 'modal'})
+ ],
+
+ showPopup: function (title) {
+ Bootstrap.ModalManager.open(
+ 'errorPopup',
+ title,
+ 'job/error_popup',
+ this.get('showPopupButtons'),
+ this
+ );
+ },
+
+ loadJobDetails: function () {
+ var self = this,
+ timeout = this.get('loadTimeout'),
+ yarnService = App.HiveJob.store.getById('service', 'YARN'),
+ content = this.get('content');
+ if (!Em.isNone(yarnService)) {
+ if (!Em.isNone(content)) {
+ App.Helpers.jobs.refreshJobDetails(
+ content,
+ function () {
+ self.set('content', App.HiveJob.store.getById('hiveJob', content.get('id')));
+ self.set('loaded', true);
+ },
+ function (errorId) {
+ switch (errorId) {
+ case 'job.dag.noId':
+ self.set('error_message', Em.I18n.t('jobs.hive.tez.dag.error.noDagId.message'));
+ self.showPopup(Em.I18n.t('jobs.hive.tez.dag.error.noDagId.title'));
+ break;
+ case 'job.dag.noname':
+ self.set('error_message', Em.I18n.t('jobs.hive.tez.dag.error.noDag.message'));
+ self.showPopup(Em.I18n.t('jobs.hive.tez.dag.error.noDag.title'));
+ break;
+ case 'job.dag.id.noDag':
+ self.set('error_message', Em.I18n.t('jobs.hive.tez.dag.error.noDagForId.message'));
+ self.showPopup(Em.I18n.t('jobs.hive.tez.dag.error.noDagForId.title'));
+ break;
+ case 'job.dag.id.loaderror':
+ case 'job.dag.name.loaderror':
+ break;
+ default:
+ break;
+ }
+ self.routeToJobs();
+ }
+ );
+ }
+ } else {
+ clearTimeout(timeout);
+ timeout = setTimeout(function () {
+ self.loadJobDetails();
+ }, 300);
+ }
+ },
+
+ /**
+ * open jobs page
+ * @method routeToJobs
+ */
+ routeToJobs: function () {
+ this.transitionToRoute('jobs');
+ },
+
+ actions: {
+ actionRouteToJobs: function () {
+ this.routeToJobs();
+ }
+ }
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
index 46eda9a..73a87fd 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
@@ -42,6 +42,30 @@ var urls = {
apiPrefix: ''
},
+ 'job_details': {
+ real: '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/HIVE_QUERY_ID/{job_id}?fields=events,otherinfo',
+ mock: '/scripts/assets/hive-query-2.json',
+ apiPrefix: ''
+ },
+
+ 'jobs.tezDag.NametoID': {
+ 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}',
+ 'mock': '/scripts/assets/tezDag-name-to-id.json',
+ 'apiPrefix': ''
+ },
+
+ 'jobs.tezDag.tezDagId': {
+ 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo',
+ 'mock': '/scripts/assets/tezDag.json',
+ 'apiPrefix': ''
+ },
+
+ 'jobs.tezDag.tezDagVertexId': {
+ 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo',
+ 'mock': '/scripts/assets/tezDagVertex.json',
+ 'apiPrefix': ''
+ },
+
'cluster_name': {
real: 'clusters',
mock: '/scripts/assets/clusters.json'
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
new file mode 100644
index 0000000..e67f534
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
@@ -0,0 +1,255 @@
+/**
+ * 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.
+ */
+
+App.Helpers.jobs = {
+
+ /**
+ * Refreshes the latest information for a given job
+ *
+ * @param {App.AbstractJob} job
+ * @param {Function} successCallback
+ * @param {Function} errorCallback (errorId) Called in error cases where there is no
+ * data from server. 'errorId' can be one of
+ * <ul>
+ * <li>job.dag.noId</li>
+ * <li>job.dag.noname</li>
+ * <li>job.dag.id.noDag</li>
+ * <li>job.dag.id.loaderror</li>
+ * <li>job.dag.name.loaderror</li>
+ * </ul>
+ */
+ refreshJobDetails: function (job, successCallback, errorCallback) {
+ this.refreshHiveJobDetails(job, successCallback, errorCallback);
+ },
+
+ /**
+ * Refreshes latest information of a Hive Job.
+ *
+ * @param {App.HiveJob} hiveJob
+ * @param {Function} successCallback
+ * @param {Function} errorCallback @see #refreshJobDetails()
+ * @method refreshHiveJobDetails
+ */
+ refreshHiveJobDetails: function (hiveJob, successCallback, errorCallback) {
+ var yarnService = App.HiveJob.store.getById('service', 'YARN'),
+ historyServerHostName = App.HiveJob.store.getById('component', 'APP_TIMELINE_SERVER').get('hostName'),
+ ahsWebPort = yarnService.get('ahsWebPort');
+
+ return App.ajax.send({
+ name: 'job_details',
+ sender: this,
+ data: {
+ historyServerHostName: historyServerHostName,
+ ahsWebPort: ahsWebPort,
+ job_id: hiveJob.get('id'),
+ successCallback: successCallback,
+ errorCallback: errorCallback
+ },
+ success: 'refreshHiveJobDetailsSuccessCallback'
+ });
+
+ },
+
+ refreshHiveJobDetailsSuccessCallback: function (data, opt, params) {
+ App.hiveJobMapper.map(data);
+ var hiveRecord = App.HiveJob.store.getById('hiveJob', params.job_id),
+ tezDagName = hiveRecord.get('tezDag.name'),
+ self = this;
+ if (!Em.isNone(tezDagName)) {
+ var sender = {
+ dagNameToIdSuccess: function (data) {
+ if (data && data.entities && data.entities.length > 0) {
+ var dagId = data.entities[0].entity;
+ hiveRecord.get('tezDag').set('instanceId', dagId);
+ self.refreshTezDagDetails(tezDagName, params.successCallback, params.errorCallback);
+ }
+ else {
+ params.errorCallback('job.dag.noId');
+ }
+ },
+ dagNameToIdError: function (jqXHR, url, method, showStatus) {
+ params.errorCallback('job.dag.name.loaderror');
+ }
+ };
+ App.ajax.send({
+ name: 'jobs.tezDag.NametoID',
+ sender: sender,
+ data: {
+ historyServerHostName: params.historyServerHostName,
+ tezDagName: tezDagName,
+ ahsWebPort: params.ahsWebPort
+ },
+ success: 'dagNameToIdSuccess',
+ error: 'dagNameToIdError'
+ });
+ }
+ else {
+ params.errorCallback('job.dag.noname');
+ }
+ },
+
+ /**
+ * Refreshes runtime information of a Tez DAG based on events generated.
+ * The instance ID of the Tez DAG should be set.
+ *
+ * @param {string} tezDagId ID of the Tez DAG. Example: 'HIVE-Q2:1'
+ * @param {Function} successCallback
+ * @param {Function} errorCallback @see #refreshJobDetails()
+ * @method refreshTezDagDetails
+ */
+ refreshTezDagDetails: function (tezDagId, successCallback, errorCallback) {
+ var self = this,
+ yarnService = App.HiveJob.store.getById('service', 'YARN'),
+ historyServerHostName = App.HiveJob.store.getById('component', 'APP_TIMELINE_SERVER').get('hostName'),
+ ahsWebPort = yarnService.get('ahsWebPort'),
+ tezDag = App.HiveJob.store.getById('tezDag', tezDagId);
+ if (tezDag) {
+ var tezDagInstanceId = tezDag.get('instanceId'),
+ sender = {
+ loadTezDagSuccess: function (data) {
+ if (data) {
+ if (data.otherinfo && data.otherinfo.applicationId) {
+ tezDag.set('yarnApplicationId', data.otherinfo.applicationId);
+ }
+ if (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, function () {
+ if (--count <= 0) {
+ // all vertices succeeded
+ successCallback();
+ }
+ });
+ });
+ }
+ }
+ },
+ loadTezDagError: function (jqXHR, url, method, showStatus) {
+ errorCallback('job.dag.id.loaderror');
+ }
+ };
+ App.ajax.send({
+ name: 'jobs.tezDag.tezDagId',
+ sender: sender,
+ data: {
+ historyServerHostName: historyServerHostName,
+ tezDagId: tezDagInstanceId,
+ ahsWebPort: ahsWebPort
+ },
+ success: 'loadTezDagSuccess',
+ error: 'loadTezDagError'
+ });
+ }
+ else {
+ errorCallback('job.dag.id.noDag');
+ }
+ },
+
+ /**
+ * Refreshes runtime information of the given vertex.
+ *
+ * @param {string} tezDagId ID of the Tez DAG. Exmaple: 'HIVE-Q2:1'
+ * @param {string} tezVertexInstanceId Instance ID of the vertex to refresh. Example 'vertex_1390516007863_0001_1_00'
+ * @param {Function} successCallback
+ * @method refreshTezDagVertex
+ */
+ refreshTezDagVertex: function (tezDagId, tezVertexInstanceId, successCallback) {
+ var yarnService = App.HiveJob.store.getById('service', 'YARN'),
+ historyServerHostName = App.HiveJob.store.getById('component', 'APP_TIMELINE_SERVER').get('hostName'),
+ ahsWebPort = yarnService.get('ahsWebPort'),
+ tezDag = App.HiveJob.store.getById('tezDag', tezDagId),
+ hiveJob = App.HiveJob.store.all('hiveJob').findBy('tezDag', tezDag),
+ hiveJobFailed = hiveJob.get('failed'),
+ hiveJobEndTime = hiveJob.get('endTime'),
+ sender = {
+ loadTezDagVertexSuccess: function (data) {
+ if (data && data.otherinfo) {
+ var vertexRecord = App.HiveJob.store.getById('tezDagVertex', tezDagId + "/" + data.otherinfo.vertexName);
+ if (vertexRecord != null) {
+ vertexRecord.set('startTime', data.otherinfo.startTime);
+ if (data.otherinfo.endTime == undefined && hiveJobFailed) {
+ vertexRecord.set('endTime', hiveJobEndTime);
+ }
+ else {
+ vertexRecord.set('endTime', data.otherinfo.endTime);
+ }
+ vertexRecord.set('tasksCount', data.otherinfo.numTasks);
+ if (data.otherinfo.status == null && hiveJobFailed) {
+ vertexRecord.set('state', Em.I18n.t('jobs.hive.failed'));
+ }
+ else {
+ vertexRecord.set('state', data.otherinfo.status);
+ }
+ if (data.otherinfo.counters && data.otherinfo.counters.counterGroups) {
+ data.otherinfo.counters.counterGroups.forEach(function (cGroup) {
+ var cNameToPropetyMap = {};
+ switch (cGroup.counterGroupName) {
+ case 'org.apache.tez.common.counters.FileSystemCounter':
+ cNameToPropetyMap = {
+ 'FILE_BYTES_READ': 'fileReadBytes',
+ 'FILE_BYTES_WRITTEN': 'fileWriteBytes',
+ 'FILE_READ_OPS': 'fileReadOps',
+ 'FILE_WRITE_OPS': 'fileWriteOps',
+ 'HDFS_BYTES_READ': 'hdfsReadBytes',
+ 'HDFS_BYTES_WRITTEN': 'hdfsWriteBytes',
+ 'HDFS_READ_OPS': 'hdfsReadOps',
+ 'HDFS_WRITE_OPS': 'hdfsWriteOps'
+ };
+ break;
+ case 'org.apache.tez.common.counters.TaskCounter':
+ cNameToPropetyMap = {
+ 'SPILLED_RECORDS': 'spilledRecords'
+ };
+ break;
+ case 'HIVE':
+ cNameToPropetyMap = {
+ 'RECORDS_READ': 'recordReadCount',
+ 'RECORDS_WRITE': 'recordWriteCount'
+ };
+ break;
+ default:
+ break;
+ }
+ if (cGroup.counters) {
+ cGroup.counters.forEach(function (counter) {
+ var prop = cNameToPropetyMap[counter.counterName];
+ if (prop != null) {
+ vertexRecord.set(prop, counter.counterValue);
+ }
+ });
+ }
+ });
+ }
+ successCallback();
+ }
+ }
+ },
+ loadTezDagVertexError: function (jqXHR, url, method, showStatus) {}
+ };
+ App.ajax.send({
+ name: 'jobs.tezDag.tezDagVertexId',
+ sender: sender,
+ data: {
+ historyServerHostName: historyServerHostName,
+ tezDagVertexId: tezVertexInstanceId,
+ ahsWebPort: ahsWebPort
+ },
+ success: 'loadTezDagVertexSuccess',
+ error: 'loadTezDagVertexError'
+ });
+ }
+};
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/number.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/number.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/number.js
index 164ae96..4adf792 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/number.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/number.js
@@ -30,7 +30,7 @@ App.Helpers.number = {
* @return {String} Returns converted value with abbreviation.
*/
bytesToSize: function (bytes, precision, parseType, multiplyBy) {
- if (bytes === null || bytes === undefined) {
+ if (Em.isNone(bytes)) {
return 'n/a';
} else {
if (arguments[2] === undefined) {
@@ -52,6 +52,40 @@ App.Helpers.number = {
var parsedValue = window[parseType](value);
return parsedValue.toFixed(precision) + " " + sizes[posttxt];
}
+ },
+
+ /**
+ * Validates if the given string or number is an integer between the
+ * values of min and max (inclusive). The minimum and maximum
+ * checks are ignored if their valid is NaN.
+ *
+ * @method validateInteger
+ * @param {string|number} str - input string
+ * @param {string|number} [min]
+ * @param {string|number} [max]
+ */
+ validateInteger : function(str, min, max) {
+ if (str==null || str==undefined || (str + "").trim().length < 1) {
+ return Em.I18n.t('number.validate.empty');
+ } else {
+ str = (str + "").trim();
+ var number = parseInt(str);
+ if (isNaN(number)) {
+ return Em.I18n.t('number.validate.notValidNumber');
+ } else {
+ if (str.length != (number + "").length) {
+ // parseInt("1abc") returns 1 as integer
+ return Em.I18n.t('number.validate.notValidNumber');
+ }
+ if (!isNaN(min) && number < min) {
+ return Em.I18n.t('number.validate.lessThanMinumum').fmt(min);
+ }
+ if (!isNaN(max) && number > max) {
+ return Em.I18n.t('number.validate.moreThanMaximum').fmt(max);
+ }
+ }
+ }
+ return null;
}
};
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/string.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/string.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/string.js
index eb49c4a..487f4a3 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/string.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/string.js
@@ -40,6 +40,65 @@ App.Helpers.string = {
*/
startsWith: function (str, prefix) {
return str.indexOf(prefix) == 0;
+ },
+
+ getCamelCase: function(name) {
+ if (name != null) {
+ return name.toLowerCase().replace(/(\b\w)/g, function(f) {
+ return f.toUpperCase();
+ })
+ }
+ return name;
+ },
+
+ /**
+ * Finds the value in an object where this string is a key.
+ * Optionally, the index of the key can be provided where the
+ * value of the nth key in the hierarchy is returned.
+ *
+ * Example:
+ * var tofind = 'smart';
+ * var person = {'name': 'Bob Bob', 'smart': 'no', 'age': '28', 'personality': {'smart': 'yes', 'funny': 'yes', 'emotion': 'happy'} };
+ * findIn(tofind, person); // 'no'
+ * findIn(tofind, person, 0); // 'no'
+ * findIn(tofind, person, 1); // 'yes'
+ * findIn(tofind, person, 2); // null
+ *
+ * @method findIn
+ * @param s {string}
+ * @param multi {object}
+ * @param index {number} Occurrence count of this key
+ * @param _foundValues {array}
+ * @return {*} Value of key at given index
+ */
+ findIn: function(s, multi, index, _foundValues) {
+ if (!index) {
+ index = 0;
+ }
+ if (!_foundValues) {
+ _foundValues = [];
+ }
+ multi = multi || '';
+ var value = null,
+ str = this.valueOf();
+ if (typeof multi == 'object') {
+ for ( var key in multi) {
+ if (value != null) {
+ break;
+ }
+ if (key == str) {
+ _foundValues.push(multi[key]);
+ }
+ if (_foundValues.length - 1 == index) {
+ // Found the value
+ return _foundValues[index];
+ }
+ if (typeof multi[key] == 'object') {
+ value = value || this.findIn(s, multi[key], index, _foundValues);
+ }
+ }
+ }
+ return value;
}
};
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_job_mapper.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_job_mapper.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_job_mapper.js
index ad65902..059c08a 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_job_mapper.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_job_mapper.js
@@ -34,7 +34,6 @@
*/
App.hiveJobMapper = App.QuickDataMapper.create({
- model : App.HiveJob,
map : function(json) {
var model = this.get('model'),
hiveJob = {};
@@ -80,8 +79,8 @@ App.hiveJobMapper = App.QuickDataMapper.create({
var vertexObj = {
id : dagName + "/" + vertexName,
name : vertexName,
- incoming_edges : [],
- outgoing_edges : []
+ incomingEdges : [],
+ outgoingEdges : []
};
vertexIds.push(vertexObj.id);
var operatorExtractor = function(obj) {
@@ -104,13 +103,13 @@ App.hiveJobMapper = App.QuickDataMapper.create({
if (vertex["Map Operator Tree:"] != null) {
vertexObj.type = App.TezDagVertexType.MAP;
vertexObj.operations = operatorExtractor(vertex["Map Operator Tree:"]);
- vertexObj.operation_plan = JSON.stringify(vertex["Map Operator Tree:"], undefined, " ");
+ vertexObj.operationPlan = JSON.stringify(vertex["Map Operator Tree:"], undefined, " ");
}
else
if (vertex["Reduce Operator Tree:"] != null) {
vertexObj.type = App.TezDagVertexType.REDUCE;
vertexObj.operations = operatorExtractor(vertex["Reduce Operator Tree:"]);
- vertexObj.operation_plan = JSON.stringify(vertex["Reduce Operator Tree:"], undefined, " ");
+ vertexObj.operationPlan = JSON.stringify(vertex["Reduce Operator Tree:"], undefined, " ");
}
else
if (vertex["Vertex:"] != null && vertexName==vertex['Vertex:']) {
@@ -141,21 +140,21 @@ App.hiveJobMapper = App.QuickDataMapper.create({
}
var edgeObj = {
id : dagName + "/" + parentVertex + "-" + childVertex,
- from_vertex_id : dagName + "/" + parentVertex,
- to_vertex_id : dagName + "/" + childVertex
+ fromVertex: dagName + "/" + parentVertex,
+ toVertex : dagName + "/" + childVertex
};
- vertexIdMap[edgeObj.from_vertex_id].outgoing_edges.push(edgeObj.id);
- vertexIdMap[edgeObj.to_vertex_id].incoming_edges.push(edgeObj.id);
+ vertexIdMap[edgeObj.fromVertex].outgoingEdges.push(edgeObj.id);
+ vertexIdMap[edgeObj.toVertex].incomingEdges.push(edgeObj.id);
edgeIds.push(edgeObj.id);
switch (e.type) {
case "BROADCAST_EDGE":
- edgeObj.edge_type = App.TezDagEdgeType.BROADCAST;
+ edgeObj.edgeType = App.TezDagEdgeType.BROADCAST;
break;
case "SIMPLE_EDGE":
- edgeObj.edge_type = App.TezDagEdgeType.SCATTER_GATHER;
+ edgeObj.edgeType = App.TezDagEdgeType.SCATTER_GATHER;
break;
case "CONTAINS":
- edgeObj.edge_type = App.TezDagEdgeType.CONTAINS;
+ edgeObj.edgeType = App.TezDagEdgeType.CONTAINS;
break;
default:
break;
@@ -175,40 +174,43 @@ App.hiveJobMapper = App.QuickDataMapper.create({
// reload as the structure does not change. Reloading
// here causes missing data (got from other calls)
// to propagate into UI - causing flashing.
- var newVertices = [];
- var newEdges = [];
+ var newVertices = [],
+ newEdges = [];
vertices.forEach(function(v) {
- var vertexRecord = App.TezDagVertex.find(v.id);
- if (!vertexRecord.get('isLoaded')) {
+ var vertexRecord = App.HiveJob.store.getById('tezDagVertex', v.id);
+ if (!vertexRecord || !vertexRecord.get('isLoaded')) {
newVertices.push(v);
}
});
edges.forEach(function(e) {
- var edgeRecord = App.TezDagEdge.find(e.id);
- if (!edgeRecord.get('isLoaded')) {
+ var edgeRecord = App.HiveJob.store.getById('tezDagEdge', e.id);
+ if (!edgeRecord || !edgeRecord.get('isLoaded')) {
newEdges.push(e);
}
});
- App.store.loadMany(App.TezDagVertex, newVertices);
- App.store.loadMany(App.TezDagEdge, newEdges);
- var dagRecord = App.TezDag.find(tezDag.id);
- if (!dagRecord.get('isLoaded')) {
- App.store.load(App.TezDag, tezDag);
+
+ App.HiveJob.store.pushMany('tezDagVertex', newVertices);
+ App.HiveJob.store.pushMany('tezDagEdge', newEdges);
+
+ var dagRecord = App.HiveJob.store.getById('tezDag', tezDag.id);
+ if (!dagRecord || !dagRecord.get('isLoaded')) {
+ App.HiveJob.store.push('tezDag', tezDag);
}
hiveJob.tezDag = tezDag.id;
}
}
- if(App.HiveJob.find().get('content').length == 0){
- App.store.load(model, hiveJob);
+
+ if(App.HiveJob.store.all('hiveJob').length == 0){
+ App.HiveJob.store.push('hiveJob', hiveJob);
}
- var hiveJobRecord = App.HiveJob.find(hiveJob.id);
+ var hiveJobRecord = App.HiveJob.store.getById('hiveJob', hiveJob.id);
if (hiveJobRecord != null) {
- hiveJobRecord.set('stages', hiveJob.stages.sortProperty('id'));
+ hiveJobRecord.set('stages', hiveJob.stages.sortBy('id'));
hiveJobRecord.set('startTime', hiveJob.startTime);
hiveJobRecord.set('endTime', hiveJob.endTime);
if (hiveJob.tezDag != null) {
// Some hive queries dont use Tez
- hiveJobRecord.set('tezDag', App.TezDag.find(hiveJob.tezDag));
+ hiveJobRecord.set('tezDag', App.HiveJob.store.getById('tezDag', hiveJob.tezDag));
}
}
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
index 49b373c..e87d8e9 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
@@ -83,6 +83,10 @@ App.hiveJobsMapper = App.QuickDataMapper.create({
});
}
hiveJobs.push(hiveJob);
+ var tezDag = App.HiveJob.store.all('tezDag').findBy('hiveJob.id', hiveJob.id);
+ if (!Em.isNone(tezDag)) {
+ hiveJob.tezDag = tezDag.id;
+ }
jobsToDelete = jobsToDelete.without(hiveJob.id);
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
index 53d309e..0ecdaa1 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
@@ -17,13 +17,13 @@
App.HiveJob = App.AbstractJob.extend({
- jobType : App.JobType.HIVE,
+ jobType: App.JobType.HIVE,
- queryText : DS.attr('string'),
+ queryText: DS.attr('string'),
hasTezDag: DS.attr('boolean'),
- tezDag : DS.belongsTo('tezDag', {async:true}),
+ tezDag: DS.belongsTo('tezDag'),
failed: DS.attr('boolean')
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/tez_dag.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/tez_dag.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/tez_dag.js
index 71a42b6..cd6178b 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/tez_dag.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/tez_dag.js
@@ -20,33 +20,38 @@ App.TezDag = DS.Model.extend({
/**
* When DAG is actually running on server, a unique ID is assigned.
*/
- instanceId : DS.attr('string'),
+ instanceId: DS.attr('string'),
- name : DS.attr('string'),
+ name: DS.attr('string'),
yarnApplicationId: DS.attr('string'),
- stage : DS.attr('string'),
+ stage: DS.attr('string'),
- vertices : DS.hasMany('tezDagVertex', {async:true}),
+ vertices: DS.hasMany('tezDagVertex'),
- edges : DS.hasMany('tezDagEdge', {async:true})
+ edges: DS.hasMany('tezDagEdge'),
+
+ hiveJob: DS.belongsTo('hiveJob')
});
App.TezDagEdge = DS.Model.extend({
- instanceId : DS.attr('string'),
+ instanceId: DS.attr('string'),
- fromVertex : DS.belongsTo('tezDagVertex', {async:true}),
+ fromVertex: DS.belongsTo('tezDagVertex'),
- toVertex : DS.belongsTo('tezDagVertex', {async:true}),
+ toVertex: DS.belongsTo('tezDagVertex'),
/**
* Type of this edge connecting vertices. Should be one of constants defined
* in 'App.TezDagEdgeType'.
*/
- edgeType : DS.attr('string')
+ edgeType: DS.attr('string'),
+
+ tezDag: DS.belongsTo('tezDag')
+
});
App.TezDagVertex = DS.Model.extend({
@@ -54,35 +59,37 @@ App.TezDagVertex = DS.Model.extend({
/**
* When DAG vertex is actually running on server, a unique ID is assigned.
*/
- instanceId : DS.attr('string'),
+ instanceId: DS.attr('string'),
+
+ name: DS.attr('string'),
- name : DS.attr('string'),
+ tezDag: DS.belongsTo('tezDag'),
/**
* State of this vertex. Should be one of constants defined in
* App.TezDagVertexState.
*/
- state : DS.attr('string'),
+ state: DS.attr('string'),
/**
* Vertex type has to be one of the types defined in 'App.TezDagVertexType'
* @return {string}
*/
- type : DS.attr('string'),
+ type: DS.attr('string'),
/**
* A vertex can have multiple incoming edges.
*/
- incomingEdges : DS.hasMany('tezDagEdge', {async:true}),
+ incomingEdges: DS.hasMany('tezDagEdge'),
/**
* This vertex can have multiple outgoing edges.
*/
- outgoingEdges : DS.hasMany('tezDagEdge', {async:true}),
+ outgoingEdges: DS.hasMany('tezDagEdge'),
- startTime : DS.attr('number'),
+ startTime: DS.attr('number'),
- endTime : DS.attr('number'),
+ endTime: DS.attr('number'),
/**
* Provides the duration of this job. If the job has not started, duration
@@ -90,7 +97,7 @@ App.TezDagVertex = DS.Model.extend({
*
* @return {Number} Duration in milliseconds.
*/
- duration : function() {
+ duration: function () {
return App.Helpers.date.duration(this.get('startTime'), this.get('endTime'))
}.property('startTime', 'endTime'),
@@ -101,89 +108,89 @@ App.TezDagVertex = DS.Model.extend({
*
* Array of strings. [{string}]
*/
- operations : DS.attr('array'),
+ operations: DS.attr('array'),
/**
* Provides additional information about the 'operations' performed in this
* vertex. This is shown directly to the user.
*/
- operationPlan : DS.attr('string'),
+ operationPlan: DS.attr('string'),
/**
* Number of actual Map/Reduce tasks in this vertex
*/
- tasksCount : DS.attr('number'),
+ tasksCount: DS.attr('number'),
tasksNumber: function () {
- return this.get('tasksCount') ? this.get('tasksCount') : 0;
+ return this.getWithDefault('tasksCount', 0);
}.property('tasksCount'),
/**
* Local filesystem usage metrics for this vertex
*/
- fileReadBytes : DS.attr('number'),
+ fileReadBytes: DS.attr('number'),
- fileWriteBytes : DS.attr('number'),
+ fileWriteBytes: DS.attr('number'),
- fileReadOps : DS.attr('number'),
+ fileReadOps: DS.attr('number'),
- fileWriteOps : DS.attr('number'),
+ fileWriteOps: DS.attr('number'),
/**
* Spilled records
*/
- spilledRecords : DS.attr('number'),
+ spilledRecords: DS.attr('number'),
/**
* HDFS usage metrics for this vertex
*/
- hdfsReadBytes : DS.attr('number'),
+ hdfsReadBytes: DS.attr('number'),
- hdfsWriteBytes : DS.attr('number'),
+ hdfsWriteBytes: DS.attr('number'),
- hdfsReadOps : DS.attr('number'),
+ hdfsReadOps: DS.attr('number'),
- hdfsWriteOps : DS.attr('number'),
+ hdfsWriteOps: DS.attr('number'),
/**
* Record metrics for this vertex
*/
- recordReadCount : DS.attr('number'),
+ recordReadCount: DS.attr('number'),
- recordWriteCount : DS.attr('number'),
+ recordWriteCount: DS.attr('number'),
- totalReadBytes : function() {
+ totalReadBytes: function () {
return this.get('fileReadBytes') + this.get('hdfsReadBytes');
}.property('fileReadBytes', 'hdfsReadBytes'),
- totalWriteBytes : function() {
+ totalWriteBytes: function () {
return this.get('fileWriteBytes') + this.get('hdfsWriteBytes');
}.property('fileWriteBytes', 'hdfsWriteBytes'),
- totalReadBytesDisplay : function() {
+ totalReadBytesDisplay: function () {
return App.Helpers.number.bytesToSize(this.get('totalReadBytes'));
}.property('totalReadBytes'),
- totalWriteBytesDisplay : function() {
+ totalWriteBytesDisplay: function () {
return App.Helpers.number.bytesToSize(this.get('totalWriteBytes'));
}.property('totalWriteBytes'),
- durationDisplay : function() {
+ durationDisplay: function () {
return App.Helpers.date.timingFormat(this.get('duration'), true);
}.property('duration')
});
App.TezDagVertexState = {
- NEW : "NEW",
- INITIALIZING : "INITIALIZING",
- INITED : "INITED",
- RUNNING : "RUNNING",
- SUCCEEDED : "SUCCEEDED",
- FAILED : "FAILED",
- KILLED : "KILLED",
- ERROR : "ERROR",
- TERMINATING : "TERMINATING",
+ NEW: "NEW",
+ INITIALIZING: "INITIALIZING",
+ INITED: "INITED",
+ RUNNING: "RUNNING",
+ SUCCEEDED: "SUCCEEDED",
+ FAILED: "FAILED",
+ KILLED: "KILLED",
+ ERROR: "ERROR",
+ TERMINATING: "TERMINATING",
JOBFAILED: "JOB FAILED"
};
@@ -194,8 +201,8 @@ App.TezDagVertexType = {
};
App.TezDagEdgeType = {
- SCATTER_GATHER : "SCATTER_GATHER",
- BROADCAST : "BROADCAST",
+ SCATTER_GATHER: "SCATTER_GATHER",
+ BROADCAST: "BROADCAST",
CONTAINS: "CONTAINS"
};
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/router.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/router.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/router.js
index 83df6bf..8fea4db 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/router.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/router.js
@@ -18,5 +18,5 @@
App.Router.map(function () {
this.resource("jobs", { path: "/" });
- this.resource('job', { path: 'jobs/:job_id' });
+ this.resource('job', { path: 'jobs/:hive_job_id' });
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
index fa0e128..7532197 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
@@ -16,13 +16,13 @@
* limitations under the License.
*/
-App.ApplicationRoute = Ember.Route.extend({
+App.IndexRoute = Ember.Route.extend({
model: function () {
return this.modelFor('hiveJobs');
},
- redirect: function () {
+ beforeModel: function () {
this.transitionTo('jobs');
}
@@ -31,15 +31,20 @@ App.ApplicationRoute = Ember.Route.extend({
App.JobsRoute = Ember.Route.extend({
model: function () {
- return this.store.all('hiveJob');
+ return this.get('store').find('hiveJob');
}
});
App.JobRoute = Ember.Route.extend({
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.set('loaded', false);
+ },
+
model: function (params) {
- return this.store.all('hiveJob', params.id);
+ return this.get('store').getById('hiveJob', params.hive_job_id);
}
});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
index 8389a08..95c7fcb 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
@@ -23,6 +23,20 @@ Ember.I18n.translations = {
'ok': 'Ok',
'cancel': 'Cancel',
+
+ 'common.status':'Status',
+ 'common.time.start': 'Start Time',
+ 'common.time.end': 'End Time',
+ 'common.name': 'Name',
+ 'common.tasks':'Tasks',
+ 'common.na': 'n/a',
+ 'common.value': 'Value',
+
+ 'number.validate.empty': 'cannot be empty',
+ 'number.validate.notValidNumber': 'not a valid number',
+ 'number.validate.lessThanMinumum': 'value less than %@1',
+ 'number.validate.moreThanMaximum': 'value greater than %@1',
+
'jobs.type':'Jobs Type',
'jobs.type.hive':'Hive',
'jobs.show.up.to':'Show up to',
@@ -58,9 +72,9 @@ Ember.I18n.translations = {
'jobs.hive.tez.localFiles':'Local Files',
'jobs.hive.tez.spilledRecords':'Spilled Records',
'jobs.hive.tez.records':'Records',
- 'jobs.hive.tez.reads':'{0} reads',
- 'jobs.hive.tez.writes':'{0} writes',
- 'jobs.hive.tez.records.count':'{0} Records',
+ 'jobs.hive.tez.reads':'%@1 reads',
+ 'jobs.hive.tez.writes':'%@1 writes',
+ 'jobs.hive.tez.records.count':'%@1 Records',
'jobs.hive.tez.operatorPlan':'Operator Plan',
'jobs.hive.tez.dag.summary.metric':'Summary Metric',
'jobs.hive.tez.dag.error.noDag.title':'No Tez Information',
@@ -78,6 +92,21 @@ Ember.I18n.translations = {
'jobs.hive.tez.edge.':'Unknown',
'jobs.hive.tez.edge.contains':'Contains',
'jobs.hive.tez.edge.broadcast':'Broadcast',
- 'jobs.hive.tez.edge.scatter_gather':'Shuffle'
+ 'jobs.hive.tez.edge.scatter_gather':'Shuffle',
+
+ 'app.loadingPlaceholder': 'Loading...',
+ 'apps.item.dag.job': 'Job',
+ 'apps.item.dag.jobId': 'Job Id',
+ 'apps.item.dag.type': 'Job Type',
+ 'apps.item.dag.status': 'Status',
+ 'apps.item.dag.num_stages': 'Total Stages',
+ 'apps.item.dag.stages': 'Tasks per Stage',
+ 'apps.item.dag.maps': 'Maps',
+ 'apps.item.dag.reduces': 'Reduces',
+ 'apps.item.dag.input': 'Input',
+ 'apps.item.dag.output': 'Output',
+ 'apps.item.dag.duration': 'Duration',
+
+ 'menu.item.jobs':'Jobs'
};
http://git-wip-us.apache.org/repos/asf/ambari/blob/85362043/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_tez_dag_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_tez_dag_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_tez_dag_view.js
new file mode 100644
index 0000000..7d73f06
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job/hive_job_details_tez_dag_view.js
@@ -0,0 +1,925 @@
+/**
+ * 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.
+ */
+
+App.MainHiveJobDetailsTezDagView = Em.View.extend({
+ templateName: 'job/hive_job_details_tez_dag',
+ selectedVertex: null,
+ summaryMetricType: null,
+ svgVerticesLayer: null, // The contents of the <svg> element.
+ svgTezRoot: null,
+ svgWidth: -1,
+ svgHeight: -1,
+
+ // zoomScaleFom: -1, // Bound from parent view
+ // zoomScaleTo: -1, // Bound from parent view
+ // zoomScale: -1, // Bound from parent view
+ zoomTranslate: [0, 0],
+ zoomBehavior: null,
+ svgCreated: false,
+
+ /**
+ * Populated by #drawTezDag()
+ *
+ * {
+ * "nodes": [
+ * {
+ * "id": "Map2",
+ * "name": "Map 2",
+ * "type": App.TezDagVertexType.MAP,
+ * "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();
+ },
+
+ willDestroyElement: function () {
+ $('.svg-tooltip').tooltip('destroy');
+ },
+
+ createSvg: function () {
+ var self = this;
+ var dagVisualModel = this.get('dagVisualModel');
+ dagVisualModel.nodes.clear();
+ dagVisualModel.links.clear();
+ dagVisualModel.maxMetrics = {};
+ dagVisualModel.minMetrics = {};
+
+ //this.set('content', this.get('content'));
+ var svg = d3.select("#tez-dag-svg");
+ d3.selectAll(".tez-dag-canvas").remove();
+ var tezRoot = svg.append("svg:g").attr("class", "tez-root");
+ this.set('svgTezRoot', tezRoot);
+ var tezRootRect = tezRoot.append("rect").attr("class", "tez-root-rect");
+ this.set('svgVerticesLayer', tezRoot.append("svg:g").attr("class", "tez-dag-canvas"));
+ this.adjustGraphHeight();
+ var canvasSize = this.drawTezDag();
+ var minScale = Math.min(this.get('svgHeight') / canvasSize.height, this.get('svgWidth') / canvasSize.width);
+ if (minScale > 1) {
+ minScale = 1;
+ }
+ tezRootRect.attr("width", canvasSize.width).attr("height", canvasSize.height);
+ var zoom = d3.behavior.zoom().scaleExtent([ minScale, 2 ]).on("zoom", function () {
+ tezRoot.attr("transform", "translate(" + (d3.event.translate) + ")scale(" + d3.event.scale + ")");
+ self.set('zoomScale', d3.event.scale);
+ self.set('zoomTranslate', d3.event.translate);
+ });
+ svg.call(zoom);
+ this.set('zoomBehavior', zoom);
+ this.set('zoomTranslate', [0, 0]);
+ this.set('zoomScaleFrom', minScale);
+ this.set('zoomScaleTo', 2);
+ this.set('zoomScale', minScale);
+ this.set('svgCreated', true);
+ },
+
+ zoomScaleObserver: function () {
+ var tezRoot = this.get("svgTezRoot");
+ var newScale = this.get('zoomScale');
+ var newScaleFrom = this.get('zoomScaleFrom');
+ var newScaleTo = this.get('zoomScaleTo');
+ var zoomTranslate = this.get('zoomTranslate');
+ var zoomBehavior = this.get('zoomBehavior');
+ if (d3.event == null && this.get('svgCreated')) {
+ // Values were set from actions instead of UI events
+ // We need to center in on selected vertex if available.
+ var selectedNode = null;
+ var dagVisualModel = this.get('dagVisualModel');
+ if (dagVisualModel && dagVisualModel.nodes && dagVisualModel.nodes.length > 0) {
+ dagVisualModel.nodes.every(function (node) {
+ if (node.selected) {
+ selectedNode = node;
+ return false;
+ }
+ return true;
+ })
+ }
+ if (selectedNode != null) {
+ var cX = selectedNode.x + selectedNode.width / 2;
+ var cY = selectedNode.y + selectedNode.height / 2;
+ var mX = (cX * zoomBehavior.scale()) + zoomTranslate[0];
+ var mY = (cY * zoomBehavior.scale()) + zoomTranslate[1];
+ var pX = (cX * newScale) + zoomTranslate[0];
+ var pY = (cY * newScale) + zoomTranslate[1];
+ var nX = (mX - pX);
+ var nY = (mY - pY);
+ zoomTranslate[0] += nX;
+ zoomTranslate[1] += nY;
+ this.set('zoomTranslate', zoomTranslate);
+ }
+ }
+ zoomBehavior.scale(newScale);
+ zoomBehavior.translate(zoomTranslate);
+ tezRoot.attr("transform", "translate(" + zoomTranslate + ")scale(" + newScale + ")");
+ }.observes('zoomScale', 'zoomScaleFrom', 'zoomScaleTo', 'zoomTranslate'),
+
+ /**
+ * 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', '100%');
+ this.set('svgWidth', currentWidth);
+ this.set('svgHeight', svgHeight);
+ }
+ },
+
+ vertexSelectionUpdated: function () {
+ var vertexId = this.get('selectedVertex.id');
+ var zoomTranslate = [];
+ var zoomBehavior = this.get('zoomBehavior');
+ var selectedNode = this.get('dagVisualModel').nodes.findProperty('id', vertexId);
+ var dagVisualModel = this.get('dagVisualModel');
+ if (dagVisualModel && dagVisualModel.nodes && dagVisualModel.nodes.length > 0) {
+ dagVisualModel.nodes.forEach(function (node) {
+ node.selected = node.id == vertexId;
+ })
+ }
+ if (!this.get('selectedVertex.notTableClick')) {
+ var cX = selectedNode.x + (selectedNode.width) / 2;
+ var cY = selectedNode.y + (selectedNode.height) / 2;
+ zoomTranslate[0] = (225 / zoomBehavior.scale() - cX);
+ zoomTranslate[1] = (250 / zoomBehavior.scale() - cY);
+ this.set('zoomTranslate', [0, 0]);
+ this.get('svgVerticesLayer').attr("transform", "translate(0,0)");
+ this.get('svgVerticesLayer').attr("transform", "translate(" + zoomTranslate[0] + "," + zoomTranslate[1] + ")");
+ }
+ 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 (App.Helpers.number.validateInteger(value) == null && value >= 0) {
+ if (App.Helpers.number.validateInteger(min) == null && App.Helpers.number.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 = App.Helpers.number.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: Number.MAX_VALUE,
+ output: Number.MAX_VALUE,
+ recordsRead: Number.MAX_VALUE,
+ recordsWrite: Number.MAX_VALUE,
+ tezTasks: Number.MAX_VALUE,
+ spilledRecords: Number.MAX_VALUE
+ };
+ dagVisualModel.maxMetrics = {
+ input: 0,
+ output: 0,
+ recordsRead: 0,
+ recordsWrite: 0,
+ tezTasks: 0,
+ spilledRecords: 0
+ };
+ if (dagVisualModel.nodes) {
+ dagVisualModel.nodes.forEach(function (node) {
+ var vertex = App.HiveJob.store.getById('tezDagVertex', 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');
+ node.metrics['spilledRecords'] = vertex.get('spilledRecords');
+ node.state = vertex.get('state');
+ // 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);
+ dagVisualModel.minMetrics.spilledRecords = Math.min(dagVisualModel.minMetrics.spilledRecords, node.metrics.spilledRecords);
+ // 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);
+ dagVisualModel.maxMetrics.spilledRecords = Math.max(dagVisualModel.maxMetrics.spilledRecords, node.metrics.spilledRecords);
+ }
+ });
+ }
+ 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',
+ 'content.tezDag.vertices.@each.state', 'content.tezDag.vertices.@each.spilledRecords'),
+
+ createOperationPlanObj: function (vertexName, op, opIndex) {
+ var operatorPlanObj = [],
+ text = this.get('content.tezDag.vertices').findBy('name', vertexName).get('operationPlan');
+ text = text.replace(/:"/g, '"').replace(/([:,])(?=\S)/g, '$1 ');
+ var jsonText = $.parseJSON(text);
+ opIndex = opIndex ? parseInt(opIndex) - 1 : 0;
+ jsonText = App.Helpers.string.findIn(op, jsonText, opIndex);
+ if (jsonText != null) {
+ for (var key in jsonText) {
+ if (jsonText.hasOwnProperty(key) && typeof(jsonText[key]) == "string") {
+ operatorPlanObj.push({
+ name: key,
+ value: jsonText[key]
+ });
+ }
+ }
+ }
+ return operatorPlanObj;
+ },
+
+ /**
+ * 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 self = this,
+ width = this.get('svgWidth'),
+ svgLayer = this.get('svgVerticesLayer'),
+ vertices = this.get('content.tezDag.vertices'),
+ edges = this.get('content.tezDag.edges'),
+ constants = this.get('constants'),
+ vertexIdToNode = {},
+ depthToNodes = [], // Array of id arrays
+ dagVisualModel = this.get('dagVisualModel'),
+ selectedVertex = this.get('selectedVertex'),
+ minVertexDuration = Number.MAX_VALUE,
+ maxVertexDuration = Number.MIN_VALUE;
+
+ //
+ // CALCULATE DEPTH - BFS to get correct graph depth
+ //
+ var visitEdges = [];
+ var maxRowLength = 0;
+ var maxRowDepth = 0;
+ vertices.forEach(function (v) {
+ if (v.get('incomingEdges.length') < 1) {
+ visitEdges.push({
+ depth: 0,
+ parent: null,
+ toVertex: v
+ });
+ }
+ });
+ function getNodeFromEdge(edgeObj) {
+ var vertex = edgeObj.toVertex;
+ var pName = edgeObj.parent ? edgeObj.parent.name : null;
+ var cName = edgeObj.toVertex ? edgeObj.toVertex.get('name') : null;
+ if (edgeObj.parent && edgeObj.depth < edgeObj.parent.depth + 1) {
+ edgeObj.depth = edgeObj.parent.depth + 1;
+ }
+ var node = vertexIdToNode[vertex.get('id')];
+ for (var k = depthToNodes.length; k <= edgeObj.depth; k++) {
+ depthToNodes.push([]);
+ }
+ if (!node) {
+ // New node
+ node = {
+ id: vertex.get('id'),
+ name: vertex.get('name'),
+ state: vertex.get('state'),
+ type: vertex.get('type'),
+ operations: vertex.get('operations'),
+ depth: edgeObj.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
+ },
+ duration: vertex.get('duration')
+ };
+ if (node.duration < minVertexDuration && node.duration > 0) {
+ minVertexDuration = node.duration;
+ }
+ if (node.duration > maxVertexDuration && node.duration > 0) {
+ maxVertexDuration = node.duration;
+ }
+ vertexIdToNode[vertex.get('id')] = node;
+ depthToNodes[node.depth].push(node);
+ } else {
+ // Existing node
+ if (edgeObj.depth > node.depth) {
+ function moveNodeToDepth(node, newDepth) {
+ var oldIndex = depthToNodes[node.depth].indexOf(node);
+ depthToNodes[node.depth].splice(oldIndex, 1);
+ node.depth = newDepth;
+ if (!depthToNodes[node.depth]) {
+ depthToNodes[node.depth] = [];
+ }
+ depthToNodes[node.depth].push(node);
+ if (node.children) {
+ // Move children down depth
+ node.children.forEach(function (c) {
+ moveNodeToDepth(c, node.depth + 1);
+ })
+ }
+ }
+
+ moveNodeToDepth(node, edgeObj.depth);
+ }
+ }
+ if (depthToNodes[node.depth].length > maxRowLength) {
+ maxRowLength = depthToNodes[node.depth].length;
+ maxRowDepth = node.depth;
+ }
+ if (edgeObj.parent != null) {
+ node.parents.push(edgeObj.parent);
+ edgeObj.parent.children.push(node);
+ }
+ return node;
+ }
+
+ var edgeObj;
+ var visitedVertexMap = {};
+ while (edgeObj = visitEdges.shift()) {
+ var node = getNodeFromEdge(edgeObj);
+ if (!visitedVertexMap[edgeObj.toVertex.get('id')]) {
+ visitedVertexMap[edgeObj.toVertex.get('id')] = true;
+ var outEdges = edgeObj.toVertex.get('outgoingEdges');
+ outEdges.forEach(function (oe) {
+ var childVertex = oe.get('toVertex');
+ visitEdges.push({
+ depth: node.depth + 1,
+ parent: node,
+ toVertex: childVertex
+ });
+ });
+ }
+ }
+ edges.forEach(function (e) {
+ dagVisualModel.links.push({
+ source: vertexIdToNode[e.get('fromVertex.id')],
+ target: vertexIdToNode[e.get('toVertex.id')],
+ edgeType: e.get('edgeType')
+ });
+ });
+ // Sort nodes so that parents stay together
+ for (var depth = 0; depth < depthToNodes.length; depth++) {
+ var nodes = depthToNodes[depth];
+ nodes.sort(function (n1, n2) {
+ var ck1 = '';
+ var ck2 = '';
+ if (n1.children) {
+ n1.children.forEach(function (c) {
+ ck1 += c.name;
+ });
+ }
+ if (n2.children) {
+ n2.children.forEach(function (c) {
+ ck2 += c.name;
+ });
+ }
+ if (ck1 < ck2) {
+ return -1
+ }
+ if (ck1 > ck2) {
+ return 1
+ }
+ return 0
+ });
+ depthToNodes[depth] = nodes;
+ }
+
+ //
+ // LAYOUT - Now with correct depth, we calculate layouts
+ //
+ // When a node's effective width changes, all its parent nodes are updated.
+ var updateNodeEffectiveWidth = function (node, newEffectiveWidth) {
+ if (App.Helpers.number.validateInteger(node.effectiveWidth) != null) {
+ node.effectiveWidth = newEffectiveWidth;
+ }
+ var diff = newEffectiveWidth - node.effectiveWidth;
+ if (diff > 0) {
+ var oldEffectiveWidth = node.effectiveWidth;
+ node.effectiveWidth = newEffectiveWidth;
+ if (node.parents != null) {
+ node.parents.forEach(function (parent) {
+ updateNodeEffectiveWidth(parent, parent.effectiveWidth + diff);
+ })
+ }
+ }
+ };
+ var xGap = 20;
+ var yGap = 70;
+ var currentY = 40;
+ // First pass - calculate layout widths, and Y coordinates
+ for (var depth = 0; depth < depthToNodes.length; depth++) {
+ var nodes = depthToNodes[depth];
+ var maxNodeHeight = 0;
+ for (var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
+ var node = nodes[nodeIndex];
+ var nodeDim = this.getNodeCalculatedDimensions(node, minVertexDuration, maxVertexDuration);
+ node.drawWidth = nodeDim.drawWidth;
+ node.drawHeight = nodeDim.drawHeight;
+ node.scale = nodeDim.scale;
+ node.width = nodeDim.width;
+ node.height = nodeDim.height;
+ if (maxNodeHeight < node.height) {
+ maxNodeHeight = node.height;
+ }
+ if (depth == 0) {
+ // Top nodes - position uniformly
+ updateNodeEffectiveWidth(node, xGap + node.width);
+ }
+ if (node.children && node.children.length > 0) {
+ // There can be dedicated or shared children.
+ // Dedicated children increase effective width of parent by their
+ // width.
+ // Shared children increase effective width of parent only by the
+ // fraction of parentage
+ var childrenWidth = 0;
+ node.children.forEach(function (child) {
+ var childDim = self.getNodeCalculatedDimensions(child, minVertexDuration, maxVertexDuration);
+ childrenWidth += ((childDim.width + xGap) / child.parents.length);
+ });
+ updateNodeEffectiveWidth(node, Math.max(childrenWidth, (node.width + xGap)));
+ } else {
+ updateNodeEffectiveWidth(node, xGap + node.width);
+ }
+ node.y = currentY;
+ node.incomingY = node.y;
+ node.outgoingY = node.incomingY + node.height;
+ }
+ currentY += maxNodeHeight;
+ currentY += yGap;
+ }
+ // Second pass - determine actual X coordinates
+ var maxX = 0;
+ for (var depth = 0; depth < depthToNodes.length; depth++) {
+ var nodes = depthToNodes[depth];
+ var currentX = -1;
+ var parentCurrentXMap = {};
+ for (var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
+ var node = nodes[nodeIndex];
+ var parentsKey = null;
+ if (node.parents != null && node.parents.length > 0) {
+ var parentMidX = 0;
+ var parentsKey = '';
+ var childrenEffectiveWidth = -1;
+ node.parents.forEach(function (parent) {
+ parentMidX += (parent.x + (parent.width / 2));
+ parentsKey += (parent.id + '//');
+ if (childrenEffectiveWidth < 0) {
+ parent.children.forEach(function (c) {
+ childrenEffectiveWidth += (c.effectiveWidth);
+ });
+ }
+ });
+ parentMidX = parentMidX / node.parents.length;
+ var parentCurrentX = parentCurrentXMap[parentsKey];
+ if (parentCurrentX == null || parentCurrentX == undefined) {
+ parentCurrentX = parentMidX - (childrenEffectiveWidth / 2);
+ parentCurrentXMap[parentsKey] = parentCurrentX;
+ }
+ currentX = parentCurrentX;
+ } else {
+ if (currentX < 0) {
+ currentX = 0;
+ }
+ }
+ node.x = (currentX + (node.effectiveWidth - node.width) / 2);
+ node.outgoingX = (node.x + node.width / 2);
+ node.incomingX = node.outgoingX;
+ dagVisualModel.nodes.push(node);
+ if (parentsKey != null) {
+ parentCurrentXMap[parentsKey] = currentX + node.effectiveWidth;
+ } else {
+ currentX += node.effectiveWidth;
+ }
+ if ((node.x + node.width) > maxX) {
+ maxX = node.x + node.width;
+ }
+ }
+ }
+ var canvasHeight = currentY;
+ var canvasWidth = maxX + (xGap << 1);
+
+ //
+ // Draw SVG
+ //
+ var force = d3.layout.force().nodes(dagVisualModel.nodes).links(dagVisualModel.links).start();
+ var nodeDragData = {
+ nodeRelativeX: 0,
+ nodeRelativeY: 0
+ };
+ var nodeDrag = d3.behavior.drag().on('dragstart',function (node) {
+ d3.event.sourceEvent.stopPropagation();
+ var rc = d3.mouse(this);
+ nodeDragData.nodeRelativeX = (rc[0] * node.scale);
+ nodeDragData.nodeRelativeY = (rc[1] * node.scale);
+ }).on('drag',function (node) {
+ var nx = d3.event.x - nodeDragData.nodeRelativeX;
+ var ny = d3.event.y - nodeDragData.nodeRelativeY;
+ self.dragVertex(d3.select(this), node, [nx, ny], diagonal);
+ }).on('dragend', function () {
+ nodeDragData.nodeRelativeX = 0;
+ nodeDragData.nodeRelativeY = 0;
+ });
+ // 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 - 12
+ }
+ });
+ var link = svgLayer.selectAll(".link-g").data(dagVisualModel.links).enter().append("g").attr("class", "link-g").attr("marker-end", "url(#arrow)");
+ link.append("path").attr("class",function (l) {
+ var classes = "link svg-tooltip ";
+ if (l.edgeType) {
+ classes += ("type-" + l.edgeType.toLowerCase() + " ");
+ } else {
+ classes += "type-unknown ";
+ }
+ return classes;
+ }).attr("d", diagonal).attr("title", function (l) {
+ var lower = l.edgeType ? l.edgeType.toLowerCase() : '';
+ return Em.I18n.t("jobs.hive.tez.edge." + lower);
+ });
+ // Create Nodes
+ var node = svgLayer.selectAll(".node").data(dagVisualModel.nodes).enter().append("g").attr("class", "node").call(nodeDrag);
+ node.append("rect").attr("class", "background").attr("width",function (n) {
+ return n.drawWidth;
+ }).attr("height",function (n) {
+ return n.drawHeight;
+ }).attr("rx", "10").attr("filter", "url(#shadow)").on('mousedown', function (n) {
+ //var vertex = App.TezDagVertex.find(n.id);
+ var vertex = App.HiveJob.store.getById('tezDagVertex', n.id);
+ if (vertex != null) {
+ self.get('parentView').doSelectVertex(vertex, true);
+ }
+ });
+ node.each(function (n, nodeIndex) {
+ var ops = n.operations;
+ var opCount = {};
+ if (ops != null && ops.length > 0) {
+ 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 * 55) + "," + (37 + row * 20) + ")";
+ }).attr("clip-path", "url(#operatorClipPath)").attr("opIndex",function (op) {
+ if (!opCount[op]) {
+ opCount[op] = 1;
+ }
+ else {
+ opCount[op] = opCount[op] + 1;
+ }
+ return opCount[op];
+ }).on('mouseover', function (op) {
+ var viewContent = {
+ operationName: op,
+ operatorPlanObj: []
+ };
+ /*viewContent.operatorPlanObj = self.createOperationPlanObj(n.name, op, this.getAttribute('opIndex'));
+ var template = App.HoverOpTable.create({content: viewContent});
+ $(this).find('.svg-tooltip').attr('title', template.renderToBuffer().string()).tooltip('fixTitle').tooltip('show');*/
+ });
+
+ opGroups.append("rect").attr("class", "operation svg-tooltip ").attr("width", "50").attr("height", "16");
+ 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(112,7)");
+ metricNodes.append("rect").attr("width",function (n) {
+ if (n.type == App.TezDagVertexType.UNION) {
+ return 0;
+ }
+ return 60;
+ }).attr("height",function (n) {
+ if (n.type == App.TezDagVertexType.UNION) {
+ return 0;
+ }
+ return 18;
+ }).attr("rx", "3").attr("class", "metric-title svg-tooltip");
+ metricNodes.append("text").attr("class", "metric-text").attr("x", "2").attr("dy", "1em");
+ node.append("text").attr("x", "1.9em").attr("dy", "1.5em").text(function (d) {
+ return d.name;
+ });
+ var iconContainer = node.append('g').attr('class', 'vertex-icon-container').attr('transform', 'translate(10,10)');
+ iconContainer.append('rect').attr('width', '1em').attr('height', '1em').attr('class', 'vertex-icon-rect svg-tooltip ');
+ iconContainer.append('text').attr('dy', '10px').attr("font-family", "FontAwesome").attr('class', 'vertex-icon-text');
+ node.attr("transform", function (d) {
+ return "translate(" + d.x + "," + d.y + ") scale(" + d.scale + ") ";
+ });
+ this.vertexMetricsUpdated();
+ $('.svg-tooltip').each(function () {
+ var item = $(this);
+ if (item.prop('tagName') == 'path') {
+ item.hover(function (e) {
+ var offset = $(this).offset();
+ item.prop('offsetWidth', function () {
+ return 2 * (e.pageX - offset.left);
+ });
+ item.prop('offsetHeight', function () {
+ return 2 * (e.pageY - offset.top);
+ });
+ });
+ }
+ if (item.prop('offsetWidth') == undefined) {
+ item.prop('offsetWidth', function () {
+ return item.width();
+ });
+ }
+ if (item.prop('offsetHeight') == undefined) {
+ item.prop('offsetHeight', function () {
+ return item.height();
+ });
+ }
+ });
+ App.tooltip($('.svg-tooltip'), {
+ placement: 'left',
+ template: '<div class="tooltip jobs-tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ });
+
+ /*if (App.supports.debugJobsDag) {
+ // Draws node bounding box - for debug purposes
+ node.append("rect").attr("width",function (n) {
+ return n.effectiveWidth;
+ }).attr("height",function (n) {
+ return n.height;
+ }).attr("x",function (n) {
+ return -1 * ((n.effectiveWidth - n.width) / 2);
+ }).attr("y",function (n) {
+ return 0;
+ }).attr("style", "opacity: 0.2;fill:yellow;");
+ }*/
+
+ // Position in center
+ var translateX = Math.round((width - canvasWidth) / 2);
+ if (translateX > 0) {
+ svgLayer.attr("transform", "translate(" + translateX + ",0)");
+ }
+ return {
+ width: canvasWidth,
+ height: canvasHeight
+ }
+ },
+
+ dragVertex: function (d3Vertex, node, newPosition, diagonal) {
+ // Move vertex
+ node.x = newPosition[0];
+ node.y = newPosition[1];
+ node.incomingX = newPosition[0] + (node.width / 2);
+ node.incomingY = newPosition[1];
+ node.outgoingX = newPosition[0] + (node.width / 2);
+ node.outgoingY = newPosition[1] + node.height;
+ d3Vertex.attr('transform', 'translate(' + newPosition[0] + ',' + newPosition[1] + ') scale(' + node.scale + ') ');
+ // Move links
+ d3.selectAll('.link').filter(function (l) {
+ if (l && (l.source === node || l.target === node)) {
+ return this
+ }
+ return null;
+ }).attr('d', diagonal);
+ },
+
+ /**
+ * Refreshes UI of the Tez graph with latest values
+ */
+ refreshGraphUI: function () {
+ var svgLayer = this.get('svgVerticesLayer');
+ if (svgLayer != null) {
+ var self = this;
+ var metricNodes = svgLayer.selectAll(".metric");
+ var metricNodeTexts = svgLayer.selectAll(".metric-text");
+ var metricNodeTitles = svgLayer.selectAll(".metric-title");
+ var nodeBackgrounds = svgLayer.selectAll(".background");
+ var vertexIconTexts = svgLayer.selectAll(".vertex-icon-text");
+ var vertexIconRects = svgLayer.selectAll(".vertex-icon-rect");
+ metricNodes.attr("class", function (node) {
+ var classes = "metric ";
+ var percent = node.metricPercent;
+ if (App.Helpers.number.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) {
+ if (node.type == App.TezDagVertexType.UNION) {
+ return '';
+ }
+ return node.metricDisplay;
+ });
+ metricNodeTitles.attr("title",function (node) {
+ return node.metricType;
+ }).attr("data-original-title", function (node) {
+ return node.metricType;
+ });
+ nodeBackgrounds.attr("class", function (n) {
+ var classes = "background ";
+ if (n.type) {
+ classes += (n.type.toLowerCase() + " ");
+ } else {
+ classes += "unknown-vertex-type ";
+ }
+ if (n.selected) {
+ classes += "selected ";
+ }
+ return classes;
+ });
+ vertexIconRects.attr('title',function (node) {
+ return App.Helpers.string.getCamelCase(node.state);
+ }).attr('data-original-title', function (node) {
+ return App.Helpers.string.getCamelCase(node.state);
+ });
+ vertexIconTexts.text(function (n) {
+ return self.getVertexIcon(n)
+ }).attr('class', function (n) {
+ var classes = 'vertex-icon-text ';
+ if (n.state != null) {
+ if (n.state == App.TezDagVertexState.JOBFAILED) {
+ classes += App.TezDagVertexState.FAILED.toLowerCase();
+ }
+ else {
+ classes += n.state.toLowerCase();
+ }
+ }
+ return classes;
+ });
+ }
+ },
+
+ getVertexIcon: function (node) {
+ var icon = "";
+ switch (node.state) {
+ case App.TezDagVertexState.NEW:
+ icon = '\uF10C'; //icon-circle-blank
+ case App.TezDagVertexState.RUNNING:
+ case App.TezDagVertexState.FAILED:
+ icon = '\uF111'; //icon-circle
+ break;
+ case App.TezDagVertexState.SUCCEEDED:
+ icon = '\uF00C'; //icon-ok
+ break;
+ case App.TezDagVertexState.KILLED:
+ case App.TezDagVertexState.ERROR:
+ icon = '\uF057'; //icon-remove-sign
+ break;
+ case App.TezDagVertexState.INITED:
+ case App.TezDagVertexState.INITIALIZING:
+ case App.TezDagVertexState.TERMINATING:
+ icon = '\uF141'; //icon-ellipsis-horizontal
+ break;
+ case App.TezDagVertexState.JOBFAILED:
+ icon = '\uF05C'; //icon-remove-circle
+ break;
+ }
+ return icon;
+ },
+
+ /**
+ * Determines the size of a node by taking into account its duration and
+ * number of operations performed.
+ *
+ * @return {Object} Provides various metrics necessary in drawing a node.
+ * <code>
+ * {
+ * width: 360, // Scaled width of the node
+ * height: 80, // Scaled height of the node
+ * scale: 2, // Scale used on vertex dimensions. Quickest vertex is scaled to 1 and slowest vertex is scaled to 10.
+ * drawWidth: 180, // Width of actual drawing (that will be scaled)
+ * drawHeight: 40 // Height of actual drawing (that will be scaled)
+ * }
+ * </code>
+ */
+ getNodeCalculatedDimensions: function (node, minVertexDuration, maxVertexDuration) {
+ var size = {
+ width: 180,
+ height: 40,
+ drawWidth: 180,
+ drawHeight: 40,
+ scale: 1
+ };
+ if (node.operations && node.operations.length > 0) {
+ var opsHeight = Math.ceil(node.operations.length / 3);
+ size.drawHeight += (opsHeight * 20);
+ }
+ size.width = size.drawWidth * size.scale;
+ size.height = size.drawHeight * size.scale;
+ return size;
+ }
+
+});
+
+App.HoverOpTable = Em.View.extend({
+ templateName: 'job/hover_op_table'
+});