You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ma...@apache.org on 2013/02/04 03:24:02 UTC

svn commit: r1442010 [29/29] - in /incubator/ambari/branches/branch-1.2: ./ ambari-agent/ ambari-agent/conf/unix/ ambari-agent/src/examples/ ambari-agent/src/main/puppet/modules/hdp-ganglia/files/ ambari-agent/src/main/puppet/modules/hdp-ganglia/manife...

Modified: incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step2_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step2_view.js?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step2_view.js (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step2_view.js Mon Feb  4 02:23:55 2013
@@ -20,7 +20,8 @@
 var App = require('app');
 
 App.SshKeyFileUploader = Ember.View.extend({
-  template:Ember.Handlebars.compile('<input type="file" />'),
+  template:Ember.Handlebars.compile('<input type="file" {{bindAttr disabled="view.disabled"}} />'),
+  classNames: ['ssh-key-input-indentation'],
 
   change: function (e) {
     var self=this;
@@ -61,62 +62,9 @@ App.WizardStep2View = Em.View.extend({
     this.set('controller.sshKeyError',null);
     this.loadHostsInfo();
   },
-  /**
-   * Config for displaying more hosts
-   * if oldHosts.length more than config.count that configuration will be applied
-   */
-  hostDisplayConfig: [
-    {
-      count: 0,
-      delimitery: '<br/>',
-      popupDelimitery: '<br />'
-    },
-    {
-      count: 10,
-      delimitery: ', ',
-      popupDelimitery: '<br />'
-    },
-    {
-      count: 50,
-      delimitery: ', ',
-      popupDelimitery: ', '
-    }
-  ],
-  showMoreHosts: function () {
-    var self = this;
-    App.ModalPopup.show({
-      header: "Hosts are already part of the cluster and will be ignored",
-      body: self.get('hostsInfo.oldHostNamesMore'),
-      encodeBody: false,
-      onPrimary: function () {
-        this.hide();
-      },
-      secondary: null
-    });
-  },
-  loadHostsInfo: function(){
 
+  loadHostsInfo: function(){
     var hostsInfo = Em.Object.create();
-
-    var oldHostNames = App.Host.find().getEach('id');
-    var k = 10;
-
-    var usedConfig = false;
-    this.get('hostDisplayConfig').forEach(function (config) {
-      if (oldHostNames.length > config.count) {
-        usedConfig = config;
-      }
-    });
-
-    k = usedConfig.count ? usedConfig.count : oldHostNames.length;
-    var displayedHostNames = oldHostNames.slice(0, k);
-    hostsInfo.set('oldHostNames', displayedHostNames.join(usedConfig.delimitery));
-    if (usedConfig.count) {
-      var moreHostNames = oldHostNames.slice(k + 1);
-      hostsInfo.set('oldHostNamesMore', moreHostNames.join(usedConfig.popupDelimitery));
-      hostsInfo.set('showMoreHostsText', "...and %@ more".fmt(moreHostNames.length));
-    }
-
     this.set('hostsInfo', hostsInfo);
   },
 
@@ -132,26 +80,10 @@ App.WizardStep2View = Em.View.extend({
     return this.get("controller.content.installOptions.manualInstall");
   }.property("controller.content.installOptions.manualInstall"),
 
-  sshKeyClass:function() {
-    return (this.get("isFileApi")) ? "hide" : "" ;
-  }.property("isFileApi"),
-
   isFileApi: function () {
     return (window.File && window.FileReader && window.FileList) ? true : false ;
   }.property(),
 
-  sshKeyPreviewClass: function() {
-    if (this.get('controller.content.installOptions.sshKey').trim() != '') {
-      if (this.get('controller.content.installOptions.manualInstall')) {
-        return 'sshKey-file-view disabled';
-      } else {
-        return 'sshKey-file-view';
-      }
-    } else {
-      return 'hidden';
-    }
-  }.property('controller.content.installOptions.sshKey', 'controller.content.installOptions.manualInstall'),
-
   manualInstallPopup: function(){
     if(!this.get('controller.content.installOptions.useSsh')){
       App.ModalPopup.show({
@@ -165,8 +97,35 @@ App.WizardStep2View = Em.View.extend({
       });
     }
     this.set('controller.content.installOptions.manualInstall', !this.get('controller.content.installOptions.useSsh'));
-  }.observes('controller.content.installOptions.useSsh')
+  }.observes('controller.content.installOptions.useSsh'),
 
+  providingSSHKeyRadioButton: Ember.Checkbox.extend({
+    tagName: 'input',
+    attributeBindings: ['type', 'checked'],
+    checked: function () {
+      return this.get('controller.content.installOptions.useSsh');
+    }.property('controller.content.installOptions.useSsh'),
+    type: 'radio',
+
+    click: function () {
+      this.set('controller.content.installOptions.useSsh', true);
+      this.set('controller.content.installOptions.manualInstall', false);
+    }
+  }),
+
+  manualRegistrationRadioButton: Ember.Checkbox.extend({
+    tagName: 'input',
+    attributeBindings: ['type', 'checked'],
+    checked: function () {
+      return this.get('controller.content.installOptions.manualInstall');
+    }.property('controller.content.installOptions.manualInstall'),
+    type: 'radio',
+
+    click: function () {
+      this.set('controller.content.installOptions.manualInstall', true);
+      this.set('controller.content.installOptions.useSsh', false);
+    }
+  })
 });
 
 

Modified: incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step8_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step8_view.js?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step8_view.js (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step8_view.js Mon Feb  4 02:23:55 2013
@@ -27,6 +27,7 @@ App.WizardStep8View = Em.View.extend({
     var controller = this.get('controller');
     controller.loadStep();
   },
+
   spinner : null,
 
   printReview: function() {
@@ -34,49 +35,60 @@ App.WizardStep8View = Em.View.extend({
     o.jqprint();
   },
 
-  showLoadingIndicator: function(){
-    if(this.get('controller.hasErrorOccurred')){
-      $('.spinner').hide();
+  ajaxQueueLength: function() {
+    return this.get('controller.ajaxQueueLength');
+  }.property('controller.ajaxQueueLength'),
+
+  ajaxQueueLeft: function() {
+    return this.get('controller.ajaxQueueLeft');
+  }.property('controller.ajaxQueueLeft'),
+
+  // reference to modalPopup to make sure only one instance is created
+  modalPopup: null,
+
+  showLoadingIndicator: function() {
+    if (!this.get('controller.isSubmitDisabled')) {
+      if (this.get('modalPopup')) {
+        this.get('modalPopup').hide();
+        this.set('modalPopup', null);
+      }
       return;
     }
-    if(!this.get('controller.isSubmitDisabled')){
+    // don't create popup if it already exists
+    if (this.get('modalPopup')) {
       return;
     }
+    this.set('modalPopup', App.ModalPopup.show({
+      header: '',
 
-    var opts = {
-      lines: 13, // The number of lines to draw
-      length: 7, // The length of each line
-      width: 4, // The line thickness
-      radius: 10, // The radius of the inner circle
-      corners: 1, // Corner roundness (0..1)
-      rotate: 0, // The rotation offset
-      color: '#000', // #rgb or #rrggbb
-      speed: 1, // Rounds per second
-      trail: 60, // Afterglow percentage
-      shadow: false, // Whether to render a shadow
-      hwaccel: false, // Whether to use hardware acceleration
-      className: 'spinner', // The CSS class to assign to the spinner
-      zIndex: 2e9, // The z-index (defaults to 2000000000)
-      top: 'auto', // Top position relative to parent in px
-      left: 'auto' // Left position relative to parent in px
-    };
-    var target = $('#spinner')[0];
-    this.set('spinner', new Spinner(opts).spin(target));
-
-    /*var el = $('#spinner').children('b');
-    el.css('display', 'inline-block');
-    var deg = 0;
-    var timeoutId = setInterval(function(){
-      if(!$('#spinner').length){
-        clearInterval(timeoutId);
-      }
-      deg += 15;
-      deg %= 360;
-      el.css('transform', 'rotate(' + deg + 'deg)');
-      el.css('-ms-transform', 'rotate(' + deg + 'deg)');
-      el.css('-o-transform', 'rotate(' + deg + 'deg)');
-      el.css('-moz-transform', 'rotate(' + deg + 'deg)');
-      el.css('-webkit-transform', 'rotate(' + deg + 'deg)');
-    }, 80);*/
-  }.observes('controller.isSubmitDisabled','controller.hasErrorOccurred')
+      showFooter: false,
+
+      showCloseButton: false,
+
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/wizard/step8_log_popup'),
+
+        controllerBinding: 'App.router.wizardStep8Controller',
+
+        ajaxQueueLength: function() {
+          return this.get('controller.ajaxQueueLength');
+        }.property(),
+
+        ajaxQueueComplete: function() {
+          return this.get('ajaxQueueLength') - this.get('controller.ajaxQueueLeft');
+        }.property('controller.ajaxQueueLeft', 'ajaxQueueLength'),
+
+        barWidth: function () {
+          return 'width: ' + (this.get('ajaxQueueComplete') / this.get('ajaxQueueLength') * 100) + '%;';
+        }.property('ajaxQueueComplete', 'ajaxQueueLength'),
+
+        autoHide: function() {
+          if (this.get('controller.servicesInstalled')) {
+            this.get('parentView').hide();
+          }
+        }.observes('controller.servicesInstalled')
+      })
+    }));
+  }.observes('controller.isSubmitDisabled')
 });
+

Modified: incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step9_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step9_view.js?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step9_view.js (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/app/views/wizard/step9_view.js Mon Feb  4 02:23:55 2013
@@ -146,78 +146,76 @@ App.HostStatusView = Em.View.extend({
           return this.get('parentView.obj');
         }.property('parentView.obj'),
 
-        startedTasks:[], // initialized in didInsertElement
-
         task: null, // set in showTaskLog; contains task info including stdout and stderr
         /**
-         * sort task array by request Id
+         * sort task array by Id
          * @param tasks
          * @return {Array}
          */
 
-        sortTasksByRequest: function(tasks){
+        sortTasksById: function(tasks){
           var result = [];
-          var requestId = 1;
+          var id = 1;
           for(var i = 0; i < tasks.length; i++){
-            requestId = (tasks[i].Tasks.request_id > requestId) ? tasks[i].Tasks.request_id : requestId;
+            id = (tasks[i].Tasks.id > id) ? tasks[i].Tasks.id : id;
           }
-          while(requestId >= 1){
+          while(id >= 1){
             for(var j = 0; j < tasks.length; j++){
-              if(requestId == tasks[j].Tasks.request_id){
+              if(id == tasks[j].Tasks.id){
                 result.push(tasks[j]);
               }
             }
-            requestId--;
+            id--;
           }
           result.reverse();
           return result;
         },
 
+        groupTasksByRole: function (tasks) {
+          var sortedTasks = [];
+          var taskRoles = tasks.mapProperty('Tasks.role').uniq();
+          for (var i = 0; i < taskRoles.length; i++) {
+            sortedTasks = sortedTasks.concat(tasks.filterProperty('Tasks.role', taskRoles[i]))
+          }
+          return sortedTasks;
+        },
+
         visibleTasks: function () {
-          var self=this;
+          var self = this;
           self.set("isEmptyList", true);
           if (this.get('category.value')) {
             var filter = this.get('category.value');
-            $.each(this.get("roles"),function(a,e){
-
-              e.taskInfos.setEach("isVisible", false);
+            var tasks = this.get('tasks');
+            tasks.setEach("isVisible", false);
 
-              if(filter == "all")
-              {
-                e.taskInfos.setEach("isVisible", true);
-              }
-              else if(filter == "pending")
-              {
-                e.taskInfos.filterProperty("status", "pending").setEach("isVisible", true);
-                e.taskInfos.filterProperty("status", "queued").setEach("isVisible", true);
-              }
-              else if(filter == "in_progress")
-              {
-                e.taskInfos.filterProperty("status", "in_progress").setEach("isVisible", true);
-              }
-              else if(filter == "failed")
-              {
-                e.taskInfos.filterProperty("status", "failed").setEach("isVisible", true);
-              }
-              else if(filter == "completed")
-              {
-                e.taskInfos.filterProperty("status", "completed").setEach("isVisible", true);
-              }
-              else if(filter == "aborted")
-              {
-                e.taskInfos.filterProperty("status", "aborted").setEach("isVisible", true);
-              }
-              else if(filter == "timedout")
-              {
-                e.taskInfos.filterProperty("status", "timedout").setEach("isVisible", true);
-              }
+            if (filter == "all") {
+              tasks.setEach("isVisible", true);
+            }
+            else if (filter == "pending") {
+              tasks.filterProperty("status", "pending").setEach("isVisible", true);
+              tasks.filterProperty("status", "queued").setEach("isVisible", true);
+            }
+            else if (filter == "in_progress") {
+              tasks.filterProperty("status", "in_progress").setEach("isVisible", true);
+            }
+            else if (filter == "failed") {
+              tasks.filterProperty("status", "failed").setEach("isVisible", true);
+            }
+            else if (filter == "completed") {
+              tasks.filterProperty("status", "completed").setEach("isVisible", true);
+            }
+            else if (filter == "aborted") {
+              tasks.filterProperty("status", "aborted").setEach("isVisible", true);
+            }
+            else if (filter == "timedout") {
+              tasks.filterProperty("status", "timedout").setEach("isVisible", true);
+            }
 
-              if(e.taskInfos.filterProperty("isVisible", true).length >0){
-                self.set("isEmptyList", false);
-              }
-            })
+            if (tasks.filterProperty("isVisible", true).length > 0) {
+              self.set("isEmptyList", false);
+            }
           }
-        }.observes('category'),
+        }.observes('category', 'tasks'),
 
         categories: [
             Ember.Object.create({value: 'all', label: 'All' }),
@@ -231,46 +229,40 @@ App.HostStatusView = Em.View.extend({
 
         category: null,
 
-        roles:function () {
-          var roleArr = [];
+        tasks: function () {
+          var tasksArr = [];
           var tasks = this.getStartedTasks(host);
-          tasks = this.sortTasksByRequest(tasks);
+          tasks = this.sortTasksById(tasks);
+          tasks = this.groupTasksByRole(tasks);
           if (tasks.length) {
-            var _roles = tasks.mapProperty('Tasks.role').uniq();
-            _roles.forEach(function (_role) {
-              var taskInfos = [];
-              var roleObj = {};
-              roleObj.roleName = App.format.role(_role);
-              tasks.filterProperty('Tasks.role', _role).forEach(function (_task) {
-                var taskInfo = Ember.Object.create({});
-                taskInfo.set('requestId', _task.Tasks.request_id);
-                taskInfo.set('command', _task.Tasks.command.toLowerCase());
-                taskInfo.set('status', App.format.taskStatus(_task.Tasks.status));
-                taskInfo.set('url', _task.href);
-                taskInfo.set('roleName', roleObj.roleName);
-                taskInfo.set('isVisible', true);
-                taskInfo.set('icon', '');
-                if (taskInfo.get('status') == 'pending' || taskInfo.get('status') == 'queued') {
-                  taskInfo.set('icon', 'icon-cog');
-                } else if (taskInfo.get('status') == 'in_progress') {
-                  taskInfo.set('icon', 'icon-cogs');
-                } else if (taskInfo.get('status') == 'completed') {
-                  taskInfo.set('icon', ' icon-ok');
-                } else if (taskInfo.get('status') == 'failed') {
-                  taskInfo.set('icon', 'icon-exclamation-sign');
-                } else if (taskInfo.get('status') == 'aborted') {
-                  taskInfo.set('icon', 'icon-remove');
-                } else if (taskInfo.get('status') == 'timedout') {
-                  taskInfo.set('icon', 'icon-time');
-                }
-                taskInfos.pushObject(taskInfo);
-              }, this);
-              roleObj.taskInfos = taskInfos;
-              roleArr.pushObject(roleObj);
+            tasks.forEach(function (_task) {
+              var taskInfo = Ember.Object.create({});
+              taskInfo.set('id', _task.Tasks.id);
+              taskInfo.set('command', _task.Tasks.command.toLowerCase());
+              taskInfo.set('status', App.format.taskStatus(_task.Tasks.status));
+              taskInfo.set('role', App.format.role(_task.Tasks.role));
+              taskInfo.set('stderr', _task.Tasks.stderr);
+              taskInfo.set('stdout', _task.Tasks.stdout);
+              taskInfo.set('isVisible', true);
+              taskInfo.set('icon', '');
+              if (taskInfo.get('status') == 'pending' || taskInfo.get('status') == 'queued') {
+                taskInfo.set('icon', 'icon-cog');
+              } else if (taskInfo.get('status') == 'in_progress') {
+                taskInfo.set('icon', 'icon-cogs');
+              } else if (taskInfo.get('status') == 'completed') {
+                taskInfo.set('icon', ' icon-ok');
+              } else if (taskInfo.get('status') == 'failed') {
+                taskInfo.set('icon', 'icon-exclamation-sign');
+              } else if (taskInfo.get('status') == 'aborted') {
+                taskInfo.set('icon', 'icon-remove');
+              } else if (taskInfo.get('status') == 'timedout') {
+                taskInfo.set('icon', 'icon-time');
+              }
+              tasksArr.push(taskInfo);
             }, this);
           }
-          return roleArr;
-        }.property('startedTasks.@each'),
+          return tasksArr;
+        }.property('App.router.wizardStep9Controller.logTasksChangesCounter'),
 
         backToTaskList: function(event, context) {
           this.destroyClipBoard();
@@ -292,44 +284,28 @@ App.HostStatusView = Em.View.extend({
           newdocument.close();
         },
 
-        toggleTaskLog:function (event, context) {
-          if(this.isLogWrapHidden){
-
-            var taskInfo = event.context;
-            this.set("isLogWrapHidden",false);
-
-            $(".task-detail-log-rolename")
-                .html(taskInfo.roleName + " " + taskInfo.command)
+        openedTaskId: 0,
 
-            $(".task-detail-status-ico")
-                .removeClass()
-                .addClass(taskInfo.status + " task-detail-status-ico " + taskInfo.icon);
-
-            var url = (App.testMode) ? '/data/wizard/deploy/task_log.json' : taskInfo.url;
-            $.ajax({
-              url:url,
-              dataType:'text',
-              timeout:App.timeout,
-              success:function (data) {
-                var task = $.parseJSON(data);
-                $(".stderr").html(task.Tasks.stderr);
-                $(".stdout").html(task.Tasks.stdout);
-                $(".modal").scrollTop(0);
-                $(".modal-body").scrollTop(0);
-              },
-              error:function () {
-                alert('Failed to retrieve task log');
-              }
-            });
-          }else{
-            this.set("isLogWrapHidden",true);
+        openedTask: function () {
+          if (!this.get('openedTaskId')) {
+            return Ember.Object.create();
           }
+          return this.get('tasks').findProperty('id', this.get('openedTaskId'));
+        }.property('tasks', 'openedTaskId'),
 
-
-
-
-
+        toggleTaskLog: function (event, context) {
+          if (this.isLogWrapHidden) {
+            var taskInfo = event.context;
+            this.set("isLogWrapHidden", false);
+            this.set('openedTaskId', taskInfo.id);
+            $(".modal").scrollTop(0);
+            $(".modal-body").scrollTop(0);
+          } else {
+            this.set("isLogWrapHidden", true);
+            this.set('openedTaskId', 0);
+          }
         },
+
         textTrigger:function (event) {
           if($(".task-detail-log-clipboard").length > 0)
           {

Modified: incubator/ambari/branches/branch-1.2/ambari-web/config.coffee
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/config.coffee?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/config.coffee (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/config.coffee Mon Feb  4 02:23:55 2013
@@ -78,6 +78,10 @@ exports.config =
       precompile: true
       defaultExtension: 'hbs'
       joinTo: 'javascripts/app.js' : /^app/
+      paths:
+        jquery: 'vendor/scripts/jquery-1.7.2.min.js'
+        handlebars: 'vendor/scripts/handlebars-1.0.0.beta.6.js'
+        ember: 'vendor/scripts/ember-latest.js'
 
   server:
     port: 3333

Modified: incubator/ambari/branches/branch-1.2/ambari-web/package.json
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/package.json?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/package.json (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/package.json Mon Feb  4 02:23:55 2013
@@ -18,7 +18,7 @@
     "css-brunch":">= 1.0 < 1.5",
     "uglify-js-brunch":">= 1.0 < 1.5",
     "clean-css-brunch":">= 1.0 < 1.5",
-    "ember-handlebars-brunch":"git://github.com/icholy/ember-handlebars-brunch.git",
+    "ember-precompiler-brunch":">= 1.0 < 1.5",
     "less-brunch":">= 1.0 < 1.5"
   },
   "devDependencies":{

Modified: incubator/ambari/branches/branch-1.2/ambari-web/pom.xml
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/pom.xml?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/pom.xml (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/pom.xml Mon Feb  4 02:23:55 2013
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.apache.ambari</groupId>
     <artifactId>ambari-project</artifactId>
-    <version>1.2.0-SNAPSHOT</version>
+    <version>1.2.1-SNAPSHOT</version>
     <relativePath>../ambari-project</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
@@ -27,7 +27,7 @@
   <artifactId>ambari-web</artifactId>
   <packaging>pom</packaging>
   <name>Ambari Web</name>
-  <version>1.0.3-SNAPSHOT</version>
+  <version>1.2.1-SNAPSHOT</version>
   <description>Ambari Web</description>
   <build>
     <plugins>
@@ -80,6 +80,7 @@
             <configuration>
               <target name="ambari-web-compile">
                 <exec dir="${basedir}" executable="npm" failonerror="false">
+                  <env key="PYTHON" value="python2.6" />
                   <arg value="install"/>
                 </exec>
                 <exec dir="${basedir}" executable="brunch" failonerror="false">

Modified: incubator/ambari/branches/branch-1.2/ambari-web/vendor/scripts/workflow_visualization.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/vendor/scripts/workflow_visualization.js?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/vendor/scripts/workflow_visualization.js (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/vendor/scripts/workflow_visualization.js Mon Feb  4 02:23:55 2013
@@ -1,390 +1,54 @@
 /*
- * D3 Sankey diagram 
- * another type to display graph
- */
-d3.sankey = function () {
-  var sankey = {},
-    nodeWidth = 24,
-    nodePadding = 8,
-    size = [1, 1],
-    nodes = [],
-    links = [],
-    overlapLinksAtSources = false,
-    overlapLinksAtTargets = false,
-    minValue = 1;
-
-  sankey.nodeWidth = function (_) {
-    if (!arguments.length) return nodeWidth;
-    nodeWidth = +_;
-    return sankey;
-  };
-
-  sankey.nodePadding = function (_) {
-    if (!arguments.length) return nodePadding;
-    nodePadding = +_;
-    return sankey;
-  };
-
-  sankey.nodes = function (_) {
-    if (!arguments.length) return nodes;
-    nodes = _;
-    return sankey;
-  };
-
-  sankey.links = function (_) {
-    if (!arguments.length) return links;
-    links = _;
-    return sankey;
-  };
-
-  sankey.size = function (_) {
-    if (!arguments.length) return size;
-    size = _;
-    return sankey;
-  };
-
-  sankey.overlapLinksAtSources = function (_) {
-    if (!arguments.length) return overlapLinksAtSources;
-    overlapLinksAtSources = _;
-    return sankey;
-  };
-
-  sankey.overlapLinksAtTargets = function (_) {
-    if (!arguments.length) return overlapLinksAtTargets;
-    overlapLinksAtTargets = _;
-    return sankey;
-  };
-
-  sankey.minValue = function (_) {
-    if (!arguments.length) return minValue;
-    minValue = _;
-    return sankey;
-  };
-
-  sankey.layout = function (iterations) {
-    computeNodeLinks();
-    computeNodeValues();
-    computeNodeBreadths();
-    computeNodeDepths(iterations);
-    computeLinkDepths();
-    return sankey;
-  };
-
-  sankey.relayout = function () {
-    computeLinkDepths();
-    return sankey;
-  };
-
-  sankey.link = function () {
-    var curvature = .5;
-
-    function link(d) {
-      var x0 = d.source.x + d.source.dx,
-        x1 = d.target.x,
-        xi = d3.interpolateNumber(x0, x1),
-        x2 = xi(curvature),
-        x3 = xi(1 - curvature),
-        y0 = d.source.y + (overlapLinksAtSources ? 0 : d.sy) + d.dy / 2,
-        y1 = d.target.y + (overlapLinksAtTargets ? 0 : d.ty) + d.dy / 2;
-      return "M" + x0 + "," + y0
-        + "C" + x2 + "," + y0
-        + " " + x3 + "," + y1
-        + " " + x1 + "," + y1;
-    }
-
-    link.curvature = function (_) {
-      if (!arguments.length) return curvature;
-      curvature = +_;
-      return link;
-    };
-
-    return link;
-  };
-
-  // Populate the sourceLinks and targetLinks for each node.
-  // Also, if the source and target are not objects, assume they are indices.
-  function computeNodeLinks() {
-    nodes.forEach(function (node) {
-      node.sourceLinks = [];
-      node.targetLinks = [];
-    });
-    links.forEach(function (link) {
-      var source = link.source,
-        target = link.target;
-      if (typeof source === "number") source = link.source = nodes[link.source];
-      if (typeof target === "number") target = link.target = nodes[link.target];
-      source.sourceLinks.push(link);
-      target.targetLinks.push(link);
-      if ("value" in link)
-        link.value = Math.max(link.value, minValue);
-      else
-        link.value = minValue;
-    });
-  }
-
-  // Compute the value (size) of each node by summing the associated links.
-  function computeNodeValues() {
-    nodes.forEach(function (node) {
-      if ("value" in node)
-        node.value = Math.max(node.value, minValue);
-      else
-        node.value = minValue;
-      if (node.sourceLinks.length > 0) {
-        if (overlapLinksAtSources)
-          node.value = Math.max(node.value, d3.max(node.sourceLinks, value));
-        else
-          node.value = Math.max(node.value, d3.sum(node.sourceLinks, value));
-      }
-      if (node.targetLinks.length > 0) {
-        if (overlapLinksAtTargets)
-          node.value = Math.max(node.value, d3.max(node.targetLinks, value));
-        else
-          node.value = Math.max(node.value, d3.sum(node.targetLinks, value));
-      }
-    });
-  }
-
-  // Iteratively assign the breadth (x-position) for each node.
-  // Nodes are assigned the maximum breadth of incoming neighbors plus one;
-  // nodes with no incoming links are assigned breadth zero, while
-  // nodes with no outgoing links are assigned the maximum breadth.
-  function computeNodeBreadths() {
-    var remainingNodes = nodes,
-      nextNodes,
-      x = 0;
-
-    while (remainingNodes.length) {
-      nextNodes = [];
-      remainingNodes.forEach(function (node) {
-        node.x = x;
-        node.dx = nodeWidth;
-        node.sourceLinks.forEach(function (link) {
-          nextNodes.push(link.target);
-        });
-      });
-      remainingNodes = nextNodes;
-      ++x;
-    }
-
-    //
-    moveSinksRight(x);
-    scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
-  }
-
-  function moveSourcesRight() {
-    nodes.forEach(function (node) {
-      if (!node.targetLinks.length) {
-        node.x = d3.min(node.sourceLinks, function (d) {
-          return d.target.x;
-        }) - 1;
-      }
-    });
-  }
-
-  function moveSinksRight(x) {
-    nodes.forEach(function (node) {
-      if (!node.sourceLinks.length) {
-        node.x = x - 1;
-      }
-    });
-  }
-
-  function scaleNodeBreadths(kx) {
-    nodes.forEach(function (node) {
-      node.x *= kx;
-    });
-  }
-
-  function computeNodeDepths(iterations) {
-    var nodesByBreadth = d3.nest()
-      .key(function (d) {
-        return d.x;
-      })
-      .sortKeys(d3.ascending)
-      .entries(nodes)
-      .map(function (d) {
-        return d.values;
-      });
-
-    //
-    initializeNodeDepth();
-    resolveCollisions();
-    for (var alpha = 1; iterations > 0; --iterations) {
-      relaxRightToLeft(alpha *= .99);
-      resolveCollisions();
-      relaxLeftToRight(alpha);
-      resolveCollisions();
-    }
-
-    function initializeNodeDepth() {
-      var ky = d3.min(nodesByBreadth, function (nodes) {
-        return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
-      });
-
-      nodesByBreadth.forEach(function (nodes) {
-        nodes.forEach(function (node, i) {
-          node.y = i;
-          node.dy = node.value * ky;
-        });
-      });
-
-      links.forEach(function (link) {
-        link.dy = link.value * ky;
-      });
-    }
-
-    function relaxLeftToRight(alpha) {
-      nodesByBreadth.forEach(function (nodes, breadth) {
-        nodes.forEach(function (node) {
-          if (node.targetLinks.length) {
-            var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
-            node.y += (y - center(node)) * alpha;
-          }
-        });
-      });
-
-      function weightedSource(link) {
-        return center(link.source) * link.value;
-      }
-    }
-
-    function relaxRightToLeft(alpha) {
-      nodesByBreadth.slice().reverse().forEach(function (nodes) {
-        nodes.forEach(function (node) {
-          if (node.sourceLinks.length) {
-            var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
-            node.y += (y - center(node)) * alpha;
-          }
-        });
-      });
-
-      function weightedTarget(link) {
-        return center(link.target) * link.value;
-      }
-    }
-
-    function resolveCollisions() {
-      nodesByBreadth.forEach(function (nodes) {
-        var node,
-          dy,
-          y0 = 0,
-          n = nodes.length,
-          i;
-
-        // Push any overlapping nodes down.
-        nodes.sort(ascendingDepth);
-        for (i = 0; i < n; ++i) {
-          node = nodes[i];
-          dy = y0 - node.y;
-          if (dy > 0) node.y += dy;
-          y0 = node.y + node.dy + nodePadding;
-        }
-
-        // If the bottommost node goes outside the bounds, push it back up.
-        dy = y0 - nodePadding - size[1];
-        if (dy > 0) {
-          y0 = node.y -= dy;
-
-          // Push any overlapping nodes back up.
-          for (i = n - 2; i >= 0; --i) {
-            node = nodes[i];
-            dy = node.y + node.dy + nodePadding - y0;
-            if (dy > 0) node.y -= dy;
-            y0 = node.y;
-          }
-        }
-      });
-    }
-
-    function ascendingDepth(a, b) {
-      return a.y - b.y;
-    }
-  }
-
-  function computeLinkDepths() {
-    nodes.forEach(function (node) {
-      node.sourceLinks.sort(ascendingTargetDepth);
-      node.targetLinks.sort(ascendingSourceDepth);
-    });
-    nodes.forEach(function (node) {
-      var sy = 0, ty = 0;
-      node.sourceLinks.forEach(function (link) {
-        link.sy = sy;
-        sy += link.dy;
-      });
-      node.targetLinks.forEach(function (link) {
-        link.ty = ty;
-        ty += link.dy;
-      });
-    });
-
-    function ascendingSourceDepth(a, b) {
-      return a.source.y - b.source.y;
-    }
-
-    function ascendingTargetDepth(a, b) {
-      return a.target.y - b.target.y;
-    }
-  }
-
-  function center(node) {
-    return node.y + node.dy / 2;
-  }
-
-  function value(link) {
-    return link.value;
-  }
-
-  return sankey;
-};
-/*
  * Example usage:
  *
- *  var dv = new DagViewer(false,'pig_5')
- *  .setPhysicalParametrs(width,height[,charge,gravity])
- *  .setData(dagSchema [,jobsData])
- *  .drawDag([nodeSize,largeNodeSize,linkDistance]);
+ *  var dv = new DagViewer('pig_5')
+ *  .setData(workflowData,jobsData)
+ *  .drawDag(svgWidth,svgHeight,nodeHeight);
  */
-function DagViewer(type, domId) {
-  // initialize variables and force layout
+function DagViewer(domId) {
+  // initialize variables
   this._nodes = new Array();
   this._links = new Array();
   this._numNodes = 0;
-  this._type = type;
   this._id = domId;
 }
-DagViewer.prototype.setPhysicalParametrs = function (w, h, charge, gravity) {
-  this._w = w;
-  this._h = h;
-  this._gravity = gravity || 0.1;
-  this._charge = charge || -1000;
-  this._force = d3.layout.force()
-    .size([w, h])
-    .gravity(this._gravity)
-    .charge(this._charge);
-  return this;
-}
 
-//set workflow schema
+// set workflow schema and job data
 DagViewer.prototype.setData = function (wfData, jobData) {
   // create map from entity names to nodes
   var existingNodes = new Array();
   var jobData = (jobData) ? jobData : new Array();
+  var minStartTime = 0;
+  if (jobData.length > 0)
+    minStartTime = jobData[0].submitTime;
+  var maxFinishTime = 0;
   // iterate through job data
   for (var i = 0; i < jobData.length; i++) {
+    minStartTime = Math.min(minStartTime, jobData[i].submitTime);
+    maxFinishTime = Math.max(maxFinishTime, jobData[i].submitTime + jobData[i].elapsedTime);
     this._addNode(existingNodes, jobData[i].entityName, jobData[i]);
   }
+  this._minStartTime = minStartTime;
+  this._maxFinishTime = maxFinishTime;
   var dag = eval('(' + wfData + ')').dag;
+  this._sourceMarker = new Array();
+  this._targetMarker = new Array();
+  this._sourceMap = new Array();
   // for each source node in the context, create links between it and its target nodes
   for (var source in dag) {
-    var sourceNode = this._getNode(source, existingNodes);
+    var sourceNode = null;
+    if (source in existingNodes)
+      sourceNode = existingNodes[source];
     for (var i = 0; i < dag[source].length; i++) {
-      var targetNode = this._getNode(dag[source][i], existingNodes);
+      var targetNode = null;
+      if (dag[source][i] in existingNodes)
+        targetNode = existingNodes[dag[source][i]];
       this._addLink(sourceNode, targetNode);
     }
   }
   return this;
 }
+
 // add a node to the nodes array and to a provided map of entity names to nodes
 DagViewer.prototype._addNode = function (existingNodes, entityName, node) {
   existingNodes[entityName] = node;
@@ -394,272 +58,284 @@ DagViewer.prototype._addNode = function 
 
 // add a link between sourceNode and targetNode
 DagViewer.prototype._addLink = function (sourceNode, targetNode) {
+  // if source or target is null, add marker indicating unsubmitted job and return
+  if (sourceNode==null) {
+    if (targetNode==null)
+      return;
+    this._sourceMarker.push(targetNode);
+    return;
+  }
+  if (targetNode==null) {
+    this._targetMarker.push(sourceNode);
+    return;
+  }
+  // add link between nodes
   var status = false;
   if (sourceNode.status && targetNode.status)
     status = true;
   this._links.push({"source":sourceNode, "target":targetNode, "status":status, "value":sourceNode.output});
+  // add source to map of targets to sources
+  if (!(targetNode.name in this._sourceMap))
+    this._sourceMap[targetNode.name] = new Array();
+  this._sourceMap[targetNode.name].push(sourceNode);
 }
 
-// get the node for an entity name, or add it if it doesn't exist
-// called after job nodes have all been added
-DagViewer.prototype._getNode = function (entityName, existingNodes) {
-  if (!(entityName in existingNodes))
-    this._addNode(existingNodes, entityName, { "name":entityName, "status":false, "input":1, "output":1});
-  return existingNodes[entityName];
-}
 // display the graph
-DagViewer.prototype.drawDag = function (nodeSize, largeNodeSize, linkDistance) {
-  this._nodeSize = nodeSize || 18;
-  this._largeNodeSize = largeNodeSize || 30;
-  this._linkDistance = linkDistance || 100;
-  // add new display to specified div
-  this._svg = d3.select("div#" + this._id).append("svg:svg")
-    .attr("width", this._w)
-    .attr("height", this._h);
-  // add sankey diagram or graph depending on type
-  if (this._type)
-    this._addSankey();
-  else
-    this._addDag();
+// rules of thumb: nodeHeight = 20, labelFontSize = 14, maxLabelWidth = 180
+//                 nodeHeight = 15, labelFontSize = 10, maxLabelWidth = 120
+//                 nodeHeight = 40, labelFontSize = 20, maxLabelWidth = 260
+//                 nodeHeight = 30, labelFontSize = 16
+DagViewer.prototype.drawDag = function (svgw, svgh, nodeHeight, labelFontSize, maxLabelWidth, axisPadding, svgPadding) {
+  this._addTimelineGraph(svgw, svgh, nodeHeight || 20, labelFontSize || 14, maxLabelWidth || 180, axisPadding || 30, svgPadding || 20);
   return this;
 }
-//draw the graph 
-DagViewer.prototype._addDag = function () {
-  var w = this._w;
-  var h = this._h;
-  var nodeSize = this._nodeSize;
-  var largeNodeSize = this._largeNodeSize;
-  var linkDistance = this._linkDistance;
-  // add nodes and links to force layout
-  this._force.nodes(this._nodes)
-    .links(this._links)
-    .linkDistance(this._linkDistance);
 
+// draw timeline graph
+DagViewer.prototype._addTimelineGraph = function (svgw, svgh, nodeHeight, labelFontSize, maxLabelWidth, axisPadding, svgPadding) {
+  // want to avoid having unnecessary scrollbars, so we need to size the div slightly larger than the svg
+  svgw = svgw - svgPadding;
+
+  var margin = {"top":10, "bottom":10, "left":30, "right":30};
+  var w = svgw - margin.left - margin.right;
+
+  var startTime = this._minStartTime;
+  var elapsedTime = this._maxFinishTime - this._minStartTime;
+  var x = d3.time.scale()
+    .domain([0, elapsedTime])
+    .range([0, w]);
+
+  // process nodes and determine their x and y positions, width and height
+  var minNodeSpacing = nodeHeight/2;
+  var ends = new Array();
+  var maxIndex = 0;
+  this._nodes = this._nodes.sort(function(a,b){return a.name.localeCompare(b.name);});
+  for (var i = 0; i < this._numNodes; i++) {
+    var d = this._nodes[i];
+    d.x = x(d.submitTime-startTime);
+    d.w = x(d.elapsedTime+d.submitTime-startTime) - x(d.submitTime-startTime);
+    if (d.w < nodeHeight/2) {
+      d.w = nodeHeight/2;
+      if (d.x + d.w > w)
+        d.x = w - d.w;
+    }
+    var effectiveX = d.x
+    var effectiveWidth = d.w;
+    if (d.w < maxLabelWidth) {
+      effectiveWidth = maxLabelWidth;
+      if (d.x + effectiveWidth > w)
+        effectiveX = w - effectiveWidth;
+      else if (d.x > 0)
+        effectiveX = d.x+(d.w-maxLabelWidth)/2;
+    }
+    // select "lane" (slot for y-position) for this node
+    // starting at the slot above the node's closest source node (or 0, if none exists)
+    // and moving down until a slot is found that has no nodes within minNodeSpacing of this node
+    // excluding slots that contain more than one source of this node
+    var index = 0;
+    var rejectIndices = new Array();
+    if (d.name in this._sourceMap) {
+      var sources = this._sourceMap[d.name];
+      var closestSource = sources[0];
+      var indices = new Array();
+      for (var j = 0; j < sources.length; j++) {
+        if (sources[j].index in indices)
+          rejectIndices[sources[j].index] = true;
+        indices[sources[j].index] = true;
+        if (sources[j].submitTime + sources[j].elapsedTime > closestSource.submitTime + closestSource.elapsedTime)
+          closestSource = sources[j];
+      }
+      index = Math.max(0, closestSource.index-1);
+    }
+    while ((index in ends) && ((index in rejectIndices) || (ends[index]+minNodeSpacing >= effectiveX))) {
+      index++
+    }
+    ends[index] = Math.max(effectiveX + effectiveWidth);
+    maxIndex = Math.max(maxIndex, index);
+    d.y = index*2*nodeHeight + axisPadding;
+    d.h = nodeHeight;
+    d.index = index;
+  }
+
+  var h = 2*axisPadding + 2*nodeHeight*(maxIndex+1);
+  d3.select("div#" + this._id)
+    .attr("style","width:"+(svgw+svgPadding)+"px;height:"+Math.min(svgh,h+margin.top+margin.bottom+svgPadding)+"px;overflow:auto;padding:none;");
+  svgh = h + margin.top + margin.bottom;
+  var svg = d3.select("div#" + this._id).append("svg:svg")
+    .attr("width", svgw+"px")
+    .attr("height", svgh+"px");
+  var svgg = svg.append("g")
+    .attr("transform", "translate("+margin.left+","+margin.top+")");
+  
+  // create axes
+  var x = d3.time.scale()
+    .domain([0, elapsedTime])
+    .range([0, w]);
+  var tickFormatter = function(x) {
+    d = x.getTime();
+    if (d==0) { return "0" }
+    var seconds = Math.floor(parseInt(d) / 1000);
+    if ( seconds < 60 )
+      return seconds + "s";
+    var minutes = Math.floor(seconds / 60);
+    if ( minutes < 60 ) {
+      var x = seconds - 60*minutes;
+      return minutes + "m" + (x==0 ? "" : " " + x + "s");
+    }
+    var hours = Math.floor(minutes / 60);
+    if ( hours < 24 ) {
+      var x = minutes - 60*hours;
+      return hours + "h" + (x==0 ? "" : " " + x + "m");
+    }
+    var days = Math.floor(hours / 24);
+    if ( days < 7 ) {
+      var x = hours - 24*days;
+      return days + "d " + (x==0 ? "" : " " + x + "h");
+    }
+    var weeks = Math.floor(days / 7);
+    var x = days - 7*weeks;
+    return weeks + "w " + (x==0 ? "" : " " + x + "d");
+  };
+  var topAxis = d3.svg.axis()
+    .scale(x)
+    .orient("bottom")
+    .tickFormat(tickFormatter);
+  var bottomAxis = d3.svg.axis()
+    .scale(x)
+    .orient("top")
+    .tickFormat(tickFormatter);
+  svgg.append("g")
+    .attr("class", "x axis")
+    .call(topAxis);
+  svgg.append("g")
+    .attr("class", "x axis")
+    .call(bottomAxis)
+    .attr("transform", "translate(0,"+h+")");
+  
+  // create a rectangle for each node
+  var boxes = svgg.append("svg:g").selectAll("rect")
+    .data(this._nodes)
+    .enter().append("svg:rect")
+    .attr("x", function(d) { return d.x; } )
+    .attr("y", function(d) { return d.y; } )
+    .attr("width", function(d) { return d.w; } )
+    .attr("height", function(d) { return d.h; } )
+    .attr("class", function (d) {
+      return "node " + (d.status ? " finished" : "");
+    })
+    .attr("id", function (d) {
+      return d.name;
+    });
+  
   // defs for arrowheads marked as to whether they link finished jobs or not
-  this._svg.append("svg:defs").selectAll("marker")
+  svgg.append("svg:defs").selectAll("arrowmarker")
     .data(["finished", "unfinished"])
     .enter().append("svg:marker")
     .attr("id", String)
     .attr("viewBox", "0 -5 10 10")
-    .attr("refX", nodeSize + 10)
-    .attr("refY", 0)
     .attr("markerWidth", 6)
     .attr("markerHeight", 6)
     .attr("orient", "auto")
     .append("svg:path")
-    .attr("d", "M0,-5L10,0L0,5");
+    .attr("d", "M0,-3L8,0L0,3");
+  // defs for unsubmitted node marker
+  svgg.append("svg:defs").selectAll("circlemarker")
+    .data(["circle"])
+    .enter().append("svg:marker")
+    .attr("id", String)
+    .attr("viewBox", "-2 -2 18 18")
+    .attr("markerWidth", 10)
+    .attr("markerHeight", 10)
+    .attr("refX", 10)
+    .attr("refY", 5)
+    .attr("orient", "auto")
+    .append("svg:circle")
+    .attr("cx", 5)
+    .attr("cy", 5)
+    .attr("r", 5);
+
+  // create dangling links representing unsubmitted jobs
+  var markerWidth = nodeHeight/2;
+  var sourceMarker = svgg.append("svg:g").selectAll("line")
+    .data(this._sourceMarker)
+    .enter().append("svg:line")
+    .attr("x1", function(d) { return d.x - markerWidth; } )
+    .attr("x2", function(d) { return d.x; } )
+    .attr("y1", function(d) { return d.y; } )
+    .attr("y2", function(d) { return d.y + 3; } )
+    .attr("class", "source mark")
+    .attr("marker-start", "url(#circle)");
+  var targetMarker = svgg.append("svg:g").selectAll("line")
+    .data(this._targetMarker)
+    .enter().append("svg:line")
+    .attr("x1", function(d) { return d.x + d.w + markerWidth; } )
+    .attr("x2", function(d) { return d.x + d.w; } )
+    .attr("y1", function(d) { return d.y + d.h; } )
+    .attr("y2", function(d) { return d.y + d.h - 3; } )
+    .attr("class", "target mark")
+    .attr("marker-start", "url(#circle)");
 
   // create links between the nodes
-  var lines = this._svg.append("svg:g").selectAll("line")
+  var lines = svgg.append("svg:g").selectAll("path")
     .data(this._links)
-    .enter().append("svg:line")
+    .enter().append("svg:path")
+    .attr("d", function(d) {
+      var s = d.source;
+      var t = d.target;
+      var x1 = s.x + s.w;
+      var x2 = t.x;
+      var y1 = s.y;
+      var y2 = t.y;
+      if (y1==y2) {
+        y1 += s.h/2;
+        y2 += t.h/2;
+      } else if (y1 < y2) {
+        y1 += s.h;
+      } else {
+        y2 += t.h;
+      }
+      return "M "+x1+" "+y1+" L "+((x2+x1)/2)+" "+((y2+y1)/2)+" L "+x2+" "+y2;
+    } )
     .attr("class", function (d) {
       return "link" + (d.status ? " finished" : "");
     })
-    .attr("marker-end", function (d) {
+    .attr("marker-mid", function (d) {
       return "url(#" + (d.status ? "finished" : "unfinished") + ")";
     });
-
-  // create a circle for each node
-  var circles = this._svg.append("svg:g").selectAll("circle")
-    .data(this._nodes)
-    .enter().append("svg:circle")
-    .attr("r", nodeSize)
-    .attr("class", function (d) {
-      return "node " + (d.status ? " finished" : "");
-    })
-    .attr("id", function (d) {
-      return d.name;
-    })
-    .on("dblclick", click)
-    .call(this._force.drag);
-
+  
   // create text group for each node label
-  var text = this._svg.append("svg:g").selectAll("g")
+  var text = svgg.append("svg:g").selectAll("g")
     .data(this._nodes)
     .enter().append("svg:g");
-
+  
   // add a shadow copy of the node label (will have a lighter color and thicker
-  // stroke for legibility
+  // stroke for legibility)
   text.append("svg:text")
-    .attr("x", nodeSize + 3)
-    .attr("y", ".31em")
-    .attr("class", "shadow")
+    .attr("x", function(d) {
+      var goal = d.x + d.w/2;
+      var halfLabel = maxLabelWidth/2;
+      if (goal < halfLabel) return halfLabel;      else if (goal > w-halfLabel) return w-halfLabel;
+      return goal;
+    } )
+    .attr("y", function(d) { return d.y + d.h + labelFontSize; } )
+    .attr("class", "joblabel shadow")
+    .attr("style", "font: "+labelFontSize+"px sans-serif")
     .text(function (d) {
       return d.name;
     });
-
+  
   // add the main node label
   text.append("svg:text")
-    .attr("x", nodeSize + 3)
-    .attr("y", ".31em")
+    .attr("x", function(d) {
+      var goal = d.x + d.w/2;
+      var halfLabel = maxLabelWidth/2;
+      if (goal < halfLabel) return halfLabel;
+      else if (goal > w-halfLabel) return w-halfLabel;
+      return goal;
+    } )
+    .attr("y", function(d) { return d.y + d.h + labelFontSize; } )
+    .attr("class", "joblabel")
+    .attr("style", "font: "+labelFontSize+"px sans-serif")
     .text(function (d) {
       return d.name;
     });
-
-  // add mouseover actions
-  this._addMouseoverSelection(circles);
-
-  // start the force layout
-  this._force.on("tick", tick)
-    .start();
-
-  // on force tick, adjust positions of nodes, links, and text
-  function tick() {
-    circles.attr("transform", function (d) {
-      if (d.x < largeNodeSize) d.x = largeNodeSize;
-      if (d.y < largeNodeSize) d.y = largeNodeSize;
-      if (d.x > w - largeNodeSize) d.x = w - largeNodeSize;
-      if (d.y > h - largeNodeSize) d.y = h - largeNodeSize;
-      return "translate(" + d.x + "," + d.y + ")";
-    });
-
-    lines.attr("x1", function (d) {
-      return d.source.x
-    })
-      .attr("y1", function (d) {
-        return d.source.y
-      })
-      .attr("x2", function (d) {
-        return d.target.x
-      })
-      .attr("y2", function (d) {
-        return d.target.y
-      });
-
-    text.attr("transform", function (d) {
-      return "translate(" + d.x + "," + d.y + ")";
-    });
-  }
-
-  // on double click, fix node in place or release it
-  function click() {
-    d3.select(this).attr("fixed", function (d) {
-      if (d.fixed) {
-        d.fixed = false
-      } else {
-        d.fixed = true
-      }
-      return d.fixed;
-    });
-  }
-}
-//define mouseover action on nodes
-DagViewer.prototype._addMouseoverSelection = function (nodes) {
-  var nodeSize = this._nodeSize;
-  var largeNodeSize = this._largeNodeSize;
-  // on mouseover, change size of node 
-  nodes.on("mouseover", function (d) {
-    d3.select(this).transition().attr("r", largeNodeSize);
-  })
-    .on("mouseout", function (d) {
-      d3.select(this).transition().attr("r", nodeSize);
-    });
-}
-//draw Sankey diagram
-DagViewer.prototype._addSankey = function () {
-  var w = this._w;
-  var h = this._h;
-
-  // add svg group
-  var svgg = this._svg.append("g");
-
-  var color = d3.scale.category20();
-
-  // create sankey
-  var sankey = d3.sankey()
-    .nodeWidth(15)
-    .nodePadding(10)
-    .size([w, h * 0.67]);
-
-  // get sankey links
-  var spath = sankey.link();
-
-  // set sankey nodes and links and calculate their positions and sizes
-  sankey
-    .nodes(this._nodes)
-    .links(this._links)
-    .overlapLinksAtSources(true)
-    .layout(32);
-
-  // create links and set their attributes
-  var slink = svgg.append("g").selectAll(".link")
-    .data(this._links)
-    .enter().append("path")
-    .attr("class", "slink")
-    .attr("d", spath)
-    .style("stroke-width", function (d) {
-      return Math.max(1, d.dy);
-    })
-    .sort(function (a, b) {
-      return b.dy - a.dy;
-    });
-
-  // add mouseover text to links
-  slink.append("title")
-    .text(function (d) {
-      return d.source.name + " - " + d.target.name + ": " + d.value;
-    });
-
-  // create node groups, set their attributes, and enable vertical dragging
-  var snode = svgg.append("g").selectAll(".node")
-    .data(this._nodes)
-    .enter().append("g")
-    .attr("class", "snode")
-    .attr("transform", function (d) {
-      return "translate(" + d.x + "," + d.y + ")";
-    })
-    .call(d3.behavior.drag()
-    .origin(function (d) {
-      return d;
-    })
-    .on("dragstart", function () {
-      this.parentNode.appendChild(this);
-    })
-    .on("drag", dragmove));
-
-  // add rectangles to node groups
-  snode.append("rect")
-    .attr("height", function (d) {
-      return d.dy;
-    })
-    .attr("width", sankey.nodeWidth())
-    .style("fill", function (d) {
-      return d.color = color(d.name.replace(/ .*/, ""));
-    })
-    .style("stroke", function (d) {
-      return d3.rgb(d.color).darker(2);
-    })
-    .append("title")
-    .text(function (d) {
-      return "info" in d ? d.info.join("\n") : d.name;
-    });
-
-  // add node labels
-  snode.append("text")
-    .attr("x", -6)
-    .attr("y", function (d) {
-      return d.dy / 2;
-    })
-    .attr("dy", ".35em")
-    .attr("text-anchor", "end")
-    .attr("transform", null)
-    .text(function (d) {
-      return d.name;
-    })
-    .filter(function (d) {
-      return d.x < w / 2;
-    })
-    .attr("x", 6 + sankey.nodeWidth())
-    .attr("text-anchor", "start");
-
-  // add mouseover actions
-  this._addMouseoverSelection(snode);
-
-  // enable vertical dragging with recalculation of link placement
-  function dragmove(d) {
-    d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(h - d.dy, d3.event.y))) + ")");
-    sankey.relayout();
-    slink.attr("d", spath);
-  }
 }

Modified: incubator/ambari/branches/branch-1.2/ambari-web/vendor/styles/cubism.css
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/ambari-web/vendor/styles/cubism.css?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/ambari-web/vendor/styles/cubism.css (original)
+++ incubator/ambari/branches/branch-1.2/ambari-web/vendor/styles/cubism.css Mon Feb  4 02:23:55 2013
@@ -6,69 +6,57 @@
   width: 100%;
 }
 
-#dag_viewer line.link {
+#dag_viewer .axis path,
+#dag_viewer .axis line {
+  fill: none;
+  stroke: #000;
+  shape-rendering: crispEdges;
+}
+
+#dag_viewer line.link,
+#dag_viewer path.link {
   fill: none;
   stroke: #666;
   stroke-width: 2.5px;
 }
 
+#dag_viewer line.link.finished,
+#dag_viewer path.link.finished {
+  stroke: #444;
+}
+
 #dag_viewer marker#finished {
-  fill: green;
+  fill: #444;
 }
 
-#dag_viewer line.link.finished {
-  stroke: green;
+#dag_viewer marker#circle {
+  fill: #666;
+  stroke: none;
 }
 
-#dag_viewer circle {
+#dag_viewer line.source.mark,
+#dag_viewer line.target.mark {
+  stroke: #666;
+  stroke-width: 2.5px;
+}
+
+#dag_viewer rect {
   fill: #ccc;
   stroke: #333;
   stroke-width: 1.5px;
 }
 
-#dag_viewer circle.finished {
-  fill: green;
+#dag_viewer rect.finished {
+  fill: #69BE28;
 }
 
-#dag_viewer text {
-  font: 20px sans-serif;
+#dag_viewer text.joblabel {
   pointer-events: none;
-}
-
-#dag_viewer text.hover {
-  font: 10px sans-serif;
-}
-
-#dag_viewer text.hover.shadow {
-  stroke: #fff;
-  stroke-width: 4px;
-  stroke-opacity: .8;
+  text-anchor: middle;
 }
 
 #dag_viewer text.shadow {
   stroke: #fff;
-  stroke-width: 4px;
+  stroke-width: 3px;
   stroke-opacity: .8;
 }
-
-#dag_viewer .snode rect {
-  cursor: move;
-  fill-opacity: .9;
-  shape-rendering: crispEdges;
-}
-
-#dag_viewer .snode text {
-  pointer-events: none;
-  text-shadow: 0 1px 0 #fff;
-}
-
-#dag_viewer .slink {
-  fill: none;
-  stroke: #000;
-  stroke-opacity: .2;
-}
-
-#dag_viewer .slink:hover {
-  stroke-opacity: .5;
-}
-

Modified: incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_ganglia_addon_rpm.sh
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_ganglia_addon_rpm.sh?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_ganglia_addon_rpm.sh (original)
+++ incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_ganglia_addon_rpm.sh Mon Feb  4 02:23:55 2013
@@ -27,7 +27,7 @@ if [[ -z "${BUILD_DIR}" ]]; then
 fi
 
 if [[ -z "${VERSION}" ]]; then
-  VERSION="0.0.2.15"
+  VERSION="1.2.1.2"
 fi
 
 if [[ -z "${RELEASE}" ]]; then

Modified: incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_nagios_addon_rpm.sh
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_nagios_addon_rpm.sh?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_nagios_addon_rpm.sh (original)
+++ incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/create_nagios_addon_rpm.sh Mon Feb  4 02:23:55 2013
@@ -27,7 +27,7 @@ if [[ -z "${BUILD_DIR}" ]]; then
 fi
 
 if [[ -z "${VERSION}" ]]; then
-  VERSION="0.0.2.15"
+  VERSION="1.2.1.2"
 fi
 
 if [[ -z "${RELEASE}" ]]; then

Modified: incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_ganglia_addons.spec
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_ganglia_addons.spec?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_ganglia_addons.spec (original)
+++ incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_ganglia_addons.spec Mon Feb  4 02:23:55 2013
@@ -24,7 +24,7 @@
 
 Summary: Ganglia Add-ons for HDP Monitoring Dashboard
 Name: hdp_mon_ganglia_addons
-Version: 0.0.2.15
+Version: 1.2.1.2
 URL: http://hortonworks.com
 Release: 1
 License: Apache License, Version 2.0

Modified: incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_nagios_addons.spec
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_nagios_addons.spec?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_nagios_addons.spec (original)
+++ incubator/ambari/branches/branch-1.2/contrib/addons/package/rpm/hdp_mon_nagios_addons.spec Mon Feb  4 02:23:55 2013
@@ -24,7 +24,7 @@
 
 Summary: Nagios Add-ons for HDP Monitoring Dashboard
 Name: hdp_mon_nagios_addons
-Version: 0.0.2.15
+Version: 1.2.1.2
 URL: http://hortonworks.com
 Release: 1
 License: Apache License, Version 2.0
@@ -60,7 +60,7 @@ monitoring of a Hadoop Cluster
 
 %__cp -rf scripts/* $RPM_BUILD_ROOT/%{nagioshdpscripts_dir}/
 %__cp -rf plugins/* $RPM_BUILD_ROOT/%{nagiosplugin_dir}/
-echo "Alias /hdp %{_prefix}/share/hdp" >> $RPM_BUILD_ROOT/%{httpd_confdir}/hdp_mon_nagios_addons.conf
+echo "Alias /ambarinagios %{_prefix}/share/hdp" >> $RPM_BUILD_ROOT/%{httpd_confdir}/hdp_mon_nagios_addons.conf
 echo "<Directory /usr/share/hdp>" >> $RPM_BUILD_ROOT/%{httpd_confdir}/hdp_mon_nagios_addons.conf
 echo "  Options None" >> $RPM_BUILD_ROOT/%{httpd_confdir}/hdp_mon_nagios_addons.conf
 echo "  AllowOverride None" >> $RPM_BUILD_ROOT/%{httpd_confdir}/hdp_mon_nagios_addons.conf

Modified: incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/pom.xml
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/pom.xml?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/pom.xml (original)
+++ incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/pom.xml Mon Feb  4 02:23:55 2013
@@ -20,7 +20,7 @@
   <groupId>org.apache.ambari</groupId>
   <artifactId>ambari-log4j</artifactId>
   <packaging>jar</packaging>
-  <version>1.0</version>
+  <version>1.2.1-SNAPSHOT</version>
   <name>ambari-log4j</name>
   <url>http://maven.apache.org</url>
   <repositories>

Modified: incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/main/java/org/apache/ambari/log4j/hadoop/mapreduce/jobhistory/MapReduceJobHistoryUpdater.java
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/main/java/org/apache/ambari/log4j/hadoop/mapreduce/jobhistory/MapReduceJobHistoryUpdater.java?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/main/java/org/apache/ambari/log4j/hadoop/mapreduce/jobhistory/MapReduceJobHistoryUpdater.java (original)
+++ incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/main/java/org/apache/ambari/log4j/hadoop/mapreduce/jobhistory/MapReduceJobHistoryUpdater.java Mon Feb  4 02:23:55 2013
@@ -23,7 +23,12 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -127,7 +132,7 @@ public class MapReduceJobHistoryUpdater 
     
     workflowSelectPS =
         connection.prepareStatement(
-            "SELECT workflowId FROM " + WORKFLOW_TABLE + " where workflowId = ?"
+            "SELECT workflowContext FROM " + WORKFLOW_TABLE + " where workflowId = ?"
             );
 
     workflowPS = 
@@ -154,6 +159,8 @@ public class MapReduceJobHistoryUpdater 
             "UPDATE " +
                 WORKFLOW_TABLE +
                 " SET " +
+                "workflowContext = ?, " +
+                "numJobsTotal = ?, " +
                 "lastUpdateTime = ?, " +
                 "duration = ? - (SELECT startTime FROM " +
                 WORKFLOW_TABLE +
@@ -597,6 +604,57 @@ public class MapReduceJobHistoryUpdater 
     return context;
   }
   
+  public static void mergeEntries(Map<String, Set<String>> edges, List<WorkflowDagEntry> entries) {
+    if (entries == null)
+      return;
+    for (WorkflowDagEntry entry : entries) {
+      if (!edges.containsKey(entry.getSource()))
+        edges.put(entry.getSource(), new TreeSet<String>());
+      Set<String> targets = edges.get(entry.getSource());
+      targets.addAll(entry.getTargets());
+    }
+  }
+  
+  public static WorkflowDag constructMergedDag(WorkflowContext workflowContext, WorkflowContext existingWorkflowContext) {
+    Map<String, Set<String>> edges = new TreeMap<String, Set<String>>();
+    if (existingWorkflowContext.getWorkflowDag() != null)
+      mergeEntries(edges, existingWorkflowContext.getWorkflowDag().getEntries());
+    if (workflowContext.getWorkflowDag() != null)
+      mergeEntries(edges, workflowContext.getWorkflowDag().getEntries());
+    WorkflowDag mergedDag = new WorkflowDag();
+    for (Entry<String,Set<String>> edge : edges.entrySet()) {
+      WorkflowDagEntry entry = new WorkflowDagEntry();
+      entry.setSource(edge.getKey());
+      entry.getTargets().addAll(edge.getValue());
+      mergedDag.addEntry(entry);
+    }
+    return mergedDag;
+  }
+  
+  private static WorkflowContext getSanitizedWorkflow(WorkflowContext workflowContext, WorkflowContext existingWorkflowContext) {
+    WorkflowContext sanitizedWC = new WorkflowContext();
+    if (existingWorkflowContext == null) {
+      sanitizedWC.setWorkflowDag(workflowContext.getWorkflowDag());
+      sanitizedWC.setParentWorkflowContext(workflowContext.getParentWorkflowContext());
+    } else {
+      sanitizedWC.setWorkflowDag(constructMergedDag(existingWorkflowContext, workflowContext));
+      sanitizedWC.setParentWorkflowContext(existingWorkflowContext.getParentWorkflowContext());
+    }
+    return sanitizedWC;
+  }
+  
+  private static String getWorkflowString(WorkflowContext sanitizedWC) {
+    String sanitizedWCString = null;
+    try {
+      ObjectMapper om = new ObjectMapper();
+      sanitizedWCString = om.writeValueAsString(sanitizedWC);
+    } catch (IOException e) {
+      e.printStackTrace();
+      sanitizedWCString = "";
+    }
+    return sanitizedWCString;
+  }
+  
   private void processJobSubmittedEvent(
       PreparedStatement jobPS, 
       PreparedStatement workflowSelectPS, PreparedStatement workflowPS, 
@@ -616,35 +674,35 @@ public class MapReduceJobHistoryUpdater 
       
       // Get workflow information
       boolean insertWorkflow = false;
+      String existingContextString = null;
       
+      ResultSet rs = null;
       try {
         workflowSelectPS.setString(1, workflowContext.getWorkflowId());
         workflowSelectPS.execute();
-        ResultSet rs = workflowSelectPS.getResultSet();
-        insertWorkflow = !rs.next();
+        rs = workflowSelectPS.getResultSet();
+        if (rs.next()) {
+          existingContextString = rs.getString(1);
+        } else {
+          insertWorkflow = true;
+        }
       } catch (SQLException sqle) {
         LOG.warn("workflow select failed with: ", sqle);
         insertWorkflow = false;
+      } finally {
+        try {
+          if (rs != null)
+            rs.close();
+        } catch (SQLException e) {
+          LOG.error("Exception while closing ResultSet", e);
+        }
       }
 
       // Insert workflow 
       if (insertWorkflow) {
-        WorkflowContext sanitizedWC = new WorkflowContext();
-        sanitizedWC.setWorkflowDag(workflowContext.getWorkflowDag());
-        sanitizedWC.setParentWorkflowContext(workflowContext.getParentWorkflowContext());
-
-        String sanitizedWCString = null;
-        try {
-          ObjectMapper om = new ObjectMapper();
-          sanitizedWCString = om.writeValueAsString(sanitizedWC);
-        } catch (IOException e) {
-          e.printStackTrace();
-          sanitizedWCString = "";
-        } 
-
         workflowPS.setString(1, workflowContext.getWorkflowId());
         workflowPS.setString(2, workflowContext.getWorkflowName());
-        workflowPS.setString(3, sanitizedWCString);
+        workflowPS.setString(3, getWorkflowString(getSanitizedWorkflow(workflowContext, null)));
         workflowPS.setString(4, historyEvent.getUserName());
         workflowPS.setLong(5, historyEvent.getSubmitTime());
         workflowPS.setLong(6, historyEvent.getSubmitTime());
@@ -653,10 +711,22 @@ public class MapReduceJobHistoryUpdater 
         LOG.debug("Successfully inserted workflowId = " + 
             workflowContext.getWorkflowId());
       } else {
-        workflowUpdateTimePS.setLong(1, historyEvent.getSubmitTime());
-        workflowUpdateTimePS.setLong(2, historyEvent.getSubmitTime());
-        workflowUpdateTimePS.setString(3, workflowContext.getWorkflowId());
-        workflowUpdateTimePS.setString(4, workflowContext.getWorkflowId());
+        ObjectMapper om = new ObjectMapper();
+        WorkflowContext existingWorkflowContext = null;
+        try {
+          if (existingContextString != null)
+            existingWorkflowContext = om.readValue(existingContextString.getBytes(), WorkflowContext.class);
+        } catch (IOException e) {
+          LOG.warn("Couldn't read existing workflow context for " + workflowContext.getWorkflowId(), e);
+        }
+        
+        WorkflowContext sanitizedWC = getSanitizedWorkflow(workflowContext, existingWorkflowContext);
+        workflowUpdateTimePS.setString(1, getWorkflowString(sanitizedWC));
+        workflowUpdateTimePS.setLong(2, sanitizedWC.getWorkflowDag().size());
+        workflowUpdateTimePS.setLong(3, historyEvent.getSubmitTime());
+        workflowUpdateTimePS.setLong(4, historyEvent.getSubmitTime());
+        workflowUpdateTimePS.setString(5, workflowContext.getWorkflowId());
+        workflowUpdateTimePS.setString(6, workflowContext.getWorkflowId());
         workflowUpdateTimePS.executeUpdate();
         LOG.debug("Successfully updated workflowId = " + 
             workflowContext.getWorkflowId());

Modified: incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestJobHistoryParsing.java
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestJobHistoryParsing.java?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestJobHistoryParsing.java (original)
+++ incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestJobHistoryParsing.java Mon Feb  4 02:23:55 2013
@@ -30,6 +30,7 @@ import org.apache.ambari.eventdb.model.W
 import org.apache.ambari.log4j.hadoop.mapreduce.jobhistory.MapReduceJobHistoryUpdater;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.mapred.JobHistory;
+import org.apache.hadoop.mapreduce.JobID;
 import org.apache.hadoop.tools.rumen.JobSubmittedEvent;
 import org.apache.hadoop.util.StringUtils;
 
@@ -65,20 +66,42 @@ public class TestJobHistoryParsing exten
     test("id_= 0-1", "something.name", "1=0", adj);
   }
   
+  public void test3() {
+    String s = "`~!@#$%^&*()-_=+[]{}|,.<>/?;:'\"";
+    test(s, s, s, new HashMap<String,String[]>());
+  }
+  
+  public void test4() {
+    Map<String,String[]> adj = new HashMap<String,String[]>();
+    adj.put("X", new String[] {});
+    test("", "jobName", "X", adj);
+  }
+  
   public void test(String workflowId, String workflowName, String workflowNodeName, Map<String,String[]> adjacencies) {
     Configuration conf = new Configuration();
     setProperties(conf, workflowId, workflowName, workflowNodeName, adjacencies);
     String log = log("JOB", new String[] {ID, NAME, NODE, ADJ},
         new String[] {conf.get(ID_PROP), conf.get(NAME_PROP), conf.get(NODE_PROP), JobHistory.JobInfo.getWorkflowAdjacencies(conf)});
     ParsedLine line = new ParsedLine(log);
-    JobSubmittedEvent event = new JobSubmittedEvent(null, "", "", 0l, "", null, "", line.get(ID), line.get(NAME), line.get(NODE), line.get(ADJ));
+    JobID jobid = new JobID("id", 1);
+    JobSubmittedEvent event = new JobSubmittedEvent(jobid, workflowName, "", 0l, "", null, "", line.get(ID), line.get(NAME), line.get(NODE), line.get(ADJ));
     WorkflowContext context = MapReduceJobHistoryUpdater.buildWorkflowContext(event);
-    assertEquals("Didn't recover workflowId", workflowId, context.getWorkflowId());
+    
+    String resultingWorkflowId = workflowId;
+    if (workflowId.isEmpty())
+      resultingWorkflowId = jobid.toString().replace("job_", "mr_");
+    assertEquals("Didn't recover workflowId", resultingWorkflowId, context.getWorkflowId());
     assertEquals("Didn't recover workflowName", workflowName, context.getWorkflowName());
     assertEquals("Didn't recover workflowNodeName", workflowNodeName, context.getWorkflowEntityName());
-    assertEquals("Got incorrect number of adjacencies", adjacencies.size(), context.getWorkflowDag().getEntries().size());
+    
+    Map<String,String[]> resultingAdjacencies = adjacencies;
+    if (resultingAdjacencies.size() == 0) {
+      resultingAdjacencies = new HashMap<String,String[]>();
+      resultingAdjacencies.put(workflowNodeName, new String[] {});
+    }
+    assertEquals("Got incorrect number of adjacencies", resultingAdjacencies.size(), context.getWorkflowDag().getEntries().size());
     for (WorkflowDagEntry entry : context.getWorkflowDag().getEntries()) {
-      String[] sTargets = adjacencies.get(entry.getSource());
+      String[] sTargets = resultingAdjacencies.get(entry.getSource());
       assertNotNull("No original targets for " + entry.getSource(), sTargets);
       List<String> dTargets = entry.getTargets();
       assertEquals("Got incorrect number of targets for " + entry.getSource(), sTargets.length, dTargets.size());

Added: incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestMapReduceJobHistoryUpdater.java
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestMapReduceJobHistoryUpdater.java?rev=1442010&view=auto
==============================================================================
--- incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestMapReduceJobHistoryUpdater.java (added)
+++ incubator/ambari/branches/branch-1.2/contrib/ambari-log4j/src/test/java/org/apache/ambari/TestMapReduceJobHistoryUpdater.java Mon Feb  4 02:23:55 2013
@@ -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.
+ */
+package org.apache.ambari;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.ambari.eventdb.model.WorkflowContext;
+import org.apache.ambari.eventdb.model.WorkflowDag;
+import org.apache.ambari.eventdb.model.WorkflowDag.WorkflowDagEntry;
+import org.apache.ambari.log4j.hadoop.mapreduce.jobhistory.MapReduceJobHistoryUpdater;
+
+/**
+ * 
+ */
+public class TestMapReduceJobHistoryUpdater extends TestCase {
+  public void testDagMerging() {
+    WorkflowDag dag1 = new WorkflowDag();
+    dag1.addEntry(getEntry("a", "b", "c"));
+    dag1.addEntry(getEntry("b", "d"));
+    WorkflowContext one = new WorkflowContext();
+    one.setWorkflowDag(dag1);
+    
+    WorkflowDag dag2 = new WorkflowDag();
+    dag2.addEntry(getEntry("a", "d"));
+    dag2.addEntry(getEntry("c", "e"));
+    WorkflowContext two = new WorkflowContext();
+    two.setWorkflowDag(dag2);
+    
+    WorkflowDag emptyDag = new WorkflowDag();
+    WorkflowContext three = new WorkflowContext();
+    three.setWorkflowDag(emptyDag);
+    
+    WorkflowDag mergedDag = new WorkflowDag();
+    mergedDag.addEntry(getEntry("a", "b", "c", "d"));
+    mergedDag.addEntry(getEntry("b", "d"));
+    mergedDag.addEntry(getEntry("c", "e"));
+    
+    assertEquals(mergedDag, MapReduceJobHistoryUpdater.constructMergedDag(one, two));
+    assertEquals(mergedDag, MapReduceJobHistoryUpdater.constructMergedDag(two, one));
+    
+    // test blank dag
+    assertEquals(dag1, MapReduceJobHistoryUpdater.constructMergedDag(three, one));
+    assertEquals(dag1, MapReduceJobHistoryUpdater.constructMergedDag(one, three));
+    assertEquals(dag2, MapReduceJobHistoryUpdater.constructMergedDag(three, two));
+    assertEquals(dag2, MapReduceJobHistoryUpdater.constructMergedDag(two, three));
+    
+    // test null dag
+    assertEquals(dag1, MapReduceJobHistoryUpdater.constructMergedDag(new WorkflowContext(), one));
+    assertEquals(dag1, MapReduceJobHistoryUpdater.constructMergedDag(one, new WorkflowContext()));
+    assertEquals(dag2, MapReduceJobHistoryUpdater.constructMergedDag(new WorkflowContext(), two));
+    assertEquals(dag2, MapReduceJobHistoryUpdater.constructMergedDag(two, new WorkflowContext()));
+    
+    // test same dag
+    assertEquals(dag1, MapReduceJobHistoryUpdater.constructMergedDag(one, one));
+    assertEquals(dag2, MapReduceJobHistoryUpdater.constructMergedDag(two, two));
+    assertEquals(emptyDag, MapReduceJobHistoryUpdater.constructMergedDag(three, three));
+  }
+  
+  private static WorkflowDagEntry getEntry(String source, String... targets) {
+    WorkflowDagEntry entry = new WorkflowDagEntry();
+    entry.setSource(source);
+    for (String target : targets) {
+      entry.addTarget(target);
+    }
+    return entry;
+  }
+  
+  private static void assertEquals(WorkflowDag dag1, WorkflowDag dag2) {
+    assertEquals(dag1.size(), dag2.size());
+    List<WorkflowDagEntry> entries1 = dag1.getEntries();
+    List<WorkflowDagEntry> entries2 = dag2.getEntries();
+    assertEquals(entries1.size(), entries2.size());
+    for (int i = 0; i < entries1.size(); i++) {
+      WorkflowDagEntry e1 = entries1.get(i);
+      WorkflowDagEntry e2 = entries2.get(i);
+      assertEquals(e1.getSource(), e2.getSource());
+      List<String> t1 = e1.getTargets();
+      List<String> t2 = e2.getTargets();
+      assertEquals(t1.size(), t2.size());
+      for (int j = 0; j < t1.size(); j++) {
+        assertEquals(t1.get(j), t2.get(j));
+      }
+    }
+  }
+}

Modified: incubator/ambari/branches/branch-1.2/pom.xml
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/branch-1.2/pom.xml?rev=1442010&r1=1442009&r2=1442010&view=diff
==============================================================================
--- incubator/ambari/branches/branch-1.2/pom.xml (original)
+++ incubator/ambari/branches/branch-1.2/pom.xml Mon Feb  4 02:23:55 2013
@@ -21,7 +21,7 @@
   <artifactId>ambari</artifactId>
   <packaging>pom</packaging>
   <name>Ambari Main</name>
-  <version>1.2.0-SNAPSHOT</version>
+  <version>1.2.1-SNAPSHOT</version>
   <description>Ambari</description>
   <pluginRepositories>
     <pluginRepository>
@@ -115,7 +115,6 @@
           <excludes>
             <exclude>**/*.json</exclude>
             <exclude>derby.log</exclude>
-            <exclude>CHANGES.txt</exclude>
             <exclude>AMBARI-666-CHANGES.txt</exclude>
             <exclude>pass.txt</exclude>
             <exclude>contrib/addons/test/dataServices/jmx/data/cluster_configuration.json.nohbase</exclude>
@@ -125,7 +124,6 @@
             <exclude>.git/</exclude>
             <exclude>**/.gitignore</exclude>
             <exclude>**/.gitattributes</exclude>
-
             <!--gitignore content-->
             <exclude>.DS_Store</exclude>
             <exclude>.iml/</exclude>
@@ -137,6 +135,11 @@
             <exclude>.hg</exclude>
             <exclude>.hgignore</exclude>
             <exclude>.hgtags</exclude>
+
+
+            <!--Python Mock library (BSD license)-->
+            <exclude>ambari-common/src/test/python/mock/**</exclude>
+
           </excludes>
         </configuration>
       </plugin>