You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tez.apache.org by je...@apache.org on 2014/12/10 04:33:43 UTC
[20/53] tez git commit: TEZ-1708. Make UI part of TEZ build process.
(Sreenath Somarajapuram via hitesh)
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/task_attempt_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/task_attempt_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/task_attempt_controller.js
new file mode 100644
index 0000000..8fa290e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/task_attempt_controller.js
@@ -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.
+ */
+
+App.TaskAttemptController = Em.ObjectController.extend(App.Helpers.DisplayHelper, {
+ controllerName: 'TaskAttemptController',
+
+ pageTitle: 'Task Attempt',
+
+ loading: true,
+
+ updateLoading: function() {
+ this.set('loading', false);
+ }.observes('content'),
+
+ pageSubTitle: function() {
+ return this.get('id');
+ }.property('id'),
+
+ childDisplayViews: [
+ Ember.Object.create({title: 'Details', linkTo: 'taskAttempt.index'}),
+ Ember.Object.create({title: 'Counters', linkTo: 'taskAttempt.counters'}),
+ ],
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/task_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/task_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/task_controller.js
new file mode 100644
index 0000000..f4e8358
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/task_controller.js
@@ -0,0 +1,40 @@
+/**
+ * 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.TaskController = Em.ObjectController.extend(App.Helpers.DisplayHelper, {
+ controllerName: 'TaskController',
+
+ pageTitle: 'Task',
+
+ loading: true,
+
+ updateLoading: function() {
+ this.set('loading', false);
+ }.observes('content'),
+
+ pageSubTitle: function() {
+ return this.get('id');
+ }.property('id'),
+
+ childDisplayViews: [
+ Ember.Object.create({title: 'Details', linkTo: 'task.index'}),
+ Ember.Object.create({title: 'Task Attempts', linkTo: 'task.attempts'}),
+ Ember.Object.create({title: 'Counters', linkTo: 'task.counters'}),
+ ],
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/task_index_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/task_index_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/task_index_controller.js
new file mode 100644
index 0000000..fa36314
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/task_index_controller.js
@@ -0,0 +1,27 @@
+/**
+ * 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.
+ */
+
+ //TODO: watch individual counters.
+App.TaskIndexController = Em.ObjectController.extend({
+ controllerName: 'TaskIndexController',
+
+ taskIconStatus: function() {
+ return App.Helpers.misc.getStatusClassForEntity(this.get('model'));
+ }.property('id', 'status', 'counterGroups'),
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
new file mode 100644
index 0000000..f795319
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
@@ -0,0 +1,117 @@
+/**
+ * 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.TaskAttemptsController = Em.ObjectController.extend(App.PaginatedContentMixin, {
+ // required by the PaginatedContentMixin
+ childEntityType: 'task_attempt',
+
+ needs: 'task',
+
+ queryParams: {
+ status_filter: 'status'
+ },
+ status_filter: null,
+
+ loadData: function() {
+ var filters = {
+ primary: {
+ TEZ_TASK_ID: this.get('controllers.task.id')
+ },
+ secondary: {
+ status: this.status_filter
+ }
+ }
+ this.setFiltersAndLoadEntities(filters);
+ },
+
+ actions : {
+ filterUpdated: function(filterID, value) {
+ // any validations required goes here.
+ if (!!value) {
+ this.set(filterID, value);
+ } else {
+ this.set(filterID, null);
+ }
+ this.loadData();
+ }
+ },
+
+ columns: function() {
+ var idCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Task ID',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ "{{#link-to 'taskAttempt' view.cellContent class='ember-table-content'}}{{view.cellContent}}{{/link-to}}")
+ }),
+ contentPath: 'id',
+ });
+
+ var startTimeCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Start Time',
+ getCellContent: function(row) {
+ return App.Helpers.date.dateFormat(row.get('startTime'));
+ }
+ });
+
+ var endTimeCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'End Time',
+ getCellContent: function(row) {
+ return App.Helpers.date.dateFormat(row.get('endTime'));
+ }
+ });
+
+ var statusCol = App.ExTable.ColumnDefinition.createWithMixins(App.ExTable.FilterColumnMixin,{
+ headerCellName: 'Status',
+ filterID: 'status_filter',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ '<span class="ember-table-content"> \
+ <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
+ {{view.cellContent.status}}</span>')
+ }),
+ getCellContent: function(row) {
+ return {
+ status: row.get('status'),
+ statusIcon: App.Helpers.misc.getStatusClassForEntity(row)
+ };
+ }
+ });
+
+ var nodeIdCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Node ID',
+ contentPath: 'nodeId'
+ });
+
+ var containerCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Container ID',
+ contentPath: 'containerId'
+ });
+
+ return [idCol, startTimeCol, endTimeCol, statusCol, nodeIdCol, containerCol];
+ }.property(),
+});
+
+
+App.TaskAttemptIndexController = Em.ObjectController.extend({
+ controllerName: 'TaskAttemptIndexController',
+
+ taskIconStatus: function() {
+ return App.Helpers.misc.getStatusClassForEntity(this.get('model'));
+ }.property('id', 'status', 'counterGroups'),
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/tasks_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/tasks_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/tasks_controller.js
new file mode 100644
index 0000000..3573759
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/tasks_controller.js
@@ -0,0 +1,112 @@
+/**
+ * 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.TasksController = Em.ObjectController.extend(App.PaginatedContentMixin, {
+ // Required by the PaginatedContentMixin
+ childEntityType: 'task',
+
+ controllerName: 'TasksController',
+
+ pageTitle: 'Tasks',
+
+ pageSubTitle: 'All Tasks',
+
+ queryParams: {
+ parentType: true,
+ parentID: true,
+ status_filter: 'status'
+ },
+
+ parentType: null,
+ parentID: null,
+ status_filter: null,
+
+ loadData: function() {
+ var filters = {
+ primary: {},
+ secondary: {
+ status: this.status_filter
+ }
+ }
+ filters.primary[this.parentType] = this.parentID;
+ this.setFiltersAndLoadEntities(filters);
+ },
+
+ /* table view for tasks */
+ columns: function() {
+ var store = this.get('store');
+ var columnHelper = function(columnName, valName) {
+ return Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: columnName,
+ getCellContent: function(row) {
+ return row.get(valName);
+ }
+ });
+ }
+
+ var idColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Task Id',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ "{{#link-to 'task' view.cellContent class='ember-table-content'}}{{view.cellContent}}{{/link-to}}")
+ }),
+ getCellContent: function(row) {
+ return row.get('id');
+ }
+ });
+
+ var vertexColumn = columnHelper('Vertex ID', 'vertexID');
+
+ var startTimeColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Submission Time',
+ getCellContent: function(row) {
+ return App.Helpers.date.dateFormat(row.get('startTime'));
+ }
+ });
+
+ var endTimeColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'End Time',
+ getCellContent: function(row) {
+ return App.Helpers.date.dateFormat(row.get('endTime'));
+ }
+ });
+
+ var statusColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Status',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ '<span class="ember-table-content"> \
+ <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
+ {{view.cellContent.status}}</span>')
+ }),
+ getCellContent: function(row) {
+ return {
+ status: row.get('status'),
+ statusIcon: App.Helpers.misc.getStatusClassForEntity(row)
+ };
+ }
+ });
+
+ return [idColumn, vertexColumn, startTimeColumn, endTimeColumn, statusColumn];
+ }.property(),
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/vertex_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_controller.js
new file mode 100644
index 0000000..905d8d3
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_controller.js
@@ -0,0 +1,40 @@
+/**
+ * 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.VertexController = Em.ObjectController.extend(App.Helpers.DisplayHelper, {
+ controllerName: 'VertexController',
+
+ pageTitle: 'Vertex',
+
+ loading: true,
+
+ updateLoading: function() {
+ this.set('loading', false);
+ }.observes('content'),
+
+ pageSubTitle: function() {
+ return this.get('name');
+ }.property('name'),
+
+ childDisplayViews: [
+ Ember.Object.create({title: 'Details', linkTo: 'vertex.index'}),
+ Ember.Object.create({title: 'Tasks', linkTo: 'vertex.tasks'}),
+ Ember.Object.create({title: 'Counters', linkTo: 'vertex.counters'}),
+ Ember.Object.create({title: 'Swimlane', linkTo: 'vertex.swimlane'}),
+ ],
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/vertex_index_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_index_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_index_controller.js
new file mode 100644
index 0000000..a4252d1
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_index_controller.js
@@ -0,0 +1,34 @@
+/**
+ * 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.VertexIndexController = Em.ObjectController.extend({
+ controllerName: 'VertexIndexController',
+
+ //TODO: TEZ-1705 : Create a parent class and move this function there to avoid duplication.
+ iconStatus: function() {
+ return App.Helpers.misc.getStatusClassForEntity(this.get('model'));
+ }.property('id', 'status', 'counterGroups'),
+
+ hasFailedTasks: function() {
+ return this.get('failedTasks') > 0;
+ }.property('id', 'counterGroups'),
+
+ failedTasksLink: function() {
+ return '/#tasks?status=FAILED&parentType=TEZ_VERTEX_ID&parentID=' + this.get('id');
+ }.property(),
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
new file mode 100644
index 0000000..51f3f5e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
@@ -0,0 +1,115 @@
+/**
+ * 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.VertexTasksController = Em.ObjectController.extend(App.PaginatedContentMixin, {
+ // Required by the PaginatedContentMixin
+ childEntityType: 'task',
+
+ needs: 'vertex',
+
+ queryParams: {
+ status_filter: 'status',
+ },
+ status_filter: null,
+
+ loadData: function() {
+ var filters = {
+ primary: {
+ TEZ_VERTEX_ID: this.get('controllers.vertex.id')
+ },
+ secondary: {
+ status: this.status_filter
+ }
+ }
+ this.setFiltersAndLoadEntities(filters);
+ },
+
+ actions : {
+ filterUpdated: function(filterID, value) {
+ // any validations required goes here.
+ if (!!value) {
+ this.set(filterID, value);
+ } else {
+ this.set(filterID, null);
+ }
+ this.loadData();
+ }
+ },
+
+ columns: function() {
+ var idCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Task ID',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ "{{#link-to 'task' view.cellContent class='ember-table-content'}}{{view.cellContent}}{{/link-to}}")
+ }),
+ contentPath: 'id',
+ });
+
+ var vertexCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Vertex ID',
+ contentPath: 'vertexID',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ "{{#link-to 'vertex' view.cellContent class='ember-table-content'}}{{view.cellContent}}{{/link-to}}")
+ }),
+ });
+
+ var startTimeCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Submission Time',
+ getCellContent: function(row) {
+ return App.Helpers.date.dateFormat(row.get('startTime'));
+ }
+ });
+
+ var endTimeCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Run Time',
+ getCellContent: function(row) {
+ var st = row.get('startTime');
+ var et = row.get('endTime');
+ if (st && et) {
+ return App.Helpers.date.durationSummary(st, et);
+ }
+ }
+ });
+
+ var statusCol = App.ExTable.ColumnDefinition.createWithMixins(App.ExTable.FilterColumnMixin,{
+ headerCellName: 'Status',
+ filterID: 'status_filter',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile(
+ '<span class="ember-table-content"> \
+ <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
+ {{view.cellContent.status}}</span>')
+ }),
+ getCellContent: function(row) {
+ return {
+ status: row.get('status'),
+ statusIcon: App.Helpers.misc.getStatusClassForEntity(row)
+ };
+ }
+ });
+
+ var nodeCol = App.ExTable.ColumnDefinition.create({
+ headerCellName: 'Node',
+ contentPath: 'node'
+ });
+
+ return [idCol, vertexCol, startTimeCol, endTimeCol, statusCol];
+ }.property(),
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/helpers/date.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/date.js b/tez-ui/src/main/webapp/app/scripts/helpers/date.js
new file mode 100644
index 0000000..b9dd697
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/date.js
@@ -0,0 +1,218 @@
+/**
+ * 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.date = {
+
+ /**
+ * List of monthes short names
+ * @type {string[]}
+ */
+ dateMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+
+ /**
+ * List of days short names
+ * @type {string[]}
+ */
+ dateDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ /**
+ * Add leading zero
+ *
+ * @param {string} time
+ * @returns {string}
+ * @method dateFormatZeroFirst
+ */
+ dateFormatZeroFirst: function (time) {
+ if (time < 10) return '0' + time;
+ return "" + time;
+ },
+
+ /**
+ * Convert timestamp to date-string 'DAY_OF_THE_WEEK, MONTH DAY, YEAR HOURS:MINUTES'
+ *
+ * @param {number} timestamp
+ * @param {bool} showSeconds should seconds be added to result string
+ * @param {bool} showMilliseconds should miliseconds be added to result string (if <code>showSeconds</code> is false, milliseconds wouldn't be added)
+ * @return {*} date
+ * @method dateFormat
+ */
+ dateFormat: function (timestamp, showSeconds, showMilliseconds) {
+ if (!App.Helpers.number.isValidInt(timestamp)) {
+ return timestamp;
+ }
+ var format = 'ddd, MMM DD, YYYY HH:mm';
+ if (showSeconds) {
+ format += ':ss';
+ if (showMilliseconds) {
+ format += ':SSS';
+ }
+ }
+ return moment((new Date(timestamp)).toISOString().replace('Z', '')).format(format);
+ },
+
+ /**
+ * Convert timestamp to date-string 'DAY_OF_THE_WEEK MONTH DAY YEAR'
+ *
+ * @param {string} timestamp
+ * @return {string}
+ * @method dateFormatShort
+ */
+ dateFormatShort: function (timestamp) {
+ if (!App.Helpers.number.isValidInt(timestamp)) {
+ return timestamp;
+ }
+ var format = 'ddd MMM DD YYYY';
+ var date = moment((new Date(timestamp)).toISOString().replace('Z', '')).format(format);
+ var today = moment((new Date()).toISOString().replace('Z', '')).format(format);
+ if (date === today) {
+ return 'Today ' + (new Date(timestamp)).toLocaleTimeString();
+ }
+ return date;
+ },
+
+ /**
+ * Convert starTimestamp to 'DAY_OF_THE_WEEK, MONTH DAY, YEAR HOURS:MINUTES', except for the case: year equals 1969
+ *
+ * @param {string} startTimestamp
+ * @return {string} startTimeSummary
+ * @method startTime
+ */
+ startTime: function (startTimestamp) {
+ if (!App.Helpers.number.isValidInt(startTimestamp)) {
+ return '';
+ }
+ var startDate = new Date(startTimestamp);
+ var months = this.dateMonths;
+ var days = this.dateDays;
+ // generate start time
+ if (startDate.getFullYear() == 1969 || startTimestamp < 1) {
+ return 'Not started';
+ }
+ var startTimeSummary = '';
+ if (new Date(startTimestamp).setHours(0, 0, 0, 0) == new Date().setHours(0, 0, 0, 0)) { //today
+ startTimeSummary = 'Today ' + this.dateFormatZeroFirst(startDate.getHours()) + ':' + this.dateFormatZeroFirst(startDate.getMinutes());
+ } else {
+ startTimeSummary = days[startDate.getDay()] + ' ' + months[startDate.getMonth()] + ' ' +
+ this.dateFormatZeroFirst(startDate.getDate()) + ' ' + startDate.getFullYear() + ' '
+ + this.dateFormatZeroFirst(startDate.getHours()) + ':' + this.dateFormatZeroFirst(startDate.getMinutes());
+ }
+ return startTimeSummary;
+ },
+
+ /**
+ * Provides the duration between the given start and end timestamp. If start time
+ * not valid, duration will be ''. If end time is not valid, duration will
+ * be till now, showing 'Lasted for xxx secs'.
+ *
+ * @param {string} startTimestamp
+ * @param {string} endTimestamp
+ * @return {string} durationSummary
+ * @method durationSummary
+ */
+ durationSummary: function (startTimestamp, endTimestamp) {
+ // generate duration
+ var durationSummary = '';
+ var startDate = new Date(startTimestamp);
+ var endDate = new Date(endTimestamp);
+ if (startDate.getFullYear() == 1969 || startTimestamp < 1) {
+ // not started
+ return Em.I18n.t('common.na');
+ }
+ if (endDate.getFullYear() != 1969 && endTimestamp > 0) {
+ return '' + this.timingFormat(endTimestamp - startTimestamp, 1); //lasted for xx secs
+ } else {
+ // still running, duration till now
+ var t = new Date().getTime(),
+ time = (t - startTimestamp) < 0 ? 0 : (t - startTimestamp);
+ durationSummary = '' + this.timingFormat(time, 1);
+ }
+ return durationSummary;
+ },
+
+ /**
+ * Convert time in mseconds to
+ * 30 ms = 30 ms
+ * 300 ms = 300 ms
+ * 999 ms = 999 ms
+ * 1000 ms = 1.00 secs
+ * 3000 ms = 3.00 secs
+ * 35000 ms = 35.00 secs
+ * 350000 ms = 350.00 secs
+ * 999999 ms = 999.99 secs
+ * 1000000 ms = 16.66 mins
+ * 3500000 secs = 58.33 mins
+ *
+ * @param {number} time
+ * @param {bool} zeroValid for the case to show 0 when time is 0, not null
+ * @return {string|null} formatted date
+ * @method timingFormat
+ */
+ timingFormat: function (time, /* optional */ zeroValid) {
+ var intTime = parseInt(time);
+ if (zeroValid && intTime == 0) {
+ return 0 + ' secs';
+ }
+ if (!intTime) {
+ return null;
+ }
+ var timeStr = intTime.toString();
+ var lengthOfNumber = timeStr.length;
+ var oneMinMs = 60000;
+ var oneHourMs = 3600000;
+ var oneDayMs = 86400000;
+
+ if (lengthOfNumber < 4) {
+ time = Math.floor(time);
+ return time + ' ms';
+ } else if (lengthOfNumber < 7) {
+ time = (time / 1000).toFixed(2);
+ time = Math.floor(time);
+ return time + ' secs';
+ } else if (time < oneHourMs) {
+ time = (time / oneMinMs).toFixed(2);
+ return time + ' mins';
+ } else if (time < oneDayMs) {
+ time = (time / oneHourMs).toFixed(2);
+ return time + ' hours';
+ } else {
+ time = (time / oneDayMs).toFixed(2);
+ return time + ' days';
+ }
+ },
+
+ /**
+ * Provides the duration between the given start and end time. If start time
+ * is not given, duration will be 0. If end time is not given, duration will
+ * be till now.
+ *
+ * @param {Number} startTime Start time from epoch
+ * @param {Number} endTime End time from epoch
+ * @return {Number} duration
+ * @method duration
+ */
+ duration: function (startTime, endTime) {
+ var duration = 0;
+ if (startTime && startTime > 0) {
+ if (!endTime || endTime < 1) {
+ endTime = new Date().getTime();
+ }
+ duration = endTime - startTime;
+ }
+ return duration;
+ }
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js b/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
new file mode 100644
index 0000000..72e3b0e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
@@ -0,0 +1,61 @@
+/**
+ * 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.
+ */
+
+/**
+ * formats the given unix timestamp. returns NaN if its not a number.
+ *
+ * @param {number} unixtimestamp
+ * @returns {string}
+ * @method formatUnixTimestamp
+ */
+Em.Handlebars.helper('formatUnixTimestamp', function(timestamp) {
+ if (!App.Helpers.number.isValidInt(timestamp)) return 'NaN';
+ if (timestamp > 0) {
+ return App.Helpers.date.dateFormat(timestamp);
+ }
+ return '';
+});
+
+/*
+ * formats the duration.
+ *
+ * @param {duration} duration in milliseconds
+ * @return {Number}
+ * @method formatDuration
+ */
+Em.Handlebars.helper('formatDuration', function(startTime, endTime) {
+ // unixtimestamp is in seconds. javascript expects milliseconds.
+ if (endTime < startTime || !!endTime) {
+ end = new Date().getTime();
+ }
+
+ return App.Helpers.date.durationSummary(startTime, endTime);
+});
+
+function replaceAll(str, str1, str2, ignore)
+{
+ return str.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2);
+}
+
+//TODO: needs better indendation.
+Em.Handlebars.helper('formatDiagnostics', function(diagnostics) {
+ var x = replaceAll(diagnostics, '[', '<div class="indent"><i> </i>');
+ x = replaceAll(x, '],', '</div><i> </i>');
+ x = replaceAll(x, ']', '</div>');
+ return new Handlebars.SafeString(x);
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/misc.js b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
new file mode 100644
index 0000000..8db04a0
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
@@ -0,0 +1,76 @@
+/**
+ * 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.misc = {
+ getStatusClassForEntity: function(dag) {
+ var st = dag.get('status');
+ switch(st) {
+ case 'FAILED':
+ return 'failed';
+ case 'KILLED':
+ return 'killed';
+ case 'RUNNING':
+ return 'running';
+ case 'ERROR':
+ return 'error';
+ case 'SUCCEEDED':
+ var counterGroups = dag.get('counterGroups');
+ var numFailedTasks = this.getCounterValueForDag(counterGroups,
+ dag.get('id'), 'org.apache.tez.common.counters.DAGCounter',
+ 'NUM_FAILED_TASKS'
+ );
+
+ if (numFailedTasks > 0) {
+ return 'warning';
+ }
+
+ return 'success';
+ default:
+ return 'submitted';
+ }
+ },
+
+ getCounterValueForDag: function(counterGroups, dagID, counterGroupName, counterName) {
+ if (!counterGroups) {
+ return 0;
+ }
+
+ var cgName = dagID + '/' + counterGroupName;
+ var cg = counterGroups.findBy('id', cgName);
+ if (!cg) {
+ return 0;
+ }
+ var counters = cg.get('counters');
+ if (!counters) {
+ return 0;
+ }
+
+ var counter = counters.findBy('id', cgName + '/' + counterName);
+ if (!counter) return 0;
+
+ return counter.get('value');
+ },
+
+ isValidDagStatus: function(status) {
+ return $.inArray(status, ['SUBMITTED', 'INITING', 'RUNNING', 'SUCCEEDED',
+ 'KILLED', 'FAILED', 'ERROR']) != -1;
+ },
+
+ isValidTaskStatus: function(status) {
+ return $.inArray(status, ['RUNNING', 'SUCCEEDED', 'FAILED', 'KILLED']) != -1;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/helpers/number.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/number.js b/tez-ui/src/main/webapp/app/scripts/helpers/number.js
new file mode 100644
index 0000000..66b3e8e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/number.js
@@ -0,0 +1,101 @@
+/**
+ * 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.number = {
+
+ /**
+ * Convert byte size to other metrics.
+ *
+ * @param {Number} bytes to convert to string
+ * @param {Number} precision Number to adjust precision of return value. Default is 0.
+ * @param {String} parseType
+ * JS method name for parse string to number. Default is "parseInt".
+ * @param {Number} multiplyBy bytes by this number if given. This is needed
+ * as <code>null * 1024 = 0</null>
+ * @remarks The parseType argument can be "parseInt" or "parseFloat".
+ * @return {String} Returns converted value with abbreviation.
+ */
+ bytesToSize: function (bytes, precision, parseType, multiplyBy) {
+ if (isNaN(bytes)) bytes = 0;
+ if (Em.isNone(bytes)) {
+ return 'n/a';
+ } else {
+ if (arguments[2] === undefined) {
+ parseType = 'parseInt';
+ }
+ if (arguments[3] === undefined) {
+ multiplyBy = 1;
+ }
+ var value = bytes * multiplyBy;
+ var sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB' ];
+ var posttxt = 0;
+ while (value >= 1024) {
+ posttxt++;
+ value = value / 1024;
+ }
+ if (value === 0) {
+ precision = 0;
+ }
+ 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 (Em.isNone(str) || (str + "").trim().length < 1) {
+ return Em.I18n.t('number.validate.empty');
+ }
+ str = (str + "").trim();
+ var number = parseInt(str);
+ if (isNaN(number)) {
+ return Em.I18n.t('number.validate.notValidNumber');
+ }
+ 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;
+ },
+
+ /**
+ * Checks if the value is an integer or can be converted to an integer.
+ * a value of NaN returns false.
+ * @method: isValidInt
+ * @param {string|number} value to check
+ * @return {boolean}
+ */
+ isValidInt: function(value) {
+ return value % 1 == 0;
+ }
+
+};
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/helpers/translation.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/translation.js b/tez-ui/src/main/webapp/app/scripts/helpers/translation.js
new file mode 100644
index 0000000..ed32a39
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/translation.js
@@ -0,0 +1,119 @@
+/**
+ * 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.
+ */
+
+Ember.I18n.translations = {
+
+ 'any': 'Any',
+ 'apply': 'Apply',
+ 'ok': 'Ok',
+ 'cancel': 'Cancel',
+
+ 'common.id': 'Entity Id',
+ 'common.applicationId': 'Application Id',
+ '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',
+ 'common.user': 'User',
+ 'common.time.duration': 'Duration',
+
+ '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',
+
+ 'common.loading': 'Loading...',
+ 'http.error.400': 'Unable to load data.',
+ 'dags.nothingToShow': 'No Dags to display',
+
+ 'jobs.type':'Jobs Type',
+ 'jobs.type.hive':'Hive',
+ 'jobs.show.up.to':'Show up to',
+ 'jobs.filtered.jobs':'%@ jobs showing',
+ 'jobs.filtered.clear':'clear filters',
+ 'jobs.column.id':'Id',
+ 'jobs.column.user':'User',
+ 'jobs.column.start.time':'Start Time',
+ 'jobs.column.end.time':'End Time',
+ 'jobs.column.duration':'Duration',
+ 'jobs.new_jobs.info':'New jobs available on server.',
+ 'jobs.loadingTasks': 'Loading...',
+
+ 'jobs.nothingToShow': 'No jobs to display',
+ 'jobs.error.ats.down': 'Jobs data cannot be shown since YARN App Timeline Server is not running.',
+ 'jobs.error.400': 'Unable to load data.',
+ 'jobs.table.custom.date.am':'AM',
+ 'jobs.table.custom.date.pm':'PM',
+ 'jobs.table.custom.date.header':'Select Custom Dates',
+ 'jobs.table.job.fail':'Job failed to run',
+ 'jobs.customDateFilter.error.required':'This field is required',
+ 'jobs.customDateFilter.error.date.order':'End Date must be after Start Date',
+ 'jobs.customDateFilter.startTime':'Start Time',
+ 'jobs.customDateFilter.endTime':'End Time',
+ 'jobs.hive.failed':'JOB FAILED',
+ 'jobs.hive.more':'show more',
+ 'jobs.hive.less':'show less',
+ 'jobs.hive.query':'Hive Query',
+ 'jobs.hive.stages':'Stages',
+ 'jobs.hive.yarnApplication':'YARN Application',
+ 'jobs.hive.tez.tasks':'Tez Tasks',
+ 'jobs.hive.tez.hdfs':'HDFS',
+ 'jobs.hive.tez.localFiles':'Local Files',
+ 'jobs.hive.tez.spilledRecords':'Spilled Records',
+ 'jobs.hive.tez.records':'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',
+ 'jobs.hive.tez.dag.error.noDag.message':'This job does not identify any Tez information.',
+ 'jobs.hive.tez.dag.error.noDagId.title':'No Tez Information',
+ 'jobs.hive.tez.dag.error.noDagId.message':'No Tez information was found for this job. Either it is waiting to be run, or has exited unexpectedly.',
+ 'jobs.hive.tez.dag.error.noDagForId.title':'No Tez Information',
+ 'jobs.hive.tez.dag.error.noDagForId.message':'No details were found for the Tez ID given to this job.',
+ 'jobs.hive.tez.metric.input':'Input',
+ 'jobs.hive.tez.metric.output':'Output',
+ 'jobs.hive.tez.metric.recordsRead':'Records Read',
+ 'jobs.hive.tez.metric.recordsWrite':'Records Written',
+ 'jobs.hive.tez.metric.tezTasks':'Tez Tasks',
+ 'jobs.hive.tez.metric.spilledRecords':'Spilled Records',
+ 'jobs.hive.tez.edge.':'Unknown',
+ 'jobs.hive.tez.edge.contains':'Contains',
+ 'jobs.hive.tez.edge.broadcast':'Broadcast',
+ '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/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/mixins/display_helpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/mixins/display_helpers.js b/tez-ui/src/main/webapp/app/scripts/mixins/display_helpers.js
new file mode 100644
index 0000000..a89b597
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/mixins/display_helpers.js
@@ -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.
+ */
+
+// TODO : document
+App.Helpers.DisplayHelper = Em.Mixin.create({
+ startTimeDisplay: function() {
+ var startTime = this.get('startTime');
+ return startTime > 0 ? App.Helpers.date.dateFormat(startTime) : '';
+ }.property('startTime'),
+
+ endtimeDisplay: function() {
+ var endTime = this.get('endTime');
+ return endTime > 0 ? App.Helpers.date.dateFormat(endTime) : '';
+ }.property('endTime'),
+
+ duration: function() {
+ var startTime = this.get('startTime');
+ var endTime = this.get('endTime');
+ if(endTime < startTime || endTime == undefined) {
+ endTime = new Date().getTime();
+ }
+ return App.Helpers.date.duration(startTime, endTime);
+ }.property('startTime', 'endTime')
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/mixins/paginated_content.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/mixins/paginated_content.js b/tez-ui/src/main/webapp/app/scripts/mixins/paginated_content.js
new file mode 100644
index 0000000..b650705
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/mixins/paginated_content.js
@@ -0,0 +1,157 @@
+/**
+ * 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.PaginatedContentMixin = Em.Mixin.create({
+ count: 5,
+
+ fromID: null,
+
+ /* There is currently no efficient way in ATS to get pagination data, so we fake one.
+ * store the first dag id on a page so that we can navigate back and store the last one
+ * (not shown on page to get the id where next page starts)
+ */
+ navIDs: {
+ prevIDs: [],
+ currentID: undefined,
+ nextID: undefined
+ },
+
+ entities: [],
+ _paginationFilters: {},
+ loading: true,
+
+ sortedContent: function() {
+ var sorted = Em.ArrayController.create({
+ model: this.get('entities'),
+ sortProperties: ['startTime'],
+ sortAscending: false
+ });
+ this.updatePagination(sorted.toArray());
+ return sorted.slice(0, this.count);
+ }.property('entities', 'numEntities'),
+
+ loadEntities: function() {
+ var that = this;
+ var childEntityType = this.get('childEntityType');
+ this.get('store').unloadAll(childEntityType);
+ this.get('store').findQuery(childEntityType, this.getFilterProperties()).then(function(entities){
+ that.set('entities', entities);
+ that.set('loading', false);
+ }).catch(function(jqXHR){
+ alert('failed');
+ });
+ },
+
+ setFiltersAndLoadEntities: function(filters) {
+ this._paginationFilters = filters;
+ this.resetNavigation();
+ this.loadEntities();
+ },
+
+ resetNavigation: function() {
+ this.set('navIDs.prevIDs', []);
+ this.set('navIDs.currentID', '');
+ this.set('navIDs.nextID', '');
+ this.set('fromID', null);
+ },
+
+ updatePagination: function(dataArray) {
+ if (!!dataArray && dataArray.get('length') > 0) {
+ this.set('navIDs.currentID', dataArray.objectAt(0).get('id'));
+ var nextID = undefined;
+ if (dataArray.get('length') > this.count) {
+ // save the last id, so that we can use that as firt id on next page.
+ nextID = dataArray.objectAt(this.count).get('id');
+ }
+ this.set('navIDs.nextID', nextID);
+ }
+ },
+
+ hasPrev: function() {
+ return this.navIDs.prevIDs.length > 0;
+ }.property('navIDs.prevIDs.[]', 'navIDs.prevIDs.length', 'fromID'),
+
+ hasNext: function() {
+ return !!this.navIDs.nextID;
+ }.property('navIDs.nextID'),
+
+ actions:{
+ // go to previous page
+ navigatePrev: function () {
+ var prevPageId = this.navIDs.prevIDs.popObject();
+ this.set('fromID', prevPageId);
+ this.set('loading', true);
+ this.loadEntities();
+ },
+
+ // goto first page.
+ navigateFirst: function() {
+ var firstPageId = this.navIDs.prevIDs[0];
+ this.set('navIDs.prevIDs', []);
+ this.set('fromID', firstPageId);
+ this.set('loading', true);
+ this.loadEntities();
+ },
+
+ // go to next page
+ navigateNext: function () {
+ this.navIDs.prevIDs.pushObject(this.navIDs.currentID);
+ this.set('fromID', this.get('navIDs.nextID'));
+ this.set('loading', true);
+ this.loadEntities();
+ },
+ },
+
+ _concatFilters: function(obj) {
+ var p = [];
+ for(var k in obj) {
+ if (!Em.empty(obj[k])) {
+ p.push(k + ':' + obj[k]);
+ }
+ }
+ return p.join(',');
+ },
+
+ getFilterProperties: function() {
+ var params = {
+ limit: this.count + 1
+ };
+
+ var f = this._paginationFilters;
+ var primary = f.primary;
+ var secondary = f.secondary || {};
+
+ primary = this._concatFilters(primary);
+
+ secondary = this._concatFilters(secondary);
+
+ if (!Em.empty(primary)) {
+ params['primaryFilter'] = primary;
+ }
+
+ if (!Em.empty(secondary)) {
+ params['secondaryFilter'] = secondary;
+ }
+
+ if (!Em.empty(this.fromID)) {
+ params['fromId'] = this.fromID;
+ }
+
+ return params;
+ },
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/mixins/run_periodically.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/mixins/run_periodically.js b/tez-ui/src/main/webapp/app/scripts/mixins/run_periodically.js
new file mode 100644
index 0000000..6a534a9
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/mixins/run_periodically.js
@@ -0,0 +1,78 @@
+/**
+ * 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.
+ */
+
+/**
+ * Allow to run object method periodically and stop it
+ * Example:
+ * <code>
+ * var obj = Ember.Object.createWithMixins(App.RunPeriodically, {
+ * method: Ember.K
+ * });
+ * obj.set('interval', 10000); // override default value
+ * obj.loop('method'); // run periodically
+ * obj.stop(); // stop running
+ * </code>
+ * @type {Ember.Mixin}
+ */
+App.RunPeriodically = Ember.Mixin.create({
+
+ /**
+ * Interval for loop
+ * @type {number}
+ */
+ interval: 10000,
+
+ /**
+ * setTimeout's return value
+ * @type {number}
+ */
+ timer: null,
+
+ /**
+ * Run <code>methodName</code> periodically with <code>interval</code>
+ * @param {string} methodName method name to run periodically
+ * @param {bool} initRun should methodName be run before setInterval call (default - true)
+ * @method run
+ */
+ loop: function(methodName, initRun) {
+ initRun = Em.isNone(initRun) ? true : initRun;
+ var self = this,
+ interval = this.get('interval');
+ Ember.assert('Interval should be numeric and greated than 0', $.isNumeric(interval) && interval > 0);
+ if (initRun) {
+ this[methodName]();
+ }
+ this.set('timer',
+ setInterval(function () {
+ self[methodName]();
+ }, interval)
+ );
+ },
+
+ /**
+ * Stop running <code>timer</code>
+ * @method stop
+ */
+ stop: function() {
+ var timer = this.get('timer');
+ if (!Em.isNone(timer)) {
+ clearTimeout(timer);
+ }
+ }
+
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js b/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js
new file mode 100644
index 0000000..e753b5e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js
@@ -0,0 +1,333 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+var typeToPathMap = {
+ dag: 'TEZ_DAG_ID',
+ vertex: 'TEZ_VERTEX_ID',
+ task: 'TEZ_TASK_ID',
+ taskAttempt: 'TEZ_TASK_ATTEMPT_ID'
+};
+
+App.TimelineRESTAdapter = DS.RESTAdapter.extend({
+ host: App.AtsBaseUrl,
+ namespace: 'ws/v1/timeline',
+
+ pathForType: function(type) {
+ return typeToPathMap[type];
+ },
+
+});
+
+App.TimelineSerializer = DS.RESTSerializer.extend({
+ extractSingle: function(store, primaryType, rawPayload, recordId) {
+ // rest serializer expects singular form of model as the root key.
+ var payload = {};
+ payload[primaryType.typeKey] = rawPayload;
+ return this._super(store, primaryType, payload, recordId);
+ },
+
+ extractArray: function(store, primaryType, rawPayload) {
+ // restserializer expects a plural of the model but TimelineServer returns
+ // it in entities.
+ var payload = {};
+ payload[primaryType.typeKey.pluralize()] = rawPayload.entities;
+ return this._super(store, primaryType, payload);
+ },
+
+ // normalizes countergroups returns counterGroups and counters.
+ normalizeCounterGroupsHelper: function(parentType, parentID, entity) {
+ // create empty countergroups if not there - to make code below easier.
+ entity.otherinfo.counters = entity.otherinfo.counters || {}
+ entity.otherinfo.counters.counterGroups = entity.otherinfo.counters.counterGroups || [];
+
+ var counterGroups = [];
+ var counters = [];
+
+ var counterGroupsIDs = entity.otherinfo.counters.counterGroups.map(function(counterGroup) {
+ var cg = {
+ id: parentID + '/' + counterGroup.counterGroupName,
+ name: counterGroup.counterGroupName,
+ displayName: counterGroup.counterGroupDisplayName,
+ parentID: { // polymorphic requires type and id.
+ type: parentType,
+ id: parentID
+ }
+ };
+ cg.counters = counterGroup.counters.map(function(counter){
+ var c = {
+ id: cg.id + '/' + counter.counterName,
+ name: counter.counterName,
+ displayName: counter.counterDisplayName,
+ value: counter.counterValue,
+ parentID: cg.id
+ };
+ counters.push(c);
+ return c.id;
+ });
+ counterGroups.push(cg);
+ return cg.id;
+ });
+
+ return {
+ counterGroups: counterGroups,
+ counters: counters,
+ counterGroupsIDs: counterGroupsIDs
+ }
+ }
+});
+
+
+var timelineJsonToDagMap = {
+ id: 'entity',
+ submittedTime: 'starttime',
+ startTime: 'otherinfo.startTime',
+ endTime: 'otherinfo.endTime',
+ name: 'primaryfilters.dagName.0',
+ user: 'primaryfilters.user.0',
+ applicationId: 'otherinfo.applicationId',
+ status: 'otherinfo.status',
+ diagnostics: 'otherinfo.diagnostics',
+ counterGroups: 'counterGroups'
+};
+
+App.DagSerializer = App.TimelineSerializer.extend({
+ _normalizeSingleDagPayload: function(dag) {
+ var normalizedCounterGroupData = this.normalizeCounterGroupsHelper('dag', dag.entity,
+ dag);
+ dag.counterGroups = normalizedCounterGroupData.counterGroupsIDs;
+ delete dag.otherinfo.counters;
+
+ return {
+ dag: dag,
+ counterGroups: normalizedCounterGroupData.counterGroups,
+ counters: normalizedCounterGroupData.counters
+ };
+ },
+
+ normalizePayload: function(rawPayload){
+
+ if (!!rawPayload.dags) {
+ // multiple dags - cames here through _findAll/_findQuery
+ var normalizedPayload = {
+ dags: [],
+ counterGroups: [],
+ counters: []
+ };
+ rawPayload.dags.forEach(function(dag){
+ var n = this._normalizeSingleDagPayload(dag);
+ normalizedPayload.dags.push(n.dag);
+ [].push.apply(normalizedPayload.counterGroups, n.counterGroups);
+ [].push.apply(normalizedPayload.counters, n.counters);
+ }, this);
+
+ // delete so that we dont hang on to the json data.
+ delete rawPayload.dags;
+
+ return normalizedPayload;
+ } else {
+ return this._normalizeSingleDagPayload(rawPayload.dag);
+ }
+ },
+
+ normalize: function(type, hash, prop) {
+ return Em.JsonMapper.map(hash, timelineJsonToDagMap);
+ },
+});
+
+var containerIdRegex = /.*(container_.*?)\/.*/;
+var nodeIdRegex = /([^\/]*)\//;
+var timelineJsonToTaskAttemptMap = {
+ id: 'entity',
+ startTime: 'otherinfo.startTime',
+ endTime: 'otherinfo.endTime',
+ status: 'otherinfo.status',
+ diagnostics: 'otherinfo.diagnostics',
+ counterGroups: 'counterGroups',
+ vertexID: 'primaryfilters.TEZ_VERTEX_ID.0',
+ dagID: 'primaryfilters.TEZ_DAG_ID.0',
+ containerId: { custom: function (source) {
+ var inProgressLogsURL = Em.get(source, 'otherinfo.inProgressLogsURL');
+ var match = containerIdRegex.exec(inProgressLogsURL);
+ return match[1];
+ }},
+ nodeId: {
+ custom: function(source) {
+ var inProgressLogsURL = Em.get(source, 'otherinfo.inProgressLogsURL');
+ var match = nodeIdRegex.exec(inProgressLogsURL);
+ return match[1];
+ }
+ }
+};
+
+
+App.TaskAttemptSerializer = App.TimelineSerializer.extend({
+ _normalizeSingleTaskAttemptPayload: function(taskAttempt) {
+ var normalizedCounterGroupData = this.normalizeCounterGroupsHelper('taskAttempt',
+ taskAttempt.entity, taskAttempt);
+ taskAttempt.counterGroups = normalizedCounterGroupData.counterGroupsIDs;
+ delete taskAttempt.otherinfo.counters;
+
+ return {taskAttempt: taskAttempt, counterGroups: normalizedCounterGroupData.counterGroups,
+ counters: normalizedCounterGroupData.counters
+ };
+ },
+
+ normalizePayload: function(rawPayload){
+
+ if (!!rawPayload.taskAttempts) {
+ var normalizedPayload = {
+ taskAttempts: [],
+ counterGroups: [],
+ counters: []
+ };
+ rawPayload.taskAttempts.forEach(function(taskAttempt){
+ var n = this._normalizeSingleTaskAttemptPayload(taskAttempt);
+ normalizedPayload.taskAttempts.push(n.taskAttempt);
+ [].push.apply(normalizedPayload.counterGroups, n.counterGroups);
+ [].push.apply(normalizedPayload.counters, n.counters);
+ }, this);
+
+ // delete so that we dont hang on to the json data.
+ delete rawPayload.taskAttempts;
+ return normalizedPayload;
+ } else {
+ return this._normalizeSingleTaskAttemptPayload(rawPayload.taskAttempt);
+ }
+ },
+
+ normalize: function(type, hash, prop) {
+ return Em.JsonMapper.map(hash, timelineJsonToTaskAttemptMap);
+ },
+});
+
+var timelineJsonToTaskMap = {
+ id: 'entity',
+ dagID: 'primaryfilters.TEZ_DAG_ID.0',
+ startTime: 'otherinfo.startTime',
+ vertexID: 'primaryfilters.TEZ_VERTEX_ID.0',
+ endTime: 'otherinfo.endTime',
+ status: 'otherinfo.status',
+ diagnostics: 'otherinfo.diagnostics',
+ counterGroups: 'counterGroups',
+ vertexID: 'primaryfilters.TEZ_VERTEX_ID.0',
+ dagID: 'primaryfilters.TEZ_DAG_ID.0',
+ numAttempts: 'relatedentities'
+};
+
+App.TaskSerializer = App.TimelineSerializer.extend({
+ _normalizeSingleTaskPayload: function(task) {
+ var normalizedCounterGroupData = this.normalizeCounterGroupsHelper('task', task.entity,
+ task);
+ task.counterGroups = normalizedCounterGroupData.counterGroupsIDs;
+
+ delete task.otherinfo.counters;
+
+ return {
+ task: task,
+ counterGroups: normalizedCounterGroupData.counterGroups,
+ counters: normalizedCounterGroupData.counters
+ };
+ },
+
+ normalizePayload: function(rawPayload) {
+ if (!!rawPayload.tasks) {
+ var normalizedPayload = {
+ tasks: [],
+ counterGroups: [],
+ counters: []
+ };
+ rawPayload.tasks.forEach(function(task){
+ var n = this._normalizeSingleTaskPayload(task);
+ normalizedPayload.tasks.push(n.task);
+ [].push.apply(normalizedPayload.counterGroups, n.counterGroups);
+ [].push.apply(normalizedPayload.counters, n.counters);
+ }, this);
+
+ // delete so that we dont hang on to the json data.
+ delete rawPayload.tasks;
+
+ return normalizedPayload;
+ } else {
+ return this._normalizeSingleTaskPayload(rawPayload.task);
+ }
+ },
+
+ normalize: function(type, hash, prop) {
+ return Em.JsonMapper.map(hash, timelineJsonToTaskMap);
+ },
+});
+
+var timelineJsonToVertexMap = {
+ id: 'entity',
+ name: 'otherinfo.vertexName',
+ dagID: 'primaryfilters.TEZ_DAG_ID.0',
+ counterGroups: 'counterGroups',
+
+ startTime: 'otherinfo.startTime',
+ endTime: 'otherinfo.endTime',
+
+ status: 'otherinfo.status',
+ diagnostics: 'otherinfo.diagnostics',
+
+ failedTasks: 'otherinfo.numFailedTasks',
+ sucessfulTasks: 'otherinfo.numSucceededTasks',
+ numTasks: 'otherinfo.numTasks',
+ killedTasks: 'otherinfo.numKilledTasks',
+};
+
+App.VertexSerializer = App.TimelineSerializer.extend({
+ _normalizeSingleVertexPayload: function(vertex) {
+ var normalizedCounterGroupData = this.normalizeCounterGroupsHelper('vertex', vertex.entity,
+ vertex);
+ vertex.counterGroups = normalizedCounterGroupData.counterGroupsIDs;
+
+ delete vertex.otherinfo.counters;
+
+ return {
+ vertex: vertex,
+ counterGroups: normalizedCounterGroupData.counterGroups,
+ counters: normalizedCounterGroupData.counters
+ };
+ },
+
+ normalizePayload: function(rawPayload) {
+ if (!!rawPayload.vertices) {
+ var normalizedPayload = {
+ vertices: [],
+ counterGroups: [],
+ counters: []
+ };
+ rawPayload.vertices.forEach(function(vertex){
+ var n = this._normalizeSingleVertexPayload(vertex);
+ normalizedPayload.vertices.push(n.vertex);
+ [].push.apply(normalizedPayload.counterGroups, n.counterGroups);
+ [].push.apply(normalizedPayload.counters, n.counters);
+ }, this);
+
+ // delete so that we dont hang on to the json data.
+ delete rawPayload.vertices;
+
+ return normalizedPayload;
+ } else {
+ return this._normalizeSingleVertexPayload(rawPayload.vertex);
+ }
+ },
+
+ normalize: function(type, hash, prop) {
+ return Em.JsonMapper.map(hash, timelineJsonToVertexMap);
+ },
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/models/abstract_entity.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/models/abstract_entity.js b/tez-ui/src/main/webapp/app/scripts/models/abstract_entity.js
new file mode 100644
index 0000000..a3e7332
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/models/abstract_entity.js
@@ -0,0 +1,28 @@
+/**
+ * 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.AbstractEntity = DS.Model.extend({
+ // type of the entity. should be one of App.EntityType
+ entityType: DS.attr('string')
+});
+
+App.EntityType = {
+ DAG: 'dag',
+ VERTEX: 'vertex',
+ TASK: 'task',
+ TASK_ATTEMPT: 'task_attempt',
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/models/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/models/dag.js b/tez-ui/src/main/webapp/app/scripts/models/dag.js
new file mode 100644
index 0000000..1bd3182
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/models/dag.js
@@ -0,0 +1,261 @@
+/**
+ * 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.Dag = App.AbstractEntity.extend({
+
+ submittedTime: DS.attr('number'),
+
+ // start time of the entity
+ startTime: DS.attr('number'),
+
+ // end time of the entity
+ endTime: DS.attr('number'),
+
+ // set type to DAG
+ entityType: App.EntityType.DAG,
+
+ // Name of the dag.
+ name: DS.attr('string'),
+
+ // user name who ran this dag.
+ user: DS.attr('string'),
+
+ // application ID of this dag.
+ applicationId: DS.attr('string'),
+
+ // status
+ status: DS.attr('string'),
+
+ // diagnostics info if any.
+ diagnostics: DS.attr('string'),
+
+ //vertices: DS.hasMany('vertex'),
+
+ //edges: DS.hasMany('edge'),
+
+ counterGroups: DS.hasMany('counterGroup', { inverse: 'parent' })
+});
+
+App.CounterGroup = DS.Model.extend({
+ name: DS.attr('string'),
+
+ displayName: DS.attr('string'),
+
+ counters: DS.hasMany('counter', { inverse: 'parent' }),
+
+ parent: DS.belongsTo('abstractEntity', { polymorphic: true })
+});
+
+App.Counter = DS.Model.extend({
+ name: DS.attr('string'),
+
+ displayName: DS.attr('string'),
+
+ value: DS.attr('number'),
+
+ parent: DS.belongsTo('counterGroup')
+});
+
+App.Edge = DS.Model.extend({
+
+ fromVertex: DS.belongsTo('vertex'),
+
+ toVertex: DS.belongsTo('vertex'),
+
+ /**
+ * Type of this edge connecting vertices. Should be one of constants defined
+ * in 'App.EdgeType'.
+ */
+ edgeType: DS.attr('string'),
+
+ dag: DS.belongsTo('dag')
+});
+
+App.Vertex = DS.Model.extend({
+ name: DS.attr('string'),
+
+ dag: DS.belongsTo('dag'),
+ dagID: DS.attr('string'),
+
+ /**
+ * State of this vertex. Should be one of constants defined in
+ * App.VertexState.
+ */
+ status: DS.attr('string'),
+
+ /**
+ * Vertex type has to be one of the types defined in 'App.VertexType'
+ * @return {string}
+ */
+ type: DS.attr('string'),
+
+ /**
+ * A vertex can have multiple incoming edges.
+ */
+ incomingEdges: DS.hasMany('edge', {inverse: 'fromVertex' }),
+
+ /**
+ * This vertex can have multiple outgoing edges.
+ */
+ outgoingEdges: DS.hasMany('edge', {inverse: 'toVertex'}),
+
+ startTime: DS.attr('number'),
+
+ endTime: DS.attr('number'),
+
+ /**
+ * Provides the duration of this job. If the job has not started, duration
+ * will be given as 0. If the job has not ended, duration will be till now.
+ *
+ * @return {Number} Duration in milliseconds.
+ */
+ duration: function () {
+ return App.Helpers.date.duration(this.get('startTime'), this.get('endTime'))
+ }.property('startTime', 'endTime'),
+
+ /**
+ * Each Tez vertex can perform arbitrary application specific computations
+ * inside. The application can provide a list of operations it has provided in
+ * this vertex.
+ *
+ * Array of strings. [{string}]
+ */
+ 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'),
+
+ /**
+ * Number of actual Map/Reduce tasks in this vertex
+ */
+ numTasks: DS.attr('number'),
+
+ name: DS.attr('string'),
+
+ failedTasks: DS.attr('number'),
+ sucessfulTasks: DS.attr('number'),
+ numTasks: DS.attr('number'),
+ killedTasks: DS.attr('number'),
+
+ diagnostics: DS.attr('string'),
+
+ counterGroups: DS.hasMany('counterGroup'),
+
+ tasksNumber: function () {
+ return this.getWithDefault('tasksCount', 0);
+ }.property('tasksCount'),
+
+ /**
+ * Local filesystem usage metrics for this vertex
+ */
+ fileReadBytes: DS.attr('number'),
+
+ fileWriteBytes: DS.attr('number'),
+
+ fileReadOps: DS.attr('number'),
+
+ fileWriteOps: DS.attr('number'),
+
+ /**
+ * Spilled records
+ */
+ spilledRecords: DS.attr('number'),
+
+ /**
+ * HDFS usage metrics for this vertex
+ */
+ hdfsReadBytes: DS.attr('number'),
+
+ hdfsWriteBytes: DS.attr('number'),
+
+ hdfsReadOps: DS.attr('number'),
+
+ hdfsWriteOps: DS.attr('number'),
+
+ /**
+ * Record metrics for this vertex
+ */
+ recordReadCount: DS.attr('number'),
+
+ recordWriteCount: DS.attr('number'),
+
+ totalReadBytes: function () {
+ return this.get('fileReadBytes') + this.get('hdfsReadBytes');
+ }.property('fileReadBytes', 'hdfsReadBytes'),
+
+ totalWriteBytes: function () {
+ return this.get('fileWriteBytes') + this.get('hdfsWriteBytes');
+ }.property('fileWriteBytes', 'hdfsWriteBytes'),
+
+ totalReadBytesDisplay: function () {
+ return App.Helpers.number.bytesToSize(this.get('totalReadBytes'));
+ }.property('totalReadBytes'),
+
+ totalWriteBytesDisplay: function () {
+ return App.Helpers.number.bytesToSize(this.get('totalWriteBytes'));
+ }.property('totalWriteBytes'),
+
+ durationDisplay: function () {
+ return App.Helpers.date.timingFormat(this.get('duration'), true);
+ }.property('duration')
+});
+
+App.Task = App.AbstractEntity.extend({
+ status: DS.attr('status'),
+
+ dagID: DS.attr('string'),
+
+ vertexID: DS.attr('string'),
+
+ startTime: DS.attr('number'),
+
+ endTime: DS.attr('number'),
+
+ diagnostics: DS.attr('string'),
+
+ numAttempts: DS.attr('number'),
+
+ counterGroups: DS.hasMany('counterGroup', { inverse: 'parent' })
+});
+
+App.VertexState = {
+ NEW: "NEW",
+ INITIALIZING: "INITIALIZING",
+ INITED: "INITED",
+ RUNNING: "RUNNING",
+ SUCCEEDED: "SUCCEEDED",
+ FAILED: "FAILED",
+ KILLED: "KILLED",
+ ERROR: "ERROR",
+ TERMINATING: "TERMINATING",
+ JOBFAILED: "JOB FAILED"
+};
+
+App.VertexType = {
+ MAP: 'MAP',
+ REDUCE: 'REDUCE',
+ UNION: 'UNION'
+};
+
+App.EdgeType = {
+ SCATTER_GATHER: "SCATTER_GATHER",
+ BROADCAST: "BROADCAST",
+ CONTAINS: "CONTAINS"
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/models/task_attempt.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/models/task_attempt.js b/tez-ui/src/main/webapp/app/scripts/models/task_attempt.js
new file mode 100644
index 0000000..db1d3c1
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/models/task_attempt.js
@@ -0,0 +1,43 @@
+/**
+ * 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.TaskAttempt = App.AbstractEntity.extend({
+
+ // start time of the entity
+ startTime: DS.attr('number'),
+
+ // end time of the entity
+ endTime: DS.attr('number'),
+
+ entityType: App.EntityType.TASK_ATTEMPT,
+
+ // dagId
+ dagId: DS.attr('string'),
+
+ // container
+ containerId: DS.attr('string'),
+ nodeId: DS.attr('string'),
+
+ // status of the task attempt
+ status: DS.attr('string'),
+
+ vertexID: DS.attr('string'),
+
+ dagID: DS.attr('string'),
+
+ counterGroups: DS.hasMany('counterGroup', { inverse: 'parent' })
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/router.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/router.js b/tez-ui/src/main/webapp/app/scripts/router.js
new file mode 100644
index 0000000..c2bcca8
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/router.js
@@ -0,0 +1,229 @@
+/**
+ * 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.Router.map(function() {
+ this.resource('dags', { path: '/' });
+ this.resource('dag', { path: '/dag/:dag_id'}, function() {
+ this.route('vertices'),
+ this.route('tasks'),
+ this.route('counters'),
+ this.route('swimlane')
+ });
+
+ this.resource('vertex', {path: '/vertex/:vertex_id'}, function(){
+ this.route('tasks');
+ this.route('counters');
+ this.route('details');
+ this.route('swimlane');
+ });
+
+ this.resource('tasks', {path: '/tasks'});
+ this.resource('task', {path: '/task/:task_id'}, function(){
+ this.route('attempts');
+ this.route('counters');
+ });
+ this.resource('taskAttempt', {path: '/task_attempt/:task_attempt_id'}, function() {
+ this.route('counters');
+ });
+ //this.resource('error', {path:$ '/error'});
+});
+
+/*
+App.ApplicationRoute = Em.Route.extend({
+ actions: {
+ //TODO: handle error and show proper error message.
+ error: function(error, transition) {
+ return;
+ this.transitionTo('error').then(function(newRoute) {
+ newRoute.controller.set('err', {
+ target: transition.targetName,
+ statusText: error.message,
+ responseText: error.stack
+ });
+ newRoute.controller.set('pageTitle', "Error");
+ });
+ }
+ }
+});*/
+
+App.DagsRoute = Em.Route.extend({
+ queryParams: {
+ count: {
+ refreshModel: true,
+ replace: true
+ },
+ fromID: {
+ refreshModel: true,
+ replace: true
+ },
+ user: {
+ refreshModel: true,
+ replace: true
+ },
+ status: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.loadData();
+ },
+});
+
+App.DagRoute = Em.Route.extend({
+ model: function(params) {
+ return this.store.find('dag', params.dag_id);
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ }
+});
+
+App.DagSwimlaneRoute = Em.Route.extend({
+
+ model: function(params) {
+ var model = this.modelFor('dag');
+ var queryParams = {'primaryFilter': 'TEZ_DAG_ID:' + model.id};
+ this.store.unloadAll('task_attempt');
+ return this.store.findQuery('task_attempt', queryParams);
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ }
+});
+
+App.TasksRoute = Em.Route.extend({
+ queryParams: {
+ status: {
+ refreshModel: true,
+ replace: true
+ },
+ parentType: {
+ refreshModel: true,
+ replace: true
+ },
+ parentID: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.loadData();
+ }
+});
+
+App.TaskRoute = Em.Route.extend({
+ model: function(params) {
+ return this.store.find('task', params.task_id);
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ }
+});
+
+App.VertexRoute = Em.Route.extend({
+ model: function(params) {
+ return this.store.find('vertex', params.vertex_id);
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ }
+});
+
+App.VertexSwimlaneRoute = Em.Route.extend({
+ model: function(params) {
+ var model = this.modelFor('vertex');
+ var queryParams = {'primaryFilter': 'TEZ_DAG_ID:' + model.get('dagID') };
+ this.store.unloadAll('task_attempt');
+ return this.store.filter('task_attempt', queryParams, function(ta) {
+ return ta.get('vertexID') == model.id;
+ });
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ }
+});
+
+App.DagTasksRoute = Em.Route.extend({
+ queryParams: {
+ status: {
+ refreshModel: true,
+ replace: true
+ },
+ vertex_id: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.loadData();
+ }
+});
+
+App.DagVerticesRoute = Em.Route.extend({
+ queryParams: {
+ status: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.loadData();
+ }
+});
+
+App.VertexTasksRoute = Em.Route.extend({
+ queryParams: {
+ status: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.loadData();
+ }
+});
+
+App.TaskAttemptsRoute = Em.Route.extend({
+ queryParams: {
+ status: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ controller.loadData();
+ }
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/e18a1fa7/tez-ui/src/main/webapp/app/scripts/views/swimlane_view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/views/swimlane_view.js b/tez-ui/src/main/webapp/app/scripts/views/swimlane_view.js
new file mode 100644
index 0000000..9420e18
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/views/swimlane_view.js
@@ -0,0 +1,119 @@
+App.SwimlanesView = Ember.View.extend({
+
+ didInsertElement: function() {
+ var task_attempts = this.get("content");
+ var timeBegin = d3.min(task_attempts, function (d) { return d.get('startTime') });
+ var timeEnd = d3.max(task_attempts, function (d) { return d.get('endTime') });
+ var containers = d3.set(task_attempts.map(function (d) {return d.get('containerId')})).values().sort();
+ var laneLength = containers.length;
+
+ var margin = {top: 20, right: 15, bottom: 15, left: 280};
+ var width = 960 - margin.left - margin.right;
+ var height = 500 - margin.top - margin.bottom;
+ var laneHeight = 18;
+ var miniHeight = laneLength * laneHeight;
+
+ //scales
+ var x = d3.scale.linear()
+ .range([0, width])
+ .domain([timeBegin, timeEnd]);
+
+ var y = d3.scale.ordinal()
+ .domain(containers)
+ .rangeRoundBands([0, miniHeight], .20);
+
+ var xAxis = d3.svg.axis()
+ .scale(x)
+ .orient("bottom")
+ .tickSize(0)
+ .tickFormat(function(d) { return (d - timeBegin)/1000; });
+
+ var yAxis = d3.svg.axis()
+ .scale(y)
+ .tickSize(0)
+ .orient("left");
+
+ $('#swimlane').html('');
+ var div = d3.select("#swimlane")
+ .append("div") // declare the tooltip div
+ .attr("class", "tooltip") // apply the 'tooltip' class
+ .style("opacity", 0);
+
+ var svg = d3.select("#swimlane")
+ .append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .attr("class", "svg");
+
+ var mini = svg.append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
+ .attr("class", "mini");
+
+ mini.append("g")
+ .attr("class", "y axis")
+ .call(yAxis)
+ .selectAll("text")
+ .style("text-anchor", "end");
+
+ mini.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + miniHeight + ")")
+ .call(xAxis)
+ .selectAll("text")
+ .style("text-anchor", "end")
+ .attr("transform", "rotate(-90)" );
+
+ // draw container rectangles
+ mini.append("g").selectAll("container")
+ .data(containers)
+ .enter().append("a").attr("xlink:href","file:///Users/jeagles/myember/")
+ .append("rect")
+ .attr("class", "container")
+ .attr("x", 0)
+ .attr("y", function(d) {return y(d);})
+ .attr("width", width)
+ .attr("rx", 6)
+ .attr("height", y.rangeBand());
+
+ // draw task attempt rectangles
+ mini.append("g").selectAll("task_attempt")
+ .data(task_attempts)
+ .enter().append("rect")
+ .attr("class", function(d) {return "task_attempt";})
+ .attr("x", function(d) {return x(d.get('startTime'));})
+ .attr("y", function(d) {return y(d.get('containerId'));})
+ .attr("width", function(d) {return x(d.get('endTime')) - x(d.get('startTime'));})
+ .attr("rx", 6)
+ .attr("height", y.rangeBand())
+ // Tooltip stuff after this
+ .on("mouseover", function(d) {
+ div.transition()
+ .duration(500)
+ .style("opacity", 0);
+ div.transition()
+ .duration(200)
+ .style("opacity", .9);
+ div .html(
+ // TODO: Replace with correct route to task attempt
+ '<a href= "/task_attempt/' + d.get('id') + '">' +
+ d.get('id') +
+ "</a>" +
+ "<br/>Start: " + moment(d.get('startTime')).format() +
+ "<br/>End: " + moment(d.get('endTime')).format())
+ .style("left", (d3.event.pageX) + "px")
+ .style("top", (d3.event.pageY - 28) + "px");
+ });
+
+ /*
+ // TODO: task attempt labels - draw labels if they fit
+ mini.append("g").selectAll("task_attempt_label")
+ .data(task_attempts)
+ .enter().append("text")
+ .text(function(d) {return d.get('id');})
+ .attr("x", function(d) {return x(d.get('startTime'));})
+ .attr("y", function(d) {return y(d.get('containerId'));})
+ .attr("dy", ".5ex");
+ */
+
+ },
+});