azkaban-memoizeit

Timeline view in execution page now shows all attempts made

4/6/2013 2:01:00 AM

Details

diff --git a/src/java/azkaban/executor/ExecutableNode.java b/src/java/azkaban/executor/ExecutableNode.java
index d15a963..a0a52d5 100644
--- a/src/java/azkaban/executor/ExecutableNode.java
+++ b/src/java/azkaban/executor/ExecutableNode.java
@@ -74,10 +74,10 @@ public class ExecutableNode {
 		synchronized (this) {
 			if (pastAttempts == null) {
 				pastAttempts = new ArrayList<Attempt>();
-				pastAttempts.add(pastAttempt);
 			}
+			
+			pastAttempts.add(pastAttempt);
 		}
-		
 		startTime = -1;
 		endTime = -1;
 		updateTime = System.currentTimeMillis();
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index 2536fbd..bbc2c38 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -86,8 +86,10 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		User user = session.getUser();
 		int execId = getIntParam(req, "execid");
 		String jobId = getParam(req, "job");
+		int attempt = getIntParam(req, "attempt", 0);
 		page.add("execid", execId);
 		page.add("jobid", jobId);
+		page.add("attempt", attempt);
 		
 		ExecutableFlow flow = null;
 		try {
diff --git a/src/java/azkaban/webapp/servlet/velocity/joblogpage.vm b/src/java/azkaban/webapp/servlet/velocity/joblogpage.vm
index d3e577a..2f9ea81 100644
--- a/src/java/azkaban/webapp/servlet/velocity/joblogpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/joblogpage.vm
@@ -37,6 +37,7 @@
 			var flowName = "${flowid}";
 			var execId = "${execid}";
 			var jobId = "${jobid}";
+			var attempt = ${attempt};
 		</script>
 	</head>
 	<body>
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 0828ea3..59e1820 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1995,6 +1995,11 @@ tr:hover td {
 	background-position: 16px 0px;
 }
 
+.list ul li.QUEUED .icon {
+	opacity: 0.5;
+	background-position: 32px 0px;
+}
+
 .list ul li.RUNNING .icon {
 	background-position: 32px 0px;
 }
@@ -2177,6 +2182,11 @@ svg .RUNNING circle {
 	fill: #009FC9;
 }
 
+svg .QUEUED circle {
+	opacity: 0.5;
+	fill: #009FC9;
+}
+
 svg .FAILED circle {
 	fill: #CC0000;
 }
@@ -2503,6 +2513,14 @@ tr:hover .outerProgress {
 	background-color: #CCC;
 }
 
+.progressBox.attempt:hover {
+	opacity: 1;
+}
+
+.progressBox.attempt {
+	opacity: 0.70;
+}
+
 .progressBox.SUCCEEDED {
 	background-color: #4e911e;
 	background: -moz-linear-gradient(top, #5bb41c 0, #598d1e 100%);
@@ -2527,6 +2545,15 @@ tr:hover .outerProgress {
     background: linear-gradient(top, #009FC9 0, #007b9b 100%);
 }
 
+.progressBox.QUEUED {
+	opacity: 0.5;
+	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%);
+}
+
 h3.subhead {
 	margin: 15px 20px 8px 20px;
 	font-size: 14pt;
diff --git a/src/web/js/azkaban.date.utils.js b/src/web/js/azkaban.date.utils.js
index e436648..78dbd90 100644
--- a/src/web/js/azkaban.date.utils.js
+++ b/src/web/js/azkaban.date.utils.js
@@ -55,6 +55,15 @@ var getDateFormat = function(date) {
 	return datestring;
 }
 
+var getHourMinSec = function(date) {
+	var hours = getTwoDigitStr(date.getHours());
+	var minutes = getTwoDigitStr(date.getMinutes());
+	var second = getTwoDigitStr(date.getSeconds());
+	
+	var timestring = hours + ":" + minutes + " " + second + "s";
+	return timestring;
+}
+
 var getTwoDigitStr = function(value) {
 	if (value < 10) {
 		return "0" + value;
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index 59dd6cb..b4d9764 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -271,6 +271,7 @@ var mainSvgGraphView;
 var executionListView;
 azkaban.ExecutionListView = Backbone.View.extend({
 	events: {
+//		"click .progressBox" : "handleProgressBoxClick"
 	},
 	initialize: function(settings) {
 		this.model.bind('change:graph', this.renderJobs, this);
@@ -282,6 +283,24 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		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 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 data = this.model.get("update");
 		var lastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
@@ -320,12 +339,34 @@ azkaban.ExecutionListView = Backbone.View.extend({
 				}
 				
 				var progressBar = $("#" + nodeId + "-progressbar");
-				for (var j = 0; j < statusList.length; ++j) {
-					var status = statusList[j];
-					progressBar.removeClass(status);
+				if (!progressBar.hasClass(node.status)) {
+					for (var j = 0; j < statusList.length; ++j) {
+						var status = statusList[j];
+						progressBar.removeClass(status);
+					}
+					progressBar.addClass(node.status);
 				}
-				progressBar.addClass(node.status);
-
+				
+				// Create past attempts
+				if (node.pastAttempts) {
+					for (var a = 0; a < node.pastAttempts.length; ++a) {
+						var attemptBarId = nodeId + "-progressbar-" + a;
+						var attempt = node.pastAttempts[a];
+						if ($("#" + attemptBarId).length == 0) {
+							var attemptBox = document.createElement("div");
+							$(attemptBox).attr("id", attemptBarId);
+							$(attemptBox).addClass("progressBox");
+							$(attemptBox).addClass("attempt");
+							$(attemptBox).addClass(attempt.status);
+							$(attemptBox).css("float","left");
+							$(attemptBox).bind("contextmenu", attemptRightClick);
+							$(progressBar).before(attemptBox);
+							attemptBox.job = nodeId;
+							attemptBox.attempt = a;
+						}
+					}
+				}
+				
 				if (node.endTime == -1) {
 //					$("#" + node.id + "-elapse").text("0 sec");
 					$("#" + nodeId + "-elapse").text(getDuration(node.startTime, (new Date()).getTime()));					
@@ -353,60 +394,51 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		}
 		
 		var nodes = data.nodes;
-		
+		var diff = flowLastTime - flowStartTime;
+		var factor = outerWidth/diff;
 		for (var i = 0; i < nodes.length; ++i) {
 			var node = nodes[i];
+			var nodeId = node.id.replace(".", "\\\\.");
 			// calculate the progress
-			var diff = flowLastTime - flowStartTime;
-			
-			var factor = outerWidth/diff;
 
+			var elem = $("#" + node.id + "-progressbar");
 			var offsetLeft = 0;
 			var minOffset = 0;
+			elem.attempt = 0;
+			
 			// Add all the attempts
-			if (node.attempt > 0) {
-				var logURL = contextURL + "/executor?execid=" + execId + "&job=" + node.id + "&attempt=" + node.attempt;
+			if (node.pastAttempts) {
+				var logURL = contextURL + "/executor?execid=" + execId + "&job=" + node.id + "&attempt=" +  node.pastAttempts.length;
 				var aId = node.id + "-log-link";
 				$("#" + aId).attr("href", logURL);
+				elem.attempt = node.pastAttempts.length;
 				
-				/*
-				minOffset = 1;
-				var pastAttempts = node.pastAttempts;
-				for (var j = 0; j < pastAttempts.length; ++j) {
-					var past = pastAttempts[j];
-					var id = node.id + "-past-progress-" + j;
-
-					if ($("#" + id).length == 0) {
-						var attemptBox = document.createElement("div");
-						$(attemptBox).attr("id", id);
-						$(attemptBox).addClass("progressBox");
-						$("#" + node.id + "-outerprogressbar").append(attemptBox);
-						$(attemptBox).css("float","left");
-					}
-
-					var attemptBox = $("#" + id);
+				// Calculate the node attempt bars
+				for(var p = 0; p < node.pastAttempts.length; ++p) {
+					var pastAttempt = node.pastAttempts[p];
+					var pastAttemptBox = $("#" + nodeId + "-progressbar-" + p);
 					
-					var absoluteLeft = Math.max((past.startTime-flowStartTime)*factor, 3);
-					var left = absoluteLeft - offsetLeft;
-					var width = Math.max((past.endTime - past.startTime)*factor, 1);
+					var left = (pastAttempt.startTime - flowStartTime)*factor;
+					var width =  Math.max((pastAttempt.endTime - pastAttempt.startTime)*factor, 3);
 					
-					$(attemptBox).css("margin-left", left)
-					$(attemptBox).css("width", width);
-					$(attemptBox).addClass(past.status);
-					offsetLeft += left + width;
+					var margin = left - offsetLeft;
+					$(pastAttemptBox).css("margin-left", left - offsetLeft);
+					$(pastAttemptBox).css("width", width);
+					
+					$(pastAttemptBox).attr("title", "attempt:" + p + "  start:" + getHourMinSec(new Date(pastAttempt.startTime)) + "  end:" + getHourMinSec(new Date(pastAttempt.endTime)));
+					offsetLeft += width + margin;
 				}
-				*/
 			}
-
-			var absoluteLeft = Math.max((node.startTime-flowStartTime)*factor, minOffset);
-			var left = absoluteLeft - offsetLeft;
+			
 			var nodeLastTime = node.endTime == -1 ? (new Date()).getTime() : node.endTime;
+			var left = Math.max((node.startTime-flowStartTime)*factor, minOffset);
+			var margin = left - offsetLeft;
 			var width = Math.max((nodeLastTime - node.startTime)*factor, 3);
 			width = Math.min(width, outerWidth);
 			
-			var elem = $("#" + node.id + "-progressbar");
 			elem.css("margin-left", left)
 			elem.css("width", width);
+			elem.attr("title", "attempt:" + elem.attempt + "  start:" + getHourMinSec(new Date(node.startTime)) + "  end:" + getHourMinSec(new Date(node.endTime)));
 		}
 	},
 	addNodeRow: function(node) {
@@ -439,6 +471,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		$(outerProgressBar).addClass("outerProgress");
 
 		var progressBox = document.createElement("div");
+		progressBox.job = node.id;
 		$(progressBox).attr("id", node.id + "-progressbar");
 		$(progressBox).addClass("progressBox");
 		$(outerProgressBar).append(progressBox);
@@ -635,6 +668,23 @@ var exGraphClickCallback = function(event) {
 	contextMenuView.show(event, menu);
 }
 
+var attemptRightClick = function(event) {
+	var target = event.currentTarget;
+	var job = target.job;
+	var attempt = target.attempt;
+	
+	var jobId = event.currentTarget.jobid;
+	var requestURL = contextURL + "/executor?project=" + projectName + "&execid=" + execId + "&job=" + job + "&attempt=" + attempt;
+
+	var menu = [	
+			{title: "Open Attempt Log...", callback: function() {window.location.href=requestURL;}},
+			{title: "Open Attempt Log in New Window...", callback: function() {window.open(requestURL);}}
+	];
+
+	contextMenuView.show(event, menu);
+	return false;
+}
+
 $(function() {
 	var selected;
 
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index b23cd3d..9f3536b 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -9,7 +9,8 @@ var statusStringMap = {
 	"KILLED": "Killed",
 	"DISABLED": "Disabled",
 	"READY": "Ready",
-	"UNKNOWN": "Unknown"
+	"UNKNOWN": "Unknown",
+	"QUEUED": "Queued"
 };
 
 var handleJobMenuClick = function(action, el, pos) {
diff --git a/src/web/js/azkaban.joblog.view.js b/src/web/js/azkaban.joblog.view.js
index 97c5bdf..76f5602 100644
--- a/src/web/js/azkaban.joblog.view.js
+++ b/src/web/js/azkaban.joblog.view.js
@@ -27,7 +27,7 @@ azkaban.JobLogView = Backbone.View.extend({
 				type: "get",
 				async: false,
 				dataType: "json",
-				data: {"execid": execId, "jobId": jobId, "ajax":"fetchExecJobLogs", "offset": offset, "length": 50000},
+				data: {"execid": execId, "jobId": jobId, "ajax":"fetchExecJobLogs", "offset": offset, "length": 50000, "attempt": attempt},
 				error: function(data) {
 					console.log(data);
 					finished = true;