azkaban-developers

Details

diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 676affe..150cfeb 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -42,8 +42,8 @@
 #end
 
 				<div id="all-jobs-content">
-					<div class="section-hd">
-						<h2><a href="${context}/executor?execid=${execid}">Execution <span>$execid</span></a></h2>
+					<div class="section-hd flow-header">
+						<h2><a href="${context}/executor?execid=${execid}">Flow Execution <span>$execid</span></a></h2>
 						<div class="section-sub-hd">
 							<h4><a href="${context}/manager?project=${projectName}">Project <span>$projectName</span></a></h4>
 							<h4 class="separator">&gt;</h4>
@@ -75,7 +75,21 @@
 						</div>
 					</div>
 					<div id="jobListView">
-					<p>This is my joblist view</p>
+						<table>
+							<thead>
+								<tr>
+									<th>Name</th>
+									<th class="timeline">Timeline</th>
+									<th class="date">Start Time</th>
+									<th class="date">End Time</th>
+									<th class="elapse">Elapsed</th>
+									<th class="status">Status</th>
+									<th class="logs">Logs</th>
+								</tr>
+							</thead>
+							<tbody id="executableBody">
+							</tbody>
+						</table>
 					</div>
 				</div>
 #end
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index f7ede12..f0853ff 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -40,7 +40,7 @@
 #end
 
 				<div id="all-jobs-content">
-					<div class="section-hd">
+					<div class="section-hd flow-header">
 						<h2><a href="${context}/manager?project=${project.name}&flow=${flowid}">Flow <span>$flowid</span></a></h2>
 						<div class="section-sub-hd">
 							<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index d57c62e..65437f9 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1415,7 +1415,7 @@ span.sublabel {
 }
 
 #flow-status table td.SUCCEEDED {
-	color:  #00CC33;
+	color:  #4e911e;
 }
 
 #flow-status table td.RUNNING {
@@ -1438,6 +1438,77 @@ span.sublabel {
 	font-weight: bold;
 }
 
+#jobListView table th.date {
+	width: 140px;
+}
+
+#jobListView table th.elapse {
+	width: 90px;
+}
+
+#jobListView table th.status {
+	width: 100px;
+}
+
+#jobListView table th.logs {
+	width: 10px;
+}
+
+#jobListView table th.timeline {
+	width: 280px;
+}
+
+#jobListView table td.timeline {
+	padding: 0px;
+	height: 100%;
+	vertical-align: bottom;
+	margin: 0px;
+}
+
+#jobListView table td {
+	padding-left: 6px;
+	height: 20px;
+}
+
+.flow-header {
+	height: 48px;
+}
+
+.outerProgress {
+	width: 280px;
+	margin: 4px;
+	background-color: #e2e4e3;
+}
+
+.progressBox {
+	height: 24px;
+	background-color: #CCC;
+}
+
+.progressBox.SUCCEEDED {
+	background-color: #4e911e;
+	background: -moz-linear-gradient(top, #5bb41c 0, #598d1e 100%);
+    background: -o-linear-gradient(top, #5bb41c 0, #598d1e 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0,#5bb41c), color-stop(100%,#598d1e));
+    background: linear-gradient(top, #5bb41c 0, #598d1e 100%);
+}
+
+.progressBox.FAILED {
+	background-color: #9e3600;
+	background: -moz-linear-gradient(top, #d43c00 0, #9e3600 100%);
+    background: -o-linear-gradient(top, #d43c00 0, #9e3600 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0,#d43c00), color-stop(100%,#9e3600));
+    background: linear-gradient(top, #d43c00 0, #9e3600 100%);
+}
+
+.progressBox.RUNNING {
+	background-color: #009FC9;
+	background: -moz-linear-gradient(top, #009FC9 0, #007b9b 100%);
+    background: -o-linear-gradient(top, #009FC9 0, #007b9b 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0,#009FC9), color-stop(100%,#007b9b));
+    background: linear-gradient(top, #009FC9 0, #007b9b 100%);
+}
+
 /* old styles */
 
 .azkaban-charts .hitarea {
diff --git a/src/web/js/azkaban.date.utils.js b/src/web/js/azkaban.date.utils.js
index 2f6f01e..056542d 100644
--- a/src/web/js/azkaban.date.utils.js
+++ b/src/web/js/azkaban.date.utils.js
@@ -25,13 +25,13 @@ var getDuration = function(startMs, endMs) {
 		var hours = Math.floor(mins / 60);
 		mins = mins % 60;
 		if (hours < 24) {
-			return hours + "h " + mins + " m" + seconds + "s";
+			return hours + "h " + mins + "m " + seconds + "s";
 		}
 		
 		var days = Math.floor(hours / 24);
 		hours = hours % 24;
 		
-		return days + "d " + hours + "h " + mins + "m " + seconds + "s";
+		return days + "d " + hours + "h " + mins + "m";
 	}
 
 	return "-";
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index 1ec6f2a..c95520d 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -545,6 +545,145 @@ azkaban.SvgGraphView = Backbone.View.extend({
 	}
 });
 
+var executionListView;
+azkaban.ExecutionListView = Backbone.View.extend({
+	events: {
+	},
+	initialize: function(settings) {
+		this.model.bind('change:graph', this.renderJobs, this);
+		this.model.bind('change:update', this.updateJobs, this);
+	},
+	renderJobs: function(evt) {
+		var data = this.model.get("data");
+		var lastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
+		this.updateJobRow(data.nodes);
+		this.updateProgressBar(data);
+	},
+	updateJobs: function(evt) {
+		var data = this.model.get("update");
+		var lastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
+		
+		this.updateJobRow(data.nodes);
+		this.updateProgressBar(this.model.get("data"));
+	},
+	updateJobRow: function(nodes) {
+		var executingBody = $("#executableBody");
+		nodes.sort(function(a,b) { return a.startTime - b.startTime; });
+		
+		for (var i = 0; i < nodes.length; ++i) {
+			var node = nodes[i];
+			if (node.startTime > -1) {
+				var row = document.getElementById(node.id + "-row");
+				if (!row) {
+					this.addNodeRow(node);
+				}
+				
+				$("#" + node.id + "-status").text(node.status);
+				
+				var startdate = new Date(node.startTime);
+				$("#" + node.id + "-start").text(getDateFormat(startdate));
+				
+				var endTime = node.endTime;
+				if (node.endTime == -1) {
+					$("#" + node.id + "-end").text("-");
+					endTime = node.startTime + 1;
+				}
+				else {
+					var enddate = new Date(node.endTime);
+					$("#" + node.id + "-end").text(getDateFormat(enddate));
+				}
+				
+				var progressBar = $("#" + node.id + "-progressbar");
+				for (var j = 0; j < statusList.length; ++j) {
+					var status = statusList[j];
+					progressBar.removeClass(status);
+				}
+				progressBar.addClass(node.status);
+
+				if (node.endTime == -1) {
+					$("#" + node.id + "-elapse").text("0 sec");
+				}
+				else {
+					$("#" + node.id + "-elapse").text(getDuration(node.startTime, node.endTime));
+				}
+			}
+		}
+	},
+	updateProgressBar: function(data) {
+		if(data.startTime == -1) {
+			return;
+		}
+		
+		var flowLastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
+		var flowStartTime = data.startTime;
+		
+		var outerWidth = $(".outerProgress").css("width");
+		if (outerWidth.substring(outerWidth.length - 2, outerWidth.length) == "px") {
+			outerWidth = outerWidth.substring(0, outerWidth.length - 2);
+		}
+		outerWidth = parseInt(outerWidth);
+		
+		var nodes = data.nodes;
+		for (var i = 0; i < nodes.length; ++i) {
+			var node = nodes[i];
+		
+			// calculate the progress
+			var diff = flowLastTime - flowStartTime;
+			
+			var factor = outerWidth/diff;
+			var left = Math.max((node.startTime-flowStartTime)*factor, 0);
+			var width = Math.max((node.endTime - node.startTime)*factor, 1);
+			width = Math.min(width, outerWidth);
+			
+			$("#" + node.id + "-progressbar").css("margin-left", left)
+			$("#" + node.id + "-progressbar").css("width", width);
+		}
+	},
+	addNodeRow: function(node) {
+		var executingBody = $("#executableBody");
+		var tr = document.createElement("tr");
+		var tdName = document.createElement("td");
+		var tdTimeline = document.createElement("td");
+		var tdStart = document.createElement("td");
+		var tdEnd = document.createElement("td");
+		var tdElapse = document.createElement("td");
+		var tdStatus = document.createElement("td");
+		var tdLog = document.createElement("td");
+		
+		$(tr).append(tdName);
+		$(tr).append(tdTimeline);
+		$(tr).append(tdStart);
+		$(tr).append(tdEnd);
+		$(tr).append(tdElapse);
+		$(tr).append(tdStatus);
+		$(tr).append(tdLog);
+		$(tr).attr("id", node.id + "-row");
+		$(tdTimeline).attr("id", node.id + "-timeline");
+		$(tdStart).attr("id", node.id + "-start");
+		$(tdEnd).attr("id", node.id + "-end");
+		$(tdElapse).attr("id", node.id + "-elapse");
+		$(tdStatus).attr("id", node.id + "-status");
+
+		var outerProgressBar = document.createElement("div");
+		$(outerProgressBar).addClass("outerProgress");
+
+		var progressBox = document.createElement("div");
+		$(progressBox).attr("id", node.id + "-progressbar");
+		$(progressBox).addClass("progressBox");
+		$(outerProgressBar).append(progressBox);
+		$(tdTimeline).append(outerProgressBar);
+		$(tdTimeline).addClass("timeline");
+
+		var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + node.id;
+		var a = document.createElement("a");
+		$(a).attr("href", requestURL);
+		$(a).text(node.id);
+		$(tdName).append(a);
+
+		executingBody.append(tr);
+	}
+});
+
 var graphModel;
 azkaban.GraphModel = Backbone.Model.extend({});
 
@@ -600,26 +739,14 @@ var updaterFunction = function() {
 
 $(function() {
 	var selected;
-	
-	if (window.location.hash) {
-		var hash = window.location.hash;
-		if (hash == "#jobslist") {
-			selected = "jobslist";
-		}
-		else if (hash == "#graph") {
-			// Redundant, but we may want to change the default. 
-			selected = "graph";
-		}
-		else {
-			selected = "graph";
-		}
-	}
-	flowTabView = new azkaban.FlowTabView({el:$( '#headertabs'), selectedView: selected });
+
+	flowTabView = new azkaban.FlowTabView({el:$( '#headertabs') });
 
 	graphModel = new azkaban.GraphModel();
 	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel});
 	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
 	statusView = new azkaban.StatusView({el:$('#flow-status'), model: graphModel});
+	executionListView = new azkaban.ExecutionListView({el: $('#jobListView'), model:graphModel});
 	var requestURL = contextURL + "/executor";
 
 	$.get(
@@ -643,6 +770,14 @@ $(function() {
 	          }
 	          
 	          graphModel.set({nodeMap: nodeMap});
+	          
+	          if (window.location.hash) {
+					var hash = window.location.hash;
+					if (hash == "#jobslist") {
+						flowTabView.handleJobslistLinkClick();
+					}
+			 }
+	          
 	      	  setTimeout(function() {updaterFunction()}, 5000);
 	      },
 	      "json"