azkaban-uncached
Changes
src/web/css/azkaban.css 94(+89 -5)
src/web/js/azkaban.context.menu.js 103(+87 -16)
src/web/js/azkaban.flow.execute.view.js 148(+52 -96)
Details
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index f83b49c..db1eaff 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -107,7 +107,7 @@ public class ExecutorManager {
public boolean isFlowRunning(int projectId, String flowId) {
for (Pair<ExecutionReference, ExecutableFlow> ref : runningFlows.values()) {
- if (ref.getSecond().getFlowId().equals(flowId)) {
+ if (ref.getSecond().getProjectId() == projectId && ref.getSecond().getFlowId().equals(flowId)) {
return true;
}
}
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
index 9319c46..c29cab7 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -1,5 +1,6 @@
<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
+<script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.flow.execute.view.js"></script>
@@ -13,36 +14,107 @@
<div class="panel">
<div id="executionGraphOptions">
<div id="graphOptions" class="sideMenu">
- <h3>Jobs</h3>
+ <h3 id="flowOption" viewpanel="svgDivCustom">Flow View</h3>
<div>
- <p>Jobs Panel</p>
+ <p>Right click on the jobs to disable and enable jobs in the flow.</p>
</div>
- <h3>Notification</h3>
+ <h3 viewpanel="notificationPanel">Notification</h3>
<div>
- <p>Jobs Panel</p>
+ <p>Change the addresses where success and failure emails will be sent.</p>
</div>
- <h3>Concurrent Runs</h3>
+ <h3 viewpanel="failureOptions">Failure Options</h3>
<div>
- <p>Jobs Panel</p>
+ <p>Select flow behavior when a failure is detected.</p>
</div>
- <h3>SLA</h3>
+ <h3 viewpanel="concurrentPanel">Concurrent</h3>
<div>
- <p>Jobs Panel</p>
+ <p>Change the behavior of the flow if it is already running.</p>
</div>
- <h3>Flow Parameters</h3>
+ <h3 viewpanel="slaPanel">SLA</h3>
<div>
- <p>Jobs Panel</p>
+ <p>Add service level agreements to notify or kill the flow if it exceeds certain runtime duration.</p>
+ </div>
+ <h3 viewpanel="flowParametersPanel">Flow Parameters</h3>
+ <div>
+ <p>Add temporary flow parameters that are used to override global properties for each job.</p>
</div>
</div>
</div>
- <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 id="executionGraphOptionsPanel" class="rightPanel">
+ <div id="svgDivCustom" class="svgDiv sidePanel" >
+ <svg class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
+ </svg>
+ </div>
+ <div id="notificationPanel" class="sidePanel">
+ <div>
+ <h4>Notify on Failure</h4>
+ <p>On a job failure, notify on either the first failure, and/or when the failed flow finishes.</p>
+ <input id="notifyFailureFirst" class="checkbox" type="checkbox" name="notify" value="first" checked /> <label for="notify">First Failure</label>
+ <input id="notifyFailureLast" class="checkbox" type="checkbox" name="notify" value="last"></input> <label for="notify">Flow Finished</label>
+
+
+ <h4>Failure Emails</h4>
+ <p>Notify these addresses on failure. Comma, space or semi-colon delimited list.</p>
+ <textarea id="failureEmails"></textarea>
+ </div>
+
+ <div>
+ <h4>Success Emails</h4>
+ <p>Notify when the flow finishes successfully. Comma, space or semi-colon delimited list.</p>
+ <textarea id="successEmails"></textarea>
+ </div>
+ </div>
+ <div id="failureOptions" class="failureOptions sidePanel">
+ <h4>Failure Options</h4>
+ <p>When a failure first occurs in the flow, select the execution behavior.</p>
+ <ul>
+ <li><span class="bold">Finish Current Running</span> finishes only the currently running jobs. It will not start any new jobs.</p></li>
+ <li><span class="bold">Cancel All</span> immediately kills all jobs and fails the flow.</p></li>
+ <li><span class="bold">Finish All Possible</span> will keep executing jobs as long as its dependencies are met.</p></li>
+ </ul>
+
+ <select id="failureAction" name="failureAction">
+ <option value="finishCurrent">Finish Current Running</option>
+ <option value="cancelImmediately">Cancel All</option>
+ <option value="finishPossible">Finish All Possible</option>
+ </select>
+ </div>
+ <div id="concurrentPanel" class="sidePanel">
+ <h4>Concurrent Execution Options</h4>
+ <p>If the flow is currently running, these are the options that can be set.</p>
+
+ <input id="ignore" class="radio" type="radio" name="concurrent" value="skip" checked /><label for="skip">Skip Execution</label>
+ <p>Do not run flow if it is already running.</p>
+
+ <input id="ignore" class="radio" type="radio" name="concurrent" value="ignore" checked /><label for="ignore">Run Concurrently</label>
+ <p>Run the flow anyways. Previous execution is unaffected.</p>
+
+ <input id="pipeline" class="radio" type="radio" name="concurrent" value="pipeline" /><label for="pipeline">Pipeline</label>
+ <select id="pipelineLevel" name="pipelineLevel">
+ <option value="1">Level 1</option>
+ <option value="2">Level 2</option>
+ </select>
+ <p>Pipeline the flow, so the current execution will not be overrun.</p>
+ <ul>
+ <li>Level 1: block job A until the previous flow job A has completed.</li>
+ <li>Level 2: block job A until the previous flow job A's children have completed.</li>
+ </ul>
+ <input id="queue" class="radio" type="radio" name="concurrent" value="queue" /><label for="queue">Queue Job</label>
+ <select id="queueLevel" name="queueLevel">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ </select>
+ <p>Queue up to 2. Wait until the previous execution has completed before running.</p>
+ </div>
+ <div id="slaPanel" class="sidePanel">
+ </div>
+ <div id="flowParametersPanel" class="sidePanel">
+ </div>
</div>
</div>
<div class="actions">
- <a class="btn2" id="advanced-btn">Advanced Settings</a>
+ <a class="btn2" id="schedule-btn">Schedule</a>
<a class="yes btn1" id="execute-btn">Execute</a>
<a class="no simplemodal-close btn3">Cancel</a>
</div>
src/web/css/azkaban.css 94(+89 -5)
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 845017d..e9b543e 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1336,21 +1336,82 @@ tr:hover td {
width: 100%;
height: 100%;
background: #fff;
+ left: 0px
}
-
-.svgDiv {
+.rightPanel {
position: absolute;
top: 0px;
right: 0px;
- left: 260px;
+ left: 270px;
bottom: 0px;
}
+.rightPanel .sidePanel {
+ width: 100%;
+ height: 100%;
+ background: #fff;
+ left: 0px;
+ overflow:auto;
+}
+.rightPanel h4 {
+ font-size: 11pt;
+ padding: 0px;
+ margin: 15px 10px 5px 2px;
+}
+
+.rightPanel p {
+ font-size: 10pt;
+ margin-left: 25px;
+ color: #555;
+}
+
+.rightPanel .sidePanel.svgDiv {
+ overflow:hidden;
+}
+
+.rightPanel .checkbox {
+ height: 10px;
+ margin-left: 20px;
+}
+
+.rightPanel label {
+ font-size: 11pt;
+}
+
+.rightPanel textarea {
+ font-size: 10pt;
+ width: 500px;
+}
+
+.rightPanel .radio {
+ margin-left: 15px;
+ margin-bottom: 5px;
+ margin-top: 15px;
+}
.radioLabel.disabled {
opacity: 0.3;
}
+.sideMenu p {
+ color: #555;
+}
+
+.rightPanel ul {
+ margin-left: 20px;
+}
+
+.rightPanel li {
+ list-style-type: circle;
+ margin-left: 20px;
+ font-size: 9pt;
+ color: #555;
+}
+
+.rightPanel select {
+ margin: 15px 20px 5px 20px;
+}
+
/* executing options panel*/
#executing-options {
left: 100px;
@@ -2671,7 +2732,7 @@ tr.row td.tb-name {
float: left;
}
-#advanced-btn {
+#schedule-btn {
float: left;
margin-left: 20px;
}
@@ -2688,7 +2749,7 @@ tr.row td.tb-name {
#execute-flow-panel .svgDiv {
position: absolute;
padding: 1px;
- left: 270px;
+ left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
@@ -2703,6 +2764,29 @@ tr.row td.tb-name {
border: none;
}
+h3.menuHeader {
+ font-size: 12pt;
+ margin: 0px 20px;
+ padding: 5px 3px 5px 15px;
+ color: #888;
+ border: none;
+}
+
+h3.menuHeader:hover {
+ color: #000;
+ background-color: #CCC;
+}
+
+h3.menuHeader.selected {
+ color: #000;
+}
+
+div.menuContent {
+ font-size: 10pt;
+ padding-left: 20px;
+ padding-bottom: 10px;
+}
+
.contextMenu {
position: absolute;
background-color: #FFF;
src/web/js/azkaban.context.menu.js 103(+87 -16)
diff --git a/src/web/js/azkaban.context.menu.js b/src/web/js/azkaban.context.menu.js
index cdb2c9c..e17b61f 100644
--- a/src/web/js/azkaban.context.menu.js
+++ b/src/web/js/azkaban.context.menu.js
@@ -1,19 +1,90 @@
$.namespace('azkaban');
-var flowExecuteDialogView;
-azkaban.FlowExecuteDialogView= Backbone.View.extend({
- events : {
- },
- initialize : function(settings) {
- },
- render: function() {
- },
- showContextMenu: function(menuData) {
- //$('#execute-flow-panel').show();
- $(this.el).show();
- },
- hideContextMenu: function(menuData) {
- //$('#execute-flow-panel').hide();
- $(this.el).hide();
- }
+azkaban.ContextMenuView = Backbone.View.extend({
+ events : {
+ },
+ initialize : function(settings) {
+ var div = this.el;
+ $('body').click(function(e) {
+ $(".contextMenu").remove();
+ });
+ $('body').bind("contextmenu", function(e) {$(".contextMenu").remove()});
+ },
+ show : function(evt, menu) {
+ console.log("Show context menu");
+ $(".contextMenu").remove();
+ var x = evt.pageX;
+ var y = evt.pageY;
+
+ var contextMenu = this.setupMenu(menu);
+ $(contextMenu).css({top: y, left: x});
+ $(this.el).after(contextMenu);
+ },
+ hide : function(evt) {
+ console.log("Hide context menu");
+ $(".contextMenu").remove();
+ },
+ handleClick: function(evt) {
+ console.log("handling click");
+ },
+ setupMenu: function(menu) {
+ var contextMenu = document.createElement("div");
+ $(contextMenu).addClass("contextMenu");
+ var ul = document.createElement("ul");
+ $(contextMenu).append(ul);
+
+ for (var i = 0; i < menu.length; ++i) {
+ var menuItem = document.createElement("li");
+ if (menu[i].break) {
+ $(menuItem).addClass("break");
+ }
+ else {
+ var title = menu[i].title;
+ var callback = menu[i].callback;
+ $(menuItem).addClass("menuitem");
+ $(menuItem).text(title);
+ menuItem.callback = callback;
+ $(menuItem).click(function() {
+ $(contextMenu).hide();
+ this.callback.call();});
+
+ if (menu[i].submenu) {
+ var expandSymbol = document.createElement("div");
+ $(expandSymbol).addClass("expandSymbol");
+ $(menuItem).append(expandSymbol);
+
+ var subMenu = this.setupMenu(menu[i].submenu);
+ $(subMenu).addClass("subMenu");
+ subMenu.parent = contextMenu;
+ menuItem.subMenu = subMenu;
+ $(subMenu).hide();
+ $(this.el).after(subMenu);
+
+ $(menuItem).mouseenter(function() {
+ $(".subMenu").hide();
+ var menuItem = this;
+ menuItem.selected = true;
+ setTimeout(function() {
+ if (menuItem.selected) {
+ var offset = $(menuItem).offset();
+ var left = offset.left;
+ var top = offset.top;
+ var width = $(menuItem).width();
+ var subMenu = menuItem.subMenu;
+
+ var newLeft = left + width - 5;
+ $(subMenu).css({left: newLeft, top: top});
+ $(subMenu).show();
+ }
+ }, 500);
+ });
+ $(menuItem).mouseleave(function() {this.selected = false;});
+ }
+ }
+
+ $(ul).append(menuItem);
+ }
+
+ return contextMenu;
+ }
});
\ No newline at end of file
src/web/js/azkaban.flow.execute.view.js 148(+52 -96)
diff --git a/src/web/js/azkaban.flow.execute.view.js b/src/web/js/azkaban.flow.execute.view.js
index ff2931d..fa867eb 100644
--- a/src/web/js/azkaban.flow.execute.view.js
+++ b/src/web/js/azkaban.flow.execute.view.js
@@ -95,119 +95,61 @@ azkaban.FlowExecuteDialogView= Backbone.View.extend({
var sideMenuDialogView;
azkaban.SideMenuDialogView= Backbone.View.extend({
events : {
- "click .menuHeader" : "expandItem"
+ "click .menuHeader" : "menuClick"
},
initialize : function(settings) {
var children = $(this.el).children();
+ var currentParent;
+ var parents = [];
+ var realChildren = [];
for (var i = 0; i < children.length; ++i ) {
var child = children[i];
if ((i % 2) == 0) {
+ currentParent = child;
$(child).addClass("menuHeader");
+ parents.push(child);
}
else {
$(child).addClass("menuContent");
$(child).hide();
-
-
+ currentParent.child = child;
+ realChildren.push(child);
}
}
+
+ this.menuSelect($("#flowOption"));
+
+ this.parents = parents;
+ this.children = realChildren;
},
- expandItem : function(self) {
+ menuClick : function(evt) {
+ this.menuSelect(evt.currentTarget);
+ },
+ menuSelect : function(target) {
+ if ($(target).hasClass("selected")) {
+ return;
+ }
+
+ $(".sidePanel").each(function() {
+ $(this).hide();
+ });
+
+ $(".menuHeader").each(function() {
+ $(this.child).slideUp("fast");
+ $(this).removeClass("selected");
+ });
+
+ $(".sidePanel").each(function() {
+ $(this).hide();
+ });
+ $(target).addClass("selected");
+ $(target.child).slideDown("fast");
+ var panelName = $(target).attr("viewpanel");
+ $("#" + panelName).show();
}
});
-var contextMenuView;
-azkaban.ContextMenuView = Backbone.View.extend({
- events : {
- },
- initialize : function(settings) {
- var div = this.el;
- $('body').click(function(e) {
- $(".contextMenu").remove();
- });
- $('body').bind("contextmenu", function(e) {$(".contextMenu").remove()});
- this.svgGraph = settings.graph;
- },
- show : function(evt, menu) {
- console.log("Show context menu");
- $(".contextMenu").remove();
- var x = evt.pageX;
- var y = evt.pageY;
-
- var contextMenu = this.setupMenu(menu);
- $(contextMenu).css({top: y, left: x});
- $(this.el).after(contextMenu);
- },
- hide : function(evt) {
- console.log("Hide context menu");
- $(".contextMenu").remove();
- },
- handleClick: function(evt) {
- console.log("handling click");
- },
- setupMenu: function(menu) {
- var contextMenu = document.createElement("div");
- $(contextMenu).addClass("contextMenu");
- var ul = document.createElement("ul");
- $(contextMenu).append(ul);
-
- for (var i = 0; i < menu.length; ++i) {
- var menuItem = document.createElement("li");
- if (menu[i].break) {
- $(menuItem).addClass("break");
- }
- else {
- var title = menu[i].title;
- var callback = menu[i].callback;
- $(menuItem).addClass("menuitem");
- $(menuItem).text(title);
- menuItem.callback = callback;
- $(menuItem).click(function() {
- $(contextMenu).hide();
- this.callback.call();});
-
- if (menu[i].submenu) {
- var expandSymbol = document.createElement("div");
- $(expandSymbol).addClass("expandSymbol");
- $(menuItem).append(expandSymbol);
-
- var subMenu = this.setupMenu(menu[i].submenu);
- $(subMenu).addClass("subMenu");
- subMenu.parent = contextMenu;
- menuItem.subMenu = subMenu;
- $(subMenu).hide();
- $(this.el).after(subMenu);
-
- $(menuItem).mouseenter(function() {
- $(".subMenu").hide();
- var menuItem = this;
- menuItem.selected = true;
- setTimeout(function() {
- if (menuItem.selected) {
- var offset = $(menuItem).offset();
- var left = offset.left;
- var top = offset.top;
- var width = $(menuItem).width();
- var subMenu = menuItem.subMenu;
-
- var newLeft = left + width - 5;
- $(subMenu).css({left: newLeft, top: top});
- $(subMenu).show();
- }
- }, 500);
- });
- $(menuItem).mouseleave(function() {this.selected = false;});
- }
- }
-
- $(ul).append(menuItem);
- }
-
- return contextMenu;
- }
-});
-
var handleJobMenuClick = function(action, el, pos) {
var jobid = el[0].jobid;
@@ -305,7 +247,7 @@ var nodeClickCallback = function(event) {
var flowId = executableGraphModel.get("flowId");
var requestURL = contextURL + "/manager?project=" + projectId + "&flow=" + flowId + "&job=" + jobId;
- var menu = [ {title: "Open in New Window...", callback: function() {window.location.href=requestURL;}},
+ var menu = [ {title: "Open Job in New Window...", callback: function() {window.location.href=requestURL;}},
{break: 1},
{title: "Enable", callback: function() {touchNode(jobId, false);}, submenu: [
{title: "Parents", callback: function(){touchParents(jobId, false);}},
@@ -334,14 +276,28 @@ var edgeClickCallback = function(event) {
var graphClickCallback = function(event) {
console.log("Graph clicked callback");
+ var flowId = executableGraphModel.get("flowId");
+ var requestURL = contextURL + "/manager?project=" + projectId + "&flow=" + flowId;
+
+ var menu = [ {title: "Open Flow in New Window...", callback: function() {window.location.href=requestURL;}},
+ {break: 1},
+ {title: "Enable All", callback: function() {enableAll();}},
+ {title: "Disable All", callback: function() {disableAll();}},
+ {break: 1},
+ {title: "Center Graph", callback: function() {executableGraphModel.trigger("resetPanZoom");}}
+ ];
+
+ contextMenuView.show(event, menu);
}
+var contextMenuView;
$(function() {
flowExecuteDialogView = new azkaban.FlowExecuteDialogView({el:$('#execute-flow-panel')});
executableGraphModel = new azkaban.GraphModel();
svgGraphView = new azkaban.SvgGraphView({el:$('#svgDivCustom'), model: executableGraphModel, topGId:"topG", graphMargin: 10, rightClick: { "node": nodeClickCallback, "edge": edgeClickCallback, "graph": graphClickCallback }});
sideMenuDialogView = new azkaban.SideMenuDialogView({el:$('#graphOptions')});
+
var svgGraph = document.getElementById('svgGraph');
contextMenuView = new azkaban.ContextMenuView({el:$('#contextMenu'), graph: svgGraph});
});