azkaban-aplcache
Changes
src/java/azkaban/executor/FlowExecution.java 15(+15 -0)
src/web/css/azkaban.css 38(+38 -0)
src/web/js/azkaban.flow.view.js 112(+77 -35)
src/web/js/azkaban.layout.js 290(+290 -0)
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 {
+
+}
src/java/azkaban/executor/FlowExecution.java 15(+15 -0)
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">
src/web/css/azkaban.css 38(+38 -0)
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 {
src/web/js/azkaban.flow.view.js 112(+77 -35)
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) });
src/web/js/azkaban.layout.js 290(+290 -0)
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;
+ });
+}