$.namespace('azkaban');
var flowTabView;
azkaban.FlowTabView= Backbone.View.extend({
events : {
"click #graphViewLink" : "handleGraphLinkClick",
"click #jobslistViewLink" : "handleJobslistLinkClick"
},
initialize : function(settings) {
var selectedView = settings.selectedView;
if (selectedView == "jobslist") {
this.handleJobslistLinkClick();
}
else {
this.handleGraphLinkClick();
}
},
render: function() {
console.log("render graph");
},
handleGraphLinkClick: function(){
$("#jobslistViewLink").removeClass("selected");
$("#graphViewLink").addClass("selected");
$("#jobListView").hide();
$("#graphView").show();
},
handleJobslistLinkClick: function() {
$("#graphViewLink").removeClass("selected");
$("#jobslistViewLink").addClass("selected");
$("#graphView").hide();
$("#jobListView").show();
}
});
var jobListView;
azkaban.JobListView = Backbone.View.extend({
events: {
"keyup input": "filterJobs",
"click li": "handleJobClick",
"click #resetPanZoomBtn" : "handleResetPanZoom"
},
initialize: function(settings) {
this.model.bind('change:selected', this.handleSelectionChange, this);
this.model.bind('change:graph', this.render, this);
},
filterJobs: function(self) {
var filter = $("#filter").val();
if (filter && filter.trim() != "") {
filter = filter.trim();
if (filter == "") {
if (this.filter) {
$("#jobs").children().each(
function(){
var a = $(this).find("a");
$(a).html(this.jobid);
}
);
}
this.filter = null;
return;
}
}
else {
if (this.filter) {
$("#jobs").children().each(
function(){
var a = $(this).find("a");
$(a).html(this.jobid);
}
);
}
this.filter = null;
return;
}
$("#jobs").children().each(
function(){
var jobid = this.jobid;
var index = jobid.indexOf(filter);
if (index != -1) {
var a = $(this).find("a");
var endIndex = index + filter.length;
var newHTML = jobid.substring(0, index) + "<span>" + jobid.substring(index, endIndex) + "</span>" + jobid.substring(endIndex, jobid.length);
$(a).html(newHTML);
$(this).show();
}
else {
$(this).hide();
}
});
this.filter = filter;
},
render: function(self) {
var data = this.model.get("data");
var nodes = data.nodes;
var edges = data.edges;
this.listNodes = {};
if (nodes.length == 0) {
console.log("No results");
return;
};
var nodeArray = nodes.slice(0);
nodeArray.sort(function(a,b){
var diff = a.y - b.y;
if (diff == 0) {
return a.x - b.x;
}
else {
return diff;
}
});
var ul = document.createElement("ul");
$(ul).attr("id", "jobs");
for (var i = 0; i < nodeArray.length; ++i) {
var li = document.createElement("li");
var a = document.createElement("a");
$(a).text(nodeArray[i].id);
li.appendChild(a);
ul.appendChild(li);
li.jobid=nodeArray[i].id;
this.listNodes[nodeArray[i].id] = li;
}
$("#list").append(ul);
},
handleJobClick : function(evt) {
var jobid = evt.currentTarget.jobid;
if(!evt.currentTarget.jobid) {
return;
}
if (this.model.has("selected")) {
var selected = this.model.get("selected");
if (selected == jobid) {
this.model.unset("selected");
}
else {
this.model.set({"selected": jobid});
}
}
else {
this.model.set({"selected": jobid});
}
},
handleSelectionChange: function(evt) {
if (!this.model.hasChanged("selected")) {
return;
}
var previous = this.model.previous("selected");
var current = this.model.get("selected");
if (previous) {
$(this.listNodes[previous]).removeClass("selected");
}
if (current) {
$(this.listNodes[current]).addClass("selected");
}
},
handleResetPanZoom: function(evt) {
this.model.trigger("resetPanZoom");
}
});
var svgGraphView;
azkaban.SvgGraphView = Backbone.View.extend({
events: {
},
initialize: function(settings) {
this.model.bind('change:selected', this.changeSelected, this);
this.model.bind('change:graph', this.render, this);
this.model.bind('resetPanZoom', this.resetPanZoom, this);
this.svgns = "http://www.w3.org/2000/svg";
this.xlinksn = "http://www.w3.org/1999/xlink";
var graphDiv = this.el[0];
var svg = $('#svgGraph')[0];
this.svgGraph = svg;
var gNode = document.createElementNS(this.svgns, 'g');
gNode.setAttribute("id", "group");
svg.appendChild(gNode);
this.mainG = gNode;
$(svg).svgNavigate();
},
initializeDefs: function(self) {
var def = document.createElementNS(svgns, 'defs');
def.setAttributeNS(null, "id", "buttonDefs");
// ArrowHead
var arrowHeadMarker = document.createElementNS(svgns, 'marker');
arrowHeadMarker.setAttribute("id", "triangle");
arrowHeadMarker.setAttribute("viewBox", "0 0 10 10");
arrowHeadMarker.setAttribute("refX", "5");
arrowHeadMarker.setAttribute("refY", "5");
arrowHeadMarker.setAttribute("markerUnits", "strokeWidth");
arrowHeadMarker.setAttribute("markerWidth", "4");
arrowHeadMarker.setAttribute("markerHeight", "3");
arrowHeadMarker.setAttribute("orient", "auto");
var path = document.createElementNS(svgns, 'polyline');
arrowHeadMarker.appendChild(path);
path.setAttribute("points", "0,0 10,5 0,10 1,5");
def.appendChild(arrowHeadMarker);
this.svgGraph.appendChild(def);
},
render: function(self) {
console.log("graph render");
var data = this.model.get("data");
var nodes = data.nodes;
var edges = data.edges;
if (nodes.length == 0) {
console.log("No results");
return;
};
var bounds = {};
this.nodes = {};
for (var i = 0; i < nodes.length; ++i) {
this.drawNode(this, nodes[i], bounds);
}
for (var i = 0; i < edges.length; ++i) {
this.drawEdge(this, edges[i]);
}
bounds.minX = bounds.minX ? bounds.minX - 200 : -200;
bounds.minY = bounds.minY ? bounds.minY - 200 : -200;
bounds.maxX = bounds.maxX ? bounds.maxX + 200 : 200;
bounds.maxY = bounds.maxY ? bounds.maxY + 200 : 200;
this.graphBounds = bounds;
this.resetPanZoom();
},
changeSelected: function(self) {
console.log("change selected");
var selected = this.model.get("selected");
var previous = this.model.previous("selected");
if (previous) {
// Unset previous
}
if (selected) {
//var g = document.getElementById();
var node = this.nodes[selected];
var offset = 200;
var widthHeight = offset*2;
var x = node.sx - offset;
var y = node.sy - offset;
$("#svgGraph").svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
}
},
drawEdge: function(self, edge) {
var svg = self.svgGraph;
var svgns = self.svgns;
var startNode = this.nodes[edge.from];
var endNode = this.nodes[edge.target];
if (edge.guides) {
var pointString = "" + startNode.sx + "," + startNode.sy + " ";
for (var i = 0; i < edge.guides.length; ++i ) {
edgeGuidePoint = edge.guides[i];
this.calcScalePoint(edgeGuidePoint);
pointString += edgeGuidePoint.sx + "," + edgeGuidePoint.sy + " ";
}
pointString += endNode.sx + "," + endNode.sy;
var polyLine = document.createElementNS(svgns, "polyline");
polyLine.setAttributeNS(null, "points", pointString);
polyLine.setAttributeNS(null, "style", "fill:none;stroke:black;stroke-width:3");
self.mainG.appendChild(polyLine);
}
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");
self.mainG.appendChild(line);
}
},
drawNode: function(self, node, bounds) {
var svg = self.svgGraph;
var svgns = self.svgns;
this.calcScalePoint(node);
var xOffset = 10;
var yOffset = 10;
var nodeG = document.createElementNS(svgns, "g");
nodeG.setAttributeNS(null, "id", node.id);
nodeG.setAttributeNS(null, "font-family", "helvetica");
nodeG.setAttributeNS(null, "transform", "translate(" + node.sx + "," + node.sy + ")");
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 text = document.createElementNS(svgns, 'text');
var textLabel = document.createTextNode(node.id);
text.appendChild(textLabel);
text.setAttributeNS(null, "x", 4);
text.setAttributeNS(null, "y", 0);
text.setAttributeNS(null, "height", 10);
this.addBounds(bounds, {minX:node.sx - xOffset, minY: node.sy - yOffset, maxX: node.sx + xOffset, maxY: node.sy + yOffset});
innerG.appendChild(rect1);
innerG.appendChild(text);
nodeG.appendChild(innerG);
self.mainG.appendChild(nodeG);
this.nodes[node.id] = node;
},
addBounds: function(toBounds, addBounds) {
toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
toBounds.minY = toBounds.minY ? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
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) });
}
});
var graphModel;
azkaban.GraphModel = Backbone.Model.extend({});
$(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 });
graphModel = new azkaban.GraphModel();
svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel});
jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
var requestURL = contextURL + "/manager";
$.get(
requestURL,
{"project": projectName, "json":"fetchflowgraph", "flow":flowName},
function(data) {
console.log("data fetched");
graphModel.set({data: data});
graphModel.trigger("change:graph");
},
"json"
);
});