azkaban-developers

Merge pull request #112 from davidzchen/ui-tweaks Minor

1/19/2014 6:02:23 AM

Details

diff --git a/src/java/azkaban/utils/WebUtils.java b/src/java/azkaban/utils/WebUtils.java
index 31322e6..4f65159 100644
--- a/src/java/azkaban/utils/WebUtils.java
+++ b/src/java/azkaban/utils/WebUtils.java
@@ -28,10 +28,10 @@ import azkaban.executor.Status;
 public class WebUtils {
 	public static final String DATE_TIME_STRING = "YYYY-MM-dd HH:mm:ss";
 
-    private static final long ONE_KB = 1024;
-    private static final long ONE_MB = 1024 * ONE_KB;
-    private static final long ONE_GB = 1024 * ONE_MB;
-    private static final long ONE_TB = 1024 * ONE_GB;
+	private static final long ONE_KB = 1024;
+	private static final long ONE_MB = 1024 * ONE_KB;
+	private static final long ONE_GB = 1024 * ONE_MB;
+	private static final long ONE_TB = 1024 * ONE_GB;
 	
 	public String formatDate(long timeMS) {
 		if (timeMS == -1) {
@@ -78,79 +78,76 @@ public class WebUtils {
 	
 	public String formatStatus(Status status) {
 		switch(status) {
-		case SUCCEEDED:
-			return "Success";
-		case FAILED:
-			return "Failed";
-		case RUNNING:
-			return "Running";
-		case DISABLED:
-			return "Disabled";
-		case KILLED:
-			return "Killed";
-		case FAILED_FINISHING:
-			return "Running w/Failure";
-		case PREPARING:
-			return "Preparing";
-		case READY:
-			return "Ready";
-		case PAUSED:
-			return "Paused";
-		case SKIPPED:
-			return "Skipped";
-		default:
+			case SUCCEEDED:
+				return "Success";
+			case FAILED:
+				return "Failed";
+			case RUNNING:
+				return "Running";
+			case DISABLED:
+				return "Disabled";
+			case KILLED:
+				return "Killed";
+			case FAILED_FINISHING:
+				return "Running w/Failure";
+			case PREPARING:
+				return "Preparing";
+			case READY:
+				return "Ready";
+			case PAUSED:
+				return "Paused";
+			case SKIPPED:
+				return "Skipped";
+			default:
 		}
-
 		return "Unknown";
 	}
 	
-	public String formatDateTime(DateTime dt)
-	{
+	public String formatDateTime(DateTime dt) {
 		return DateTimeFormat.forPattern(DATE_TIME_STRING).print(dt);
 	}
 	
-    public String formatDateTime(long timestamp) {
-        return formatDateTime(new DateTime(timestamp));
-    }
+	public String formatDateTime(long timestamp) {
+		return formatDateTime(new DateTime(timestamp));
+	}
 	
-	public String formatPeriod(ReadablePeriod period)
-	{
-        String periodStr = "null";
+	public String formatPeriod(ReadablePeriod period) {
+		String periodStr = "null";
 
-        if (period == null) {
-            return periodStr;
-        }
+		if (period == null) {
+			return periodStr;
+		}
 
-        if (period.get(DurationFieldType.years()) > 0) {
-        	int years = period.get(DurationFieldType.years());
-        	periodStr = years + " year(s)";
-        }
-        else if (period.get(DurationFieldType.months()) > 0) {
-            int months = period.get(DurationFieldType.months());
-            periodStr = months + " month(s)";
-        }
-        else if (period.get(DurationFieldType.weeks()) > 0) {
-            int weeks = period.get(DurationFieldType.weeks());
-            periodStr = weeks + " week(s)";
-        }
-        else if (period.get(DurationFieldType.days()) > 0) {
-            int days = period.get(DurationFieldType.days());
-            periodStr = days + " day(s)";
-        }
-        else if (period.get(DurationFieldType.hours()) > 0) {
-            int hours = period.get(DurationFieldType.hours());
-            periodStr = hours + " hour(s)";
-        }
-        else if (period.get(DurationFieldType.minutes()) > 0) {
-            int minutes = period.get(DurationFieldType.minutes());
-            periodStr = minutes + " minute(s)";
-        }
-        else if (period.get(DurationFieldType.seconds()) > 0) {
-            int seconds = period.get(DurationFieldType.seconds());
-            periodStr = seconds + " second(s)";
-        }
-        
-        return periodStr;
+		if (period.get(DurationFieldType.years()) > 0) {
+			int years = period.get(DurationFieldType.years());
+			periodStr = years + " year(s)";
+		}
+		else if (period.get(DurationFieldType.months()) > 0) {
+			int months = period.get(DurationFieldType.months());
+			periodStr = months + " month(s)";
+		}
+		else if (period.get(DurationFieldType.weeks()) > 0) {
+			int weeks = period.get(DurationFieldType.weeks());
+			periodStr = weeks + " week(s)";
+		}
+		else if (period.get(DurationFieldType.days()) > 0) {
+			int days = period.get(DurationFieldType.days());
+			periodStr = days + " day(s)";
+		}
+		else if (period.get(DurationFieldType.hours()) > 0) {
+			int hours = period.get(DurationFieldType.hours());
+			periodStr = hours + " hour(s)";
+		}
+		else if (period.get(DurationFieldType.minutes()) > 0) {
+			int minutes = period.get(DurationFieldType.minutes());
+			periodStr = minutes + " minute(s)";
+		}
+		else if (period.get(DurationFieldType.seconds()) > 0) {
+			int seconds = period.get(DurationFieldType.seconds());
+			periodStr = seconds + " second(s)";
+		}
+		
+		return periodStr;
 	}
 	
 	public String extractNumericalId(String execId) {
@@ -160,18 +157,18 @@ public class WebUtils {
 		return execId.substring(0, index2);
 	}
 	
-    public String displayBytes(long sizeBytes) {
-        NumberFormat nf = NumberFormat.getInstance();
-        nf.setMaximumFractionDigits(2);
-        if(sizeBytes >= ONE_TB)
-            return nf.format(sizeBytes / (double) ONE_TB) + " tb";
-        else if(sizeBytes >= ONE_GB)
-            return nf.format(sizeBytes / (double) ONE_GB) + " gb";
-        else if(sizeBytes >= ONE_MB)
-            return nf.format(sizeBytes / (double) ONE_MB) + " mb";
-        else if(sizeBytes >= ONE_KB)
-            return nf.format(sizeBytes / (double) ONE_KB) + " kb";
-        else
-            return sizeBytes + " B";
-    }
+	public String displayBytes(long sizeBytes) {
+		NumberFormat nf = NumberFormat.getInstance();
+		nf.setMaximumFractionDigits(2);
+		if(sizeBytes >= ONE_TB)
+			return nf.format(sizeBytes / (double) ONE_TB) + " tb";
+		else if(sizeBytes >= ONE_GB)
+			return nf.format(sizeBytes / (double) ONE_GB) + " gb";
+		else if(sizeBytes >= ONE_MB)
+			return nf.format(sizeBytes / (double) ONE_MB) + " mb";
+		else if(sizeBytes >= ONE_KB)
+			return nf.format(sizeBytes / (double) ONE_KB) + " kb";
+		else
+			return sizeBytes + " B";
+	}
 }
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 678c288..5454c99 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -28,6 +28,9 @@
 		<script type="text/javascript" src="${context}/js/flowstats.js"></script>
 		<script type="text/javascript" src="${context}/js/flowstats-no-data.js"></script>
 
+		<script type="text/javascript" src="${context}/js/azkaban/view/flow-execution-list.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban/view/flow-execute-dialog.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban/view/flow-stats.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban/view/exflow.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index ddc9551..cc6940b 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -32,6 +32,8 @@
 		<script type="text/javascript" src="${context}/js/flowstats.js"></script>
 		
 		<script type="text/javascript" src="${context}/js/azkaban/view/time-graph.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban/view/flow-stats.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban/view/flow-execute-dialog.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban/view/flow.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
index 631ec0e..cd76ebd 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -21,9 +21,9 @@
 #parse ("azkaban/webapp/servlet/velocity/style.vm")
 #parse ("azkaban/webapp/servlet/velocity/javascript.vm")
 #parse ("azkaban/webapp/servlet/velocity/svgflowincludes.vm")
-		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
 		<script type="text/javascript" src="${context}/js/moment.min.js"></script>
 		<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban/view/flow-execute-dialog.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban/view/project.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban/view/project-modals.js"></script>
 		<script type="text/javascript">
@@ -37,6 +37,7 @@
 			var execAccess = ${exec};
 			var projectName = "$project.name";
 		</script>
+		<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
 	</head>
 	<body>
 
diff --git a/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm b/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
index 773416e..70a3ff6 100644
--- a/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/svgflowincludes.vm
@@ -18,21 +18,20 @@
 	<script type="text/javascript" src="${context}/js/jquery.svganim.min.js"></script> 
 	<script type="text/javascript" src="${context}/js/jquery.svgfilter.min.js"></script>
 	
-	<script type="text/javascript" src="${context}/js/azkaban/util/svgutils.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/view/flow-execute.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/util/flow-loader.js"></script>
 	<script type="text/javascript" src="${context}/js/azkaban/util/common.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>	
+	<script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
+	
+	<script type="text/javascript" src="${context}/js/azkaban/util/svgutils.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/util/layout.js"></script>
 	
 	<script type="text/javascript" src="${context}/js/azkaban/view/context-menu.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
 	<script type="text/javascript" src="${context}/js/azkaban/util/job-status.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/util/layout.js"></script>
 
-	<script type="text/javascript" src="${context}/js/azkaban/util/date.js"></script>	
-	<script type="text/javascript" src="${context}/js/azkaban/view/flow-stats.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/view/flow-job.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/view/flow-execution-list.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/util/flow-loader.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/view/job-list.js"></script>
+	<script type="text/javascript" src="${context}/js/azkaban/model/svg-graph.js"></script>
 	<script type="text/javascript" src="${context}/js/azkaban/view/svg-graph.js"></script>
-	<script type="text/javascript" src="${context}/js/azkaban/util/svg-navigate.js"></script>
 
-	<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
\ No newline at end of file
+	<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
diff --git a/src/web/js/azkaban/model/log-data.js b/src/web/js/azkaban/model/log-data.js
index 90a958f..958ee5d 100644
--- a/src/web/js/azkaban/model/log-data.js
+++ b/src/web/js/azkaban/model/log-data.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 $.namespace('azkaban');
 
 azkaban.LogDataModel = Backbone.Model.extend({
diff --git a/src/web/js/azkaban/model/svg-graph.js b/src/web/js/azkaban/model/svg-graph.js
new file mode 100644
index 0000000..c683997
--- /dev/null
+++ b/src/web/js/azkaban/model/svg-graph.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+$.namespace('azkaban');
+
+azkaban.GraphModel = Backbone.Model.extend({
+	initialize: function() {
+
+	},
+
+	/*
+	 * Process and add data from JSON.
+	 */
+	addFlow: function(data) {
+		this.processFlowData(data);
+		this.set({'data': data});
+	},
+		
+	processFlowData: function(data) {
+		var nodes = {};
+		var edges = new Array();
+
+		// Create a node map
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var node = data.nodes[i];
+			nodes[node.id] = node;
+			if (!node.status) {
+				node.status = "READY";
+			}
+		}
+
+		// Create each node in and out nodes. Create an edge list.
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var node = data.nodes[i];
+			if (node.in) {
+				for (var j = 0; j < node.in.length; ++j) {
+					var fromNode = nodes[node.in[j]];
+					if (!fromNode.outNodes) {
+						fromNode.outNodes = {};
+					}
+					if (!node.inNodes) {
+						node.inNodes = {};
+					}
+					
+					fromNode.outNodes[node.id] = node;
+					node.inNodes[fromNode.id] = fromNode;
+					edges.push({to: node.id, from: fromNode.id});
+				}
+			}
+		}
+
+		// Iterate over the nodes again. Parse the data if they're embedded flow data.
+		// Assign each nodes to the parent flow data.
+		for (var key in nodes) {
+			var node = nodes[key];
+			node.parent = data;
+			if (node.type == "flow") {
+				this.processFlowData(node);
+			}
+		}
+		
+		// Assign the node map and the edge list
+		data.nodeMap = nodes;
+		data.edges = edges;
+	}
+});
diff --git a/src/web/js/azkaban/util/flow-loader.js b/src/web/js/azkaban/util/flow-loader.js
index 97e447c..ee5ed8b 100644
--- a/src/web/js/azkaban/util/flow-loader.js
+++ b/src/web/js/azkaban/util/flow-loader.js
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 var statusStringMap = {
 	"FAILED": "Failed",
 	"SUCCEEDED": "Success",
@@ -57,57 +73,6 @@ var createNewPanel = function(node, model, evt) {
 	backboneView.showExtendedView(evt);
 }
 
-/**
- * Processes the flow data from Javascript
- */
-var processFlowData = function(data) {
-	var nodes = {};
-	var edges = new Array();
-
-	//Create a node map
-	for (var i=0; i < data.nodes.length; ++i) {
-		var node = data.nodes[i];
-		nodes[node.id] = node;
-		if (!node.status) {
-			node.status = "READY";
-		}
-	}
-
-	// Create each node in and out nodes. Create an edge list.
-	for (var i=0; i < data.nodes.length; ++i) {
-		var node = data.nodes[i];
-		if (node.in) {
-			for (var j=0; j < node.in.length; ++j) {
-				var fromNode = nodes[node.in[j]];
-				if (!fromNode.outNodes) {
-					fromNode.outNodes = {};
-				}
-				if (!node.inNodes) {
-					node.inNodes = {};
-				}
-				
-				fromNode.outNodes[node.id] = node;
-				node.inNodes[fromNode.id] = fromNode;
-				edges.push({to: node.id, from: fromNode.id});
-			}
-		}
-	}
-
-	// Iterate over the nodes again. Parse the data if they're embedded flow data.
-	// Assign each nodes to the parent flow data.
-	for (var key in nodes) {
-		var node = nodes[key];
-		node.parent = data;
-		if (node.type == "flow") {
-			processFlowData(node);
-		}
-	}
-	
-	// Assign the node map and the edge list
-	data.nodeMap = nodes;
-	data.edges = edges;
-}
-
 var closeAllSubDisplays = function() {
 	$(".flowExtendedView").hide();
 }
@@ -133,25 +98,25 @@ var nodeClickCallback = function(event, model, node) {
 		}
 
 		$.merge(menu, [
-		//		{title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
-				{break: 1},
-				{title: "Open Flow...", callback: function() {window.location.href=flowRequestURL;}},
-				{title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}},
-				{break: 1},
-				{title: "Open Properties...", callback: function() {window.location.href=requestURL;}},
-				{title: "Open Properties in New Window...", callback: function() {window.open(requestURL);}},
-				{break: 1},
-				{title: "Center Flow", callback: function() {model.trigger("centerNode", node);}}
+		//	{title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
+			{break: 1},
+			{title: "Open Flow...", callback: function() {window.location.href=flowRequestURL;}},
+			{title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}},
+			{break: 1},
+			{title: "Open Properties...", callback: function() {window.location.href=requestURL;}},
+			{title: "Open Properties in New Window...", callback: function() {window.open(requestURL);}},
+			{break: 1},
+			{title: "Center Flow", callback: function() {model.trigger("centerNode", node);}}
 		]);
 	}
 	else {
 		menu = [
-		//		{title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
-		//		{break: 1},
-				{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-				{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
-				{break: 1},
-				{title: "Center Job", callback: function() {model.trigger("centerNode", node)}}
+		//	{title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
+		//	{break: 1},
+			{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
+			{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+			{break: 1},
+			{title: "Center Job", callback: function() {model.trigger("centerNode", node)}}
 		];
 	}
 	contextMenuView.show(event, menu);
@@ -170,25 +135,25 @@ var jobClickCallback = function(event, model, node) {
 	if (type == "flow") {
 		var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + node.flowId;
 		menu = [
-		//		{title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
-		//		{break: 1},
-				{title: "Open Flow...", callback: function() {window.location.href=flowRequestURL;}},
-				{title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}},
-				{break: 1},
-				{title: "Open Properties...", callback: function() {window.location.href=requestURL;}},
-				{title: "Open Properties in New Window...", callback: function() {window.open(requestURL);}},
-				{break: 1},
-				{title: "Center Flow", callback: function() {model.trigger("centerNode", node)}}
+		//	{title: "View Properties...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
+		//	{break: 1},
+			{title: "Open Flow...", callback: function() {window.location.href=flowRequestURL;}},
+			{title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}},
+			{break: 1},
+			{title: "Open Properties...", callback: function() {window.location.href=requestURL;}},
+			{title: "Open Properties in New Window...", callback: function() {window.open(requestURL);}},
+			{break: 1},
+			{title: "Center Flow", callback: function() {model.trigger("centerNode", node)}}
 		];
 	}
 	else {
 		menu = [
-		//		{title: "View Job...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
-		//		{break: 1},
-				{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
-				{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
-				{break: 1},
-				{title: "Center Job", callback: function() {graphModel.trigger("centerNode", node)}}
+		//	{title: "View Job...", callback: function() {openJobDisplayCallback(jobId, flowId, event)}},
+		//	{break: 1},
+			{title: "Open Job...", callback: function() {window.location.href=requestURL;}},
+			{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+			{break: 1},
+			{title: "Center Job", callback: function() {graphModel.trigger("centerNode", node)}}
 		];
 	}
 	contextMenuView.show(event, menu);
diff --git a/src/web/js/azkaban/view/exflow.js b/src/web/js/azkaban/view/exflow.js
index eee5a1a..c495584 100644
--- a/src/web/js/azkaban/view/exflow.js
+++ b/src/web/js/azkaban/view/exflow.js
@@ -377,7 +377,6 @@ azkaban.StatsView = Backbone.View.extend({
 });
 
 var graphModel;
-azkaban.GraphModel = Backbone.Model.extend({});
 
 var logModel;
 azkaban.LogModel = Backbone.Model.extend({});
@@ -582,8 +581,7 @@ $(function() {
 	var requestData = {"execid": execId, "ajax":"fetchexecflow"};
 	var successHandler = function(data) {
 		console.log("data fetched");
-		processFlowData(data);
-		graphModel.set({data:data});
+		graphModel.addFlow(data);
 		graphModel.trigger("change:graph");
 		
 		updateTime = Math.max(updateTime, data.submitTime);
diff --git a/src/web/js/azkaban/view/flow.js b/src/web/js/azkaban/view/flow.js
index 14ee620..32ae40a 100644
--- a/src/web/js/azkaban/view/flow.js
+++ b/src/web/js/azkaban/view/flow.js
@@ -366,7 +366,7 @@ azkaban.SummaryView = Backbone.View.extend({
 });
 
 var graphModel;
-azkaban.GraphModel = Backbone.Model.extend({});
+var mainSvgGraphView;
 
 var executionModel;
 azkaban.ExecutionModel = Backbone.Model.extend({});
@@ -379,8 +379,6 @@ var flowStatsModel;
 
 var executionsTimeGraphView;
 
-var mainSvgGraphView;
-
 $(function() {
 	var selected;
 	// Execution model has to be created before the window switches the tabs.
@@ -452,8 +450,7 @@ $(function() {
 	};
 	var successHandler = function(data) {
 		console.log("data fetched");
-		processFlowData(data);
-		graphModel.set({data:data});
+		graphModel.addFlow(data);
 		graphModel.trigger("change:graph");
 		
 		// Handle the hash changes here so the graph finishes rendering first.
diff --git a/src/web/js/azkaban/view/flow-execution-list.js b/src/web/js/azkaban/view/flow-execution-list.js
index 0ec15a9..5a844a7 100644
--- a/src/web/js/azkaban/view/flow-execution-list.js
+++ b/src/web/js/azkaban/view/flow-execution-list.js
@@ -14,6 +14,10 @@
  * the License.
  */
 
+/*
+ * List of executing jobs on executing flow page.
+ */
+
 var executionListView;
 azkaban.ExecutionListView = Backbone.View.extend({
 	events: {
@@ -39,6 +43,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		var flowStartTime = data.startTime;
 		this.updateProgressBar(data, flowStartTime, flowLastTime);
 	},
+
 //
 //	handleProgressBoxClick: function(evt) {
 //		var target = evt.currentTarget;
@@ -61,7 +66,9 @@ azkaban.ExecutionListView = Backbone.View.extend({
 	
 	updateJobs: function(evt) {
 		var update = this.model.get("update");
-		var lastTime = update.endTime == -1 ? (new Date()).getTime() : update.endTime;
+		var lastTime = update.endTime == -1 
+				? (new Date()).getTime() 
+				: update.endTime;
 		var executingBody = $("#executableBody");
 		
 		if (update.nodes) {
@@ -69,17 +76,19 @@ azkaban.ExecutionListView = Backbone.View.extend({
 		}
 		
 		var data = this.model.get("data");
-		var flowLastTime = data.endTime == -1 ? (new Date()).getTime() : data.endTime;
+		var flowLastTime = data.endTime == -1 
+				? (new Date()).getTime() 
+				: data.endTime;
 		var flowStartTime = data.startTime;
 		this.updateProgressBar(data, flowStartTime, flowLastTime);
 	},
+
 	updateJobRow: function(nodes, body) {
 		if (!nodes) {
 			return;
 		}
 		
 		nodes.sort(function(a,b) { return a.startTime - b.startTime; });
-		
 		for (var i = 0; i < nodes.length; ++i) {
 			var node = nodes[i].changedNode ? nodes[i].changedNode : nodes[i];
 			
@@ -235,6 +244,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			}
 		}
 	},
+
 	toggleExpandFlow: function(flow) {
 		console.log("Toggle Expand");
 		var tr = flow.joblistrow;
@@ -256,6 +266,7 @@ azkaban.ExecutionListView = Backbone.View.extend({
 			$(subFlowRow).show();
 		}
 	},
+
 	addNodeRow: function(node, body) {
 		var self = this;
 		var tr = document.createElement("tr");
diff --git a/src/web/js/azkaban/view/main.js b/src/web/js/azkaban/view/main.js
index bf5e9a9..2ec3c5b 100644
--- a/src/web/js/azkaban/view/main.js
+++ b/src/web/js/azkaban/view/main.js
@@ -47,14 +47,14 @@ azkaban.ProjectTableView = Backbone.View.extend({
 				var expander = $(target).children('.project-expander-icon')[0];
 				$(expander).removeClass('glyphicon-chevron-up');
 				$(expander).addClass('glyphicon-chevron-down');
-				$(targetExpanded).slideUp();
+				$(targetExpanded).slideUp(300);
 			}
 			else {
 				$(target).addClass('collapsed').removeClass('expanded');
 				var expander = $(target).children('.project-expander-icon')[0];
 				$(expander).removeClass('glyphicon-chevron-down');
 				$(expander).addClass('glyphicon-chevron-up');
-				$(targetExpanded).slideDown();
+				$(targetExpanded).slideDown(300);
 			}
 		}
 		else {
@@ -78,7 +78,7 @@ azkaban.ProjectTableView = Backbone.View.extend({
 				var expander = $(target).children('.project-expander-icon')[0];
 				$(expander).removeClass('glyphicon-chevron-down');
 				$(expander).addClass('glyphicon-chevron-up');
-				$(targetExpanded).slideDown();
+				$(targetExpanded).slideDown(300);
 			};
 			
 			$.get(requestURL, request, successHandler, "json");
diff --git a/src/web/js/azkaban/view/svg-graph.js b/src/web/js/azkaban/view/svg-graph.js
index 6910edc..7e0d179 100644
--- a/src/web/js/azkaban/view/svg-graph.js
+++ b/src/web/js/azkaban/view/svg-graph.js
@@ -13,12 +13,17 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
+
+/*
+ * SVG graph view.
+ */
+
 $.namespace('azkaban');
 
 azkaban.SvgGraphView = Backbone.View.extend({
 	events: {
-		
 	},
+
 	initialize: function(settings) {
 		this.model.bind('change:selected', this.changeSelected, this);
 		this.model.bind('centerNode', this.centerNode, this);
@@ -74,6 +79,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			this.render();
 		}
 	},
+
 	render: function() {
 		console.log("graph render");
 		$(this.mainG).empty();
@@ -81,6 +87,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		this.graphBounds = this.renderGraph(this.model.get("data"), this.mainG);
 		this.resetPanZoom(0);
 	},
+
 	renderGraph: function(data, g) {
 		g.data = data;
 		var nodes = data.nodes;
@@ -160,17 +167,25 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		};
 		
-		$(".node").each( 
-				function(d,i){$(this).tooltip({container:self.tooltipcontainer, delay: {show: 500, hide: 100}});
+		$(".node").each(function(d,i) {
+			$(this).tooltip({
+				container: self.tooltipcontainer, 
+				delay: {
+					show: 500, 
+					hide: 100
+				}
+			});
 		});
 
 		return bounds;
 	},
+
 	handleDisabledChange: function(evt) {
 		this.changeDisabled(this.model.get('data'));
 	},
+
 	changeDisabled: function(data) {
-		for (var i =0; i < data.nodes.length; ++i) {
+		for (var i = 0; i < data.nodes.length; ++i) {
 			var node = data.nodes[i];
 			if (node.disabled) {
 				if (node.gNode) {
@@ -189,6 +204,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		}
 	},
+
 	assignInitialStatus: function(evt, data) {
 		for (var i = 0; i < data.nodes.length; ++i) {
 			var updateNode = data.nodes[i];
@@ -205,6 +221,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			$(g).attr("title", title);
 		}
 	},
+
 	changeSelected: function(self) {
 		console.log("change selected");
 		var selected = this.model.get("selected");
@@ -226,17 +243,20 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		}
 	},
+
 	propagateExpansion: function(node) {
 		if (node.parent.type) {
 			this.propagateExpansion(node.parent);
 			this.expandFlow(node.parent);
 		}
 	},
+
 	handleStatusUpdate: function(evt) {
 		var updateData = this.model.get("update");
 		var data = this.model.get("data");
 		this.updateStatusChanges(updateData, data);
 	},
+
 	updateStatusChanges: function(updateData, data) {
 		// Assumes all changes have been applied.
 		if (updateData.nodes) {
@@ -264,13 +284,14 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		}
 	},
+
   handleRemoveAllStatus: function(gNode) {
 		for (var j = 0; j < statusList.length; ++j) {
 			var status = statusList[j];
 			removeClass(gNode, status);
 		}
 	},
-	
+
   handleRightClick: function(self) {
 		if (this.rightClick) {
 			var callbacks = this.rightClick;
@@ -278,7 +299,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			if (callbacks.node && currentTarget.jobid) {
 				callbacks.node(self, this.model, currentTarget.nodeobj);
 			}
-			else if (callbacks.edge && (currentTarget.nodeName == "polyline" || currentTarget.nodeName == "line")) {
+			else if (callbacks.edge && 
+					(currentTarget.nodeName == "polyline" || 
+					 currentTarget.nodeName == "line")) {
 				callbacks.edge(self, this.model);
 			}
 			else if (callbacks.graph) {
@@ -286,9 +309,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 			return false;
 		}
-	
 		return true;
-	},	
+	},
+
 	drawEdge: function(self, edge, g) {
 		var svg = this.svg;
 		var svgns = self.svgns;
@@ -318,6 +341,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			edge.line.data = edge;
 		}
 	},
+
 	drawNode: function(self, node, g) {
 		if (node.type == 'flow') {
 			this.drawFlowNode(self, node, g);
@@ -326,6 +350,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			this.drawBoxNode(self, node, g);
 		}
 	},
+
 	moveNodes: function(nodes) {
 		var svg = this.svg;
 		for (var i = 0; i < nodes.length; ++i) {
@@ -335,6 +360,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			svg.change(gNode, {"transform": translateStr(node.x, node.y)});
 		}
 	},
+
 	expandFlow: function(node) {
 		var svg = this.svg;
 		var gnode = node.gNode;
@@ -376,6 +402,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		bounds.maxY = bounds.maxY ? bounds.maxY + margin : margin;
 		this.graphBounds = bounds;
 	},
+
 	collapseFlow: function(node) {
 		console.log("Collapsing flow");
 		var svg = this.svg;
@@ -403,8 +430,8 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		bounds.maxX = bounds.maxX ? bounds.maxX + margin : margin;
 		bounds.maxY = bounds.maxY ? bounds.maxY + margin : margin;
 		this.graphBounds = bounds;
-
 	},
+
 	relayoutFlow: function(node) {
 		if (node.expanded) {
 			this.layoutExpandedFlowNode(node);
@@ -419,9 +446,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			this.animateExpandedFlowNode(node, 250);
 		}
 	},
+
 	moveNodeEdges: function(nodes, edges) {
 		var svg = this.svg;
-		
 		for (var i = 0; i < nodes.length; ++i) {
 			var node = nodes[i];
 			var gNode = node.gNode;
@@ -452,11 +479,16 @@ azkaban.SvgGraphView = Backbone.View.extend({
 				edge.oldpoints = pointArray;
 			}
 			else {
-				$(line).animate({svgX1: startNode.x, svgY1: startPointY, svgX2: endNode.x, svgY2: endPointY});
+				$(line).animate({
+					svgX1: startNode.x, 
+					svgY1: startPointY, 
+					svgX2: endNode.x, 
+					svgY2: endPointY
+				});
 			}
 		}
-
 	},
+
 	calculateBounds: function(nodes) {
 		var bounds = {};
 		var node = nodes[0];
@@ -485,6 +517,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		
 		return bounds;
 	},
+
 	drawBoxNode: function(self, node, g) {
 		var svg = this.svg;
 		var horizontalMargin = 8;
@@ -511,6 +544,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		node.gNode = nodeG;
 		nodeG.data = node;
 	},
+
 	drawFlowNode: function(self, node, g) {
 		var svg = this.svg;
 		
@@ -529,7 +563,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		var jobNameText = svg.text(labelG, textOffset, 1, node.label);
 		var flowIdText = svg.text(labelG, textOffset, 11, node.flowId, {"font-size": 8})
 		var tempLabelG = labelG.getBBox();
-		var iconImage = svg.image(labelG, 0, -iconHeight/2, iconWidth, iconHeight, contextURL + "/images/graph-icon.png", {}); 
+		var iconImage = svg.image(
+				labelG, 0, -iconHeight/2, iconWidth, iconHeight, 
+				contextURL + "/images/graph-icon.png", {}); 
 
 		// Assign key values to make searching quicker
 		node.gNode=nodeG;
@@ -543,6 +579,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		// Layout everything in the node
 		this.layoutFlowNode(self, node);
 	},
+
 	layoutFlowNode: function(self, node) {
 		var svg = this.svg;
 		var horizontalMargin = 8;
@@ -566,6 +603,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		node.collapsedHeight = totalHeight;
 		node.collapsedWidth = totalWidth;
 	},
+
 	layoutExpandedFlowNode: function(node) {
 		var svg = this.svg;
 		var topmargin= 30, bottommargin=5;
@@ -585,6 +623,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		//$(innerG).animate({svgTransform: translateStr(-node.width/2, -node.height/2)}, 50);
 		//$(borderRect).animate({svgWidth: node.width, svgHeight: node.height}, 50);
 	},
+
 	animateExpandedFlowNode: function(node, time) {
 		var gNode = node.gNode;
 		var innerG = gNode.innerG;
@@ -594,26 +633,36 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		$(borderRect).animate({svgWidth: node.width, svgHeight: node.height}, time);
 		$(borderRect).animate({svgFill: 'white'}, time);
 	},
+
 	resetPanZoom: function(duration) {
 		var bounds = this.graphBounds;
-		var param = {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY), duration: duration };
+		var param = {
+			x: bounds.minX, 
+			y: bounds.minY, 
+			width: (bounds.maxX - bounds.minX), 
+			height: (bounds.maxY - bounds.minY), duration: duration
+		};
 
 		this.panZoom(param);
 	},
+
 	centerNode: function(node) {
 		// The magic of affine transformation. 
-		// Multiply the inverse root matrix with the current matrix to get the node position.
+		// Multiply the inverse root matrix with the current matrix to get the node 
+		// position.
 		// Rather do this than to traverse backwards through the scene graph.
 		var ctm = node.gNode.getCTM();
 		var transform = node.gNode.getTransformToElement();
 		var globalCTM = this.mainG.getCTM().inverse();
 		var otherTransform = globalCTM.multiply(ctm);
-		// Also a beauty of affine transformation. The translate is always the left most column of the matrix.
+		// Also a beauty of affine transformation. The translate is always the 
+		// left most column of the matrix.
 		var x = otherTransform.e - node.width/2;
 		var y = otherTransform.f - node.height/2;
 
 		this.panZoom({x: x, y: y, width: node.width, height: node.height});
 	},
+
 	globalNodePosition: function(gNode) {
 		if (node.parent) {
 		
@@ -624,6 +673,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			return {x: node.x, y: node.y};		
 		}
 	},
+
 	panZoom: function(params) {
 		params.maxScale = 2;
 		$(this.svgGraph).svgNavigate("transformToBox", params);