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 2016/05/28 11:09:42 UTC

tez git commit: TEZ-3063. Tez UI: Display Input, Output, Processor, Source and Sink configurations under a vertex (sree)

Repository: tez
Updated Branches:
  refs/heads/master de7fd9aa5 -> bcf382ed9


TEZ-3063. Tez UI: Display Input, Output, Processor, Source and Sink configurations under a vertex (sree)


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

Branch: refs/heads/master
Commit: bcf382ed9bbedd25ba4a9cea074d99ef39da5093
Parents: de7fd9a
Author: Sreenath Somarajapuram <sr...@apache.org>
Authored: Sat May 28 16:40:45 2016 +0530
Committer: Sreenath Somarajapuram <sr...@apache.org>
Committed: Sat May 28 16:40:45 2016 +0530

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../src/main/webapp/app/controllers/vertex.js   |   3 +
 .../webapp/app/controllers/vertex/configs.js    | 183 +++++++++++
 tez-ui/src/main/webapp/app/models/dag.js        |   1 +
 tez-ui/src/main/webapp/app/router.js            |   1 +
 .../src/main/webapp/app/routes/app/configs.js   |   2 +-
 .../main/webapp/app/routes/vertex/configs.js    |  37 +++
 tez-ui/src/main/webapp/app/serializers/dag.js   |   1 +
 tez-ui/src/main/webapp/app/styles/app.less      |   1 +
 tez-ui/src/main/webapp/app/styles/colors.less   |   6 +-
 .../main/webapp/app/styles/details-page.less    |   7 +
 .../webapp/app/styles/vertex-configs-page.less  | 101 ++++++
 .../webapp/app/templates/vertex/configs.hbs     | 189 +++++++++++
 tez-ui/src/main/webapp/package.json             |   3 +-
 .../webapp/tests/unit/controllers/app-test.js   |   4 +-
 .../tests/unit/controllers/attempt-test.js      |   2 +
 .../webapp/tests/unit/controllers/dag-test.js   |   2 +
 .../webapp/tests/unit/controllers/task-test.js  |   2 +
 .../tests/unit/controllers/vertex-test.js       |   2 +
 .../unit/controllers/vertex/configs-test.js     | 310 +++++++++++++++++++
 .../main/webapp/tests/unit/models/dag-test.js   |   1 +
 .../tests/unit/routes/vertex/configs-test.js    |  46 +++
 22 files changed, 899 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index ed41b07..f520ee8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -40,6 +40,7 @@ ALL CHANGES:
   TEZ-3255. Tez UI: Hide swimlane while displaying running DAGs from old versions of Tez
   TEZ-3259. Tez UI: Build issue - File saver package is not working well with bower
   TEZ-3262. Tez UI : zip.js is not having a bower friendly versioning system
+  TEZ-3063. Tez UI: Display Input, Output, Processor, Source and Sink configurations under a vertex
 
 Release 0.8.4: Unreleased
 

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/controllers/vertex.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/vertex.js b/tez-ui/src/main/webapp/app/controllers/vertex.js
index 78f5db9..10d992a 100644
--- a/tez-ui/src/main/webapp/app/controllers/vertex.js
+++ b/tez-ui/src/main/webapp/app/controllers/vertex.js
@@ -48,5 +48,8 @@ export default ParentController.extend({
   }, {
     text: "Task Attempts",
     routeName: "vertex.attempts"
+  }, {
+    text: "Configurations",
+    routeName: "vertex.configs"
   }]
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/controllers/vertex/configs.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/vertex/configs.js b/tez-ui/src/main/webapp/app/controllers/vertex/configs.js
new file mode 100644
index 0000000..1cf4a3d
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/vertex/configs.js
@@ -0,0 +1,183 @@
+/*global more*/
+/**
+ * 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 TableController from '../table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+var MoreObject = more.Object;
+
+// Better fits in more-js
+function arrayfy(object) {
+  var array = [];
+  MoreObject.forEach(object, function (key, value) {
+    array.push({
+      key: key,
+      value: value
+    });
+  });
+  return array;
+}
+
+export default TableController.extend({
+  searchText: "tez",
+
+  queryParams: ["configType", "configID"],
+  configType: null,
+  configID: null,
+
+  breadcrumbs: Ember.computed("configDetails", "configID", "configType", function () {
+    var crumbs = [{
+      text: "Configurations",
+      routeName: "vertex.configs",
+      queryParams: {
+        configType: null,
+        configID: null,
+      }
+    }],
+    type = this.get("configType"),
+    name;
+
+    if(this.get("configType")) {
+      name = this.get("configDetails.name") || this.get("configDetails.desc");
+    }
+
+    if(type && name) {
+      type = type.capitalize();
+      crumbs.push({
+        text: `${type} [ ${name} ]`,
+        routeName: "vertex.configs",
+      });
+    }
+
+    return crumbs;
+  }),
+
+  setBreadcrumbs: function() {
+    this._super();
+    Ember.run.later(this, "send", "bubbleBreadcrumbs", []);
+  },
+
+  columns: ColumnDefinition.make([{
+    id: 'configName',
+    headerTitle: 'Configuration Name',
+    contentPath: 'configName',
+  }, {
+    id: 'configValue',
+    headerTitle: 'Configuration Value',
+    contentPath: 'configValue',
+  }]),
+
+  normalizeConfig: function (config) {
+    var userPayload = config.userPayloadAsText ? JSON.parse(config.userPayloadAsText) : {};
+    return {
+      id: config.name || null,
+      name: config.name,
+      desc: userPayload.desc,
+      class: config.class || config.processorClass,
+      initializer: config.initializer,
+      configs: arrayfy(userPayload.config || {})
+    };
+  },
+
+  configsHash: Ember.computed("model.name", "model.dag.vertices", function () {
+    var vertexName = this.get("model.name"),
+
+        inputConfigs = [],
+        outputConfigs = [],
+        vertexDetails;
+
+    if(!this.get("model")) {
+      return {};
+    }
+
+    vertexDetails = this.get("model.dag.vertices").findBy("vertexName", vertexName);
+
+    (this.get("model.dag.edges") || []).forEach(function (edge) {
+      if(edge.outputVertexName === vertexName) {
+        let payload = edge.outputUserPayloadAsText;
+        inputConfigs.push({
+          id: edge.edgeId,
+          desc: `From ${edge.inputVertexName}`,
+          class: edge.edgeDestinationClass,
+          configs: arrayfy(payload ? Ember.get(JSON.parse(payload), "config") : {})
+        });
+      }
+      else if(edge.inputVertexName === vertexName) {
+        let payload = edge.inputUserPayloadAsText;
+        outputConfigs.push({
+          id: edge.edgeId,
+          desc: `To ${edge.outputVertexName}`,
+          class: edge.edgeSourceClass,
+          configs: arrayfy(payload ? Ember.get(JSON.parse(payload), "config") : {})
+        });
+      }
+    });
+
+    return {
+      processor: this.normalizeConfig(vertexDetails),
+
+      sources: (vertexDetails.additionalInputs || []).map(this.normalizeConfig),
+      sinks: (vertexDetails.additionalOutputs || []).map(this.normalizeConfig),
+
+      inputs: inputConfigs,
+      outputs: outputConfigs
+    };
+  }),
+
+  configDetails: Ember.computed("configsHash", "configType", "configID", function () {
+    var configType = this.get("configType"),
+        details;
+
+    if(configType) {
+      details = Ember.get(this.get("configsHash"), configType);
+    }
+
+    if(Array.isArray(details)) {
+      details = details.findBy("id", this.get("configID"));
+    }
+
+    return details;
+  }),
+
+  configs: Ember.computed("configDetails", function () {
+    var configs = this.get("configDetails.configs");
+
+    if(Array.isArray(configs)) {
+      return Ember.A(configs.map(function (config) {
+        return Ember.Object.create({
+          configName: config.key,
+          configValue: config.value
+        });
+      }));
+    }
+  }),
+
+  actions: {
+    showConf: function (type, details) {
+      this.setProperties({
+        configType: type,
+        configID: details.id
+      });
+      Ember.run.later(this, "send", "bubbleBreadcrumbs", []);
+    }
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/models/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/models/dag.js b/tez-ui/src/main/webapp/app/models/dag.js
index 84509e1..5e011e2 100644
--- a/tez-ui/src/main/webapp/app/models/dag.js
+++ b/tez-ui/src/main/webapp/app/models/dag.js
@@ -63,6 +63,7 @@ export default AMTimelineModel.extend({
   }),
 
   vertexIdNameMap: DS.attr("object"),
+  vertexNameIdMap: DS.attr("object"),
 
   callerID: DS.attr("string"),
   callerContext: DS.attr("string"),

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/router.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/router.js b/tez-ui/src/main/webapp/app/router.js
index 98a456a..7f18ae6 100644
--- a/tez-ui/src/main/webapp/app/router.js
+++ b/tez-ui/src/main/webapp/app/router.js
@@ -38,6 +38,7 @@ Router.map(function() {
     this.route('tasks');
     this.route('attempts');
     this.route('counters');
+    this.route('configs');
   });
   this.route('task', {path: '/task/:task_id'}, function() {
     this.route('attempts');

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/routes/app/configs.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/routes/app/configs.js b/tez-ui/src/main/webapp/app/routes/app/configs.js
index bdd53ae..c58f2f1 100644
--- a/tez-ui/src/main/webapp/app/routes/app/configs.js
+++ b/tez-ui/src/main/webapp/app/routes/app/configs.js
@@ -20,7 +20,7 @@ import Ember from 'ember';
 import SingleAmPollsterRoute from '../single-am-pollster';
 
 export default SingleAmPollsterRoute.extend({
-  title: "Application Details",
+  title: "Application Configurations",
 
   loaderNamespace: "app",
 

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/routes/vertex/configs.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/routes/vertex/configs.js b/tez-ui/src/main/webapp/app/routes/vertex/configs.js
new file mode 100644
index 0000000..a131896
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/routes/vertex/configs.js
@@ -0,0 +1,37 @@
+/**
+ * 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 SingleAmPollsterRoute from '../single-am-pollster';
+
+export default SingleAmPollsterRoute.extend({
+  title: "Vertex Configurations",
+
+  loaderNamespace: "vertex",
+
+  canPoll: false,
+
+  setupController: function (controller, model) {
+    this._super(controller, model);
+    Ember.run.later(this, "startCrumbBubble");
+  },
+
+  load: function (value, query, options) {
+    return this.get("loader").queryRecord('vertex', this.modelFor("vertex").get("id"), options);
+  },
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/serializers/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/serializers/dag.js b/tez-ui/src/main/webapp/app/serializers/dag.js
index a64bfe1..bf31174 100644
--- a/tez-ui/src/main/webapp/app/serializers/dag.js
+++ b/tez-ui/src/main/webapp/app/serializers/dag.js
@@ -125,6 +125,7 @@ export default TimelineSerializer.extend({
     containerLogs: getContainerLogs,
 
     vertexIdNameMap: getIdNameMap,
+    vertexNameIdMap: 'otherinfo.vertexNameIdMapping',
 
     callerID: 'primaryfilters.callerId.0',
     callerContext: 'callerContext',

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/styles/app.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/app.less b/tez-ui/src/main/webapp/app/styles/app.less
index c881553..b4a4c47 100644
--- a/tez-ui/src/main/webapp/app/styles/app.less
+++ b/tez-ui/src/main/webapp/app/styles/app.less
@@ -50,3 +50,4 @@
 @import "page-layout";
 @import "details-page";
 @import "swimlane-page";
+@import "vertex-configs-page";

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/styles/colors.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/colors.less b/tez-ui/src/main/webapp/app/styles/colors.less
index af470ff..bc4a398 100644
--- a/tez-ui/src/main/webapp/app/styles/colors.less
+++ b/tez-ui/src/main/webapp/app/styles/colors.less
@@ -19,14 +19,14 @@
 // Colors
 @logo-orange: #D27A22;
 
-@bg-lite: #f5f5f5;
+@bg-lite: #f0f0f0;
 @bg-liter: #f5f5f5;
 @bg-red-light: #FFE6E6;
 
 @bg-grey: #f0f0f0;
 
 @border-lite: #e5e5e5;
-@border-color: #dcdcdc;
+@border-color: #ddd;
 
 @white: #fff;
 
@@ -41,4 +41,4 @@
 @success-color: limegreen;
 @error-color: crimson;
 @warning-color: orange;
-@unknown-color: crimson;
\ No newline at end of file
+@unknown-color: crimson;

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/styles/details-page.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/details-page.less b/tez-ui/src/main/webapp/app/styles/details-page.less
index 75d5e11..54380e2 100644
--- a/tez-ui/src/main/webapp/app/styles/details-page.less
+++ b/tez-ui/src/main/webapp/app/styles/details-page.less
@@ -22,6 +22,10 @@
 
   margin: 0 10px 10px 0;
 
+  &:first-child {
+    margin: 0 0 10px 0;
+  }
+
   table-layout: fixed;
 
   .progress {
@@ -40,7 +44,10 @@
 
   td {
     padding: 0px 20px 0px 0px;
+
+    max-width: 600px;
     white-space: nowrap;
+    overflow: auto;
 
     .ember-view {
       display: inline;

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/styles/vertex-configs-page.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/vertex-configs-page.less b/tez-ui/src/main/webapp/app/styles/vertex-configs-page.less
new file mode 100644
index 0000000..ffa1bdd
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/styles/vertex-configs-page.less
@@ -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.
+ */
+
+.vertex-configs-container {
+  display: inline-block;
+  width: 40%;
+  vertical-align: top;
+
+  .row-container {
+    display: flex;
+  }
+
+  .column-container {
+    display: inline-block;
+  }
+
+  .column-container, .processor {
+    margin: 0 5px 0 5px;
+    flex: 1 1;
+
+    .box {
+      border: 1px solid @border-color;
+      border-radius: 5px;
+      overflow: hidden;
+
+      .header, .config-cell {
+        padding: 5px;
+      }
+
+      .header {
+        height: auto;
+        margin: 0px;
+      }
+
+      .config-cell {
+        cursor: pointer;
+
+        border-top: 1px dotted @border-color;
+
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+
+        .count-txt {
+          color: @text-light;
+          font-size: .8em;
+        }
+
+        &:nth-child(2) {
+          border-top: none;
+        }
+
+        &.selected {
+          background-color: @bg-liter;
+        }
+
+        &:hover {
+          background-color: @bg-lite;
+        }
+      }
+    }
+
+    .link {
+      border-left: 1px solid @border-color;
+      margin-left: 50%;
+      height: 10px;
+    }
+  }
+
+  .top-column {
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+  }
+
+  .processor {
+    text-align: center;
+  }
+
+}
+
+.configuration-details {
+  display: inline-block;
+  width: 60%;
+  padding-left: 10px;
+}

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/app/templates/vertex/configs.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/vertex/configs.hbs b/tez-ui/src/main/webapp/app/templates/vertex/configs.hbs
new file mode 100644
index 0000000..de6d3a9
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/vertex/configs.hbs
@@ -0,0 +1,189 @@
+{{!
+ * 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 loaded}}
+
+  <!-- Vertex configuration visualization -->
+  <div class="vertex-configs-container">
+    <div class="row-container">
+      {{#if configsHash.sources.length}}
+        <div class="column-container top-column">
+          <div class="box">
+            <div class="header">Sources</div>
+            {{#each configsHash.sources as |source|}}
+              <div class="config-cell {{if (eq source configDetails) 'selected'}}"
+                {{action 'showConf' 'sources' source}}>
+                {{source.name}}
+                {{#if source.desc}}
+                  [ {{source.desc}} ]
+                {{/if}}
+                <div class="count-txt">
+                  {{#if source.configs.length}}
+                    Configurations: {{source.configs.length}}
+                  {{else}}
+                    Configuration not available!
+                  {{/if}}
+                </div>
+              </div>
+            {{/each}}
+          </div>
+          <div class="link"></div>
+        </div>
+      {{/if}}
+      {{#if configsHash.inputs.length}}
+        <div class="column-container top-column">
+          <div class="box">
+            <div class="header">Inputs</div>
+            {{#each configsHash.inputs as |input|}}
+              <div class="config-cell {{if (eq input configDetails) 'selected'}}"
+                {{action 'showConf' 'inputs' input}}>
+                {{input.desc}}
+                <div class="count-txt">
+                  {{#if input.configs.length}}
+                    Configurations: {{input.configs.length}}
+                  {{else}}
+                    Configuration not available!
+                  {{/if}}
+                </div>
+              </div>
+            {{/each}}
+          </div>
+          <div class="link"></div>
+        </div>
+      {{/if}}
+    </div>
+    <div class="processor">
+      <div class="box">
+        <div class="header">Processor</div>
+        <div class="config-cell {{if (eq configsHash.processor configDetails) 'selected'}}"
+          {{action 'showConf' 'processor' configsHash.processor}}>
+          {{configsHash.processor.desc}}
+          <div class="count-txt">
+            {{#if configsHash.processor.configs.length}}
+              Configurations: {{configsHash.processor.configs.length}}
+            {{else}}
+              Configuration not available!
+            {{/if}}
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row-container">
+      {{#if configsHash.sinks.length}}
+        <div class="column-container">
+          <div class="link"></div>
+          <div class="box">
+            <div class="header">Sinks</div>
+            {{#each configsHash.sinks as |sink|}}
+              <div class="config-cell {{if (eq sink configDetails) 'selected'}}"
+                {{action 'showConf' 'sinks' sink}}>
+                {{sink.name}}
+                {{#if sink.desc}}
+                  [ {{sink.desc}} ]
+                {{/if}}
+                <div class="count-txt">
+                  {{#if sink.configs.length}}
+                    Configurations: {{sink.configs.length}}
+                  {{else}}
+                    Configuration not available!
+                  {{/if}}
+                </div>
+              </div>
+            {{/each}}
+          </div>
+        </div>
+      {{/if}}
+      {{#if configsHash.outputs.length}}
+        <div class="column-container">
+          <div class="link"></div>
+          <div class="box">
+            <div class="header">Outputs</div>
+            {{#each configsHash.outputs as |output|}}
+              <div class="config-cell {{if (eq output configDetails) 'selected'}}"
+                {{action 'showConf' 'outputs' output}}>
+                {{output.desc}}
+                <div class="count-txt">
+                  {{#if output.configs.length}}
+                    Configurations: {{output.configs.length}}
+                  {{else}}
+                    Configuration not available!
+                  {{/if}}
+                </div>
+              </div>
+            {{/each}}
+          </div>
+        </div>
+      {{/if}}
+    </div>
+  </div><div class="configuration-details">
+
+    {{#if configType}}
+      <!-- Configuration details display -->
+      <table class='detail-list'>
+        <thead>
+        <tr>
+          <th colspan=2>Details</th>
+        </tr>
+        </thead>
+        <tbody>
+        {{#if configDetails.name}}
+          <tr>
+            <td>Name</td>
+            <td>{{configDetails.name}}</td>
+          </tr>
+        {{/if}}
+        {{#if configDetails.desc}}
+          <tr>
+            <td>Description</td>
+            <td>{{configDetails.desc}}</td>
+          </tr>
+        {{/if}}
+        {{#if configDetails.class}}
+          <tr>
+            <td>Class</td>
+            <td>{{configDetails.class}}</td>
+          </tr>
+        {{/if}}
+        {{#if configDetails.initializer}}
+          <tr>
+            <td>Initializer</td>
+            <td>{{configDetails.initializer}}</td>
+          </tr>
+        {{/if}}
+        </tbody>
+      </table>
+
+      <!-- Configurations display -->
+      {{em-table
+        columns=columns
+        rows=configs
+
+        rowCount=configs.length
+        definition=definition
+
+        enablePagination=false
+
+        searchAction="searchChanged"
+        sortAction="sortChanged"
+      }}
+    {{/if}}
+  </div>
+
+{{else}}
+  {{partial "loading"}}
+{{/if}}

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/package.json
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/package.json b/tez-ui/src/main/webapp/package.json
index accddec..7916d85 100644
--- a/tez-ui/src/main/webapp/package.json
+++ b/tez-ui/src/main/webapp/package.json
@@ -53,10 +53,11 @@
     "ember-disable-proxy-controllers": "1.0.1",
     "ember-export-application-global": "1.0.5",
     "ember-resolver": "2.0.3",
+    "ember-truth-helpers": "1.2.0",
     "phantomjs": "1.9.19"
   },
   "dependencies": {
-    "em-helpers": "0.5.8",
+    "em-helpers": "0.5.9",
     "em-table": "0.3.12",
     "em-tgraph": "0.0.5"
   }

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/controllers/app-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/app-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/app-test.js
index 2fc7276..f969d7e 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/app-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/app-test.js
@@ -20,7 +20,7 @@ import Ember from 'ember';
 
 import { moduleFor, test } from 'ember-qunit';
 
-moduleFor('controller:dag', 'Unit | Controller | dag', {
+moduleFor('controller:app', 'Unit | Controller | app', {
   // Specify the other units that are required for this test.
   // needs: ['controller:foo']
 });
@@ -34,4 +34,6 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.tabs);
+
+  assert.equal(controller.tabs.length, 3);
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/controllers/attempt-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/attempt-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/attempt-test.js
index da451f7..4053470 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/attempt-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/attempt-test.js
@@ -34,4 +34,6 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.tabs);
+
+  assert.equal(controller.tabs.length, 2);
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/controllers/dag-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/dag-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/dag-test.js
index 2fc7276..f40cd48 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/dag-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/dag-test.js
@@ -34,4 +34,6 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.tabs);
+
+  assert.equal(controller.tabs.length, 7);
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/controllers/task-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/task-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/task-test.js
index c9cee79..98c338b 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/task-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/task-test.js
@@ -34,4 +34,6 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.tabs);
+
+  assert.equal(controller.tabs.length, 3);
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/controllers/vertex-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/vertex-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/vertex-test.js
index e8e9b3f..2283110 100644
--- a/tez-ui/src/main/webapp/tests/unit/controllers/vertex-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/vertex-test.js
@@ -34,4 +34,6 @@ test('Basic creation test', function(assert) {
   assert.ok(controller);
   assert.ok(controller.breadcrumbs);
   assert.ok(controller.tabs);
+
+  assert.equal(controller.tabs.length, 5);
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/controllers/vertex/configs-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/controllers/vertex/configs-test.js b/tez-ui/src/main/webapp/tests/unit/controllers/vertex/configs-test.js
new file mode 100644
index 0000000..89015a5
--- /dev/null
+++ b/tez-ui/src/main/webapp/tests/unit/controllers/vertex/configs-test.js
@@ -0,0 +1,310 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:vertex/configs', 'Unit | Controller | vertex/configs', {
+  // Specify the other units that are required for this test.
+  // needs: ['controller:foo']
+});
+
+test('Basic creation test', function(assert) {
+  let controller = this.subject({
+    send: Ember.K,
+    initVisibleColumns: Ember.K
+  });
+
+  assert.ok(controller);
+
+  assert.ok(controller.breadcrumbs);
+  assert.ok(controller.setBreadcrumbs);
+
+  assert.ok(controller.columns);
+  assert.equal(controller.columns.length, 2);
+
+  assert.ok(controller.normalizeConfig);
+  assert.ok(controller.configsHash);
+  assert.ok(controller.configDetails);
+  assert.ok(controller.configs);
+
+  assert.ok(controller.actions.showConf);
+
+  assert.equal(controller.searchText, "tez");
+  assert.notEqual(controller.queryParams.indexOf("configType"), -1);
+  assert.notEqual(controller.queryParams.indexOf("configID"), -1);
+});
+
+test('Breadcrumbs test', function(assert) {
+  let controller = this.subject({
+    send: Ember.K,
+    initVisibleColumns: Ember.K,
+    configDetails: {
+      name: "name"
+    }
+  });
+
+  assert.equal(controller.get("breadcrumbs").length, 1);
+  assert.equal(controller.get("breadcrumbs")[0].text, "Configurations");
+  assert.equal(controller.get("breadcrumbs")[0].queryParams.configType, null);
+  assert.equal(controller.get("breadcrumbs")[0].queryParams.configID, null);
+
+  controller.setProperties({
+    configType: "TestType",
+    configID: "ID",
+  });
+  assert.equal(controller.get("breadcrumbs").length, 2);
+  assert.equal(controller.get("breadcrumbs")[1].text, "TestType [ name ]");
+});
+
+test('normalizeConfig test', function(assert) {
+  let controller = this.subject({
+    send: Ember.K,
+    initVisibleColumns: Ember.K,
+  }),
+  testName = "name",
+  testClass = "TestClass",
+  testInit = "TestInit",
+  payload = {
+    desc: 'abc',
+    config: {
+      x:1,
+      y:2
+    }
+  },
+  config;
+
+  // Processor
+  config = controller.normalizeConfig({
+    processorClass: testClass,
+    userPayloadAsText: JSON.stringify(payload)
+  });
+  assert.equal(config.id, null);
+  assert.equal(config.class, testClass);
+  assert.equal(config.desc, payload.desc);
+  assert.deepEqual(config.configs, [{key: "x", value: 1}, {key: "y", value: 2}]);
+
+  // Inputs & outputs
+  config = controller.normalizeConfig({
+    name: testName,
+    class: testClass,
+    initializer: testInit,
+    userPayloadAsText: JSON.stringify(payload)
+  });
+  assert.equal(config.id, testName);
+  assert.equal(config.class, testClass);
+  assert.equal(config.initializer, testInit);
+  assert.equal(config.desc, payload.desc);
+  assert.deepEqual(config.configs, [{key: "x", value: 1}, {key: "y", value: 2}]);
+});
+
+test('configsHash test', function(assert) {
+  let controller = this.subject({
+        send: Ember.K,
+        initVisibleColumns: Ember.K,
+      });
+
+  assert.deepEqual(controller.get("configsHash"), {});
+
+  controller.set("model", {
+    dag: {
+      vertices: [
+        {
+          "vertexName": "v1",
+          "processorClass": "org.apache.tez.mapreduce.processor.map.MapProcessor",
+          "userPayloadAsText": "{\"desc\":\"Tokenizer Vertex\",\"config\":{\"config.key\":\"11\"}}",
+          "additionalInputs": [
+            {
+              "name": "MRInput",
+              "class": "org.apache.tez.mapreduce.input.MRInputLegacy",
+              "initializer": "org.apache.tez.mapreduce.common.MRInputAMSplitGenerator",
+              "userPayloadAsText": "{\"desc\":\"HDFS Input\",\"config\":{\"config.key\":\"22\"}}"
+            }
+          ]
+        },
+        {
+          "vertexName": "v2",
+          "processorClass": "org.apache.tez.mapreduce.processor.reduce.ReduceProcessor",
+          "userPayloadAsText": "{\"desc\":\"Summation Vertex\",\"config\":{\"config.key\":\"33\"}}"
+        },
+        {
+          "vertexName": "v3",
+          "processorClass": "org.apache.tez.mapreduce.processor.reduce.ReduceProcessor",
+          "userPayloadAsText": "{\"desc\":\"Sorter Vertex\",\"config\":{\"config.key1\":\"44\", \"config.key2\":\"444\"}}",
+          "additionalOutputs": [
+            {
+              "name": "MROutput",
+              "class": "org.apache.tez.mapreduce.output.MROutputLegacy",
+              "initializer": "org.apache.tez.mapreduce.committer.MROutputCommitter",
+              "userPayloadAsText": "{\"desc\":\"HDFS Output\",\"config\":{\"config.key\":\"55\"}}"
+            }
+          ]
+        }
+      ],
+      edges: [
+        {
+          "edgeId": "edg1",
+          "inputVertexName": "v2",
+          "outputVertexName": "v3",
+          "edgeSourceClass": "org.apache.tez.runtime.library.output.OrderedPartitionedKVOutput",
+          "edgeDestinationClass": "org.apache.tez.runtime.library.input.OrderedGroupedInputLegacy",
+          "outputUserPayloadAsText": "{\"config\":{\"config.key\":\"66\"}}",
+          "inputUserPayloadAsText": "{\"config\":{\"config.key\":\"77\"}}",
+        },
+        {
+          "edgeId": "edg2",
+          "inputVertexName": "v1",
+          "outputVertexName": "v2",
+          "edgeSourceClass": "org.apache.tez.runtime.library.output.OrderedPartitionedKVOutput",
+          "edgeDestinationClass": "org.apache.tez.runtime.library.input.OrderedGroupedInputLegacy",
+          "outputUserPayloadAsText": "{\"config\":{\"config.key\":\"88\"}}",
+          "inputUserPayloadAsText": "{\"config\":{\"config.key\":\"99\"}}",
+        }
+      ]
+    }
+  });
+
+  // Test for vertex v1
+  controller.set("model.name", "v1");
+
+  assert.ok(controller.get("configsHash.processor"));
+  assert.equal(controller.get("configsHash.processor.name"), null);
+  assert.equal(controller.get("configsHash.processor.desc"), "Tokenizer Vertex");
+  assert.equal(controller.get("configsHash.processor.class"), "org.apache.tez.mapreduce.processor.map.MapProcessor");
+  assert.equal(controller.get("configsHash.processor.configs.length"), 1);
+  assert.equal(controller.get("configsHash.processor.configs.0.key"), "config.key");
+  assert.equal(controller.get("configsHash.processor.configs.0.value"), 11);
+
+  assert.ok(controller.get("configsHash.sources"));
+  assert.equal(controller.get("configsHash.sources.length"), 1);
+  assert.equal(controller.get("configsHash.sources.0.name"), "MRInput");
+  assert.equal(controller.get("configsHash.sources.0.desc"), "HDFS Input");
+  assert.equal(controller.get("configsHash.sources.0.class"), "org.apache.tez.mapreduce.input.MRInputLegacy");
+  assert.equal(controller.get("configsHash.sources.0.initializer"), "org.apache.tez.mapreduce.common.MRInputAMSplitGenerator");
+  assert.equal(controller.get("configsHash.sources.0.configs.length"), 1);
+  assert.equal(controller.get("configsHash.sources.0.configs.0.key"), "config.key");
+  assert.equal(controller.get("configsHash.sources.0.configs.0.value"), 22);
+
+  assert.ok(controller.get("configsHash.sinks"));
+  assert.equal(controller.get("configsHash.sinks.length"), 0);
+
+  assert.ok(controller.get("configsHash.inputs"));
+  assert.equal(controller.get("configsHash.inputs.length"), 0);
+
+  assert.ok(controller.get("configsHash.outputs"));
+  assert.equal(controller.get("configsHash.outputs.length"), 1);
+  assert.equal(controller.get("configsHash.outputs.0.name"), null);
+  assert.equal(controller.get("configsHash.outputs.0.desc"), "To v2");
+  assert.equal(controller.get("configsHash.outputs.0.class"), "org.apache.tez.runtime.library.output.OrderedPartitionedKVOutput");
+  assert.equal(controller.get("configsHash.outputs.0.configs.length"), 1);
+  assert.equal(controller.get("configsHash.outputs.0.configs.0.key"), "config.key");
+  assert.equal(controller.get("configsHash.outputs.0.configs.0.value"), 99);
+
+  // Test for vertex v3
+  controller.set("model.name", "v3");
+
+  assert.ok(controller.get("configsHash.processor"));
+  assert.equal(controller.get("configsHash.processor.name"), null);
+  assert.equal(controller.get("configsHash.processor.desc"), "Sorter Vertex");
+  assert.equal(controller.get("configsHash.processor.class"), "org.apache.tez.mapreduce.processor.reduce.ReduceProcessor");
+  assert.equal(controller.get("configsHash.processor.configs.length"), 2);
+  assert.equal(controller.get("configsHash.processor.configs.0.key"), "config.key1");
+  assert.equal(controller.get("configsHash.processor.configs.0.value"), 44);
+  assert.equal(controller.get("configsHash.processor.configs.1.key"), "config.key2");
+  assert.equal(controller.get("configsHash.processor.configs.1.value"), 444);
+
+  assert.ok(controller.get("configsHash.sources"));
+  assert.equal(controller.get("configsHash.sources.length"), 0);
+
+  assert.ok(controller.get("configsHash.sinks"));
+  assert.equal(controller.get("configsHash.sinks.length"), 1);
+  assert.equal(controller.get("configsHash.sinks.0.name"), "MROutput");
+  assert.equal(controller.get("configsHash.sinks.0.desc"), "HDFS Output");
+  assert.equal(controller.get("configsHash.sinks.0.class"), "org.apache.tez.mapreduce.output.MROutputLegacy");
+  assert.equal(controller.get("configsHash.sinks.0.initializer"), "org.apache.tez.mapreduce.committer.MROutputCommitter");
+  assert.equal(controller.get("configsHash.sinks.0.configs.length"), 1);
+  assert.equal(controller.get("configsHash.sinks.0.configs.0.key"), "config.key");
+  assert.equal(controller.get("configsHash.sinks.0.configs.0.value"), 55);
+
+  assert.ok(controller.get("configsHash.inputs"));
+  assert.equal(controller.get("configsHash.inputs.length"), 1);
+  assert.equal(controller.get("configsHash.inputs.0.name"), null);
+  assert.equal(controller.get("configsHash.inputs.0.desc"), "From v2");
+  assert.equal(controller.get("configsHash.inputs.0.class"), "org.apache.tez.runtime.library.input.OrderedGroupedInputLegacy");
+  assert.equal(controller.get("configsHash.inputs.0.configs.length"), 1);
+  assert.equal(controller.get("configsHash.inputs.0.configs.0.key"), "config.key");
+  assert.equal(controller.get("configsHash.inputs.0.configs.0.value"), 66);
+
+  assert.ok(controller.get("configsHash.outputs"));
+  assert.equal(controller.get("configsHash.outputs.length"), 0);
+
+});
+
+test('configDetails test', function(assert) {
+  let configsHash = {
+        type: [{
+          id: "id1"
+        },{
+          id: "id2"
+        }]
+      },
+      controller = this.subject({
+        send: Ember.K,
+        initVisibleColumns: Ember.K,
+        configsHash: configsHash
+      });
+
+  assert.equal(controller.get("configDetails"), undefined);
+
+  controller.set("configType", "random");
+  assert.equal(controller.get("configDetails"), undefined);
+
+  controller.set("configType", "type");
+  assert.equal(controller.get("configDetails"), undefined);
+
+  controller.set("configID", "id1");
+  assert.equal(controller.get("configDetails"), configsHash.type[0]);
+
+  controller.set("configID", "id2");
+  assert.equal(controller.get("configDetails"), configsHash.type[1]);
+});
+
+test('configs test', function(assert) {
+  let controller = this.subject({
+    send: Ember.K,
+    initVisibleColumns: Ember.K,
+    configDetails: {
+      configs: [{
+        key: "x",
+        value: 1
+      }, {
+        key: "y",
+        value: 2
+      }]
+    }
+  });
+
+  assert.equal(controller.get("configs").length, 2);
+  assert.ok(controller.get("configs.0") instanceof Ember.Object);
+
+  assert.equal(controller.get("configs.0.configName"), "x");
+  assert.equal(controller.get("configs.0.configValue"), 1);
+  assert.equal(controller.get("configs.1.configName"), "y");
+  assert.equal(controller.get("configs.1.configValue"), 2);
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/models/dag-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/models/dag-test.js b/tez-ui/src/main/webapp/tests/unit/models/dag-test.js
index 78affcf..2c07f7b 100644
--- a/tez-ui/src/main/webapp/tests/unit/models/dag-test.js
+++ b/tez-ui/src/main/webapp/tests/unit/models/dag-test.js
@@ -49,6 +49,7 @@ test('Basic creation test', function(assert) {
   assert.ok(model.containerLogs);
 
   assert.ok(model.vertexIdNameMap);
+  assert.ok(model.vertexNameIdMap);
 
   assert.ok(model.callerID);
   assert.ok(model.callerContext);

http://git-wip-us.apache.org/repos/asf/tez/blob/bcf382ed/tez-ui/src/main/webapp/tests/unit/routes/vertex/configs-test.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/tests/unit/routes/vertex/configs-test.js b/tez-ui/src/main/webapp/tests/unit/routes/vertex/configs-test.js
new file mode 100644
index 0000000..e293a7e
--- /dev/null
+++ b/tez-ui/src/main/webapp/tests/unit/routes/vertex/configs-test.js
@@ -0,0 +1,46 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('route:vertex/configs', 'Unit | Route | vertex/configs', {
+  // Specify the other units that are required for this test.
+  // needs: ['controller:foo']
+});
+
+test('Basic creation test', function(assert) {
+  let route = this.subject();
+
+  assert.ok(route);
+  assert.ok(route.title);
+  assert.ok(route.loaderNamespace);
+  assert.ok(route.setupController);
+  assert.ok(route.load);
+});
+
+test('setupController test', function(assert) {
+  assert.expect(1);
+
+  let route = this.subject({
+    startCrumbBubble: function () {
+      assert.ok(true);
+    }
+  });
+
+  route.setupController({}, {});
+});