azkaban-developers

Details

diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index ba37274..6099b5c 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -366,7 +366,8 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 					break;
 				}
 			}
-		} catch (ScheduleManagerException e) {
+		} 
+    catch (ScheduleManagerException e) {
 			// TODO Auto-generated catch block
 			throw new ServletException(e);
 		}
@@ -395,7 +396,8 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		//project.info("Project removing by '" + user.getUserId() + "'");
 		try {
 			projectManager.removeProject(project, user);
-		} catch (ProjectManagerException e) {
+		} 
+    catch (ProjectManagerException e) {
 			this.setErrorMessageInCookie(resp, e.getMessage());
 			resp.sendRedirect(req.getRequestURI() + "?project=" + projectName);
 			return;
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index 3ac004b..c20fd66 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -23,8 +23,11 @@
 
 		<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/dust-core-2.2.2.min.js"></script>
+		
+    <script type="text/javascript" src="${context}/js/dust-core-2.2.2.min.js"></script>
 		<script type="text/javascript" src="${context}/js/flowsummary.js"></script>
+		<script type="text/javascript" src="${context}/js/flowscheduling.js"></script>
+
 		<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
@@ -108,6 +111,7 @@
 			<ul class="nav nav-tabs" id="headertabs">
 				<li id="graphViewLink"><a href="#graph">Graph</a></li>
 				<li id="executionsViewLink"><a href="#executions">Executions</a></li>
+				<li id="schedulingViewLink"><a href="#scheduling">Scheduling</a></li>
 				<li id="summaryViewLink"><a href="#summary">Summary</a></li>
 			</ul>
 					
@@ -145,12 +149,15 @@
 					</ul>
 				</div>
 			</div>
+
+  ## Scheduling view.
+
+      <div class="row" id="schedulingView">
+      </div>
 		
 	## Summary view.
 
-			<div class="row">
-				<div class="col-lg-12" id="summaryView">
-				</div>
+			<div class="row" id="summaryView">
 			</div><!-- /#summaryView -->
 
 			<div id="contextMenu">
diff --git a/src/less/bootstrap-azkaban.less b/src/less/bootstrap-azkaban.less
index f1afe32..3795278 100644
--- a/src/less/bootstrap-azkaban.less
+++ b/src/less/bootstrap-azkaban.less
@@ -26,11 +26,15 @@
 }
 
 // Flow summary.
-td.worksheet-key {
+td.flow-summary-key {
   width: 25%;
   font-weight: bold;
 }
 
+td.flow-summary-value {
+  width: 25%;
+}
+
 // Job table.
 #all-jobs {
   .tb-name {
diff --git a/src/tl/flowscheduling.tl b/src/tl/flowscheduling.tl
new file mode 100644
index 0000000..f8a01a1
--- /dev/null
+++ b/src/tl/flowscheduling.tl
@@ -0,0 +1,34 @@
+        <div class="col-lg-12">
+          <table class="table table-striped table-condensed table-bordered table-hover">
+            <thead>
+              <tr>
+                <!--th class="execid">Execution Id</th-->
+                <th>ID</th>
+                <th>Submitted By</th>
+                <th class="date">First Scheduled to Run</th>
+                <th class="date">Next Execution Time</th>
+                <th class="date">Repeats Every</th>
+                <th>Has SLA</th>
+                <th colspan="2" class="action ignoresort">Action</th>
+              </tr>
+            </thead>
+            <tbody>
+            {#schedules}
+              <tr>
+                <td>{scheduleId}</td>
+                <td>{submitUser}</td>
+                <td>{firstSchedTime}</td>
+                <td>{nextExecTime}</td>
+                <td>{period}</td>
+                <td>{?slaOptions} true {:else} false {/slaOptions}</td>
+                <td><button type="button" id="removeSchedBtn" class="btn btn-sm btn-danger" onclick="removeSched({scheduleId})" >Remove Schedule</button></td>
+                <td><button type="button" id="addSlaBtn" class="btn btn-sm btn-primary" onclick="slaView.initFromSched({scheduleId}, '{flowName}')" >Set SLA</button></td>
+              </tr>
+            {:else}
+              <tr>
+                <td colspan="8">No scheduled flow found.</td>
+              </tr>
+            {/schedules}
+            </tbody>
+          </table>
+        </div>

src/tl/flowsummary.tl 169(+46 -123)

diff --git a/src/tl/flowsummary.tl b/src/tl/flowsummary.tl
index c9f71d3..94b5a82 100644
--- a/src/tl/flowsummary.tl
+++ b/src/tl/flowsummary.tl
@@ -1,136 +1,59 @@
-          <div class="panel panel-default">
-						<table class="table table-striped table-bordered table-condensed table-hover">
-							<tbody>
-								<tr>
-									<td class="worksheet-key">Workflow name</td>
-									<td>{general.flowId}</td>
-									<td class="worksheet-key">Project name</td>
-									<td>{general.projectName}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Workflow Hadoop User Name</td>
-									<td>{general.user}</td>
-									<td class="worksheet-key">Job Types Used</td>
-									<td>{general.apis}</td>
-								</tr>
-							</tbody>
-						</table>
-					</div>
-
-					<div class="panel panel-default">
-						<div class="panel-heading">Scheduling</div>
-						<table class="table table-striped table-bordered table-condensed table-hover">
-							<tbody>
-								<tr>
-									<td class="worksheet-key">Recurrence</td>
-									<td>{scheduling.schedule}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Scheduled Time</td>
-									<td>{scheduling.launchTime}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Total Workflow Run Time (hours)</td>
-									<td>{scheduling.totalFlowTime}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Expected Time of Completion</td>
-									<td>{scheduling.expectedCompletionTime}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Max Permitted Delay</td>
-									<td>{scheduling.maxPermittedDelay}</td>
-								</tr>
-							</tbody>
-						</table>
-					</div>
+        <div class="col-lg-12">
+          <table class="table table-striped table-bordered table-condensed table-hover">
+            <tbody>
+              <tr>
+                <td class="flow-summary-key">Workflow name</td>
+                <td class="flow-summary-value">{general.flowId}</td>
+                <td class="flow-summary-key">Project name</td>
+                <td class="flow-summary-value">{general.projectName}</td>
+              </tr>
+              <tr>
+                <td class="flow-summary-key">Workflow Hadoop User Name</td>
+                <td class="flow-summary-value">{general.user}</td>
+                <td class="flow-summary-key">Job Types Used</td>
+                <td class="flow-summary-value">{general.apis}</td>
+              </tr>
+            </tbody>
+          </table>
 
           <div class="panel panel-default">
-            <div class="panel-heading">Performance</div>
+            <div class="panel-heading">Last Run Stats</div>
+            {?lastRun}
             <table class="table table-striped table-bordered table-condensed table-hover">
               <tbody>
 								<tr>
-									<td class="worksheet-key">Max Map Slots from Largest Job</td>
-									<td>{scheduling.maxMapSlots}</td>
+									<td class="flow-summary-key">Max Map Slots from Largest Job</td>
+									<td>{lastRun.maxMapSlots}</td>
+                </tr>
+                <tr>
+									<td class="flow-summary-key">Max Reduce Slots from Largest Job</td>
+									<td>{lastRun.maxReduceSlots}</td>
 								</tr>
 								<tr>
-									<td class="worksheet-key">Max Reduce Slots from Largest Job</td>
-									<td>{scheduling.maxReduceSlots}</td>
+									<td class="flow-summary-key">Total Map Slots from All Jobs</td>
+									<td>{lastRun.totalMapSlots}</td>
+                </tr>
+                <tr>
+									<td class="flow-summary-key">Total Reduce Slots from All Jobs</td>
+									<td>{lastRun.totalReduceSlots}</td>
 								</tr>
 								<tr>
-									<td class="worksheet-key">Total Reduce Slots from All Jobs</td>
-									<td>{scheduling.totalReduceSlots}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Total Number of Jobs</td>
-									<td>{scheduling.numJobs}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Longest Task Time</td>
-									<td>{scheduling.longestTaskTime}</td>
+									<td class="flow-summary-key">Total Number of Jobs</td>
+									<td>{lastRun.numJobs}</td>
+                </tr>
+                <tr>
+									<td class="flow-summary-key">Longest Task Time</td>
+									<td>{lastRun.longestTaskTime}</td>
 								</tr>
               </tbody>
             </table>
+            {:else}
+            <div class="panel-body">
+              <div class="alert alert-info">
+                <h4>No last run stats available</h4>
+                <p>Last run stats requires at least one successful run of the flow.</p>
+              </div>
+            </div>
+            {/lastRun}
           </div>
-
-					<div class="panel panel-default">
-						<div class="panel-heading">Resources</div>
-						<table class="table table-striped table-bordered table-condensed table-hover">
-							<tbody>
-								<tr>
-									<td class="worksheet-key">Is any of the code specifically multi-threaded?</td>
-									<td colspan="3">{resources.multithreaded}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Do you 'fat jar' any hadoop-core jars?</td>
-									<td colspan="3">{resources.fatJar}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Which job has the largest spill count?</td>
-									<td>{resources.largestSpill.job}</td>
-									<td class="worksheet-key">Largest spill count for any given task?</td>
-									<td>{resources.largestSpill.count}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Is there a distributed cache in use?</td>
-									<td>{resources.distributedCache.using}</td>
-									<td class="worksheet-key">How big is the distributed cache?</td>
-									<td>{resources.distributedCache.size}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Size of largest <code>-Xmx</code> value?</td>
-									<td>{resources.largestXmx.size}</td>
-									<td class="worksheet-key">If this is above 1G, please explain why</td>
-									<td>{resources.largestXmx.reason}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Do any jobs use <code>-Xms</code>?</td>
-									<td>{resources.xms.using}</td>
-									<td class="worksheet-key">If so, why?</td>
-									<td>{resources.xms.reason}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Is <em>intermediate compression</em> specifically turned on?</td>
-									<td>{resources.intermediateCompression.on}</td>
-									<td class="worksheet-key">If so, which codec</td>
-									<td>{resources.intermediateCompression.codec}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Are there combiners in use?</td>
-									<td colspan="3">{resources.combiners}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Size of largest <code>mapred.job.map.memory.mb</code></td>
-									<td>{resources.largestMapredJobMapMemoryMb.size}</td>
-									<td class="worksheet-key">Used by job</td>
-									<td>{resources.largestMapredJobMapMemoryMb.job}</td>
-								</tr>
-								<tr>
-									<td class="worksheet-key">Size of largest <code>mapred.job.reduce.memory.mb</code></td>
-									<td>{resources.largestMapredJobReduceMemoryMb.size}</td>
-									<td class="worksheet-key">Used by job</td>
-									<td>{resources.largestMapredJobMapMemoryMb.job}</td>
-								</tr>
-							</tbody>
-						</table>
-					</div>
+        </div><!-- /.col-lg-12 -->
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 8d8eb6e..9bb3d12 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -46,6 +46,7 @@ azkaban.FlowTabView = Backbone.View.extend({
 	events: {
 		"click #graphViewLink": "handleGraphLinkClick",
 		"click #executionsViewLink": "handleExecutionLinkClick",
+		"click #schedulingViewLink": "handleSchedulingLinkClick",
 		"click #summaryViewLink": "handleSummaryLinkClick"
 	},
 	
@@ -66,31 +67,49 @@ azkaban.FlowTabView = Backbone.View.extend({
 	handleGraphLinkClick: function(){
 		$("#executionsViewLink").removeClass("active");
 		$("#graphViewLink").addClass("active");
+		$('#schedulingViewLink').removeClass('active');
 		$('#summaryViewLink').removeClass('active');
 		
 		$("#executionsView").hide();
 		$("#graphView").show();
+		$('#schedulingView').hide();
 		$('#summaryView').hide();
 	},
 	
 	handleExecutionLinkClick: function() {
 		$("#graphViewLink").removeClass("active");
 		$("#executionsViewLink").addClass("active");
+		$('#schedulingViewLink').removeClass('active');
 		$('#summaryViewLink').removeClass('active');
 		
 		$("#graphView").hide();
 		$("#executionsView").show();
+		$('#schedulingView').hide();
 		$('#summaryView').hide();
 		executionModel.trigger("change:view");
 	},
 
-	handleSummaryLinkClick: function() {
+	handleSchedulingLinkClick: function() {
 		$('#graphViewLink').removeClass('active');
 		$('#executionsViewLink').removeClass('active');
+		$('#schedulingViewLink').addClass('active');
+		$('#summaryViewLink').removeClass('active');
+
+		$('#graphView').hide();
+		$('#executionsView').hide();
+		$('#schedulingView').show();
+		$('#summaryView').hide();
+	},
+	
+  handleSummaryLinkClick: function() {
+		$('#graphViewLink').removeClass('active');
+		$('#executionsViewLink').removeClass('active');
+		$('#schedulingViewLink').removeClass('active');
 		$('#summaryViewLink').addClass('active');
 
 		$('#graphView').hide();
 		$('#executionsView').hide();
+		$('#schedulingView').hide();
 		$('#summaryView').show();
 	},
 });
@@ -181,7 +200,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
 		var total = this.model.get("total");
 		total = total? total : 1;
 		var pageSize = this.model.get("pageSize");
-		var numPages = Math.ceil(total/pageSize);
+		var numPages = Math.ceil(total / pageSize);
 		
 		this.model.set({"numPages": numPages});
 		var page = this.model.get("page");
@@ -292,33 +311,65 @@ azkaban.ExecutionsView = Backbone.View.extend({
 	}
 });
 
+var schedulingView;
+azkaban.SchedulingView = Backbone.View.extend({
+	events: {
+	},
+	
+	initialize: function(settings) {
+		console.log("schedulingView initialize");
+		this.model.bind('change:view', this.handleChangeView, this);
+		this.model.bind('render', this.render, this);
+    var schedules = [
+      {
+        scheduleId: "0",
+        submitUser: "azkaban",
+        firstSchedTime: "1",
+        nextExecTime: "2",
+        period: "3",
+      }
+    ];
+
+    this.model.set({
+      'schedules': schedules
+    });
+    this.model.trigger('render');
+	},
+
+  handleChangeView: function(evt) {
+    console.log("schedulingView handleChangeView");
+  },
+
+	render: function(evt) {
+		console.log("schedulingView render");
+    var data = {
+      schedules: this.model.get('schedules')
+    };
+		dust.render("flowscheduling", data, function(err, out) {
+      console.log(err);
+			$('#schedulingView').html(out);
+		});
+	},
+});
+
 var summaryView;
 azkaban.SummaryView = Backbone.View.extend({
 	events: {
-		"click": "closeEditingTarget",
-		"click table .editable": "handleEditField"
 	},
 	
 	initialize: function(settings) {
 		console.log("summaryView initialize");
 		var general = {
-			flowName: "",
-			flowDescription: "",
 			projectName: projectName,
 			flowId: flowId
 		};
-
-		var scheduling = {};
-		var resources = {};
-		var io = {};
+    var lastRun = {};
 
 		this.model.bind('change:view', this.handleChangeView, this);
 		this.model.bind('render', this.render, this);
 		this.model.set({
 			'general': general,
-			'scheduling': scheduling,
-			'resources': resources,
-			'io': io
+			'lastRun': lastRun
 		});
 		this.model.trigger('render');
 	},
@@ -327,61 +378,11 @@ azkaban.SummaryView = Backbone.View.extend({
 		console.log("summaryView handleChangeView");
 	},
 
-	handleEditField: function(evt) {
-		var curTarget = evt.currentTarget;
-		console.log("summaryView handleEditField");
-		if (this.editingTarget != curTarget) {
-			this.closeEditingTarget(evt);
-
-			var text = $(curTarget).children('.spanValue').text();
-			$(curTarget).empty();
-
-			var input = document.createElement('input');
-			$(input).attr('type', 'text');
-			$(input).css('width', '100%');
-			$(input).val(text);
-
-			$(curTarget).addClass('editing');
-			$(curTarget).append(input);
-			$(input).focus();
-			var obj = this;
-			$(input).keypress(function(evt) {
-				if (evt.which == 13) {
-					obj.closeEditingTarget(evt);
-				}
-			});
-			this.editingTarget = curTarget;
-		}
-		evt.preventDefault();
-		evt.stopPropagation();
-	},
-
-	closeEditingTarget: function(evt) {
-		console.log("summaryView closeEditingTarget");
-		if (this.editingTarget != null &&
-				this.editingTarget != evt.target &&
-				this.editingTarget != evt.target.myparent) {
-			var input = $(this.editingTarget).children("input")[0];
-			var text = $(input).val();
-			$(input).remove();
-
-			var valueData = document.createElement("span");
-			$(valueData).addClass("spanValue");
-			$(valueData).text(text);
-
-			$(this.editingTarget).removeClass("editing");
-			$(this.editingTarget).append(valueData);
-			valueData.myparent = this.editingTarget;
-			this.editingTarget = null;
-		}
-	},
-	
 	render: function(evt) {
 		console.log("summaryView render");
 		var data = {
 			general: this.model.get('general'),
-			scheduling: this.model.get('scheduling'),
-			resources: this.model.get('resources')
+			lastRun: this.model.get('lastRun')
 		};
 		dust.render("flowsummary", data, function(err, out) {
 			$('#summaryView').html(out);
@@ -441,6 +442,9 @@ azkaban.GraphModel = Backbone.Model.extend({});
 var executionModel;
 azkaban.ExecutionModel = Backbone.Model.extend({});
 
+var schedulingModel;
+azkaban.SchedulingModel = Backbone.Model.extend({});
+
 var summaryModel;
 azkaban.SummaryModel = Backbone.Model.extend({});
 
@@ -454,12 +458,20 @@ $(function() {
 		el: $('#executionsView'), 
 		model: executionModel
 	});
-	summaryModel = new azkaban.SummaryModel();
+	
+  schedulingModel = new azkaban.SchedulingModel();
+	schedulingView = new azkaban.SchedulingView({
+		el: $('#schedulingView'),
+		model: schedulingModel
+	});
+
+  summaryModel = new azkaban.SummaryModel();
 	summaryView = new azkaban.SummaryView({
 		el: $('#summaryView'),
 		model: summaryModel
 	});
-	flowTabView = new azkaban.FlowTabView({
+	
+  flowTabView = new azkaban.FlowTabView({
 		el: $('#headertabs'), 
 		selectedView: selected 
 	});
@@ -474,7 +486,8 @@ $(function() {
 			"graph": exGraphClickCallback 
 		}
 	});
-	jobsListView = new azkaban.JobListView({
+	
+  jobsListView = new azkaban.JobListView({
 		el: $('#jobList'), 
 		model: graphModel, 
 		contextMenuCallback: exJobClickCallback