You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tez.apache.org by sr...@apache.org on 2017/01/18 13:59:07 UTC

tez git commit: TEZ-3496. Tez UI: Optimize display of all tasks table (sree)

Repository: tez
Updated Branches:
  refs/heads/master 4dfb74fe0 -> 051ed16ed


TEZ-3496. Tez UI: Optimize display of all tasks table (sree)


Project: http://git-wip-us.apache.org/repos/asf/tez/repo
Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/051ed16e
Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/051ed16e
Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/051ed16e

Branch: refs/heads/master
Commit: 051ed16eda859b54b36ad43cfe921a0c9880eeda
Parents: 4dfb74f
Author: Sreenath Somarajapuram <sr...@apache.org>
Authored: Wed Jan 18 19:29:08 2017 +0530
Committer: Sreenath Somarajapuram <sr...@apache.org>
Committed: Wed Jan 18 19:29:08 2017 +0530

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 .../components/em-table-tasks-log-link-cell.js  | 33 ++++++++++++
 .../main/webapp/app/controllers/dag/tasks.js    | 12 +++++
 .../main/webapp/app/controllers/vertex/tasks.js | 12 +++++
 tez-ui/src/main/webapp/app/models/task.js       |  3 ++
 tez-ui/src/main/webapp/app/routes/dag/tasks.js  | 20 ++++++++
 .../src/main/webapp/app/routes/vertex/tasks.js  | 20 ++++++++
 tez-ui/src/main/webapp/app/serializers/task.js  |  5 +-
 .../components/em-table-tasks-log-link-cell.hbs | 25 +++++++++
 .../src/main/webapp/app/utils/virtual-anchor.js | 32 ++++++++++++
 .../em-table-tasks-log-link-cell-test.js        | 53 ++++++++++++++++++++
 .../tests/unit/controllers/dag/tasks-test.js    | 25 +++++++++
 .../tests/unit/controllers/vertex/tasks-test.js | 25 +++++++++
 .../main/webapp/tests/unit/models/task-test.js  |  5 ++
 .../webapp/tests/unit/routes/dag/tasks-test.js  | 43 ++++++++++++++++
 .../tests/unit/routes/vertex/tasks-test.js      | 41 +++++++++++++++
 .../webapp/tests/unit/serializers/task-test.js  |  2 +
 .../tests/unit/utils/virtual-anchor-test.js     | 40 +++++++++++++++
 18 files changed, 396 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 61c0459..67aaff9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -167,6 +167,7 @@ ALL CHANGES:
   TEZ-3530. Tez UI: Add query details page, and link the page from All Queries table
   TEZ-3531. Tez UI: All Queries table: Improve searchability
   TEZ-3556. Tez UI: Display query configurations
+  TEZ-3496. Tez UI: Optimize display of all tasks table
 
 Release 0.8.5: Unreleased
 

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/components/em-table-tasks-log-link-cell.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/em-table-tasks-log-link-cell.js b/tez-ui/src/main/webapp/app/components/em-table-tasks-log-link-cell.js
new file mode 100644
index 0000000..24cc8ee
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/em-table-tasks-log-link-cell.js
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+
+  content: null,
+
+  actions: {
+    click: function (download) {
+      // Directly goes to the route
+      this.get("targetObject.targetObject.targetObject.targetObject").
+          send("logCellClicked", this.get("content"), download);
+    },
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/tasks.js b/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
index 113d37e..4ab2360 100644
--- a/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
+++ b/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
@@ -78,5 +78,17 @@ export default MultiTableController.extend({
     cellDefinition: {
       type: 'duration'
     }
+  },{
+    id: 'log',
+    headerTitle: 'Successful/Last Attempt Log',
+    cellComponentName: 'em-table-tasks-log-link-cell',
+    getCellContent: function (row) {
+      var attemptID = row.get("successfulAttemptID");
+      if(!attemptID) {
+        let allAttemptIDs = row.get("attemptIDs") || [];
+        attemptID = allAttemptIDs[allAttemptIDs.length - 1];
+      }
+      return attemptID;
+    }
   }])
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js b/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js
index ed26173..94f5cc9 100644
--- a/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js
+++ b/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js
@@ -68,5 +68,17 @@ export default MultiTableController.extend(AutoCounterColumn, {
     cellDefinition: {
       type: 'duration'
     }
+  },{
+    id: 'log',
+    headerTitle: 'Successful/Last Attempt Log',
+    cellComponentName: 'em-table-tasks-log-link-cell',
+    getCellContent: function (row) {
+      var attemptID = row.get("successfulAttemptID");
+      if(!attemptID) {
+        let allAttemptIDs = row.get("attemptIDs") || [];
+        attemptID = allAttemptIDs[allAttemptIDs.length - 1];
+      }
+      return attemptID;
+    }
   }])
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/models/task.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/models/task.js b/tez-ui/src/main/webapp/app/models/task.js
index b2e7f71..c80d9cd 100644
--- a/tez-ui/src/main/webapp/app/models/task.js
+++ b/tez-ui/src/main/webapp/app/models/task.js
@@ -63,4 +63,7 @@ export default AMTimelineModel.extend({
   dag: DS.attr('object'), // Auto-loaded by need
 
   failedTaskAttempts: DS.attr('number'),
+
+  successfulAttemptID: DS.attr('string'),
+  attemptIDs: DS.attr("object"),
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/routes/dag/tasks.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/routes/dag/tasks.js b/tez-ui/src/main/webapp/app/routes/dag/tasks.js
index 744913d..9de48fc 100644
--- a/tez-ui/src/main/webapp/app/routes/dag/tasks.js
+++ b/tez-ui/src/main/webapp/app/routes/dag/tasks.js
@@ -19,6 +19,8 @@
 import Ember from 'ember';
 import MultiAmPollsterRoute from '../multi-am-pollster';
 
+import virtualAnchor from '../../utils/virtual-anchor';
+
 export default MultiAmPollsterRoute.extend({
   title: "All Tasks",
 
@@ -33,5 +35,23 @@ export default MultiAmPollsterRoute.extend({
     return this.get("loader").query('task', {
       dagID: this.modelFor("dag").get("id")
     }, options);
+  },
+
+  actions: {
+    logCellClicked: function (attemptID, download) {
+      var that = this;
+      return this.get("loader").queryRecord('attempt', attemptID).then(function (attempt) {
+        var logURL = attempt.get("logURL");
+        if(logURL) {
+          return virtualAnchor(logURL, download ? attempt.get("entityID") : undefined);
+        }
+        else {
+          that.send("openModal", {
+            title: "Log Link Not Available!",
+            content: `Log is missing for task attempt : ${attemptID}!`
+          });
+        }
+      });
+    }
   }
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/routes/vertex/tasks.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/routes/vertex/tasks.js b/tez-ui/src/main/webapp/app/routes/vertex/tasks.js
index 25e0f4b..5817297 100644
--- a/tez-ui/src/main/webapp/app/routes/vertex/tasks.js
+++ b/tez-ui/src/main/webapp/app/routes/vertex/tasks.js
@@ -19,6 +19,8 @@
 import Ember from 'ember';
 import MultiAmPollsterRoute from '../multi-am-pollster';
 
+import virtualAnchor from '../../utils/virtual-anchor';
+
 export default MultiAmPollsterRoute.extend({
   title: "All Tasks",
 
@@ -33,5 +35,23 @@ export default MultiAmPollsterRoute.extend({
     return this.get("loader").query('task', {
       vertexID: this.modelFor("vertex").get("id")
     }, options);
+  },
+
+  actions: {
+    logCellClicked: function (attemptID, download) {
+      var that = this;
+      return this.get("loader").queryRecord('attempt', attemptID).then(function (attempt) {
+        var logURL = attempt.get("logURL");
+        if(logURL) {
+          return virtualAnchor(logURL, download ? attempt.get("entityID") : undefined);
+        }
+        else {
+          that.send("openModal", {
+            title: "Log Link Not Available!",
+            content: `Log is missing for task attempt : ${attemptID}!`
+          });
+        }
+      });
+    }
   }
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/serializers/task.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/serializers/task.js b/tez-ui/src/main/webapp/app/serializers/task.js
index 3c57be2..991f67e 100644
--- a/tez-ui/src/main/webapp/app/serializers/task.js
+++ b/tez-ui/src/main/webapp/app/serializers/task.js
@@ -23,6 +23,9 @@ export default TimelineSerializer.extend({
     vertexID: 'primaryfilters.TEZ_VERTEX_ID.0',
     dagID: 'primaryfilters.TEZ_DAG_ID.0',
 
-    failedTaskAttempts: 'otherinfo.numFailedTaskAttempts'
+    failedTaskAttempts: 'otherinfo.numFailedTaskAttempts',
+
+    successfulAttemptID: 'otherinfo.successfulAttemptId',
+    attemptIDs: 'otherinfo.relatedentities.TEZ_TASK_ATTEMPT_ID',
   }
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/templates/components/em-table-tasks-log-link-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-tasks-log-link-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-tasks-log-link-cell.hbs
new file mode 100644
index 0000000..08e5395
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/em-table-tasks-log-link-cell.hbs
@@ -0,0 +1,25 @@
+{{!
+ * 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.
+}}
+
+{{#if content}}
+  <a href="javascript:void(0);" {{action "click"}}>View</a>
+  &nbsp;
+  <a href="javascript:void(0);" {{action "click" true}}>Download</a>
+{{else}}
+  <span class="txt-message">Not Available!</span>
+{{/if}}

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/app/utils/virtual-anchor.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/utils/virtual-anchor.js b/tez-ui/src/main/webapp/app/utils/virtual-anchor.js
new file mode 100644
index 0000000..281250a
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/utils/virtual-anchor.js
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+
+export default function virtualAnchor(url, downloadFileName) {
+  var anchor = document.createElement('a');
+
+  anchor.href = url;
+  anchor.target = '_blank';
+  if(downloadFileName) {
+    anchor.download = downloadFileName;
+  }
+
+  if(anchor.click) {
+    anchor.click();
+  }
+
+  return anchor;
+}

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/integration/components/em-table-tasks-log-link-cell-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-tasks-log-link-cell-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-tasks-log-link-cell-test.js
new file mode 100644
index 0000000..aa424b7
--- /dev/null
+++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-tasks-log-link-cell-test.js
@@ -0,0 +1,53 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('em-table-tasks-log-link-cell', 'Integration | Component | em table tasks log link cell', {
+  integration: true
+});
+
+test('Basic render test', function(assert) {
+  this.render(hbs`{{em-table-tasks-log-link-cell}}`);
+
+  assert.equal(this.$().text().trim(), 'Not Available!');
+
+  // Template block usage:" + EOL +
+  this.render(hbs`
+    {{#em-table-tasks-log-link-cell}}
+      template block text
+    {{/em-table-tasks-log-link-cell}}
+  `);
+
+  assert.equal(this.$().text().trim(), 'Not Available!');
+});
+
+test('Test with content', function(assert) {
+  let attemptID = "attempt_1";
+
+  this.set("content", attemptID);
+  this.render(hbs`{{em-table-tasks-log-link-cell content=content}}`);
+
+  let tags = this.$().find("a");
+  assert.equal(tags.length, 2);
+  assert.equal(Ember.$(tags[0]).text().trim(), 'View');
+  assert.equal(Ember.$(tags[1]).text().trim(), 'Download');
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/controllers/dag/tasks-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/dag/tasks-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/dag/tasks-test.js
index a288244..3dbf78f 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/dag/tasks-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/dag/tasks-test.js
@@ -38,4 +38,29 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.columns);
+  assert.equal(controller.columns.length, 8);
+});
+
+test('Log column test', function(assert) {
+  let controller = this.subject({
+        send: Ember.K,
+        beforeSort: {bind: Ember.K},
+        initVisibleColumns: Ember.K,
+        getCounterColumns: function () {
+          return [];
+        }
+      }),
+      testAttemptID = "attempt_1";
+
+  var getLogCellContent = controller.get("columns").findBy("id", "log").getCellContent;
+
+  assert.equal(getLogCellContent(Ember.Object.create()), undefined);
+
+  assert.equal(getLogCellContent(Ember.Object.create({
+    successfulAttemptID: testAttemptID
+  })), testAttemptID);
+
+  assert.equal(getLogCellContent(Ember.Object.create({
+    attemptIDs: ["1", "2", testAttemptID]
+  })), testAttemptID);
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/controllers/vertex/tasks-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/vertex/tasks-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/vertex/tasks-test.js
index 6949ba5..00703da 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/vertex/tasks-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/vertex/tasks-test.js
@@ -38,4 +38,29 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.columns);
+  assert.equal(controller.columns.length, 7);
 });
+
+test('Log column test', function(assert) {
+  let controller = this.subject({
+        send: Ember.K,
+        beforeSort: {bind: Ember.K},
+        initVisibleColumns: Ember.K,
+        getCounterColumns: function () {
+          return [];
+        }
+      }),
+      testAttemptID = "attempt_1";
+
+  var getLogCellContent = controller.get("columns").findBy("id", "log").getCellContent;
+
+  assert.equal(getLogCellContent(Ember.Object.create()), undefined);
+
+  assert.equal(getLogCellContent(Ember.Object.create({
+    successfulAttemptID: testAttemptID
+  })), testAttemptID);
+
+  assert.equal(getLogCellContent(Ember.Object.create({
+    attemptIDs: ["1", "2", testAttemptID]
+  })), testAttemptID);
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/models/task-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/models/task-test.js b/tez-ui/src/main/webapp/tests/unit/models/task-test.js
index 076b4eb..ed2b30d 100644
--- a/tez-ui/src/main/webapp/tests/unit/models/task-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/models/task-test.js
@@ -36,6 +36,11 @@ test('Basic creation test', function(assert) {
 
   assert.ok(model.dagID);
   assert.ok(model.dag);
+
+  assert.ok(model.failedTaskAttempts);
+
+  assert.ok(model.successfulAttemptID);
+  assert.ok(model.attemptIDs);
 });
 
 test('index test', function(assert) {

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/routes/dag/tasks-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/routes/dag/tasks-test.js b/tez-ui/src/main/webapp/tests/unit/routes/dag/tasks-test.js
index 0dc533d..ec0ad33 100644
--- a/tez-ui/src/main/webapp/tests/unit/routes/dag/tasks-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/routes/dag/tasks-test.js
@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+import Ember from 'ember';
+
 import { moduleFor, test } from 'ember-qunit';
 
 moduleFor('route:dag/tasks', 'Unit | Route | dag/tasks', {
@@ -31,6 +33,7 @@ test('Basic creation test', function(assert) {
   assert.ok(route.loaderNamespace);
   assert.ok(route.setupController);
   assert.ok(route.load);
+  assert.ok(route.actions.logCellClicked);
 });
 
 test('setupController test', function(assert) {
@@ -44,3 +47,43 @@ test('setupController test', function(assert) {
 
   route.setupController({}, {});
 });
+
+test('logCellClicked test', function(assert) {
+  assert.expect(2 * 3 + 2 + 2 + 1);
+
+  let testID = "attempt_1",
+      testLogURL = "http://abc.com/xyz",
+      route = this.subject(),
+      attemptRecord = Ember.Object.create({
+        logURL: testLogURL,
+        entityID: testID
+      });
+
+  route.loader = {
+    queryRecord: function (type, id) {
+      assert.equal(type, "attempt");
+      assert.equal(id, testID);
+
+      return Ember.RSVP.resolve(attemptRecord);
+    }
+  };
+  route.send = function (actionName) {
+    assert.equal(actionName, "openModal");
+  };
+
+  // Download false
+  route.actions.logCellClicked.call(route, testID, false).then(function (virtualAnchorInstance) {
+    assert.equal(virtualAnchorInstance.href, testLogURL);
+    assert.notOk(virtualAnchorInstance.hasOwnProperty("download"));
+  });
+
+  // Download true
+  route.actions.logCellClicked.call(route, testID, true).then(function (virtualAnchorInstance) {
+    assert.equal(virtualAnchorInstance.href, testLogURL);
+    assert.equal(virtualAnchorInstance.download, testID);
+  });
+
+  // No log
+  attemptRecord = Ember.Object.create();
+  route.actions.logCellClicked.call(route, testID, true);
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/routes/vertex/tasks-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/routes/vertex/tasks-test.js b/tez-ui/src/main/webapp/tests/unit/routes/vertex/tasks-test.js
index ee983d8..382309e 100644
--- a/tez-ui/src/main/webapp/tests/unit/routes/vertex/tasks-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/routes/vertex/tasks-test.js
@@ -36,6 +36,7 @@ test('Basic creation test', function(assert) {
   assert.ok(route.loaderNamespace);
   assert.ok(route.setupController);
   assert.ok(route.load);
+  assert.ok(route.actions.logCellClicked);
 });
 
 test('setupController test', function(assert) {
@@ -49,3 +50,43 @@ test('setupController test', function(assert) {
 
   route.setupController({}, {});
 });
+
+test('logCellClicked test', function(assert) {
+  assert.expect(2 * 3 + 2 + 2 + 1);
+
+  let testID = "attempt_1",
+      testLogURL = "http://abc.com/xyz",
+      route = this.subject(),
+      attemptRecord = Ember.Object.create({
+        logURL: testLogURL,
+        entityID: testID
+      });
+
+  route.loader = {
+    queryRecord: function (type, id) {
+      assert.equal(type, "attempt");
+      assert.equal(id, testID);
+
+      return Ember.RSVP.resolve(attemptRecord);
+    }
+  };
+  route.send = function (actionName) {
+    assert.equal(actionName, "openModal");
+  };
+
+  // Download false
+  route.actions.logCellClicked.call(route, testID, false).then(function (virtualAnchorInstance) {
+    assert.equal(virtualAnchorInstance.href, testLogURL);
+    assert.notOk(virtualAnchorInstance.hasOwnProperty("download"));
+  });
+
+  // Download true
+  route.actions.logCellClicked.call(route, testID, true).then(function (virtualAnchorInstance) {
+    assert.equal(virtualAnchorInstance.href, testLogURL);
+    assert.equal(virtualAnchorInstance.download, testID);
+  });
+
+  // No log
+  attemptRecord = Ember.Object.create();
+  route.actions.logCellClicked.call(route, testID, true);
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/serializers/task-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/serializers/task-test.js b/tez-ui/src/main/webapp/tests/unit/serializers/task-test.js
index fc79ae9..4dcec2f 100644
--- a/tez-ui/src/main/webapp/tests/unit/serializers/task-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/serializers/task-test.js
@@ -28,4 +28,6 @@ test('Basic creation test', function(assert) {
 
   assert.ok(serializer);
   assert.ok(serializer.maps);
+
+  assert.equal(Object.keys(serializer.maps).length, 5 + 7); // 5 own and 7 inherited
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/051ed16e/tez-ui/src/main/webapp/tests/unit/utils/virtual-anchor-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/utils/virtual-anchor-test.js b/tez-ui/src/main/webapp/tests/unit/utils/virtual-anchor-test.js
new file mode 100644
index 0000000..e294afa
--- /dev/null
+++ b/tez-ui/src/main/webapp/tests/unit/utils/virtual-anchor-test.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.
+ */
+
+import virtualAnchor from '../../../utils/virtual-anchor';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | virtual anchor');
+
+test('Basic creation test', function(assert) {
+  let anchor = virtualAnchor();
+  assert.ok(anchor);
+});
+
+test('Param set test', function(assert) {
+  let testURL = "http://xyz.com/abc",
+      downloadFileName = "abc.txt",
+
+      anchor = virtualAnchor(testURL, downloadFileName);
+
+  assert.ok(anchor);
+  assert.equal(anchor.href, testURL);
+  assert.equal(anchor.download, downloadFileName);
+  assert.equal(anchor.target, "_blank");
+});
+