azkaban-aplcache

Two phase execution.

9/29/2012 6:36:15 AM

Details

diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index b6fd420..a48a9c3 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -159,14 +159,15 @@ public class ExecutorManager {
 		return flows;
 	}
 	
-	public boolean isFlowRunning(String projectId, String flowId) {
+	public List<ExecutableFlow> getFlowRunningFlows(String projectId, String flowId) {
+		ArrayList<ExecutableFlow> list = new ArrayList<ExecutableFlow>();
 		for (ExecutableFlow flow: getRunningFlows()) {
 			if (flow.getProjectId().equals(projectId) && flow.getFlowId().equals(flowId)) {
-				return true;
+				list.add(flow);
 			}
 		}
 		
-		return false;
+		return list;
 	}
 	
 	public List<ExecutableFlow> getRunningFlows() {
diff --git a/src/java/azkaban/flow/Flow.java b/src/java/azkaban/flow/Flow.java
index aaf206a..caae883 100644
--- a/src/java/azkaban/flow/Flow.java
+++ b/src/java/azkaban/flow/Flow.java
@@ -25,6 +25,8 @@ public class Flow {
 	private HashMap<String, Set<Edge>> inEdges = new HashMap<String, Set<Edge>>();
 	private HashMap<String, FlowProps> flowProps = new HashMap<String, FlowProps>(); 
 
+	private List<String> failureEmail = new ArrayList<String>();
+	private List<String> successEmail = new ArrayList<String>();
 	private ArrayList<String> errors;
 
 	private boolean isLayedOut = false;
@@ -78,6 +80,22 @@ public class Flow {
 		return nodes.get(nodeId);
 	}
 	
+	public List<String> getSuccessEmails() {
+		return successEmail;
+	}
+	
+	public List<String> getFailureEmails() {
+		return failureEmail;
+	}
+	
+	public void addSuccessEmails(Collection<String> emails) {
+		successEmail.addAll(emails);
+	}
+	
+	public void addFailureEmails(Collection<String> emails) {
+		failureEmail.addAll(emails);
+	}
+	
 	public int getNumLevels() {
 		return numLevels;
 	}
@@ -182,6 +200,8 @@ public class Flow {
 		flowObj.put("props", objectizeProperties());
 		flowObj.put("nodes", objectizeNodes());
 		flowObj.put("edges", objectizeEdges());
+		flowObj.put("failure.email", failureEmail);
+		flowObj.put("success.email", successEmail);
 		flowObj.put("layedout", isLayedOut);
 		if (errors != null) {
 			flowObj.put("errors", errors);
@@ -246,6 +266,8 @@ public class Flow {
 		List<Edge> edges = loadEdgeFromObjects(edgeList, nodes);
 		flow.addAllEdges(edges);
 		
+		flow.failureEmail = (List<String>)flowObject.get("failure.email");
+		flow.successEmail = (List<String>)flowObject.get("success.email");
 		return flow;
 	}
 
diff --git a/src/java/azkaban/scheduler/ScheduleManager.java b/src/java/azkaban/scheduler/ScheduleManager.java
index f5eeb75..742bf42 100644
--- a/src/java/azkaban/scheduler/ScheduleManager.java
+++ b/src/java/azkaban/scheduler/ScheduleManager.java
@@ -204,7 +204,7 @@ public class ScheduleManager {
 	private void saveSchedule() {
 		loader.saveSchedule(getSchedule());
 	}
-
+	
 	/**
 	 * Thread that simply invokes the running of flows when the schedule is
 	 * ready.
diff --git a/src/java/azkaban/utils/DirectoryFlowLoader.java b/src/java/azkaban/utils/DirectoryFlowLoader.java
index ff1cca2..7c80368 100644
--- a/src/java/azkaban/utils/DirectoryFlowLoader.java
+++ b/src/java/azkaban/utils/DirectoryFlowLoader.java
@@ -5,6 +5,7 @@ import java.io.FileFilter;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -204,6 +205,16 @@ public class DirectoryFlowLoader {
 		for (Node base: nodeMap.values()) {
 			if (!nonRootNodes.contains(base.getId())) {
 				Flow flow = new Flow(base.getId());
+				Props jobProp = jobPropsMap.get(base.getId());
+				
+				// Dedup with sets
+				@SuppressWarnings("unchecked")
+				Set<String> successEmail = new HashSet<String>(jobProp.getStringList("success.emails", Collections.EMPTY_LIST));
+				@SuppressWarnings("unchecked")
+				Set<String> failureEmail = new HashSet<String>(jobProp.getStringList("failure.emails", Collections.EMPTY_LIST));
+				flow.addFailureEmails(failureEmail);
+				flow.addSuccessEmails(successEmail);
+				
 				flow.addAllFlowProperties(flowPropsList);
 				constructFlow(flow, base, visitedNodes);
 				flow.initialize();
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index 0aaf143..29745ef 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -22,15 +22,19 @@ import azkaban.flow.Flow;
 import azkaban.project.Project;
 import azkaban.project.ProjectManager;
 import azkaban.project.ProjectManagerException;
+import azkaban.scheduler.ScheduleManager;
+import azkaban.scheduler.ScheduledFlow;
 import azkaban.user.Permission;
 import azkaban.user.User;
 import azkaban.user.Permission.Type;
+import azkaban.utils.Utils;
 import azkaban.webapp.session.Session;
 
 public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 	private static final long serialVersionUID = 1L;
 	private ProjectManager projectManager;
 	private ExecutorManager executorManager;
+	private ScheduleManager scheduleManager;
 	private static final int STRING_BUFFER_SIZE = 1024*5;
 
 	@Override
@@ -38,6 +42,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		super.init(config);
 		projectManager = this.getApplication().getProjectManager();
 		executorManager = this.getApplication().getExecutorManager();
+		scheduleManager = this.getApplication().getScheduleManager();
 	}
 
 	@Override
@@ -227,6 +232,11 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 			String flowName = getParam(req, "flow");
 			ajaxIsFlowRunning(req, resp, ret, session.getUser(), projectName, flowName);
 		}
+		else if (ajaxName.equals("flowInfo")) {
+			String projectName = getParam(req, "project");
+			String flowName = getParam(req, "flow");
+			ajaxFetchFlowInfo(req, resp, ret, session.getUser(), projectName, flowName);
+		}
 		else {
 			String projectName = getParam(req, "project");
 			
@@ -315,7 +325,35 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		}
 	}
 
-
+	private void ajaxFetchFlowInfo(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, String projectId, String flowId) throws ServletException {
+		Project project = getProjectAjaxByPermission(ret, projectId, user, Type.READ);
+		if (project == null) {
+			return;
+		}
+		
+		Flow flow = project.getFlow(flowId);
+		if (flow == null) {
+			ret.put("error", "Error loading flow. Flow " + flowId + " doesn't exist in " + projectId);
+			return;
+		}
+		
+		ret.put("successEmails", flow.getSuccessEmails());
+		ret.put("failureEmails", flow.getFailureEmails());
+		ret.put("running", executorManager.getFlowRunningFlows(projectId, flowId));
+		
+		ScheduledFlow sflow = null;
+		for (ScheduledFlow schedFlow: scheduleManager.getSchedule()) {
+			if (schedFlow.getProjectId().equals(projectId) && schedFlow.getFlowId().equals(flowId)) {
+				sflow = schedFlow;
+				break;
+			}
+		}
+		
+		if (sflow != null) {
+			ret.put("scheduled", sflow.getNextExecTime().getMillis());
+		}
+	}
+	
 	private void ajaxCancelFlow(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException{
 		Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.EXECUTE);
 		if (project == null) {
@@ -335,7 +373,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 			return;
 		}
 		
-		ret.put("running", executorManager.isFlowRunning(projectId, flowId));
+		ret.put("running", executorManager.getFlowRunningFlows(projectId, flowId));
 	}
 	
 	private void ajaxRestartFlow(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException{
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index f8096c1..68c06e0 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -201,20 +201,20 @@
 	                		</div>
 	                		<div class="optionsPane">
 	                			<div id="generalPanel" class="generalPanel panel">
-	                				<div>
+	                				<div id="completeActions">
 	                					<h4>Completion Actions</h4>
 		                				<dl>
 		                					<dt>Failure Action</dt>
 		                					<dd>
 		                						<select name="failureAction">
-		                							<option value="finishCurrent">Finish Current</option>
+		                							<option value="finishCurrent">Finish Current Running</option>
 		                							<option value="cancelImmediately">Cancel All</option>
 		                							<option value="finishPossible">Finish All Possible</option>
 		                						</select>
 											</dd>
 		                					<dt>Failure Email</dt>
 		                					<dd>
-		                						<textarea></textarea>
+		                						<textarea id="failureEmails"></textarea>
 		                					</dd>
 		                					<dt>Notify on Failure</dt>
 		                					<dd>
@@ -223,7 +223,13 @@
 		                					</dd>
 		                					<dt>Success Email</dt>
 		                					<dd>
-		                						<textarea></textarea>
+		                						<textarea id="successEmails"></textarea>
+		                					</dd>
+		                					<dt>Executing Job</dt>
+		                					<dd id="executingJob">
+		                						<input id="ignore" class="radio" type="radio" name="concurrent" value="ignore" checked /><label class="radioLabel" for="ignore">Run Concurrently</label>
+		                						<input id="pipeline" class="radio" type="radio" name="concurrent" value="pipeline" /><label class="radioLabel" for="pipeline">Pipeline</label>
+		                						<input id="queue" class="radio" type="radio" name="concurrent" value="queue" /><label class="radioLabel" for="queue">Queue Job</label>
 		                					</dd>
 		                				</dl>
 	                				</div>
@@ -245,15 +251,17 @@
 	                				</div>
 	                			</div>
 								<div id="graphPanel" class="graphPanel panel">
-									<div class="jobList">
-										<div id="filterList2">
-											<input id="filter2" placeholder="  Job Filter" />
+									<div id="jobListCustom" class="jobList">
+										<div class="filterList">
+											<input class="filter" placeholder="  Job Filter" />
 										</div>
-										<div id="list2">
+										<div class="list">
 										</div>
-										<div id="resetPanZoomBtn2" class="btn5" >Reset Pan Zoom</div>
+										<div class="btn5 resetPanZoomBtn" >Reset Pan Zoom</div>
 									</div>
-								    <div class="svgDiv" >
+								    <div id="svgDivCustom" class="svgDiv" >
+								    	<svg class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
+										</svg>
 									</div>
 		                        </div>
 	                        </div>
@@ -265,7 +273,12 @@
                 </div>
                 
 #end
-		<ul id="jobMenu" class="contextMenu flowSubmenu">  
+		<ul id="jobMenu" class="contextMenu">  
+			<li class="open"><a href="#open">Open...</a></li>
+			<li class="openwindow"><a href="#openwindow">Open in New Window...</a></li>
+		</ul>
+
+		<ul id="disableJobMenu" class="contextMenu flowSubmenu">  
 			<li class="open"><a href="#open">Open...</a></li>
 			<li class="openwindow"><a href="#openwindow">Open in New Window...</a></li>
 			<li id="disable" class="disable separator"><a href="#disable">Disable</a><div id="disableArrow" class="context-sub-icon"></div></li>
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 2d3fdb1..20d62f8 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1280,14 +1280,14 @@ tr:hover td {
 	margin-left: 3px;
 }
 
-#svgGraph {
+.svgGraph {
 	width: 100%;
 	height: 100%;
 	background: #fff;
 }
 
 
-#svgDiv {
+.svgDiv {
 	position: absolute;
 	top: 0px;
 	right: 0px;
@@ -1295,16 +1295,8 @@ tr:hover td {
 	bottom: 0px;
 }
 
-#svgDiv.svgDivStaging {
-	top: 30px;
-}
-
-#svgDivModal {
-	position: absolute;
-	top: 0px;
-	right: 0px;
-	left: 260px;
-	height: 100%;
+#graphPanel {
+	background-color: #F0F0F0;
 }
 
 #executing-options {
@@ -1314,10 +1306,19 @@ tr:hover td {
 	bottom: 40px;
 }
 
+#scheduled {
+	
+}
+
+.radioLabel.disabled {
+	opacity: 0.3;
+}
+
 #executing-options .svgDiv {
 	position: absolute;
 	background-color: #CCC;
-	left: 260px;
+	padding: 1px;
+	left: 270px;
 	right: 0px;
 	top: 0px;
 	bottom: 0px;
@@ -1325,10 +1326,15 @@ tr:hover td {
 
 #executing-options .jobList {
 	position: absolute;
-	width: 260px;
+	width: 255px;
 	top: 0px;
 	bottom: 0px;
-	background-color: #F00;
+	padding: 5px;
+	background-color: #F0F0F0;
+}
+
+#executing-options .list {
+	width: 255px;
 }
 
 #executing-options ul.optionsPicker {
@@ -1477,19 +1483,6 @@ tr:hover td {
 	width: 250px;
 }
 
-#list {
-	position: absolute;
-	background-color: #fff;
-	margin-right: 10px;
-	border: solid;
-	border-color: #CCC;
-	border-width: 1px;
-	overflow: auto;
-	top: 26px;
-	width: 100%;
-	bottom: 120px;
-}
-
 .resetPanZoomBtn {
 	position: absolute;
 	bottom: 90px;
@@ -1512,6 +1505,20 @@ tr:hover td {
 	width: 100%;
 }
 
+
+.list {
+	position: absolute;
+	background-color: #fff;
+	margin-right: 10px;
+	border: solid;
+	border-color: #CCC;
+	border-width: 1px;
+	overflow: auto;
+	top: 26px;
+	width: 100%;
+	bottom: 120px;
+}
+
 .list ul {
 	white-space: nowrap
 }
diff --git a/src/web/js/azkaban.exflow.options.view.js b/src/web/js/azkaban.exflow.options.view.js
index 1c19104..a9a5034 100644
--- a/src/web/js/azkaban.exflow.options.view.js
+++ b/src/web/js/azkaban.exflow.options.view.js
@@ -1,4 +1,53 @@
 var executeFlowView;
+var customSvgGraphView;
+var customJobListView;
+var cloneModel;
+
+azkaban.ContextMenu = Backbone.View.extend({
+	events : {
+		"click #disableArrow" : "handleDisabledClick",
+		"click #enableArrow" : "handleEnabledClick"
+	},
+	initialize: function(settings) {
+		$('#disableSub').hide();
+		$('#enableSub').hide();
+	},
+	handleEnabledClick: function(evt) {
+		if(evt.stopPropagation) {
+			evt.stopPropagation();
+		}
+		evt.cancelBubble=true;
+		
+		if (evt.currentTarget.expanded) {
+			evt.currentTarget.expanded=false;
+			$('#enableArrow').removeClass('collapse');
+			$('#enableSub').hide();
+		}
+		else {
+			evt.currentTarget.expanded=true;
+			$('#enableArrow').addClass('collapse');
+			$('#enableSub').show();
+		}
+	},
+	handleDisabledClick: function(evt) {
+		if(evt.stopPropagation) {
+			evt.stopPropagation();
+		}
+		evt.cancelBubble=true;
+		
+		if (evt.currentTarget.expanded) {
+			evt.currentTarget.expanded=false;
+			$('#disableArrow').removeClass('collapse');
+			$('#disableSub').hide();
+		}
+		else {
+			evt.currentTarget.expanded=true;
+			$('#disableArrow').addClass('collapse');
+			$('#disableSub').show();
+		}
+	}
+});
+
 azkaban.ExecuteFlowView = Backbone.View.extend({
   	  events : {
   	  	"click" : "closeEditingTarget",
@@ -12,14 +61,36 @@ azkaban.ExecuteFlowView = Backbone.View.extend({
 	    "click table .editable": "handleEditColumn",
 	    "click table .removeIcon": "handleRemoveColumn"
 	  },
-	  initialize: function(evt) {
-	  	 $('#executebtn').click( function() {
-	  	 	$('#modalBackground').show();
-	  	 	$('#executing-options').show();
-	  	 });
-	  	
+	  initialize: function(setting) {
+	  	 this.contextMenu = new azkaban.ContextMenu({el:$('#disableJobMenu')});
 	  	 this.handleGeneralOptionsSelect();
 	  },
+	  show: function() {
+	  	this.handleGeneralOptionsSelect();
+	  	$('#modalBackground').show();
+	  	$('#executing-options').show();
+	  	
+	  	var executeURL = contextURL + "/executor";
+	  	$.get(
+			executeURL,
+			{"project": projectName, "ajax":"flowInfo", "flow":flowName},
+			function(data) {
+				if (data.error) {
+					alert(data.error);
+				}
+				else {
+					$('#successEmails').val(data.successEmails);
+					$('#failureEmails').val(data.failureEmails);
+					
+					if (data.running.length == 0) {
+						$(".radio").attr("disabled", "disabled");
+						$(".radioLabel").addClass("disabled", "disabled");
+					}
+				}
+			},
+			"json"
+		);
+	  },
 	  handleCancelExecution: function(evt) {
 	  	var executeURL = contextURL + "/executor";
 		$('#modalBackground').hide();
@@ -38,12 +109,24 @@ azkaban.ExecuteFlowView = Backbone.View.extend({
 
 	  	$('#graphPanel').show();	  	
 	  	$('#generalPanel').hide();
+	  	
+	  	if (this.flowSetup) {
+	  		return;
+	  	}
+	  	
+ 	  	this.cloneModel = this.model.clone();
+ 	  	cloneModel = this.cloneModel;
+	  	customSvgGraphView = new azkaban.SvgGraphView({el:$('#svgDivCustom'), model: this.cloneModel, rightClick: {id: 'disableJobMenu', callback: this.handleDisableMenuClick}});
+		customJobsListView = new azkaban.JobListView({el:$('#jobListCustom'), model: this.cloneModel, rightClick: {id: 'disableJobMenu', callback: this.handleDisableMenuClick}});
+		this.cloneModel.trigger("change:graph");
+
+		this.flowSetup = true;
 	  },
 	  handleExecuteFlow: function(evt) {
 	  	var executeURL = contextURL + "/executor";
 		$.get(
 			executeURL,
-			{"project": projectName, "ajax":"executeFlow", "flow":flowName, "disabled":graphModel.get("disabled")},
+			{"project": projectName, "ajax":"executeFlow", "flow":flowName, "disabled":this.cloneModel.get("disabled")},
 			function(data) {
 				if (data.error) {
 					alert(data.error);
@@ -134,5 +217,121 @@ azkaban.ExecuteFlowView = Backbone.View.extend({
 	  },
 	  addRowData : function(evt) {
 
-	  }
+	  },
+	  handleDisableMenuClick : function(action, el, pos) {
+			var jobid = el[0].jobid;
+			var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
+			if (action == "open") {
+				window.location.href = requestURL;
+			}
+			else if(action == "openwindow") {
+				window.open(requestURL);
+			}
+			else if(action == "disable") {
+				var disabled = cloneModel.get("disabled");
+		
+				disabled[jobid] = true;
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "disableParents") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				var inNodes = nodes[jobid].inNodes;
+		
+				if (inNodes) {
+					for (var key in inNodes) {
+					  disabled[key] = true;
+					}
+				}
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "disableChildren") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				var outNodes = nodes[jobid].outNodes;
+		
+				if (outNodes) {
+					for (var key in outNodes) {
+					  disabled[key] = true;
+					}
+				}
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "disableAncestors") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				
+				recurseAllAncestors(nodes, disabled, jobid, true);
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "disableDescendents") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				
+				recurseAllDescendents(nodes, disabled, jobid, true);
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if(action == "enable") {
+				var disabled = cloneModel.get("disabled");
+		
+				disabled[jobid] = false;
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "enableParents") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				var inNodes = nodes[jobid].inNodes;
+		
+				if (inNodes) {
+					for (var key in inNodes) {
+					  disabled[key] = false;
+					}
+				}
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "enableChildren") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				var outNodes = nodes[jobid].outNodes;
+		
+				if (outNodes) {
+					for (var key in outNodes) {
+					  disabled[key] = false;
+					}
+				}
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "enableAncestors") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				
+				recurseAllAncestors(nodes, disabled, jobid, false);
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+			else if (action == "enableDescendents") {
+				var disabled = cloneModel.get("disabled");
+				var nodes = cloneModel.get("nodes");
+				
+				recurseAllDescendents(nodes, disabled, jobid, false);
+				
+				cloneModel.set({disabled: disabled});
+				cloneModel.trigger("change:disabled");
+			}
+		}
 });
\ No newline at end of file
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index ff1cb39..318e514 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -508,13 +508,16 @@ $(function() {
 	graphModel = new azkaban.GraphModel();
 	logModel = new azkaban.LogModel();
 	flowTabView = new azkaban.FlowTabView({el:$( '#headertabs'), model: graphModel});
-	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel});
-	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
+	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel, rightClick: {id: 'jobMenu', callback: handleJobMenuClick}});
+	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel, rightClick: {id: 'jobMenu', callback: handleJobMenuClick}});
 	statusView = new azkaban.StatusView({el:$('#flow-status'), model: graphModel});
 	flowLogView = new azkaban.FlowLogView({el:$('#flowLogView'), model: logModel});
 	executionListView = new azkaban.ExecutionListView({el: $('#jobListView'), model:graphModel});
 	var requestURL = contextURL + "/executor";
 
+	// This is to set up the execution flow
+	
+
 	ajaxCall(
 	      requestURL,
 	      {"execid": execId, "ajax":"fetchexecflow"},
diff --git a/src/web/js/azkaban.flow.graph.view.js b/src/web/js/azkaban.flow.graph.view.js
index 75b3096..33db1ec 100644
--- a/src/web/js/azkaban.flow.graph.view.js
+++ b/src/web/js/azkaban.flow.graph.view.js
@@ -9,7 +9,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		this.model.bind('resetPanZoom', this.resetPanZoom, this);
 		this.model.bind('change:update', this.handleStatusUpdate, this);
 		this.model.bind('change:disabled', this.handleDisabledChange, this);
-				
+		this.model.bind('change:updateAll', this.handleUpdateAllStatus, this);
+		
 		this.svgns = "http://www.w3.org/2000/svg";
 		this.xlinksn = "http://www.w3.org/1999/xlink";
 		
@@ -17,6 +18,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		var svg = $(this.el).find('svg')[0];
 		this.svgGraph = svg;
 		
+		this.contextMenu = settings.rightClick;
+		
 		var gNode = document.createElementNS(this.svgns, 'g');
 		svg.appendChild(gNode);
 		this.mainG = gNode;
@@ -140,15 +143,17 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			var updateNode = updateData.nodes[i];
 			
 			var g = this.gNodes[updateNode.id];
-			
-			for (var j = 0; j < statusList.length; ++j) {
-				var status = statusList[j];
-				removeClass(g, status);
-			}
+			this.handleRemoveAllStatus(g);
 			
 			addClass(g, updateNode.status);
 		}
 	},
+	handleRemoveAllStatus: function(gNode) {
+		for (var j = 0; j < statusList.length; ++j) {
+			var status = statusList[j];
+			removeClass(g, status);
+		}
+	},
 	clickGraph: function(self) {
 		console.log("click");
 		if (self.currentTarget.jobid) {
@@ -246,9 +251,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		nodeG.setAttributeNS(null, "class", "node");
 		nodeG.jobid=node.id;
 		$(nodeG).contextMenu({
-				menu: 'jobMenu'
+				menu: this.contextMenu.id
 			},
-			handleJobMenuClick
+			this.contextMenu.callback
 		);
 	},
 	addBounds: function(toBounds, addBounds) {
diff --git a/src/web/js/azkaban.flow.job.view.js b/src/web/js/azkaban.flow.job.view.js
index 210b404..b165e28 100644
--- a/src/web/js/azkaban.flow.job.view.js
+++ b/src/web/js/azkaban.flow.job.view.js
@@ -12,6 +12,7 @@ azkaban.JobListView = Backbone.View.extend({
 		
 		this.filterInput = $(this.el).find(".filter");
 		this.list = $(this.el).find(".list");
+		this.contextMenu = settings.rightClick;
 	},
 	filterJobs: function(self) {
 		var filter = this.filterInput.val();
@@ -123,9 +124,9 @@ azkaban.JobListView = Backbone.View.extend({
 			li.jobid=nodeArray[i].id;
 			
 			$(li).contextMenu({
-					menu: 'jobMenu'
+					menu: this.contextMenu.id
 				},
-				handleJobMenuClick
+				this.contextMenu.callback
 			);
 			
 			this.listNodes[nodeArray[i].id] = li;
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index d65dc9d..ad6dec9 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -21,112 +21,6 @@ var handleJobMenuClick = function(action, el, pos) {
 	else if(action == "openwindow") {
 		window.open(requestURL);
 	}
-	else if(action == "disable") {
-		var disabled = graphModel.get("disabled");
-
-		disabled[jobid] = true;
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "disableParents") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		var inNodes = nodes[jobid].inNodes;
-
-		if (inNodes) {
-			for (var key in inNodes) {
-			  disabled[key] = true;
-			}
-		}
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "disableChildren") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		var outNodes = nodes[jobid].outNodes;
-
-		if (outNodes) {
-			for (var key in outNodes) {
-			  disabled[key] = true;
-			}
-		}
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "disableAncestors") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		
-		recurseAllAncestors(nodes, disabled, jobid, true);
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "disableDescendents") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		
-		recurseAllDescendents(nodes, disabled, jobid, true);
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if(action == "enable") {
-		var disabled = graphModel.get("disabled");
-
-		disabled[jobid] = false;
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "enableParents") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		var inNodes = nodes[jobid].inNodes;
-
-		if (inNodes) {
-			for (var key in inNodes) {
-			  disabled[key] = false;
-			}
-		}
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "enableChildren") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		var outNodes = nodes[jobid].outNodes;
-
-		if (outNodes) {
-			for (var key in outNodes) {
-			  disabled[key] = false;
-			}
-		}
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "enableAncestors") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		
-		recurseAllAncestors(nodes, disabled, jobid, false);
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
-	else if (action == "enableDescendents") {
-		var disabled = graphModel.get("disabled");
-		var nodes = graphModel.get("nodes");
-		
-		recurseAllDescendents(nodes, disabled, jobid, false);
-		
-		graphModel.set({disabled: disabled});
-		graphModel.trigger("change:disabled");
-	}
 }
 
 function recurseAllAncestors(nodes, disabledMap, id, disable) {
@@ -216,246 +110,7 @@ azkaban.FlowTabView= Backbone.View.extend({
 var jobListView;
 
 var svgGraphView;
-/*
-azkaban.SvgGraphView = Backbone.View.extend({
-	events: {
-		"click g" : "clickGraph"
-	},
-	initialize: function(settings) {
-		this.model.bind('change:selected', this.changeSelected, this);
-		this.model.bind('change:disabled', this.handleDisabledChange, 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;
-		};
-	
-		// layout
-		layoutGraph(nodes, edges);
-		
-		var bounds = {};
-		this.nodes = {};
-		for (var i = 0; i < nodes.length; ++i) {
-			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;
-		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
-			var g = document.getElementById(previous);
-			removeClass(g, "selected");
-		}
-		
-		if (selected) {
-			var g = document.getElementById(selected);
-			var node = this.nodes[selected];
-			
-			addClass(g, "selected");
-			
-			var offset = 200;
-			var widthHeight = offset*2;
-			var x = node.x - offset;
-			var y = node.y - offset;
-			
-			
-			$("#svgGraph").svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
-		}
-	},
-	clickGraph: function(self) {
-		console.log("click");
-		if (self.currentTarget.jobid) {
-			this.model.set({"selected": self.currentTarget.jobid});
-		}
-	},
-	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.x + "," + startNode.y + " ";
-
-			for (var i = 0; i < edge.guides.length; ++i ) {
-				edgeGuidePoint = edge.guides[i];
-				pointString += edgeGuidePoint.x + "," + edgeGuidePoint.y + " ";
-			}
-			
-			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;");
-			self.mainG.appendChild(polyLine);
-		}
-		else { 
-			var line = document.createElementNS(svgns, 'line');
-			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);
-		}
-	},
-	handleDisabledChange: function(evt) {
-		var disabledMap = this.model.get("disabled");
-		for(var id in disabledMap) {
-		    if(disabledMap.hasOwnProperty(id)) {
-		    	var disabled = disabledMap[id];
-		    	this.nodes[id].disabled = disabled;
-		    	var g = document.getElementById(id);
-		    	
-		    	if (disabled) {
-		    		this.nodes[id].disabled = disabled;
-					addClass(g, "disabled");
-		    	}
-		    	else {
-		    		removeClass(g, "disabled");
-		    	}
-		    }
-		}
-	},
-	drawNode: function(self, node, bounds) {
-		var svg = self.svgGraph;
-		var svgns = self.svgns;
-
-		var xOffset = 10;
-		var yOffset = 10;
-
-		var nodeG = document.createElementNS(svgns, "g");
-		nodeG.setAttributeNS(null, "class", "jobnode");
-		nodeG.setAttributeNS(null, "id", node.id);
-		nodeG.setAttributeNS(null, "font-family", "helvetica");
-		nodeG.setAttributeNS(null, "transform", "translate(" + node.x + "," + node.y + ")");
-		
-		var innerG = document.createElementNS(svgns, "g");
-		innerG.setAttributeNS(null, "transform", "translate(-10,-10)");
-		
-		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.label);
-		text.appendChild(textLabel);
-		text.setAttributeNS(null, "x", 4);
-		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});
-		
-		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(circle);
-		innerG.appendChild(backRect);
-		innerG.appendChild(text);
-		innerG.jobid = node.id;
-
-		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
-		);
-	},
-	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;
-	},
-	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 executionsView;
 azkaban.ExecutionsView = Backbone.View.extend({
 	events: {
@@ -641,52 +296,6 @@ azkaban.ExecutionsView = Backbone.View.extend({
 	}
 });
 
-var contextMenu;
-azkaban.ContextMenu = Backbone.View.extend({
-	events : {
-		"click #disableArrow" : "handleDisabledClick",
-		"click #enableArrow" : "handleEnabledClick"
-	},
-	initialize: function(settings) {
-		$('#disableSub').hide();
-		$('#enableSub').hide();
-	},
-	handleEnabledClick: function(evt) {
-		if(evt.stopPropagation) {
-			evt.stopPropagation();
-		}
-		evt.cancelBubble=true;
-		
-		if (evt.currentTarget.expanded) {
-			evt.currentTarget.expanded=false;
-			$('#enableArrow').removeClass('collapse');
-			$('#enableSub').hide();
-		}
-		else {
-			evt.currentTarget.expanded=true;
-			$('#enableArrow').addClass('collapse');
-			$('#enableSub').show();
-		}
-	},
-	handleDisabledClick: function(evt) {
-		if(evt.stopPropagation) {
-			evt.stopPropagation();
-		}
-		evt.cancelBubble=true;
-		
-		if (evt.currentTarget.expanded) {
-			evt.currentTarget.expanded=false;
-			$('#disableArrow').removeClass('collapse');
-			$('#disableSub').hide();
-		}
-		else {
-			evt.currentTarget.expanded=true;
-			$('#disableArrow').addClass('collapse');
-			$('#disableSub').show();
-		}
-	}
-});
-
 var graphModel;
 azkaban.GraphModel = Backbone.Model.extend({});
 
@@ -770,17 +379,22 @@ $(function() {
 	var selected;
 	// Execution model has to be created before the window switches the tabs.
 	executionModel = new azkaban.ExecutionModel();
+	executionsView = new azkaban.ExecutionsView({el:$('#executionsView'), model: executionModel});
 	
 	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});
-	contextMenu = new azkaban.ContextMenu({el:$('#jobMenu')});
+	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel, rightClick: {id: 'jobMenu', callback: handleJobMenuClick}});
+	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel, rightClick: {id: 'jobMenu', callback: handleJobMenuClick}});
 	scheduleFlowView = new azkaban.ScheduleFlowView({el:$('#schedule-flow')});
-	executeFlowView = new azkaban.ExecuteFlowView({el:$('#executing-options')});
+	executeFlowView = new azkaban.ExecuteFlowView({el:$('#executing-options'), model: graphModel});
 	var requestURL = contextURL + "/manager";
 
+	// Set up the Flow options view. Create a new one every time :p
+	 $('#executebtn').click( function() {
+	  	executeFlowView.show();
+	 });
+
 	$.get(
 	      requestURL,
 	      {"project": projectName, "ajax":"fetchflowgraph", "flow":flowName},