azkaban-developers

Details

diff --git a/src/tl/flowstats.tl b/src/tl/flowstats.tl
index ef24ad0..ffb5e2f 100644
--- a/src/tl/flowstats.tl
+++ b/src/tl/flowstats.tl
@@ -1,3 +1,4 @@
+      {?histogram}
       <div class="row">
         <div class="col-xs-12">
           <div class="well well-clear well-sm">
@@ -5,6 +6,7 @@
           </div>
         </div>
       </div>
+      {/histogram}
 
       <div class="row">
         <div class="col-xs-12">
diff --git a/src/web/js/azkaban/view/exflow.js b/src/web/js/azkaban/view/exflow.js
index 6129676..7c2548c 100644
--- a/src/web/js/azkaban/view/exflow.js
+++ b/src/web/js/azkaban/view/exflow.js
@@ -1,12 +1,12 @@
 /*
  * Copyright 2012 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
@@ -35,16 +35,16 @@ azkaban.StatusView = Backbone.View.extend({
 	},
 	render: function(evt) {
 		var data = this.model.get("data");
-		
+
 		var user = data.submitUser;
 		$("#submitUser").text(user);
-		
+
 		this.statusUpdate(evt);
 	},
-	
+
 	statusUpdate: function(evt) {
 		var data = this.model.get("data");
-		
+
 		statusItem = $("#flowStatus");
 		for (var j = 0; j < statusList.length; ++j) {
 			var status = statusList[j];
@@ -52,27 +52,27 @@ azkaban.StatusView = Backbone.View.extend({
 		}
 		$("#flowStatus").addClass(data.status);
 		$("#flowStatus").text(data.status);
-		
+
 		var startTime = data.startTime;
 		var endTime = data.endTime;
-		
+
 		if (!startTime || startTime == -1) {
 			$("#startTime").text("-");
 		}
 		else {
 			var date = new Date(startTime);
 			$("#startTime").text(getDateFormat(date));
-			
+
 			var lastTime = endTime;
 			if (endTime == -1) {
 				var currentDate = new Date();
 				lastTime = currentDate.getTime();
 			}
-			
+
 			var durationString = getDuration(startTime, lastTime);
 			$("#duration").text(durationString);
 		}
-		
+
 		if (!endTime || endTime == -1) {
 			$("#endTime").text("-");
 		}
@@ -96,17 +96,17 @@ azkaban.FlowTabView = Backbone.View.extend({
 		"click #resumebtn": "handleResumeClick",
 		"click #retrybtn": "handleRetryClick"
 	},
-	
+
 	initialize: function(settings) {
 		$("#cancelbtn").hide();
 		$("#executebtn").hide();
 		$("#pausebtn").hide();
 		$("#resumebtn").hide();
 		$("#retrybtn").hide();
-	
+
 		this.model.bind('change:graph', this.handleFlowStatusChange, this);
 		this.model.bind('change:update', this.handleFlowStatusChange, this);
-	
+
 		var selectedView = settings.selectedView;
 		if (selectedView == "jobslist") {
 			this.handleJobslistLinkClick();
@@ -115,60 +115,60 @@ azkaban.FlowTabView = Backbone.View.extend({
 			this.handleGraphLinkClick();
 		}
 	},
-	
+
 	render: function() {
 		console.log("render graph");
 	},
-	
+
 	handleGraphLinkClick: function(){
 		$("#jobslistViewLink").removeClass("active");
 		$("#graphViewLink").addClass("active");
 		$("#flowLogViewLink").removeClass("active");
 		$("#statsViewLink").removeClass("active");
-		
+
 		$("#jobListView").hide();
 		$("#graphView").show();
 		$("#flowLogView").hide();
 		$("#statsView").hide();
 	},
-	
+
 	handleJobslistLinkClick: function() {
 		$("#graphViewLink").removeClass("active");
 		$("#jobslistViewLink").addClass("active");
 		$("#flowLogViewLink").removeClass("active");
 		$("#statsViewLink").removeClass("active");
-		
+
 		$("#graphView").hide();
 		$("#jobListView").show();
 		$("#flowLogView").hide();
 		$("#statsView").hide();
 	},
-	
+
 	handleLogLinkClick: function() {
 		$("#graphViewLink").removeClass("active");
 		$("#jobslistViewLink").removeClass("active");
 		$("#flowLogViewLink").addClass("active");
 		$("#statsViewLink").removeClass("active");
-		
+
 		$("#graphView").hide();
 		$("#jobListView").hide();
 		$("#flowLogView").show();
 		$("#statsView").hide();
 	},
-	
+
   handleStatsLinkClick: function() {
 		$("#graphViewLink").removeClass("active");
 		$("#jobslistViewLink").removeClass("active");
 		$("#flowLogViewLink").removeClass("active");
 		$("#statsViewLink").addClass("active");
-		
+
 		$("#graphView").hide();
 		$("#jobListView").hide();
 		$("#flowLogView").hide();
     statsView.show();
 		$("#statsView").show();
 	},
-	
+
 	handleFlowStatusChange: function() {
 		var data = this.model.get("data");
 		$("#cancelbtn").hide();
@@ -203,7 +203,7 @@ azkaban.FlowTabView = Backbone.View.extend({
 			$("#executebtn").show();
 		}
 	},
-	
+
 	handleCancelClick: function(evt) {
 		var requestURL = contextURL + "/executor";
 		var requestData = {"execid": execId, "ajax": "cancelFlow"};
@@ -219,7 +219,7 @@ azkaban.FlowTabView = Backbone.View.extend({
 		};
 		ajaxCall(requestURL, requestData, successHandler);
 	},
-	
+
 	handleRetryClick: function(evt) {
 		var graphData = graphModel.get("data");
 		var requestURL = contextURL + "/executor";
@@ -236,11 +236,11 @@ azkaban.FlowTabView = Backbone.View.extend({
 		};
 		ajaxCall(requestURL, requestData, successHandler);
 	},
-	
+
 	handleRestartClick: function(evt) {
 		console.log("handleRestartClick");
 		var data = graphModel.get("data");
-		
+
 		var executingData = {
 			project: projectName,
 			ajax: "executeFlow",
@@ -250,7 +250,7 @@ azkaban.FlowTabView = Backbone.View.extend({
 		};
 		flowExecuteDialogView.show(executingData);
 	},
-	
+
 	handlePauseClick: function(evt) {
 		var requestURL = contextURL + "/executor";
 		var requestData = {"execid": execId, "ajax":"pauseFlow"};
@@ -266,7 +266,7 @@ azkaban.FlowTabView = Backbone.View.extend({
 		};
 		ajaxCall(requestURL, requestData, successHandler);
 	},
-	
+
 	handleResumeClick: function(evt) {
 		var requestURL = contextURL + "/executor";
 		var requestData = {"execid": execId, "ajax":"resumeFlow"};
@@ -304,17 +304,17 @@ azkaban.FlowLogView = Backbone.View.extend({
 	},
 	handleUpdate: function(evt) {
 		var offset = this.model.get("offset");
-		var requestURL = contextURL + "/executor"; 
+		var requestURL = contextURL + "/executor";
 		var model = this.model;
 		console.log("fetchLogs offset is " + offset)
 
 		$.ajax({
-			async: false, 
+			async: false,
 			url: requestURL,
 			data: {
-				"execid": execId, 
-				"ajax": "fetchExecFlowLogs", 
-				"offset": offset, 
+				"execid": execId,
+				"ajax": "fetchExecFlowLogs",
+				"offset": offset,
 				"length": 50000
 			},
 			success: function(data) {
@@ -346,7 +346,7 @@ var statsView;
 azkaban.StatsView = Backbone.View.extend({
 	events: {
 	},
-	
+
   initialize: function(settings) {
     this.model.bind('change:graph', this.statusUpdate, this);
     this.model.bind('change:update', this.statusUpdate, this);
@@ -385,22 +385,22 @@ var updateStatus = function(updateTime) {
 	var requestURL = contextURL + "/executor";
 	var oldData = graphModel.get("data");
 	var nodeMap = graphModel.get("nodeMap");
-	
+
 	if (!updateTime) {
 		updateTime = oldData.updateTime ? oldData.updateTime : 0;
 	}
 
 	var requestData = {
-		"execid": execId, 
-		"ajax": "fetchexecflowupdate", 
+		"execid": execId,
+		"ajax": "fetchexecflowupdate",
 		"lastUpdateTime": updateTime
 	};
-	
+
 	var successHandler = function(data) {
 		console.log("data updated");
 		if (data.updateTime) {
 			updateGraph(oldData, data);
-	
+
 			graphModel.set({"update": data});
 			graphModel.trigger("change:update");
 		}
@@ -415,12 +415,12 @@ var updateGraph = function(data, update) {
 	data.updateTime = update.updateTime;
 	data.status = update.status;
 	update.changedNode = data;
-	
+
 	if (update.nodes) {
 		for (var i = 0; i < update.nodes.length; ++i) {
 			var newNode = update.nodes[i];
 			var oldNode = nodeMap[newNode.id];
-			
+
 			updateGraph(oldNode, newNode);
 		}
 	}
@@ -429,17 +429,17 @@ var updateGraph = function(data, update) {
 var updateTime = -1;
 var updaterFunction = function() {
 	var oldData = graphModel.get("data");
-	var keepRunning = 
-			oldData.status != "SUCCEEDED" && 
-			oldData.status != "FAILED" && 
+	var keepRunning =
+			oldData.status != "SUCCEEDED" &&
+			oldData.status != "FAILED" &&
 			oldData.status != "KILLED";
 
 	if (keepRunning) {
 		updateStatus();
 
 		var data = graphModel.get("data");
-		if (data.status == "UNKNOWN" || 
-			data.status == "WAITING" || 
+		if (data.status == "UNKNOWN" ||
+			data.status == "WAITING" ||
 			data.status == "PREPARING") {
 			setTimeout(function() {updaterFunction();}, 1000);
 		}
@@ -459,9 +459,9 @@ var updaterFunction = function() {
 
 var logUpdaterFunction = function() {
 	var oldData = graphModel.get("data");
-	var keepRunning = 
-			oldData.status != "SUCCEEDED" && 
-			oldData.status != "FAILED" && 
+	var keepRunning =
+			oldData.status != "SUCCEEDED" &&
+			oldData.status != "FAILED" &&
 			oldData.status != "KILLED";
 	if (keepRunning) {
 		// update every 30 seconds for the logs until finished
@@ -479,7 +479,7 @@ var exNodeClickCallback = function(event) {
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
 	var visualizerURL = contextURL + "/pigvisualizer?execid=" + execId + "&jobid=" + jobId;
 
-	var menu = [	
+	var menu = [
 		{title: "Open Job...", callback: function() {window.location.href = requestURL;}},
 		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
 		{title: "Visualize Job...", callback: function() {window.location.href = visualizerURL;}}
@@ -494,7 +494,7 @@ var exJobClickCallback = function(event) {
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
 	var visualizerURL = contextURL + "/pigvisualizer?execid=" + execId + "&jobid=" + jobId;
 
-	var menu = [	
+	var menu = [
 		{title: "Open Job...", callback: function() {window.location.href = requestURL;}},
 		{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
 		{title: "Visualize Job...", callback: function() {window.location.href = visualizerURL;}}
@@ -511,13 +511,13 @@ var exGraphClickCallback = function(event) {
 	console.log("Graph clicked callback");
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
 
-	var menu = [	
+	var menu = [
 		{title: "Open Flow...", callback: function() {window.location.href=requestURL;}},
 		{title: "Open Flow in New Window...", callback: function() {window.open(requestURL);}},
 		{break: 1},
 		{title: "Center Graph", callback: function() {graphModel.trigger("resetPanZoom");}}
 	];
-	
+
 	contextMenuView.show(event, menu);
 }
 
@@ -526,68 +526,69 @@ var flowStatsModel;
 
 $(function() {
 	var selected;
-	
+
 	graphModel = new azkaban.GraphModel();
 	logModel = new azkaban.LogModel();
-	
+
 	flowTabView = new azkaban.FlowTabView({
-		el: $('#headertabs'), 
+		el: $('#headertabs'),
 		model: graphModel
 	});
-	
+
 	mainSvgGraphView = new azkaban.SvgGraphView({
-		el: $('#svgDiv'), 
-		model: graphModel, 
-		rightClick:	{ 
-			"node": nodeClickCallback, 
-			"edge": edgeClickCallback, 
-			"graph": graphClickCallback 
+		el: $('#svgDiv'),
+		model: graphModel,
+		rightClick:	{
+			"node": nodeClickCallback,
+			"edge": edgeClickCallback,
+			"graph": graphClickCallback
 		}
 	});
-	
+
 	jobsListView = new azkaban.JobListView({
-		el: $('#joblist-panel'), 
-		model: graphModel, 
+		el: $('#joblist-panel'),
+		model: graphModel,
 		contextMenuCallback: jobClickCallback
 	});
-	
+
 	flowLogView = new azkaban.FlowLogView({
-		el: $('#flowLogView'), 
+		el: $('#flowLogView'),
 		model: logModel
 	});
-	
+
 	statusView = new azkaban.StatusView({
-		el: $('#flow-status'), 
+		el: $('#flow-status'),
 		model: graphModel
 	});
-  
+
   flowStatsModel = new azkaban.FlowStatsModel();
 	flowStatsView = new azkaban.FlowStatsView({
 		el: $('#flow-stats-container'),
-		model: flowStatsModel
+		model: flowStatsModel,
+    histogram: false
 	});
 
   statsView = new azkaban.StatsView({
-		el: $('#statsView'), 
+		el: $('#statsView'),
 		model: graphModel
 	});
-	
+
 	executionListView = new azkaban.ExecutionListView({
-		el: $('#jobListView'), 
+		el: $('#jobListView'),
 		model: graphModel
 	});
-	
+
 	var requestURL = contextURL + "/executor";
 	var requestData = {"execid": execId, "ajax":"fetchexecflow"};
 	var successHandler = function(data) {
 		console.log("data fetched");
 		graphModel.addFlow(data);
 		graphModel.trigger("change:graph");
-		
+
 		updateTime = Math.max(updateTime, data.submitTime);
 		updateTime = Math.max(updateTime, data.startTime);
 		updateTime = Math.max(updateTime, data.endTime);
-		
+
 		if (window.location.hash) {
 			var hash = window.location.hash;
 			if (hash == "#jobslist") {
diff --git a/src/web/js/azkaban/view/flow.js b/src/web/js/azkaban/view/flow.js
index 3934055..95aba17 100644
--- a/src/web/js/azkaban/view/flow.js
+++ b/src/web/js/azkaban/view/flow.js
@@ -1,12 +1,12 @@
 /*
  * Copyright 2012 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
@@ -18,7 +18,7 @@ $.namespace('azkaban');
 
 var handleJobMenuClick = function(action, el, pos) {
 	var jobid = el[0].jobid;
-	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + 
+	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" +
 			flowId + "&job=" + jobid;
 	if (action == "open") {
 		window.location.href = requestURL;
@@ -35,7 +35,7 @@ azkaban.FlowTabView = Backbone.View.extend({
 		"click #executionsViewLink": "handleExecutionLinkClick",
 		"click #summaryViewLink": "handleSummaryLinkClick"
 	},
-	
+
 	initialize: function(settings) {
 		var selectedView = settings.selectedView;
 		if (selectedView == "executions") {
@@ -45,26 +45,26 @@ azkaban.FlowTabView = Backbone.View.extend({
 			this.handleGraphLinkClick();
 		}
 	},
-	
+
 	render: function() {
 		console.log("render graph");
 	},
-	
+
 	handleGraphLinkClick: function(){
 		$("#executionsViewLink").removeClass("active");
 		$("#graphViewLink").addClass("active");
 		$('#summaryViewLink').removeClass('active');
-		
+
 		$("#executionsView").hide();
 		$("#graphView").show();
 		$('#summaryView').hide();
 	},
-	
+
 	handleExecutionLinkClick: function() {
 		$("#graphViewLink").removeClass("active");
 		$("#executionsViewLink").addClass("active");
 		$('#summaryViewLink').removeClass('active');
-		
+
 		$("#graphView").hide();
 		$("#executionsView").show();
 		$('#summaryView').hide();
@@ -90,35 +90,35 @@ azkaban.ExecutionsView = Backbone.View.extend({
 	events: {
 		"click #pageSelection li": "handleChangePageSelection"
 	},
-	
+
 	initialize: function(settings) {
 		this.model.bind('change:view', this.handleChangeView, this);
 		this.model.bind('render', this.render, this);
 		this.model.set({page: 1, pageSize: 16});
 		this.model.bind('change:page', this.handlePageChange, this);
 	},
-	
+
 	render: function(evt) {
 		console.log("render");
 		// Render page selections
 		var tbody = $("#execTableBody");
 		tbody.empty();
-		
+
 		var executions = this.model.get("executions");
 		for (var i = 0; i < executions.length; ++i) {
 			var row = document.createElement("tr");
-			
+
 			var tdId = document.createElement("td");
 			var execA = document.createElement("a");
 			$(execA).attr("href", contextURL + "/executor?execid=" + executions[i].execId);
 			$(execA).text(executions[i].execId);
 			tdId.appendChild(execA);
 			row.appendChild(tdId);
-			
+
 			var tdUser = document.createElement("td");
 			$(tdUser).text(executions[i].submitUser);
 			row.appendChild(tdUser);
-			
+
 			var startTime = "-";
 			if (executions[i].startTime != -1) {
 				var startDateTime = new Date(executions[i].startTime);
@@ -128,7 +128,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
 			var tdStartTime = document.createElement("td");
 			$(tdStartTime).text(startTime);
 			row.appendChild(tdStartTime);
-			
+
 			var endTime = "-";
 			var lastTime = executions[i].endTime;
 			if (executions[i].endTime != -1) {
@@ -142,11 +142,11 @@ azkaban.ExecutionsView = Backbone.View.extend({
 			var tdEndTime = document.createElement("td");
 			$(tdEndTime).text(endTime);
 			row.appendChild(tdEndTime);
-			
+
 			var tdElapsed = document.createElement("td");
 			$(tdElapsed).text( getDuration(executions[i].startTime, lastTime));
 			row.appendChild(tdElapsed);
-			
+
 			var tdStatus = document.createElement("td");
 			var status = document.createElement("div");
 			$(status).addClass("status");
@@ -160,22 +160,22 @@ azkaban.ExecutionsView = Backbone.View.extend({
 
 			tbody.append(row);
 		}
-		
+
 		this.renderPagination(evt);
 	},
-	
+
 	renderPagination: function(evt) {
 		var total = this.model.get("total");
 		total = total? total : 1;
 		var pageSize = this.model.get("pageSize");
 		var numPages = Math.ceil(total / pageSize);
-		
+
 		this.model.set({"numPages": numPages});
 		var page = this.model.get("page");
-		
+
 		//Start it off
 		$("#pageSelection .active").removeClass("active");
-		
+
 		// Disable if less than 5
 		console.log("Num pages " + numPages)
 		var i = 1;
@@ -185,7 +185,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		for (; i <= 5; ++i) {
 			$("#page" + i).addClass("disabled");
 		}
-		
+
 		// Disable prev/next if necessary.
 		if (page > 1) {
 			$("#previous").removeClass("disabled");
@@ -195,7 +195,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		else {
 			$("#previous").addClass("disabled");
 		}
-		
+
 		if (page < numPages) {
 			$("#next")[0].page = page + 1;
 			$("#next").removeClass("disabled");
@@ -205,7 +205,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
 			$("#next")[0].page = page + 1;
 			$("#next").addClass("disabled");
 		}
-		
+
 		// Selection is always in middle unless at barrier.
 		var startPage = 0;
 		var selectionPosition = 0;
@@ -235,14 +235,14 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		for (var j = 0; j < 5; ++j) {
 			var realPage = startPage + j;
 			var elementId = "#page" + (j+1);
-			
+
 			$(elementId)[0].page = realPage;
 			var a = $(elementId + " a");
 			a.text(realPage);
 			a.attr("href", "#page" + realPage);
 		}
 	},
-	
+
 	handleChangePageSelection: function(evt) {
 		if ($(evt.currentTarget).hasClass("disabled")) {
 			return;
@@ -250,7 +250,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		var page = evt.currentTarget.page;
 		this.model.set({"page": page});
 	},
-	
+
 	handleChangeView: function(evt) {
 		if (this.init) {
 			return;
@@ -259,23 +259,23 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		this.handlePageChange(evt);
 		this.init = true;
 	},
-	
+
 	handlePageChange: function(evt) {
 		var page = this.model.get("page") - 1;
 		var pageSize = this.model.get("pageSize");
 		var requestURL = contextURL + "/manager";
-		
+
 		var model = this.model;
 		var requestData = {
-			"project": projectName, 
-			"flow": flowId, 
-			"ajax": "fetchFlowExecutions", 
-			"start": page * pageSize, 
+			"project": projectName,
+			"flow": flowId,
+			"ajax": "fetchFlowExecutions",
+			"start": page * pageSize,
 			"length": pageSize
 		};
 		var successHandler = function(data) {
 			model.set({
-				"executions": data.executions, 
+				"executions": data.executions,
 				"total": data.total
 			});
 			model.trigger("render");
@@ -289,11 +289,11 @@ azkaban.SummaryView = Backbone.View.extend({
 	events: {
     'click #analyze-btn': 'fetchLastRun'
 	},
-	
+
 	initialize: function(settings) {
 		this.model.bind('change:view', this.handleChangeView, this);
 		this.model.bind('render', this.render, this);
-		
+
 		this.fetchDetails();
 		this.fetchSchedule();
 		this.model.trigger('render');
@@ -306,7 +306,7 @@ azkaban.SummaryView = Backbone.View.extend({
 			'project': projectName,
 			'flow': flowId
 		};
-		
+
 		var model = this.model;
 
 		var successHandler = function(data) {
@@ -318,7 +318,7 @@ azkaban.SummaryView = Backbone.View.extend({
 		};
 		$.get(requestURL, requestData, successHandler, 'json');
 	},
-	
+
   fetchSchedule: function() {
 		var requestURL = contextURL + "/schedule"
 		var requestData = {
@@ -390,10 +390,10 @@ $(function() {
 	// Execution model has to be created before the window switches the tabs.
 	executionModel = new azkaban.ExecutionModel();
 	executionsView = new azkaban.ExecutionsView({
-		el: $('#executionsView'), 
+		el: $('#executionsView'),
 		model: executionModel
 	});
-	
+
   summaryModel = new azkaban.SummaryModel();
 	summaryView = new azkaban.SummaryView({
 		el: $('#summaryView'),
@@ -407,35 +407,35 @@ $(function() {
 	});
 
   flowTabView = new azkaban.FlowTabView({
-		el: $('#headertabs'), 
-		selectedView: selected 
+		el: $('#headertabs'),
+		selectedView: selected
 	});
 
 	graphModel = new azkaban.GraphModel();
 	mainSvgGraphView = new azkaban.SvgGraphView({
-		el: $('#svgDiv'), 
-		model: graphModel, 
-		rightClick: { 
-			"node": nodeClickCallback, 
-			"edge": edgeClickCallback, 
-			"graph": graphClickCallback 
+		el: $('#svgDiv'),
+		model: graphModel,
+		rightClick: {
+			"node": nodeClickCallback,
+			"edge": edgeClickCallback,
+			"graph": graphClickCallback
 		}
 	});
-	
+
   jobsListView = new azkaban.JobListView({
-		el: $('#joblist-panel'), 
-		model: graphModel, 
+		el: $('#joblist-panel'),
+		model: graphModel,
 		contextMenuCallback: jobClickCallback
 	});
-	
+
   executionsTimeGraphView = new azkaban.TimeGraphView({
-		el: $('#timeGraph'), 
+		el: $('#timeGraph'),
 		model: executionModel,
     modelField: 'executions'
 	});
-	
+
 	slaView = new azkaban.ChangeSlaView({el:$('#sla-options')});
-	
+
 	var requestURL = contextURL + "/manager";
 	// Set up the Flow options view. Create a new one every time :p
 	$('#executebtn').click(function() {
@@ -451,15 +451,15 @@ $(function() {
 	});
 
 	var requestData = {
-		"project": projectName, 
-		"ajax": "fetchflowgraph", 
+		"project": projectName,
+		"ajax": "fetchflowgraph",
 		"flow": flowId
 	};
 	var successHandler = function(data) {
 		console.log("data fetched");
 		graphModel.addFlow(data);
 		graphModel.trigger("change:graph");
-		
+
 		// Handle the hash changes here so the graph finishes rendering first.
 		if (window.location.hash) {
 			var hash = window.location.hash;
@@ -470,7 +470,7 @@ $(function() {
 				flowTabView.handleSummaryLinkClick();
 			}
 			else if (hash == "#graph") {
-				// Redundant, but we may want to change the default. 
+				// Redundant, but we may want to change the default.
 				selected = "graph";
 			}
 			else {
diff --git a/src/web/js/azkaban/view/flow-stats.js b/src/web/js/azkaban/view/flow-stats.js
index 3aee5be..dcf5985 100644
--- a/src/web/js/azkaban/view/flow-stats.js
+++ b/src/web/js/azkaban/view/flow-stats.js
@@ -21,9 +21,14 @@ azkaban.FlowStatsView = Backbone.View.extend({
   events: {
   },
 
+  histogram: true,
+
 	initialize: function(settings) {
 		this.model.bind('change:view', this.handleChangeView, this);
 		this.model.bind('render', this.render, this);
+    if (settings.histogram != null) {
+      this.histogram = settings.histogram;
+    }
   },
 
   render: function(evt) {
@@ -38,6 +43,9 @@ azkaban.FlowStatsView = Backbone.View.extend({
     var requestData = {"execid": execId, "ajax":"fetchexecflow"};
     var jobs = [];
     var successHandler = function(data) {
+      data.nodes.sort(function(a, b) {
+        return a.startTime - b.startTime;
+      });
       jobs = data.nodes;
     };
     $.ajax({
@@ -217,6 +225,7 @@ azkaban.FlowStatsView = Backbone.View.extend({
       message: null,
       warnings: [],
       durations: [],
+      histogram: this.histogram,
       stats: {
         mapSlots: {
           max: 0,
@@ -310,15 +319,24 @@ azkaban.FlowStatsView = Backbone.View.extend({
       });
     }
     else {
+      var histogram = this.histogram;
       dust.render("flowstats", data, function(err, out) {
         view.display(out);
-        Morris.Bar({
-          element: "job-histogram",
-          data: data.durations,
-          xkey: "job",
-          ykeys: ["duration"],
-          labels: ["Duration"]
-        });
+        if (histogram == true) {
+          var yLabelFormatCallback = function(y) {
+            var seconds = y / 1000.0;
+            return seconds.toString() + " s";
+          };
+
+          Morris.Bar({
+            element: "job-histogram",
+            data: data.durations,
+            xkey: "job",
+            ykeys: ["duration"],
+            labels: ["Duration"],
+            yLabelFormat: yLabelFormatCallback
+          });
+        }
       });
     }
   },