azkaban-aplcache

Details

diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
new file mode 100644
index 0000000..30f7582
--- /dev/null
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -0,0 +1,5 @@
+package azkaban.executor;
+
+public interface ExecutorManager {
+
+}
diff --git a/src/java/azkaban/executor/FlowExecution.java b/src/java/azkaban/executor/FlowExecution.java
new file mode 100644
index 0000000..49b15ff
--- /dev/null
+++ b/src/java/azkaban/executor/FlowExecution.java
@@ -0,0 +1,15 @@
+package azkaban.executor;
+
+import azkaban.flow.Node;
+
+public class FlowExecution {
+	
+	
+	public FlowExecution() {
+		
+	}
+	
+	public class RunningJob {
+		private Node reference;
+	}
+}
diff --git a/src/java/azkaban/executor/FlowExecutorManager.java b/src/java/azkaban/executor/FlowExecutorManager.java
new file mode 100644
index 0000000..982f3d3
--- /dev/null
+++ b/src/java/azkaban/executor/FlowExecutorManager.java
@@ -0,0 +1,10 @@
+package azkaban.executor;
+
+public class FlowExecutorManager {
+	
+	public FlowExecutorManager() {
+		
+	}
+	
+	
+}
diff --git a/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java b/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java
new file mode 100644
index 0000000..143b9ae
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/FlowExecutorServlet.java
@@ -0,0 +1,27 @@
+package azkaban.webapp.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import azkaban.webapp.session.Session;
+
+public class FlowExecutorServlet extends LoginAbstractAzkabanServlet {
+
+	@Override
+	protected void handleGet(HttpServletRequest req, HttpServletResponse resp,
+			Session session) throws ServletException, IOException {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	protected void handlePost(HttpServletRequest req, HttpServletResponse resp,
+			Session session) throws ServletException, IOException {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 8523b16..325fdbf 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -197,6 +197,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 			nodeObj.put("id", node.getId());
 			nodeObj.put("x", node.getPosition().getX());
 			nodeObj.put("y", node.getPosition().getY());
+			nodeObj.put("level", node.getLevel());
 			if (node.getState() != Node.State.WAITING) {
 				nodeObj.put("state", node.getState());
 			}
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index 2ff14e9..5ba18ad 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -10,6 +10,7 @@
 		<script type="text/javascript" src="${context}/js/jquery.simplemodal.js"></script>
 		<script type="text/javascript" src="${context}/js/jquery.contextMenu.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.view.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
 		<script type="text/javascript">
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 8a24b89..83e0137 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1145,6 +1145,44 @@ table.parameters tr td {
 	text-align:center;
 }
 
+svg .edge {
+	stroke: #777;
+	stroke-width: 2;
+}
+
+svg .edge:hover {
+	stroke: #009FC9;
+	stroke-width: 4;
+}
+
+svg .node .backboard {
+	fill: #FFF;
+	opacity: 0.05;
+}
+
+svg .node:hover .backboard {
+	opacity: 0.6;
+}
+
+svg .node circle {
+	fill: #FFF;
+	stroke: #777;
+	stroke-width: 2;
+}
+
+svg .node:hover circle {
+	stroke: #009FC9;
+}
+
+svg .node:hover text {
+	fill: #009FC9;
+}
+
+svg .selected circle {
+	stroke: #777;
+	fill: #444;
+}
+
 /* old styles */
 
 .azkaban-charts .hitarea {
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 36dd514..0f0cd3d 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -12,6 +12,29 @@ var handleJobMenuClick = function(action, el, pos) {
 
 }
 
+function hasClass(el, name) 
+{
+	var classes = el.getAttribute("class");
+   return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
+}
+
+function addClass(el, name)
+{
+   if (!hasClass(el, name)) { 
+   		var classes = el.getAttribute("class");
+   		classes += classes ? ' ' + name : '' +name;
+   		el.setAttribute("class", classes);
+   }
+}
+
+function removeClass(el, name)
+{
+   if (hasClass(el, name)) {
+      var classes = el.getAttribute("class");
+      el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
+   }
+}
+
 var flowTabView;
 azkaban.FlowTabView= Backbone.View.extend({
   events : {
@@ -253,16 +276,23 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			return;
 		};
 	
+		// layout
+		layoutGraph(nodes, edges);
+		
 		var bounds = {};
 		this.nodes = {};
 		for (var i = 0; i < nodes.length; ++i) {
-			this.drawNode(this, nodes[i], bounds);
+			this.nodes[nodes[i].id] = nodes[i];
 		}
 		
 		for (var i = 0; i < edges.length; ++i) {
 			this.drawEdge(this, edges[i]);
 		}
 		
+		for (var i = 0; i < nodes.length; ++i) {
+			this.drawNode(this, nodes[i], bounds);
+		}
+		
 		bounds.minX = bounds.minX ? bounds.minX - 200 : -200;
 		bounds.minY = bounds.minY ? bounds.minY - 200 : -200;
 		bounds.maxX = bounds.maxX ? bounds.maxX + 200 : 200;
@@ -278,16 +308,21 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		
 		if (previous) {
 			// Unset previous
+			var g = document.getElementById(previous);
+			removeClass(g, "selected");
 		}
 		
 		if (selected) {
-			//var g = document.getElementById();
+			var g = document.getElementById(selected);
 			var node = this.nodes[selected];
 			
+			addClass(g, "selected");
+			
 			var offset = 200;
 			var widthHeight = offset*2;
-			var x = node.sx - offset;
-			var y = node.sy - offset;
+			var x = node.x - offset;
+			var y = node.y - offset;
+			
 			
 			$("#svgGraph").svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
 		}
@@ -300,28 +335,27 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		var endNode = this.nodes[edge.target];
 		
 		if (edge.guides) {
-			var pointString = "" + startNode.sx + "," + startNode.sy + " ";
+			var pointString = "" + startNode.x + "," + startNode.y + " ";
 
 			for (var i = 0; i < edge.guides.length; ++i ) {
 				edgeGuidePoint = edge.guides[i];
-				this.calcScalePoint(edgeGuidePoint);
-				pointString += edgeGuidePoint.sx + "," + edgeGuidePoint.sy + " ";
+				pointString += edgeGuidePoint.x + "," + edgeGuidePoint.y + " ";
 			}
 			
-			pointString += endNode.sx + "," + endNode.sy;
+			pointString += endNode.x + "," + endNode.y;
 			var polyLine = document.createElementNS(svgns, "polyline");
+			polyLine.setAttributeNS(null, "class", "edge");
 			polyLine.setAttributeNS(null, "points", pointString);
-			polyLine.setAttributeNS(null, "style", "fill:none;stroke:black;stroke-width:3");
+			polyLine.setAttributeNS(null, "style", "fill:none;");
 			self.mainG.appendChild(polyLine);
 		}
-		else {
+		else { 
 			var line = document.createElementNS(svgns, 'line');
-			line.setAttributeNS(null, "x1", startNode.sx);
-			line.setAttributeNS(null, "y1", startNode.sy);
-			
-			line.setAttributeNS(null, "x2", endNode.sx);
-			line.setAttributeNS(null, "y2", endNode.sy);
-			line.setAttributeNS(null, "style", "stroke:rgb(255,0,0);stroke-width:2");
+			line.setAttributeNS(null, "class", "edge");
+			line.setAttributeNS(null, "x1", startNode.x);
+			line.setAttributeNS(null, "y1", startNode.y);
+			line.setAttributeNS(null, "x2", endNode.x);
+			line.setAttributeNS(null, "y2", endNode.y);
 			
 			self.mainG.appendChild(line);
 		}
@@ -330,7 +364,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		var svg = self.svgGraph;
 		var svgns = self.svgns;
 
-		this.calcScalePoint(node);
 		var xOffset = 10;
 		var yOffset = 10;
 
@@ -338,42 +371,55 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		nodeG.setAttributeNS(null, "class", "jobnode");
 		nodeG.setAttributeNS(null, "id", node.id);
 		nodeG.setAttributeNS(null, "font-family", "helvetica");
-		nodeG.setAttributeNS(null, "transform", "translate(" + node.sx + "," + node.sy + ")");
+		nodeG.setAttributeNS(null, "transform", "translate(" + node.x + "," + node.y + ")");
 		
 		var innerG = document.createElementNS(svgns, "g");
 		innerG.setAttributeNS(null, "transform", "translate(-10,-10)");
 		
-		var rect1 = document.createElementNS(svgns, 'rect');
-		rect1.setAttributeNS(null, "y", 0);
-		rect1.setAttributeNS(null, "x", 0);
-		rect1.setAttributeNS(null, "ry", 12);
-		rect1.setAttributeNS(null, "width", 20);
-		rect1.setAttributeNS(null, "height", 20);
-		rect1.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
+		var circle = document.createElementNS(svgns, 'circle');
+		circle.setAttributeNS(null, "cy", 10);
+		circle.setAttributeNS(null, "cx", 10);
+		circle.setAttributeNS(null, "r", 12);
+		circle.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
 		
 		
 		var text = document.createElementNS(svgns, 'text');
-		var textLabel = document.createTextNode(node.id);
+		var textLabel = document.createTextNode(node.label);
 		text.appendChild(textLabel);
 		text.setAttributeNS(null, "x", 4);
-		text.setAttributeNS(null, "y", 0);
+		text.setAttributeNS(null, "y", 15);
 		text.setAttributeNS(null, "height", 10); 
+				
+		this.addBounds(bounds, {minX:node.x - xOffset, minY: node.y - yOffset, maxX: node.x + xOffset, maxY: node.y + yOffset});
 		
-		this.addBounds(bounds, {minX:node.sx - xOffset, minY: node.sy - yOffset, maxX: node.sx + xOffset, maxY: node.sy + yOffset});
+		var backRect = document.createElementNS(svgns, 'rect');
+		backRect.setAttributeNS(null, "x", 0);
+		backRect.setAttributeNS(null, "y", 2);
+		backRect.setAttributeNS(null, "class", "backboard");
+		backRect.setAttributeNS(null, "width", 10);
+		backRect.setAttributeNS(null, "height", 15);
 		
-		innerG.appendChild(rect1);
+		innerG.appendChild(circle);
+		innerG.appendChild(backRect);
 		innerG.appendChild(text);
+
 		nodeG.appendChild(innerG);
 		self.mainG.appendChild(nodeG);
 
+		// Need to get text width after attaching to SVG.
+		var computeText = text.getComputedTextLength();
+		var halfWidth = computeText/2;
+		text.setAttributeNS(null, "x", -halfWidth + 10);
+		backRect.setAttributeNS(null, "x", -halfWidth);
+		backRect.setAttributeNS(null, "width", computeText + 20);
+
+		nodeG.setAttributeNS(null, "class", "node");
 		nodeG.jobid=node.id;
 		$(nodeG).contextMenu({
 				menu: 'jobMenu'
 			},
 			handleJobMenuClick
 		);
-		
-		this.nodes[node.id] = node;
 	},
 	addBounds: function(toBounds, addBounds) {
 		toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
@@ -381,10 +427,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		toBounds.maxX = toBounds.maxX ? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
 		toBounds.maxY = toBounds.maxY ? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
 	},
-	calcScalePoint : function(pointObj) {
-		pointObj.sx = 50*pointObj.x;
-		pointObj.sy = 50*pointObj.y;
-	},
 	resetPanZoom : function(self) {
 		var bounds = this.graphBounds;
 		$("#svgGraph").svgNavigate("transformToBox", {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY) });
diff --git a/src/web/js/azkaban.layout.js b/src/web/js/azkaban.layout.js
new file mode 100644
index 0000000..13dc54b
--- /dev/null
+++ b/src/web/js/azkaban.layout.js
@@ -0,0 +1,290 @@
+var maxTextSize = 32;
+var reductionSize = 26;
+var degreeRatio = 1/8;
+var maxHeight = 200;
+var cornerGap = 10;
+
+function layoutGraph(nodes, edges) {
+	var startLayer = [];
+	var numLayer = 0;
+	var nodeMap = {};
+	
+	var maxLayer = 0;
+	var layers = {};
+	
+	// Assign to layers
+	for (var i = 0; i < nodes.length; ++i) {
+		numLayer = Math.max(numLayer, nodes[i].level);
+		/*
+		if (nodes[i].id.length > maxTextSize) {
+			var label = nodes[i].id.substr(0, reductionSize) + "...";
+			nodes[i].label = label;
+		}
+		else {*/
+			nodes[i].label = nodes[i].id;
+		//}
+		
+		var width = nodes[i].label.length * 10;
+		var node = { id: nodes[i].id, node: nodes[i], level: nodes[i].level, in:[], out:[], width: width, x:0 };
+		nodeMap[nodes[i].id] = node;
+
+		maxLayer = Math.max(node.level, maxLayer);
+		if(!layers[node.level]) {
+			layers[node.level] = [];
+		}
+		
+		layers[node.level].push(node);
+	}
+	
+	// Create dummy nodes
+	var edgeDummies = {};
+	
+	for (var i=0; i < edges.length; ++i ) {
+		var edge = edges[i];
+		var src = edges[i].from;
+		var dest = edges[i].target;
+		
+		var edgeId = src + ">>" + dest;
+		
+		var srcNode = nodeMap[src];
+		var destNode = nodeMap[dest];
+		
+		var lastNode = srcNode;
+		// TODO GUIDE EDGES
+		var guides = [];
+		
+		for (var j = srcNode.level + 1; j < destNode.level; ++j) {
+			var dummyNode = {level: j, in: [], x: lastNode.x, out: [], realSrc: srcNode, realDest: destNode, width: 10};
+			layers[j].push(dummyNode);
+			dummyNode.in.push(lastNode);
+			lastNode.out.push(dummyNode);
+			lastNode = dummyNode;
+			
+			guides.push(dummyNode);
+		}
+		
+		destNode.in.push(lastNode);
+		lastNode.out.push(destNode);
+		
+		if (edgeDummies.length != 0) {
+			edgeDummies[edgeId] = guides;
+		}
+	}
+
+	spreadLayerSmart(layers[maxLayer]);
+	sort(layers[maxLayer]);
+	for (var i=maxLayer - 1; i >=0; --i) {
+		uncrossWithOut(layers[i]);
+		sort(layers[i]);
+		
+		spreadLayerSmart(layers[i]);
+	}
+	
+	// Uncross down
+	for (var i=1; i <= maxLayer; ++i) {
+		uncrossWithIn(layers[i]);
+		sort(layers[i]);
+		spreadLayerSmart(layers[i]);
+	}
+	
+	// Space it vertically
+	spaceVertically(layers, maxLayer);
+	
+	// Assign points to nodes
+	for (var i = 0; i < nodes.length; ++i) {
+		var node = nodes[i];
+		var layerNode = nodeMap[node.id];
+		node.x = layerNode.x;
+		node.y = layerNode.y;
+	}
+	
+	// Dummy node for more points.
+	for (var i = 0; i < edges.length; ++i) {
+		var edge = edges[i];
+		var src = edges[i].from;
+		var dest = edges[i].target;
+		
+		var edgeId = src + ">>" + dest;
+
+		if (edgeDummies[edgeId] && edgeDummies[edgeId].length > 0) {
+			var prevX = nodeMap[src].x;
+			var destX = nodeMap[dest].x; 
+			
+			var guides = [];
+			var dummies = edgeDummies[edgeId];
+			for (var j=0; j< dummies.length; ++j) {
+				var point = {x: dummies[j].x, y: dummies[j].y};
+				guides.push(point);
+				
+				var nextX = j == dummies.length - 1 ? destX: dummies[j + 1].x; 
+
+				if (point.x != prevX && point.x != nextX) {
+					// Add gap
+					if ((point.x > prevX) == (point.x > nextX)) {
+						guides.push({x: point.x, y:point.y + cornerGap});
+					}
+				}
+				prevX = point.x;
+			}
+						
+			edge.guides = guides;
+		}
+		else {
+			edge.guides = null;
+		}
+	}
+}
+
+function spreadLayerSmart(layer) {
+	var ranges = [];
+	ranges.push({start: 0, end:0, width: layer[0].width, x: layer[0].x, index: 0});
+	var largestRangeIndex = -1;
+	
+	var totalX = layer[0].x;
+	var totalWidth = layer[0].width;
+	var count = 1;
+	
+	for (var i = 1; i < layer.length; ++i ) {
+		var prevRange = ranges[ranges.length - 1];
+		var delta = layer[i].x - prevRange.x;
+				
+		if (delta == 0) {
+			prevRange.end = i;
+			prevRange.width += layer[i].width;
+			totalWidth+=layer[i].width;
+		}
+		else {
+			totalWidth+=Math.max(layer[i].width, delta);
+			ranges.push({start: i, end: i, width: layer[i].width, x: layer[i].x, index: ranges.length});
+		}
+		
+		totalX += layer[i].x;
+		count++;
+	}
+
+	// Space the ranges, but place the left and right most last
+	var startIndex = 0;
+	var endIndex = 0;
+	if (ranges.length == 1) {
+		startIndex = -1;
+		endIndex = 1;
+	}
+	else if ((ranges.length % 2) == 1) {
+		var index = Math.ceil(ranges.length/2);
+		startIndex = index - 1;
+		endIndex = index + 1;
+	}
+	else {
+		var e = ranges.length/2;
+		var s = e - 1;
+		
+		var crossPointS = ranges[s].x + ranges[s].width/2;
+		var crossPointE = ranges[e].x - ranges[e].width/2;
+		
+		if (crossPointS > crossPointE) {
+			var midPoint = (ranges[s].x + ranges[e].x)/2;
+			ranges[s].x = midPoint - ranges[s].width/2;
+			ranges[e].x = midPoint + ranges[e].width/2;
+		}
+		
+		startIndex = s - 1;
+		endIndex = e + 1;
+	}
+	
+	for (var i=startIndex; i >= 0; --i) {
+		var range = ranges[i];
+		var crossPointS = range.x + range.width/2;
+		var crossPointE = ranges[i + 1].x - ranges[i + 1].width/2;
+		
+		if (crossPointE < crossPointS) {
+			range.x -= crossPointS - crossPointE;
+		}
+	}
+	
+	for (var i=endIndex; i < ranges.length; ++i) {
+		var range = ranges[i];
+		var crossPointE = range.x - range.width/2;
+		var crossPointS = ranges[i - 1].x + ranges[i - 1].width/2;
+		
+		if (crossPointE < crossPointS) {
+			range.x += crossPointS - crossPointE;
+		}
+	}
+	
+	for (var i=0; i < ranges.length; ++i) {
+		var range = ranges[i];
+		if (range.start == range.end) {
+			layer[range.start].x = range.x;
+		}
+		else {
+			var start = range.x - range.width/2;
+			
+			for (var j=range.start;j <=range.end; ++j) {
+				layer[j].x = start + layer[j].width/2;
+				start += layer[j].width;
+			}
+		}
+	}
+}
+
+
+function spaceVertically(layers, maxLayer) {
+	var startY = 0;
+	var startLayer = layers[0];
+	for (var i=0; i < startLayer.length; ++i) {
+		startLayer[i].y = startY;
+	}
+	
+	var minHeight = 50;
+	for (var a=1; a <= maxLayer; ++a) {
+		var maxDelta = 0;
+		var layer = layers[a];
+		for (var i=0; i < layer.length; ++i) {
+			for (var j=0; j < layer[i].in.length; ++j) {
+				var upper = layer[i].in[j];
+				var delta = Math.abs(upper.x - layer[i].x);
+				
+				maxDelta = Math.max(maxDelta, delta);
+			}
+		}
+		
+		console.log("Max " + maxDelta);
+		var calcHeight = maxDelta*degreeRatio;
+		
+		calcHeight = Math.min(calcHeight, maxHeight); 
+		startY += Math.max(calcHeight, minHeight);
+		for (var i=0; i < layer.length; ++i) {
+			layer[i].y=startY;
+		}
+	}
+
+}
+
+function uncrossWithIn(layer) {
+	for (var i = 0; i < layer.length; ++i) {
+		var pos = findAverage(layer[i].in);
+		layer[i].x = pos;
+	}
+}
+
+function findAverage(nodes) {
+	var sum = 0;
+	for (var i = 0; i < nodes.length; ++i) {
+		sum += nodes[i].x;
+	}
+	
+	return sum/nodes.length;
+}
+
+function uncrossWithOut(layer) {
+	for (var i = 0; i < layer.length; ++i) {
+		var pos = findAverage(layer[i].out);
+		layer[i].x = pos;
+	}
+}
+
+function sort(layer) {
+	layer.sort(function(a, b) {
+		return a.x - b.x;
+	});
+}