azkaban-developers

Details

diff --git a/src/java/azkaban/execapp/FlowRunner.java b/src/java/azkaban/execapp/FlowRunner.java
index 2159f4f..993d1b4 100644
--- a/src/java/azkaban/execapp/FlowRunner.java
+++ b/src/java/azkaban/execapp/FlowRunner.java
@@ -358,7 +358,6 @@ public class FlowRunner extends EventHandler implements Runnable {
 			return true;
 		}
 
-		long currentTime = System.currentTimeMillis();
 		for (ExecutableNode node: jobsReadyToRun) {
 			Status nextStatus = getImpliedStatus(node);
 			
@@ -368,12 +367,12 @@ public class FlowRunner extends EventHandler implements Runnable {
 			}
 			else if (nextStatus == Status.KILLED || isCancelled()) {
 				logger.info("Killing " + node.getId() + " due to prior errors.");
-				node.killNode(currentTime);
+				node.killNode(System.currentTimeMillis());
 				fireEventListeners(Event.create(this, Type.JOB_FINISHED, node));
 			}
 			else if (nextStatus == Status.DISABLED) {
 				logger.info("Skipping disabled job " + node.getId() + ".");
-				node.skipNode(currentTime);
+				node.skipNode(System.currentTimeMillis());
 				fireEventListeners(Event.create(this, Type.JOB_FINISHED, node));
 			}
 			else {
@@ -401,7 +400,7 @@ public class FlowRunner extends EventHandler implements Runnable {
 		for(String end: flow.getEndNodes()) {
 			ExecutableNode node = flow.getExecutableNode(end);
 
-			if (node.getStatus() == Status.KILLED) {
+			if (node.getStatus() == Status.KILLED || node.getStatus() == Status.FAILED) {
 				succeeded = false;
 			}
 			
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 7dcee5e..d944f4c 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -28,7 +28,6 @@
 		<script type="text/javascript" src="${context}/js/flowstats.js"></script>
 		<script type="text/javascript" src="${context}/js/flowstats-no-data.js"></script>
 
-		<script type="text/javascript" src="${context}/js/azkaban/view/flow-execution-list.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban/view/exflow.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -119,7 +118,7 @@
     <div class="container-full" id="jobListView">
 			<div class="row">
 				<div class="col-xs-12">
-					<table class="table table-striped table-bordered table-condensed table-hover executions-table">
+					<table class="table table-bordered table-condensed table-hover executions-table">
 						<thead>
 							<tr>
 								<th>Name</th>
diff --git a/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm b/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
index 364a968..773416e 100644
--- a/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
@@ -31,7 +31,8 @@
 	<script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>	
 	<script type="text/javascript" src="${context}/js/azkaban/view/flow-stats.js"></script>
 	<script type="text/javascript" src="${context}/js/azkaban/view/flow-job.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/view/flow-execution-list.js"></script>
 	<script type="text/javascript" src="${context}/js/azkaban/view/svg-graph.js"></script>
 	<script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
 
-	<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" /> 	
\ No newline at end of file
+	<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
\ No newline at end of file
diff --git a/src/less/flow.less b/src/less/flow.less
index 5b1a957..99f6be5 100644
--- a/src/less/flow.less
+++ b/src/less/flow.less
@@ -60,6 +60,10 @@
   &.QUEUED {
     background-color: #009fc9;
   }
+  
+  &.KILLED {
+    background-color: #ff9999;
+  }
 }
 
 td {
@@ -111,7 +115,7 @@ td {
     }
 
     &.KILLED {
-      background-color: #d9534f;
+      background-color: #ff9999;
     }
   }
 }

src/less/tables.less 44(+34 -10)

diff --git a/src/less/tables.less b/src/less/tables.less
index d67061b..d96815f 100644
--- a/src/less/tables.less
+++ b/src/less/tables.less
@@ -53,7 +53,26 @@ table.table-properties {
 
 // Table of executions.
 .executions-table {
-  th {
+  tr {
+    &.expanded {
+      opacity: 0.6;
+    }
+  }
+
+  td {
+  	&.subflowrow {
+  		padding: 0px 0px;
+  	
+  		table {
+  			margin: 0px;
+  			background-color: rgba(230, 230, 230, 0.75);
+  			
+  			td {
+  				background-color: none;
+  			}
+  		}
+  	}
+
     &.date {
       width: 160px;
     }
@@ -74,7 +93,7 @@ table.table-properties {
       width: 90px;
     }
 
-    &.status {
+    &.statustd {
       width: 100px;
     }
 
@@ -89,19 +108,24 @@ table.table-properties {
     &.logs {
       width: 30px;
     }
-  }
-
-  td {
-    &.timeline {
+    
+     &.timeline {
       width: 280px;
       padding: 0px;
       height: 100%;
       vertical-align: bottom;
       margin: 0px;
     }
-
-    &.execId {
-      font-weight: bold;
-    }
+    
+    &.startTime {
+  		width: 160px;
+  	}
+  	
+  	&.endTime {
+  		width: 160px;
+  	}
+    &.elapsedTime {
+  		width: 90px;
+  	}
   }
 }
diff --git a/src/web/js/azkaban/view/exflow.js b/src/web/js/azkaban/view/exflow.js
index 1452014..eee5a1a 100644
--- a/src/web/js/azkaban/view/exflow.js
+++ b/src/web/js/azkaban/view/exflow.js
@@ -382,25 +382,24 @@ azkaban.GraphModel = Backbone.Model.extend({});
 var logModel;
 azkaban.LogModel = Backbone.Model.extend({});
 
-var updateStatus = function() {
+var updateStatus = function(updateTime) {
 	var requestURL = contextURL + "/executor";
 	var oldData = graphModel.get("data");
 	var nodeMap = graphModel.get("nodeMap");
 	
-	var updateTime = oldData.updateTime ? oldData.updateTime : 0;
+	if (!updateTime) {
+		updateTime = oldData.updateTime ? oldData.updateTime : 0;
+	}
+
 	var requestData = {
 		"execid": execId, 
 		"ajax": "fetchexecflowupdate", 
 		"lastUpdateTime": updateTime
 	};
-
-	graphModel.set({"lastUpdateTime":updateTime})
 	
 	var successHandler = function(data) {
 		console.log("data updated");
 		if (data.updateTime) {
-			updateTime = data.updateTime;
-			
 			updateGraph(oldData, data);
 	
 			graphModel.set({"update": data});
@@ -451,6 +450,7 @@ var updaterFunction = function() {
 		}
 		else {
 			console.log("Flow finished, so no more updates");
+			setTimeout(function() {updateStatus(0);}, 500);
 		}
 	}
 	else {
diff --git a/src/web/js/azkaban/view/flow-execution-list.js b/src/web/js/azkaban/view/flow-execution-list.js
index 23210a5..69621a3 100644
--- a/src/web/js/azkaban/view/flow-execution-list.js
+++ b/src/web/js/azkaban/view/flow-execution-list.js
@@ -1,56 +1,67 @@
 var executionListView;
 azkaban.ExecutionListView = Backbone.View.extend({
 	events: {
-		//"click .flow-progress-bar": "handleProgressBoxClick"
+		//"contextmenu .flow-progress-bar": "handleProgressBoxClick"
 	},
 	
 	initialize: function(settings) {
 		this.model.bind('change:graph', this.renderJobs, this);
 		this.model.bind('change:update', this.updateJobs, this);
+		
+		// This is for tabbing. Blah, hacky
+		var executingBody = $("#executableBody")[0];
+		executingBody.level = 0;
 	},
 	
 	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);
-	},
-
-	/*handleProgressBoxClick: function(evt) {
-		var target = evt.currentTarget;
-		var job = target.job;
-		var attempt = target.attempt;
-		
-		var data = this.model.get("data");
-		var node = data.nodes[job];
+		var executingBody = $("#executableBody");
+		this.updateJobRow(data.nodes, executingBody);
 		
-		var jobId = event.currentTarget.jobid;
-		var requestURL = contextURL + "/manager?project=" + projectName + "&execid=" + execId + "&job=" + job + "&attempt=" + attempt;
-	
-		var menu = [	
-				{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-				{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
-		];
-	
-		contextMenuView.show(evt, menu);
-	},*/
+		var flowLastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
+		var flowStartTime = data.startTime;
+		this.updateProgressBar(data, flowStartTime, flowLastTime);
+	},
+//
+//	handleProgressBoxClick: function(evt) {
+//		var target = evt.currentTarget;
+//		var job = target.job;
+//		var attempt = target.attempt;
+//		
+//		var data = this.model.get("data");
+//		var node = data.nodes[job];
+//		
+//		var jobId = event.currentTarget.jobid;
+//		var requestURL = contextURL + "/manager?project=" + projectName + "&execid=" + execId + "&job=" + job + "&attempt=" + attempt;
+//	
+//		var menu = [	
+//				{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
+//				{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}}
+//		];
+//	
+//		contextMenuView.show(evt, menu);
+//	},
 	
 	updateJobs: function(evt) {
 		var update = this.model.get("update");
 		var lastTime = update.endTime == -1 ? (new Date()).getTime() : update.endTime;
+		var executingBody = $("#executableBody");
 		
 		if (update.nodes) {
-			this.updateJobRow(update.nodes);
+			this.updateJobRow(update.nodes, executingBody);
 		}
-		this.updateProgressBar(this.model.get("data"));
+		
+		var data = this.model.get("data");
+		var flowLastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
+		var flowStartTime = data.startTime;
+		this.updateProgressBar(data, flowStartTime, flowLastTime);
 	},
-	
-	updateJobRow: function(nodes) {
+	updateJobRow: function(nodes, body) {
 		if (!nodes) {
 			return;
 		}
 		
-		var executingBody = $("#executableBody");
 		nodes.sort(function(a,b) { return a.startTime - b.startTime; });
 		
 		for (var i = 0; i < nodes.length; ++i) {
@@ -62,7 +73,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			//var nodeId = node.id.replace(".", "\\\\.");
 			var row = node.joblistrow;
 			if (!row) {
-				this.addNodeRow(node);
+				this.addNodeRow(node, body);
 			}
 			
 			row = node.joblistrow;
@@ -117,21 +128,24 @@ azkaban.ExecutionListView = Backbone.View.extend({
   
 			var elapsedTime = $(row).find("> td.elapsedTime");
 			if (node.endTime == -1) {
-				$(elapsedTime).text(getDuration(node.startTime, (new Date()).getTime()));					
+				$(elapsedTime).text(getDuration(node.startTime, (new Date()).getTime()));
 			}
 			else {
 				$(elapsedTime).text(getDuration(node.startTime, node.endTime));
 			}
+			
+			if (node.nodes) {
+				var subtableBody = $(row.subflowrow).find("> td > table");
+				subtableBody[0].level = $(body)[0].level + 1;
+				this.updateJobRow(node.nodes, subtableBody);
+			}
 		}
 	},
 	
-	updateProgressBar: function(data) {
+	updateProgressBar: function(data, flowStartTime, flowLastTime) {
 		if (data.startTime == -1) {
 			return;
 		}
-		
-		var flowLastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
-		var flowStartTime = data.startTime;
 
 		var outerWidth = $(".flow-progress").css("width");
 		if (outerWidth) {
@@ -190,21 +204,31 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			progressBar.css("margin-left", left)
 			progressBar.css("width", width);
 			progressBar.attr("title", "attempt:" + progressBar.attempt + "	start:" + getHourMinSec(new Date(node.startTime)) + "	end:" + getHourMinSec(new Date(node.endTime)));
+		
+			if (node.nodes) {
+				this.updateProgressBar(node, flowStartTime, flowLastTime);
+			}
 		}
 	},
 	toggleExpandFlow: function(flow) {
 		console.log("Toggle Expand");
-		var tr = flow.progressbar;
+		var tr = flow.joblistrow;
+		var subFlowRow = tr.subflowrow;
 		var expandIcon = $(tr).find("> td > .listExpand");
 		if (tr.expanded) {
 			tr.expanded = false;
 			$(expandIcon).removeClass("glyphicon-chevron-up");
 			$(expandIcon).addClass("glyphicon-chevron-down");
+			
+			$(tr).removeClass("expanded");
+			$(subFlowRow).hide();
 		}
 		else {
 			tr.expanded = true;
 			$(expandIcon).addClass("glyphicon-chevron-up");
 			$(expandIcon).removeClass("glyphicon-chevron-down");
+			$(tr).addClass("expanded");
+			$(subFlowRow).show();
 		}
 	},
 	expandFlow: function(flow) {
@@ -213,9 +237,8 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			///@TODO Expand.
 		}
 	},
-	addNodeRow: function(node) {
+	addNodeRow: function(node, body) {
 		var self = this;
-		var executingBody = $("#executableBody");
 		var tr = document.createElement("tr");
 		var tdName = document.createElement("td");
 		var tdTimeline = document.createElement("td");
@@ -226,6 +249,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		var tdDetails = document.createElement("td");
 		node.joblistrow = tr;
 		tr.node = node;
+		var padding = 15*$(body)[0].level;
 		
 		$(tr).append(tdName);
 		$(tr).append(tdTimeline);
@@ -235,6 +259,11 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		$(tr).append(tdStatus);
 		$(tr).append(tdDetails);
 		$(tr).addClass("jobListRow");
+		
+		$(tdName).addClass("jobname");
+		if (padding) {
+			$(tdName).css("padding-left", padding);
+		}
 		$(tdTimeline).addClass("timeline");
 		$(tdStart).addClass("startTime");
 		$(tdEnd).addClass("endTime");
@@ -242,13 +271,6 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		$(tdStatus).addClass("statustd");
 		$(tdDetails).addClass("details");
 		
-//		$(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).attr("id", node.id + "-outerprogressbar");
 		$(outerProgressBar).addClass("flow-progress");
@@ -294,7 +316,27 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			$(a).text("Details");
 			$(tdDetails).append(a);
 		}
-		executingBody.append(tr);
+
+		$(body).append(tr);
+		if (node.type=="flow") {
+			var subFlowRow = document.createElement("tr");
+			var subFlowCell = document.createElement("td");
+			$(subFlowCell).addClass("subflowrow");
+			
+			var numColumn = $(tr).children("td").length;
+			$(subFlowCell).attr("colspan", numColumn);
+			tr.subflowrow = subFlowRow;
+			
+			$(subFlowRow).append(subFlowCell);
+			$(body).append(subFlowRow);
+			$(subFlowRow).hide();
+			var subtable = document.createElement("table");
+			var parentClasses = $(body).closest("table").attr("class");
+			
+			$(subtable).attr("class", parentClasses);
+			$(subtable).addClass("subtable");
+			$(subFlowCell).append(subtable);
+		}
 	}
 });
 
diff --git a/src/web/js/azkaban/view/flow-job.js b/src/web/js/azkaban/view/flow-job.js
index 05ed20a..e7f0875 100644
--- a/src/web/js/azkaban/view/flow-job.js
+++ b/src/web/js/azkaban/view/flow-job.js
@@ -102,25 +102,23 @@ azkaban.JobListView = Backbone.View.extend({
 	
 	handleStatusUpdate: function(evt) {
 		var data = this.model.get("data");
-		var lastUpdateTime = this.model.get("lastUpdateTime");
-		if (data.nodes) {
-			this.changeStatuses(data, lastUpdateTime);
-		}
+		this.changeStatuses(data);
 	},
 	
-	changeStatuses: function(data, lastUpdateTime) {
+	changeStatuses: function(data) {
 		for (var i = 0; i < data.nodes.length; ++i) {
 			var node = data.nodes[i];
-			if (lastUpdateTime <= 0 || lastUpdateTime < node.updateTime) {
-				var liElement = node.listElement;
-				var child = $(liElement).children("a");
+
+			// Confused? In updates, a node reference is given to the update node.
+			var liElement = node.listElement;
+			var child = $(liElement).children("a");
+			if (!$(child).hasClass(node.status)) {
 				$(child).removeClass(statusList.join(' '));
 				$(child).addClass(node.status);
 				$(child).attr("title", node.status + " (" + node.type + ")");
 			}
-			
 			if (node.nodes) {
-				this.changeStatuses(node, lastUpdateTime);
+				this.changeStatuses(node);
 			}
 		}
 	},
@@ -133,7 +131,7 @@ azkaban.JobListView = Backbone.View.extend({
 		
 		//this.assignInitialStatus(self);
 		this.handleDisabledChange(self);
-		this.changeStatuses(data, 0);
+		this.changeStatuses(data);
 	},
 	
 	renderTree: function(el, data, prefix) {