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">&nbsp;\
+          <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
+          &nbsp;&nbsp;{{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">&nbsp;\
+          <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
+          &nbsp;&nbsp;{{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">&nbsp;\
+          <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
+          &nbsp;&nbsp;{{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>&nbsp;</i>');
+  x = replaceAll(x, '],', '</div><i>&nbsp;</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&nbsp;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");
+    */
+
+  },
+});