azkaban-developers
Changes
src/java/azkaban/executor/ExecutorManager.java 48(+29 -19)
src/java/azkaban/jobExecutor/ProcessJob.java 22(+17 -5)
src/java/azkaban/webapp/servlet/ExecutorServlet.java 227(+83 -144)
src/less/flow.less 2(+1 -1)
src/web/css/azkaban-graph.css 278(+18 -260)
src/web/js/azkaban.exflow.view.js 100(+54 -46)
src/web/js/azkaban.flow.execute.view.js 294(+162 -132)
src/web/js/azkaban.flow.job.view.js 38(+23 -15)
src/web/js/azkaban.flow.view.js 56(+22 -34)
src/web/js/azkaban.project.view.js 4(+2 -2)
src/web/js/azkaban.svg.flow.loader.js 20(+15 -5)
src/web/js/azkaban.svg.graph.view.js 102(+50 -52)
Details
diff --git a/src/java/azkaban/execapp/ExecutorServlet.java b/src/java/azkaban/execapp/ExecutorServlet.java
index 95ca583..94fb4c2 100644
--- a/src/java/azkaban/execapp/ExecutorServlet.java
+++ b/src/java/azkaban/execapp/ExecutorServlet.java
@@ -57,8 +57,7 @@ public class ExecutorServlet extends HttpServlet implements ConnectorParams {
application = (AzkabanExecutorServer) config.getServletContext().getAttribute(AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY);
if (application == null) {
- throw new IllegalStateException(
- "No batch application is defined in the servlet context!");
+ throw new IllegalStateException("No batch application is defined in the servlet context!");
}
flowRunnerManager = application.getFlowRunnerManager();
diff --git a/src/java/azkaban/execapp/JobRunner.java b/src/java/azkaban/execapp/JobRunner.java
index 12805c3..912844e 100644
--- a/src/java/azkaban/execapp/JobRunner.java
+++ b/src/java/azkaban/execapp/JobRunner.java
@@ -471,7 +471,7 @@ public class JobRunner extends EventHandler implements Runnable {
job = jobtypeManager.buildJobExecutor(this.jobId, props, logger);
}
catch (JobTypeManagerException e) {
- logger.error("Failed to build job type, skipping this job");
+ logger.error("Failed to build job type");
return false;
}
}
diff --git a/src/java/azkaban/executor/ExecutionOptions.java b/src/java/azkaban/executor/ExecutionOptions.java
index d5ecc7f..7913751 100644
--- a/src/java/azkaban/executor/ExecutionOptions.java
+++ b/src/java/azkaban/executor/ExecutionOptions.java
@@ -20,10 +20,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import azkaban.executor.mail.DefaultMailCreator;
import azkaban.utils.TypedMapWrapper;
@@ -73,7 +71,7 @@ public class ExecutionOptions {
private FailureAction failureAction = FailureAction.FINISH_CURRENTLY_RUNNING;
- private Set<String> initiallyDisabledJobs = new HashSet<String>();
+ private List<Object> initiallyDisabledJobs = new ArrayList<Object>();
public void addAllFlowParameters(Map<String,String> flowParam) {
flowParameters.putAll(flowParam);
@@ -175,12 +173,12 @@ public class ExecutionOptions {
return queueLevel;
}
- public List<String> getDisabledJobs() {
- return new ArrayList<String>(initiallyDisabledJobs);
+ public List<Object> getDisabledJobs() {
+ return new ArrayList<Object>(initiallyDisabledJobs);
}
- public void setDisabledJobs(List<String> disabledJobs) {
- initiallyDisabledJobs = new HashSet<String>(disabledJobs);
+ public void setDisabledJobs(List<Object> disabledJobs) {
+ initiallyDisabledJobs = disabledJobs;
}
public Map<String,Object> toObject() {
@@ -223,7 +221,7 @@ public class ExecutionOptions {
options.concurrentOption = wrapper.getString(CONCURRENT_OPTION, options.concurrentOption);
if (wrapper.containsKey(DISABLE)) {
- options.initiallyDisabledJobs = new HashSet<String>(wrapper.<String>getCollection(DISABLE));
+ options.initiallyDisabledJobs = wrapper.<Object>getList(DISABLE);
}
if (optionsMap.containsKey(MAIL_CREATOR)) {
src/java/azkaban/executor/ExecutorManager.java 48(+29 -19)
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index be910d3..408ef2e 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -403,6 +403,34 @@ public class ExecutorManager extends EventHandler implements ExecutorManagerAdap
}
}
+ private void applyDisabledJobs(List<Object> disabledJobs, ExecutableFlowBase exflow) {
+ for (Object disabled: disabledJobs) {
+ if (disabled instanceof String) {
+ String nodeName = (String)disabled;
+ ExecutableNode node = exflow.getExecutableNode(nodeName);
+ if (node != null) {
+ node.setStatus(Status.DISABLED);
+ }
+ }
+ else if (disabled instanceof Map) {
+ @SuppressWarnings("unchecked")
+ Map<String,Object> nestedDisabled = (Map<String, Object>)disabled;
+ String nodeName = (String)nestedDisabled.get("id");
+ @SuppressWarnings("unchecked")
+ List<Object> subDisabledJobs = (List<Object>)nestedDisabled.get("children");
+
+ if (nodeName == null || subDisabledJobs == null) {
+ return;
+ }
+
+ ExecutableNode node = exflow.getExecutableNode(nodeName);
+ if (node != null && node instanceof ExecutableFlowBase) {
+ applyDisabledJobs(subDisabledJobs, (ExecutableFlowBase)node);
+ }
+ }
+ }
+ }
+
@Override
public String submitExecutableFlow(ExecutableFlow exflow, String userId) throws ExecutorManagerException {
synchronized(exflow) {
@@ -422,25 +450,7 @@ public class ExecutorManager extends EventHandler implements ExecutorManagerAdap
String message = "";
if (options.getDisabledJobs() != null) {
- // Disable jobs
- for(String disabledId : options.getDisabledJobs()) {
- String[] splits = disabledId.split(":");
- ExecutableNode node = exflow;
-
- for (String split: splits) {
- if (node instanceof ExecutableFlowBase) {
- node = ((ExecutableFlowBase)node).getExecutableNode(split);
- }
- else {
- message = "Cannot disable job " + disabledId + " since flow " + split + " cannot be found. \n";
- }
- }
-
- if (node == null) {
- throw new ExecutorManagerException("Cannot disable job " + disabledId + ". Cannot find corresponding node.");
- }
- node.setStatus(Status.DISABLED);
- }
+ applyDisabledJobs(options.getDisabledJobs(), exflow);
}
if (!running.isEmpty()) {
diff --git a/src/java/azkaban/executor/mail/DefaultMailCreator.java b/src/java/azkaban/executor/mail/DefaultMailCreator.java
index 0802cae..831c809 100644
--- a/src/java/azkaban/executor/mail/DefaultMailCreator.java
+++ b/src/java/azkaban/executor/mail/DefaultMailCreator.java
@@ -52,7 +52,7 @@ public class DefaultMailCreator implements MailCreator {
public boolean createFirstErrorMessage(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars) {
ExecutionOptions option = flow.getExecutionOptions();
- List<String> emailList = option.getDisabledJobs();
+ List<String> emailList = option.getFailureEmails();
int execId = flow.getExecutionId();
if (emailList != null && !emailList.isEmpty()) {
src/java/azkaban/jobExecutor/ProcessJob.java 22(+17 -5)
diff --git a/src/java/azkaban/jobExecutor/ProcessJob.java b/src/java/azkaban/jobExecutor/ProcessJob.java
index 13272d4..fc9891c 100644
--- a/src/java/azkaban/jobExecutor/ProcessJob.java
+++ b/src/java/azkaban/jobExecutor/ProcessJob.java
@@ -48,20 +48,23 @@ public class ProcessJob extends AbstractProcessJob {
resolveProps();
}
catch (Exception e) {
- error("Bad property definition! " + e.getMessage());
-
+ handleError("Bad property definition! " + e.getMessage(), e);
}
List<String> commands = null;
try {
- commands = getCommandList();
+ commands = getCommandList();
}
catch (Exception e) {
- error("Job set up failed " + e.getCause());
+ handleError("Job set up failed " + e.getCause(), e);
}
long startMs = System.currentTimeMillis();
+ if (commands == null) {
+ handleError("There are no commands to execute", null);
+ }
+
info(commands.size() + " commands to execute.");
File[] propFiles = initPropsFiles();
Map<String, String> envVars = getEnvironmentVariables();
@@ -100,7 +103,16 @@ public class ProcessJob extends AbstractProcessJob {
generateProperties(propFiles[1]);
}
-
+ protected void handleError(String errorMsg, Exception e) throws Exception {
+ error(errorMsg);
+ if (e != null) {
+ throw new Exception(errorMsg, e);
+ }
+ else {
+ throw new Exception(errorMsg);
+ }
+ }
+
protected List<String> getCommandList() {
List<String> commands = new ArrayList<String>();
commands.add(jobProps.getString(COMMAND));
diff --git a/src/java/azkaban/utils/JSONUtils.java b/src/java/azkaban/utils/JSONUtils.java
index e811b82..ad78b6c 100644
--- a/src/java/azkaban/utils/JSONUtils.java
+++ b/src/java/azkaban/utils/JSONUtils.java
@@ -88,6 +88,15 @@ public class JSONUtils {
stream.close();
}
+ public static Object parseJSONFromStringQuiet(String json) {
+ try {
+ return parseJSONFromString(json);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
public static Object parseJSONFromString(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = new JsonFactory();
src/java/azkaban/webapp/servlet/ExecutorServlet.java 227(+83 -144)
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index 00ed276..0e155c3 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -643,125 +643,103 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
}
}
- private long fillUpdateExecutableFlowInfo(ExecutableFlowBase flow, long lastUpdateTime, HashMap<String, Object> ret) {
- // Just update the nodes and flow states
- ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
- HashMap<String, Map<String,Object>> nodeMap = new HashMap<String, Map<String,Object>>();
-
- long updateTime = flow.getUpdateTime();
- for (ExecutableNode node : flow.getExecutableNodes()) {
- HashMap<String, Object> nodeObj = null;
- if (node instanceof ExecutableFlowBase) {
- nodeObj = new HashMap<String, Object>();
- long subUpdateTime = fillUpdateExecutableFlowInfo((ExecutableFlowBase)node, lastUpdateTime, nodeObj);
- updateTime = Math.max(updateTime, subUpdateTime);
- if (updateTime <= lastUpdateTime) {
- continue;
- }
+ private Map<String,Object> getExecutableFlowUpdateInfo(ExecutableNode node, long lastUpdateTime) {
+ HashMap<String, Object> nodeObj = new HashMap<String,Object>();
+ if (node.getUpdateTime() > lastUpdateTime) {
+ nodeObj.put("id", node.getId());
+ nodeObj.put("status", node.getStatus());
+ nodeObj.put("startTime", node.getStartTime());
+ nodeObj.put("endTime", node.getEndTime());
+ nodeObj.put("updateTime", node.getUpdateTime());
+
+ nodeObj.put("attempt", node.getAttempt());
+ if (node.getAttempt() > 0) {
+ nodeObj.put("pastAttempts", node.getAttemptObjects());
}
- else if (node.getUpdateTime() <= lastUpdateTime){
- continue;
+ }
+
+ if (node instanceof ExecutableFlowBase) {
+ ExecutableFlowBase base = (ExecutableFlowBase)node;
+ ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
+
+ for (ExecutableNode subNode: base.getExecutableNodes()) {
+ Map<String,Object> subNodeObj = getExecutableFlowUpdateInfo(subNode, lastUpdateTime);
+ if (!subNodeObj.isEmpty()) {
+ nodeList.add(subNodeObj);
+ }
}
- else {
- nodeObj = new HashMap<String, Object>();
- updateTime = Math.max(updateTime, node.getUpdateTime());
-
+
+ if (!nodeList.isEmpty()) {
+ nodeObj.put("flow", base.getFlowId());
+ nodeObj.put("nodes", nodeList);
+ // We do this again, because the above update time may not have been built.
nodeObj.put("id", node.getId());
- nodeObj.put("status", node.getStatus());
- nodeObj.put("startTime", node.getStartTime());
- nodeObj.put("endTime", node.getEndTime());
- nodeObj.put("updateTime", node.getUpdateTime());
- nodeObj.put("attempt", node.getAttempt());
+ }
+ }
+
+ return nodeObj;
+ }
- if (node.getAttempt() > 0) {
- nodeObj.put("pastAttempts", node.getAttemptObjects());
+ private Map<String,Object> getExecutableNodeInfo(ExecutableNode node) {
+ HashMap<String, Object> nodeObj = new HashMap<String,Object>();
+ nodeObj.put("id", node.getId());
+ nodeObj.put("status", node.getStatus());
+ nodeObj.put("startTime", node.getStartTime());
+ nodeObj.put("endTime", node.getEndTime());
+ nodeObj.put("updateTime", node.getUpdateTime());
+ nodeObj.put("type", node.getType());
+
+ nodeObj.put("attempt", node.getAttempt());
+ if (node.getAttempt() > 0) {
+ nodeObj.put("pastAttempts", node.getAttemptObjects());
+ }
+
+ if (node.getInNodes() != null && !node.getInNodes().isEmpty()) {
+ nodeObj.put("in", node.getInNodes());
+ }
+
+ if (node instanceof ExecutableFlowBase) {
+ ExecutableFlowBase base = (ExecutableFlowBase)node;
+ ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
+
+ for (ExecutableNode subNode: base.getExecutableNodes()) {
+ Map<String,Object> subNodeObj = getExecutableNodeInfo(subNode);
+ if (!subNodeObj.isEmpty()) {
+ nodeList.add(subNodeObj);
}
}
- nodeMap.put(node.getId(), nodeObj);
- nodeList.add(nodeObj);
+ nodeObj.put("flow", base.getFlowId());
+ nodeObj.put("nodes", nodeList);
+ nodeObj.put("flowId", base.getFlowId());
}
-
- ret.put("nodes", nodeList);
- ret.put("status", flow.getStatus().toString());
- ret.put("startTime", flow.getStartTime());
- ret.put("endTime", flow.getEndTime());
- ret.put("updateTime", updateTime);
- return updateTime;
+
+ return nodeObj;
}
- private void ajaxFetchExecutableFlowUpdate(HttpServletRequest req,
- HttpServletResponse resp, HashMap<String, Object> ret, User user,
+ private void ajaxFetchExecutableFlowUpdate(
+ HttpServletRequest req,
+ HttpServletResponse resp,
+ HashMap<String, Object> ret,
+ User user,
ExecutableFlow exFlow) throws ServletException {
Long lastUpdateTime = Long.parseLong(getParam(req, "lastUpdateTime"));
System.out.println("Fetching " + exFlow.getExecutionId());
- Project project = getProjectAjaxByPermission(ret,
- exFlow.getProjectId(), user, Type.READ);
+ Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.READ);
if (project == null) {
return;
}
- fillUpdateExecutableFlowInfo(exFlow, lastUpdateTime, ret);
+ Map<String, Object> map = getExecutableFlowUpdateInfo(exFlow, lastUpdateTime);
+ ret.putAll(map);
}
- private long fillExecutableFlowInfo(ExecutableFlowBase flow, HashMap<String, Object> ret) {
- long updateTime = flow.getUpdateTime();
-
- ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
- ArrayList<Map<String, Object>> edgeList = new ArrayList<Map<String, Object>>();
-
- ArrayList<String> executorQueue = new ArrayList<String>();
- executorQueue.addAll(flow.getStartNodes());
-
- for (ExecutableNode node : flow.getExecutableNodes()) {
- HashMap<String, Object> nodeObj = new HashMap<String, Object>();
- nodeObj.put("id", node.getId());
- nodeObj.put("status", node.getStatus());
- nodeObj.put("startTime", node.getStartTime());
- nodeObj.put("endTime", node.getEndTime());
- nodeObj.put("type", node.getType());
-
- // Add past attempts
- if (node.getPastAttemptList() != null) {
- ArrayList<Object> pastAttempts = new ArrayList<Object>();
- for (ExecutionAttempt attempt : node.getPastAttemptList()) {
- pastAttempts.add(attempt.toObject());
- }
- nodeObj.put("pastAttempts", pastAttempts);
- }
-
- nodeList.add(nodeObj);
-
- // Add edges
- for (String out : node.getOutNodes()) {
- HashMap<String, Object> edgeObj = new HashMap<String, Object>();
- edgeObj.put("from", node.getId());
- edgeObj.put("target", out);
- edgeList.add(edgeObj);
- }
-
- // If it's an embedded flow, add the embedded flow info
- if (node instanceof ExecutableFlowBase) {
- long subUpdateTime = fillExecutableFlowInfo((ExecutableFlowBase)node, nodeObj);
- updateTime = Math.max(updateTime, subUpdateTime);
- }
- else {
- nodeObj.put("updateTime", updateTime);
- }
- }
-
- ret.put("nodes", nodeList);
- ret.put("edges", edgeList);
- ret.put("status", flow.getStatus().toString());
- ret.put("startTime", flow.getStartTime());
- ret.put("endTime", flow.getEndTime());
- ret.put("updateTime", updateTime);
- return updateTime;
- }
-
- private void ajaxFetchExecutableFlow(HttpServletRequest req,
- HttpServletResponse resp, HashMap<String, Object> ret, User user,
+ private void ajaxFetchExecutableFlow(
+ HttpServletRequest req,
+ HttpServletResponse resp,
+ HashMap<String, Object> ret,
+ User user,
ExecutableFlow exFlow) throws ServletException {
System.out.println("Fetching " + exFlow.getExecutionId());
@@ -771,53 +749,14 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
return;
}
- fillExecutableFlowInfo(exFlow, ret);
ret.put("submitTime", exFlow.getSubmitTime());
ret.put("submitUser", exFlow.getSubmitUser());
-//
-//
-// ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
-// ArrayList<Map<String, Object>> edgeList = new ArrayList<Map<String, Object>>();
-// for (ExecutableNode node : exFlow.getExecutableNodes()) {
-// HashMap<String, Object> nodeObj = new HashMap<String, Object>();
-// nodeObj.put("id", node.getId());
-// nodeObj.put("status", node.getStatus());
-// nodeObj.put("startTime", node.getStartTime());
-// nodeObj.put("endTime", node.getEndTime());
-// nodeObj.put("type", node.getType());
-//
-// // Add past attempts
-// if (node.getPastAttemptList() != null) {
-// ArrayList<Object> pastAttempts = new ArrayList<Object>();
-// for (ExecutionAttempt attempt : node.getPastAttemptList()) {
-// pastAttempts.add(attempt.toObject());
-// }
-// nodeObj.put("pastAttempts", pastAttempts);
-// }
-//
-// nodeList.add(nodeObj);
-//
-// // Add edges
-// for (String out : node.getOutNodes()) {
-// HashMap<String, Object> edgeObj = new HashMap<String, Object>();
-// edgeObj.put("from", node.getId());
-// edgeObj.put("target", out);
-// edgeList.add(edgeObj);
-// }
-//
-// // If it's an embedded flow, add the embedded flow info
-// if (node instanceof ExecutableFlowBase) {
-//
-// }
-// }
-//
-// ret.put("nodes", nodeList);
-// ret.put("edges", edgeList);
-// ret.put("status", exFlow.getStatus().toString());
-// ret.put("startTime", exFlow.getStartTime());
-// ret.put("endTime", exFlow.getEndTime());
-// ret.put("submitTime", exFlow.getSubmitTime());
-// ret.put("submitUser", exFlow.getSubmitUser());
+ ret.put("execid", exFlow.getExecutionId());
+ ret.put("projectId", exFlow.getProjectId());
+ ret.put("project", project.getName());
+
+ Map<String,Object> flowObj = getExecutableNodeInfo(exFlow);
+ ret.putAll(flowObj);
}
private void ajaxAttemptExecuteFlow(HttpServletRequest req,
diff --git a/src/java/azkaban/webapp/servlet/HttpRequestUtils.java b/src/java/azkaban/webapp/servlet/HttpRequestUtils.java
index 264d3d4..6da9d88 100644
--- a/src/java/azkaban/webapp/servlet/HttpRequestUtils.java
+++ b/src/java/azkaban/webapp/servlet/HttpRequestUtils.java
@@ -19,6 +19,7 @@ package azkaban.webapp.servlet;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
@@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
import azkaban.executor.ExecutionOptions;
import azkaban.executor.ExecutionOptions.FailureAction;
import azkaban.executor.mail.DefaultMailCreator;
+import azkaban.utils.JSONUtils;
public class HttpRequestUtils {
public static ExecutionOptions parseFlowOptions(HttpServletRequest req) throws ServletException {
@@ -101,8 +103,9 @@ public class HttpRequestUtils {
if (hasParam(req, "disabled")) {
String disabled = getParam(req, "disabled");
if (!disabled.isEmpty()) {
- String[] disabledNodes = disabled.split("\\s*,\\s*");
- execOptions.setDisabledJobs(Arrays.asList(disabledNodes));
+ @SuppressWarnings("unchecked")
+ List<Object> disabledList = (List<Object>)JSONUtils.parseJSONFromStringQuiet(disabled);
+ execOptions.setDisabledJobs(disabledList);
}
}
return execOptions;
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 9f907cc..e998b00 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -553,10 +553,10 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
private void ajaxFetchFlowGraph(Project project, HashMap<String, Object> ret, HttpServletRequest req) throws ServletException {
String flowId = getParam(req, "flow");
- fillFlowInfo2(project, flowId, ret);
+ fillFlowInfo(project, flowId, ret);
}
- private void fillFlowInfo2(Project project, String flowId, HashMap<String, Object> ret) {
+ private void fillFlowInfo(Project project, String flowId, HashMap<String, Object> ret) {
Flow flow = project.getFlow(flowId);
ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
@@ -566,9 +566,9 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
nodeObj.put("type", node.getType());
if (node.getEmbeddedFlowId() != null) {
nodeObj.put("flowId", node.getEmbeddedFlowId());
- HashMap<String, Object> embeddedNodeObj = new HashMap<String, Object>();
- fillFlowInfo2(project, node.getEmbeddedFlowId(), embeddedNodeObj);
- nodeObj.put("flowData", embeddedNodeObj);
+ //HashMap<String, Object> embeddedNodeObj = new HashMap<String, Object>();
+ fillFlowInfo(project, node.getEmbeddedFlowId(), nodeObj);
+ //nodeObj.put("flowData", embeddedNodeObj);
}
nodeList.add(nodeObj);
@@ -629,9 +629,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
if (node.getType().equals("flow")) {
if (node.getEmbeddedFlowId() != null) {
- HashMap<String, Object> flowMap = new HashMap<String, Object>();
- fillFlowInfo2(project, node.getEmbeddedFlowId(), flowMap);
- ret.put("flowData", flowMap);
+ fillFlowInfo(project, node.getEmbeddedFlowId(), ret);
}
}
}
diff --git a/src/java/azkaban/webapp/servlet/ScheduleServlet.java b/src/java/azkaban/webapp/servlet/ScheduleServlet.java
index c373608..6cc913e 100644
--- a/src/java/azkaban/webapp/servlet/ScheduleServlet.java
+++ b/src/java/azkaban/webapp/servlet/ScheduleServlet.java
@@ -303,21 +303,12 @@ public class ScheduleServlet extends LoginAbstractAzkabanServlet {
}
}
}
-
- List<String> disabledJobs;
- if(flowOptions != null) {
- disabledJobs = flowOptions.getDisabledJobs() == null ? new ArrayList<String>() : flowOptions.getDisabledJobs();
- }
- else {
- disabledJobs = new ArrayList<String>();
- }
-
+
List<String> allJobs = new ArrayList<String>();
for(Node n : flow.getNodes()) {
- if(!disabledJobs.contains(n.getId())) {
- allJobs.add(n.getId());
- }
+ allJobs.add(n.getId());
}
+
ret.put("allJobNames", allJobs);
} catch (ServletException e) {
ret.put("error", e);
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index b290f48..edc4c5b 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -20,19 +20,26 @@
#parse("azkaban/webapp/servlet/velocity/style.vm")
#parse("azkaban/webapp/servlet/velocity/javascript.vm")
-
<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.common.utils.js"></script>
+
+ <script type="text/javascript" src="${context}/js/jquery.svg.min.js"></script>
+ <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/svgutils.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.job.status.utils.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.flow.execute.view.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.svg.flow.loader.js"></script>
<script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
- <script type="text/javascript" src="${context}/js/azkaban.exflow.view.js"></script>
<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban.exflow.view.js"></script>
+
<script type="text/javascript">
var contextURL = "${context}";
var currentTime = ${currentTime};
@@ -44,7 +51,6 @@
var flowId = "${flowid}";
var execId = "${execid}";
</script>
- <link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/azkaban-graph.css" />
src/less/flow.less 2(+1 -1)
diff --git a/src/less/flow.less b/src/less/flow.less
index 042f1cd..452a66e 100644
--- a/src/less/flow.less
+++ b/src/less/flow.less
@@ -139,7 +139,7 @@ td {
color : #3398cc;
}
- a {
+ > a {
clear:both;
border-bottom-width: 0;
src/web/css/azkaban-graph.css 278(+18 -260)
diff --git a/src/web/css/azkaban-graph.css b/src/web/css/azkaban-graph.css
index 8b1184a..5958152 100644
--- a/src/web/css/azkaban-graph.css
+++ b/src/web/css/azkaban-graph.css
@@ -13,10 +13,12 @@
.node.selected > .nodebox .border {
stroke-width: 3;
+ stroke: #39b3d7;
}
.node.selected > .nodebox .flowborder {
stroke-width: 3;
+ fill: #D9EDFF;
}
.nodebox > .border:hover {
fill-opacity: 0.7;
@@ -72,6 +74,19 @@
fill: #FFF;
}
+.KILLED {
+ opacity: 0.5;
+}
+
+.KILLED > g > rect {
+ fill: #d2322d;
+ stroke: #d2322d;
+}
+
+.KILLED > g > text {
+ fill: #FFF;
+}
+
.FAILED_FINISHING > g > rect {
fill: #ed9c28;
stroke: #ed9c28;
@@ -91,13 +106,12 @@
stroke: #800000;
}
-.SKIPPED > g > rect {
- fill: #CCC;
- stroke: #CCC;
+.nodeDisabled {
+ opacity: 0.25;
}
.SKIPPED > g > rect {
- fill: #CCC;
+ fill: #DDD;
stroke: #CCC;
}
@@ -120,259 +134,3 @@
stroke-width: 1.5;
}
-/*
-svg text1 {
- pointer-events: none;
-}
-
-svg g.nodebox1 {
- pointer-events: none;
-}
-
-svg .edge {
- stroke: #BBB;
- stroke-width: 2;
-}
-
-svg .edge:hover {
- stroke: #009FC9;
- stroke-width: 4;
-}
-
-svg .node.disabled {
- opacity: 0.3;
-}
-
-svg .node .backboard {
- fill: #FFF;
- opacity: 0.05;
-}
-
-svg .node:hover {
- cursor: pointer;
-}
-
-svg .node:hover .backboard {
- opacity: 0.7;
-}
-
-svg .selected .backboard {
- opacity: 0.4;
-}
-
-svg .node circle {
- fill: #888;
- stroke: #777;
- stroke-width: 2;
-}
-
-svg .node:hover circle {
- stroke: #009FC9;
-}
-
-svg .node:hover text {
- fill: #009FC9;
-}
-
-svg .selected text {
- fill: #338AB0;
-}
-
-svg .selected circle {
- stroke: #009FC9;
- stroke-width: 4;
-}
-
-svg .READY circle {
- fill: #CCC;
-}
-
-svg .RUNNING circle {
- fill: #009FC9;
-}
-
-svg .QUEUED circle {
- opacity: 0.5;
- fill: #009FC9;
-}
-
-svg .FAILED circle {
- fill: #CC0000;
-}
-
-svg .KILLED circle {
- fill: #CC0000;
-}
-
-svg .SUCCEEDED circle {
- fill: #00CC33;
-}
-
-svg .DISABLED circle {
- opacity: 0.3;
-}
-
-svg .SKIPPED circle {
- opacity: 0.3;
-}
-
-svg .selected circle {
- stroke: #009FC9;
- stroke-width: 4;
-}
-
-svg .selected .nodebox rect {
- stroke: #009FC9;
- stroke-width: 3;
-}
-
-svg .node rect {
- fill: #CCC;
- stroke: #CCC;
- stroke-width: 2;
-}
-
-svg .node .nodebox text {
- fill: #FFF;
-}
-
-svg .READY .nodebox text {
- fill: #000;
-}
-
-svg .node:hover rect {
- stroke: #009FC9;
-}
-
-svg .READY rect {
- fill: #CCC;
-}
-
-svg .RUNNING rect {
- fill: #009FC9;
-}
-
-svg .QUEUED rect {
- opacity: 0.5;
- fill: #009FC9;
-}
-
-svg .FAILED rect {
- fill: #CC0000;
-}
-
-svg .KILLED rect {
- fill: #CC0000;
-}
-
-svg .SUCCEEDED rect {
- fill: #30ad23;
-}
-
-svg .DISABLED rect {
- opacity: 0.3;
-}
-
-svg .SKIPPED rect {
- opacity: 0.3;
-}
-
-svg .nodebox text {
- fill: #fff;
-}
-
-
-#Used for charts
-svg circle.READY {
- stroke: #CCC;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-svg circle.RUNNING {
- stroke: #009FC9;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-svg circle.FAILED {
- stroke: #CC0000;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-svg circle.KILLED {
- stroke: #CC0000;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-svg circle.SUCCEEDED {
- stroke: #00CC33;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-svg circle.DISABLED {
- stroke: #CCC;
- opacity: 0.3;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-svg circle.SKIPPED {
- stroke: #CCC;
- opacity: 0.3;
- stroke-width: 2px;
- fill: #FFF;
-}
-
-.flowExtendedView {
- position: absolute;
- background-color: rgba(255, 255, 255, 0.95);
- -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
- -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
- box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
-
- min-width: 200px;
- min-height: 150px;
-}
-
-.dataJobProperties {
-
-}
-
-.flowInfoTitle {
- padding-top: 8px;
- padding-left: 8px;
- padding-bottom: 5px;
- cursor: pointer;
-}
-
-.flowInfoTitle:hover {
- background-color: #CCC;
-}
-
-.nodeId {
- font-size: 16px;
- font-weight: bold;
- margin: 20px 20px;
-}
-
-.nodeType {
- font-style: italic;
-}
-
-.dataContent {
- margin: 5px;
-}
-
-.dataFlow {
- width: 100%;
-}
-
-.svgTiny {
- width: 100%;
- height: 100%;
-}
-*/
src/web/js/azkaban.exflow.view.js 100(+54 -46)
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index 4ac616b..e078112 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -732,20 +732,20 @@ $(function() {
model: graphModel
});
- mainSvgGraphView = new azkaban.SvgGraphView({
+ mainSvgGraphView = new azkaban.SvgGraphView({
el: $('#svgDiv'),
model: graphModel,
rightClick: {
- "node": exNodeClickCallback,
- "edge": exEdgeClickCallback,
- "graph": exGraphClickCallback
+ "node": nodeClickCallback,
+ "edge": edgeClickCallback,
+ "graph": graphClickCallback
}
});
jobsListView = new azkaban.JobListView({
el: $('#jobList'),
model: graphModel,
- contextMenuCallback: exJobClickCallback
+ contextMenuCallback: jobClickCallback
});
flowLogView = new azkaban.FlowLogView({
@@ -767,52 +767,60 @@ $(function() {
var requestData = {"execid": execId, "ajax":"fetchexecflow"};
var successHandler = function(data) {
console.log("data fetched");
- graphModel.set({data: data});
- graphModel.set({disabled: {}});
+ processFlowData(data);
+ graphModel.set({data:data});
graphModel.trigger("change:graph");
updateTime = Math.max(updateTime, data.submitTime);
updateTime = Math.max(updateTime, data.startTime);
updateTime = Math.max(updateTime, data.endTime);
-
- var nodeMap = {};
- for (var i = 0; i < data.nodes.length; ++i) {
- var node = data.nodes[i];
- nodeMap[node.id] = node;
- updateTime = Math.max(updateTime, node.startTime);
- updateTime = Math.max(updateTime, node.endTime);
- }
- for (var i = 0; i < data.edges.length; ++i) {
- var edge = data.edges[i];
-
- if (!nodeMap[edge.target].in) {
- nodeMap[edge.target].in = {};
- }
- var targetInMap = nodeMap[edge.target].in;
- targetInMap[edge.from] = nodeMap[edge.from];
-
- if (!nodeMap[edge.from].out) {
- nodeMap[edge.from].out = {};
- }
- var sourceOutMap = nodeMap[edge.from].out;
- sourceOutMap[edge.target] = nodeMap[edge.target];
- }
-
- graphModel.set({nodeMap: nodeMap});
- if (window.location.hash) {
- var hash = window.location.hash;
- if (hash == "#jobslist") {
- flowTabView.handleJobslistLinkClick();
- }
- else if (hash == "#log") {
- flowTabView.handleLogLinkClick();
- }
- }
- else {
- flowTabView.handleGraphLinkClick();
- }
- updaterFunction();
- logUpdaterFunction();
+//
+// graphModel.set({data: data});
+// graphModel.set({disabled: {}});
+// graphModel.trigger("change:graph");
+//
+// updateTime = Math.max(updateTime, data.submitTime);
+// updateTime = Math.max(updateTime, data.startTime);
+// updateTime = Math.max(updateTime, data.endTime);
+//
+// var nodeMap = {};
+// for (var i = 0; i < data.nodes.length; ++i) {
+// var node = data.nodes[i];
+// nodeMap[node.id] = node;
+// updateTime = Math.max(updateTime, node.startTime);
+// updateTime = Math.max(updateTime, node.endTime);
+// }
+// for (var i = 0; i < data.edges.length; ++i) {
+// var edge = data.edges[i];
+//
+// if (!nodeMap[edge.target].in) {
+// nodeMap[edge.target].in = {};
+// }
+// var targetInMap = nodeMap[edge.target].in;
+// targetInMap[edge.from] = nodeMap[edge.from];
+//
+// if (!nodeMap[edge.from].out) {
+// nodeMap[edge.from].out = {};
+// }
+// var sourceOutMap = nodeMap[edge.from].out;
+// sourceOutMap[edge.target] = nodeMap[edge.target];
+// }
+//
+// graphModel.set({nodeMap: nodeMap});
+// if (window.location.hash) {
+// var hash = window.location.hash;
+// if (hash == "#jobslist") {
+// flowTabView.handleJobslistLinkClick();
+// }
+// else if (hash == "#log") {
+// flowTabView.handleLogLinkClick();
+// }
+// }
+// else {
+// flowTabView.handleGraphLinkClick();
+// }
+// updaterFunction();
+// logUpdaterFunction();
};
ajaxCall(requestURL, requestData, successHandler);
});
src/web/js/azkaban.flow.execute.view.js 294(+162 -132)
diff --git a/src/web/js/azkaban.flow.execute.view.js b/src/web/js/azkaban.flow.execute.view.js
index c09af4c..909917d 100644
--- a/src/web/js/azkaban.flow.execute.view.js
+++ b/src/web/js/azkaban.flow.execute.view.js
@@ -16,26 +16,6 @@
$.namespace('azkaban');
-function recurseAllAncestors(nodes, disabledMap, id, disable) {
- var node = nodes[id];
- if (node.in) {
- for (var key in node.in) {
- disabledMap[key] = disable;
- recurseAllAncestors(nodes, disabledMap, key, disable);
- }
- }
-}
-
-function recurseAllDescendents(nodes, disabledMap, id, disable) {
- var node = nodes[id];
- if (node.out) {
- for (var key in node.out) {
- disabledMap[key] = disable;
- recurseAllDescendents(nodes, disabledMap, key, disable);
- }
- }
-}
-
var flowExecuteDialogView;
azkaban.FlowExecuteDialogView = Backbone.View.extend({
events: {
@@ -90,20 +70,15 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
}
}
- var disabled = "";
- var disabledMap = this.model.get('disabled');
- for (var dis in disabledMap) {
- if (disabledMap[dis]) {
- disabled += dis + ",";
- }
- }
+ var data = this.model.get("data");
+ var disabledList = gatherDisabledNodes(data);
var executingData = {
projectId: projectId,
project: this.projectName,
ajax: "executeFlow",
flow: this.flowId,
- disabled: disabled,
+ disabled: JSON.stringify(disabledList),
failureEmailsOverride:failureEmailsOverride,
successEmailsOverride:successEmailsOverride,
failureAction: failureAction,
@@ -128,7 +103,6 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
return executingData;
},
-
changeFlowInfo: function() {
var successEmails = this.model.get("successEmails");
var failureEmails = this.model.get("failureEmails");
@@ -183,28 +157,7 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
if (queueLevel) {
$('#queueLevel').val(queueLevel);
}
-
- if (nodeStatus) {
- var nodeMap = this.model.get("nodeMap");
- var disabled = {};
- for (var key in nodeStatus) {
- var status = nodeStatus[key];
-
- var node = nodeMap[key];
- if (node) {
- node.status = status;
- if (node.status == "DISABLED" || node.status == "SKIPPED") {
- node.status = "READY";
- disabled[node.id] = true;
- }
- if (node.status == "SUCCEEDED" || node.status=="RUNNING") {
- disabled[node.id] = true;
- }
- }
- }
- this.model.set({"disabled":disabled});
- }
-
+
if (flowParams) {
for (var key in flowParams) {
editTableView.handleAddRow({
@@ -253,15 +206,12 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
var disabled = this.model.get("disabled");
// Disable all, then re-enable those you want.
- for (var key in nodes) {
- disabled[key] = true;
- }
+ disableAll();
var jobNode = nodes[jobId];
- disabled[jobId] = false;
if (withDep) {
- recurseAllAncestors(nodes, disabled, jobId, false);
+ recurseAllAncestors(jobNode, false);
}
this.showExecutionOptionPanel();
@@ -293,7 +243,7 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
var requestURL = contextURL + "/manager";
var graphModel = executableGraphModel;
- //fetchFlow(this.model, projectName, flowId, true);
+ // fetchFlow(this.model, projectName, flowId, true);
var requestData = {
"project": projectName,
"ajax": "fetchflowgraph",
@@ -302,6 +252,7 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
var successHandler = function(data) {
console.log("data fetched");
processFlowData(data);
+ disableFinishedJobs(data);
graphModel.set({data:data});
executingSvgGraphView = new azkaban.SvgGraphView({
@@ -309,9 +260,9 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
model: graphModel,
render: true,
rightClick: {
- "node": nodeClickCallback,
- "edge": edgeClickCallback,
- "graph": graphClickCallback
+ "node": exNodeClickCallback,
+ "edge": exEdgeClickCallback,
+ "graph": exGraphClickCallback
}
});
};
@@ -350,16 +301,16 @@ azkaban.EditTableView = Backbone.View.extend({
var tr = document.createElement("tr");
var tdName = document.createElement("td");
- $(tdName).addClass('property-key');
+ $(tdName).addClass('property-key');
var tdValue = document.createElement("td");
var remove = document.createElement("div");
- $(remove).addClass("pull-right").addClass('remove-btn');
- var removeBtn = document.createElement("button");
- $(removeBtn).attr('type', 'button');
- $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
- $(removeBtn).text('Delete');
- $(remove).append(removeBtn);
+ $(remove).addClass("pull-right").addClass('remove-btn');
+ var removeBtn = document.createElement("button");
+ $(removeBtn).attr('type', 'button');
+ $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
+ $(removeBtn).text('Delete');
+ $(remove).append(removeBtn);
var nameData = document.createElement("span");
$(nameData).addClass("spanValue");
@@ -372,7 +323,7 @@ azkaban.EditTableView = Backbone.View.extend({
$(tdName).addClass("editable");
$(tdValue).append(valueData);
- $(tdValue).append(remove);
+ $(tdValue).append(remove);
$(tdValue).addClass("editable").addClass('value');
$(tr).addClass("editRow");
@@ -391,7 +342,7 @@ azkaban.EditTableView = Backbone.View.extend({
var input = document.createElement("input");
$(input).attr("type", "text");
- $(input).addClass('form-control').addClass('input-sm');
+ $(input).addClass('form-control').addClass('input-sm');
$(input).css("width", "100%");
$(input).val(text);
$(curTarget).addClass("editing");
@@ -428,13 +379,13 @@ azkaban.EditTableView = Backbone.View.extend({
$(valueData).text(text);
if ($(parent).hasClass("value")) {
- var remove = document.createElement("div");
- $(remove).addClass("pull-right").addClass('remove-btn');
- var removeBtn = document.createElement("button");
- $(removeBtn).attr('type', 'button');
- $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
- $(removeBtn).text('Delete');
- $(remove).append(removeBtn);
+ var remove = document.createElement("div");
+ $(remove).addClass("pull-right").addClass('remove-btn');
+ var removeBtn = document.createElement("button");
+ $(removeBtn).attr('type', 'button');
+ $(removeBtn).addClass('btn').addClass('btn-xs').addClass('btn-danger');
+ $(removeBtn).text('Delete');
+ $(remove).append(removeBtn);
$(parent).append(remove);
}
@@ -488,7 +439,7 @@ azkaban.SideMenuDialogView = Backbone.View.extend({
var handleJobMenuClick = function(action, el, pos) {
var jobid = el[0].jobid;
- var requesgURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
+ var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
if (action == "open") {
window.location.href = requestURL;
}
@@ -500,106 +451,185 @@ var handleJobMenuClick = function(action, el, pos) {
var executableGraphModel;
azkaban.GraphModel = Backbone.Model.extend({});
+/**
+ * Disable jobs that need to be disabled
+ */
+var disableFinishedJobs = function(data) {
+ for (var i=0; i < data.nodes.length; ++i) {
+ var node = data.nodes[i];
+ node.status = status;
+ if (node.status == "DISABLED" || node.status == "SKIPPED") {
+ node.status = "READY";
+ node.disabled = true;
+ }
+ else if (node.status == "SUCCEEDED" || node.status=="RUNNING") {
+ node.disabled = true;
+ }
+ else {
+ node.disabled = false;
+ if (node.flowData) {
+ disableFinishedJobs(node.flowData);
+ }
+ }
+ }
+}
+
+/**
+ * Enable all jobs. Recurse
+ */
var enableAll = function() {
- disabled = {};
- executableGraphModel.set({disabled: disabled});
+ recurseTree(executableGraphModel.get("data"), false, false);
executableGraphModel.trigger("change:disabled");
}
var disableAll = function() {
- var disabled = executableGraphModel.get("disabled");
-
- var nodes = executableGraphModel.get("nodes");
- for (var key in nodes) {
- disabled[key] = true;
- }
-
- executableGraphModel.set({disabled: disabled});
+ recurseTree(executableGraphModel.get("data"), true, false);
executableGraphModel.trigger("change:disabled");
}
-var touchNode = function(jobid, disable) {
- var disabled = executableGraphModel.get("disabled");
+var recurseTree = function(data, disabled, recurse) {
+ for (var i=0; i < data.nodes.length; ++i) {
+ var node = data.nodes[i];
+ node.disabled = disabled;
+
+ if (node.flowData && recurse) {
+ recurseTree(node.flowData, disabled);
+ }
+ }
+}
- disabled[jobid] = disable;
- executableGraphModel.set({disabled: disabled});
+var touchNode = function(node, disable) {
+ node.disabled = disable;
executableGraphModel.trigger("change:disabled");
}
-var touchParents = function(jobid, disable) {
- var disabled = executableGraphModel.get("disabled");
- var nodes = executableGraphModel.get("nodes");
- var inNodes = nodes[jobid].inNodes;
+var touchParents = function(node, disable) {
+ var inNodes = node.inNodes;
if (inNodes) {
for (var key in inNodes) {
- disabled[key] = disable;
+ inNodes[key].disabled = disable;
}
}
-
- executableGraphModel.set({disabled: disabled});
+
executableGraphModel.trigger("change:disabled");
}
-var touchChildren = function(jobid, disable) {
- var disabledMap = executableGraphModel.get("disabled");
- var nodes = executableGraphModel.get("nodes");
- var outNodes = nodes[jobid].outNodes;
+var touchChildren = function(node, disable) {
+ var outNodes = node.outNodes;
if (outNodes) {
for (var key in outNodes) {
- disabledMap[key] = disable;
+ outNodes[key].disabled = disable;
}
}
- executableGraphModel.set({disabled: disabledMap});
executableGraphModel.trigger("change:disabled");
}
-var touchAncestors = function(jobid, disable) {
- var disabled = executableGraphModel.get("disabled");
- var nodes = executableGraphModel.get("nodes");
-
- recurseAllAncestors(nodes, disabled, jobid, disable);
+var touchAncestors = function(node, disable) {
+ recurseAllAncestors(node, disable);
- executableGraphModel.set({disabled: disabled});
executableGraphModel.trigger("change:disabled");
}
var touchDescendents = function(jobid, disable) {
- var disabled = executableGraphModel.get("disabled");
- var nodes = executableGraphModel.get("nodes");
-
- recurseAllDescendents(nodes, disabled, jobid, disable);
+ recurseAllDescendents(node, disable);
- executableGraphModel.set({disabled: disabled});
executableGraphModel.trigger("change:disabled");
}
-var exNodeClickCallback = function(event) {
+var gatherDisabledNodes = function(data) {
+ var nodes = data.nodes;
+ var disabled = [];
+
+ for (var i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+ if (node.disabled) {
+ disabled.push(node.id);
+ }
+ else {
+ if (node.flowData) {
+ var array = gatherDisabledNodes(node.flowData);
+ if (array && array.length > 0) {
+ disabled.push({id: node.id, children: array});
+ }
+ }
+ }
+ }
+
+ return disabled;
+}
+
+function recurseAllAncestors(node, disable) {
+ var inNodes = node.inNodes;
+ if (inNodes) {
+ for (var key in inNodes) {
+ inNodes[key].disabled = disable;
+ recurseAllAncestors(inNodes[key], disable);
+ }
+ }
+}
+
+function recurseAllDescendents(node, disable) {
+ var outNodes = node.outNodes;
+ if (outNodes) {
+ for (var key in outNodes) {
+ outNodes[key].disabled = disable;
+ recurseAllDescendents(outNodes[key], disable);
+ }
+ }
+}
+
+var exNodeClickCallback = function(event, model, node) {
console.log("Node clicked callback");
- var jobId = event.currentTarget.jobid;
+ var jobId = node.id;
var flowId = executableGraphModel.get("flowId");
- var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+ var type = node.type;
+
+ var menu;
+ if (type == "flow") {
+ var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + node.flowId;
+ if (node.expanded) {
+ menu = [
+ {title: "Collapse Flow...", callback: function() {model.trigger("collapseFlow", node);}},
+ {title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}}
+ ];
+
+ }
+ else {
+ menu = [
+ {title: "Expand Flow...", callback: function() {model.trigger("expandFlow", node);}},
+ {title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}}
+ ];
+ }
+ }
+ else {
+ var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
+ menu = [
+ {title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+ ];
+ }
- var menu = [
- {title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
+ $.merge(menu, [
{break: 1},
- {title: "Enable", callback: function() {touchNode(jobId, false);}, submenu: [
- {title: "Parents", callback: function(){touchParents(jobId, false);}},
- {title: "Ancestors", callback: function(){touchAncestors(jobId, false);}},
- {title: "Children", callback: function(){touchChildren(jobId, false);}},
- {title: "Descendents", callback: function(){touchDescendents(jobId, false);}},
+ {title: "Enable", callback: function() {touchNode(node, false);}, submenu: [
+ {title: "Parents", callback: function(){touchParents(node, false);}},
+ {title: "Ancestors", callback: function(){touchAncestors(node, false);}},
+ {title: "Children", callback: function(){touchChildren(node, false);}},
+ {title: "Descendents", callback: function(){touchDescendents(node, false);}},
{title: "Enable All", callback: function(){enableAll();}}
]},
- {title: "Disable", callback: function() {touchNode(jobId, true)}, submenu: [
- {title: "Parents", callback: function(){touchParents(jobId, true);}},
- {title: "Ancestors", callback: function(){touchAncestors(jobId, true);}},
- {title: "Children", callback: function(){touchChildren(jobId, true);}},
- {title: "Descendents", callback: function(){touchDescendents(jobId, true);}},
+ {title: "Disable", callback: function() {touchNode(node, true)}, submenu: [
+ {title: "Parents", callback: function(){touchParents(node, true);}},
+ {title: "Ancestors", callback: function(){touchAncestors(node, true);}},
+ {title: "Children", callback: function(){touchChildren(node, true);}},
+ {title: "Descendents", callback: function(){touchDescendents(node, true);}},
{title: "Disable All", callback: function(){disableAll();}}
- ]}
- ];
+ ]},
+ {title: "Center Job", callback: function() {model.trigger("centerNode", node);}}
+ ]);
+
contextMenuView.show(event, menu);
}
src/web/js/azkaban.flow.job.view.js 38(+23 -15)
diff --git a/src/web/js/azkaban.flow.job.view.js b/src/web/js/azkaban.flow.job.view.js
index 4cf6c13..c987a90 100644
--- a/src/web/js/azkaban.flow.job.view.js
+++ b/src/web/js/azkaban.flow.job.view.js
@@ -33,6 +33,7 @@ azkaban.JobListView = Backbone.View.extend({
this.list = $(this.el).find("#joblist");
this.contextMenu = settings.contextMenuCallback;
this.listNodes = {};
+
},
filterJobs: function(self) {
var filter = this.filterInput.val();
@@ -110,12 +111,14 @@ azkaban.JobListView = Backbone.View.extend({
var node = data.nodes[i];
if (node.status) {
var liElement = node.listElement;
- $(liElement).removeClass(statusList.join(' '));
- $(liElement).addClass(node.status);
+ var child = $(liElement).children("a");
+ $(child).removeClass(statusList.join(' '));
+ $(child).addClass(node.status);
+ $(child).attr("title", node.status + " (" + node.type + ")");
}
- if (node.flowData) {
- this.changeStatuses(node.flowData);
+ if (node.type == "flow") {
+ this.changeStatuses(node);
}
}
},
@@ -126,7 +129,9 @@ azkaban.JobListView = Backbone.View.extend({
this.renderTree(this.list, data);
//
// this.assignInitialStatus(self);
-// this.handleDisabledChange(self);
+ this.handleDisabledChange(self);
+ this.changeStatuses(data);
+ $("li.listElement > a").tooltip({delay: {show: 500, hide: 100}, placement: 'top'});
},
renderTree : function(el, data, prefix) {
var nodes = data.nodes;
@@ -174,14 +179,14 @@ azkaban.JobListView = Backbone.View.extend({
$(li).append(a);
$(ul).append(li);
- if (nodeArray[i].flowData) {
+ if (nodeArray[i].type == "flow") {
// Add the up down
var expandDiv = document.createElement("div");
$(expandDiv).addClass("expandarrow glyphicon glyphicon-chevron-down");
$(a).append(expandDiv);
// Create subtree
- var subul = this.renderTree(li, nodeArray[i].flowData, listNodeName + ":");
+ var subul = this.renderTree(li, nodeArray[i], listNodeName + ":");
$(subul).hide();
}
}
@@ -245,18 +250,21 @@ azkaban.JobListView = Backbone.View.extend({
else {
this.model.set({"selected": node});
}
-
},
handleDisabledChange: function(evt) {
- var disabledMap = this.model.get("disabled");
- var nodes = this.model.get("nodes");
-
- for(var id in nodes) {
- if (disabledMap[id]) {
- $(this.listNodes[id]).addClass("nodedisabled");
+ this.changeDisabled(this.model.get('data'));
+ },
+ changeDisabled: function(data) {
+ for (var i =0; i < data.nodes; ++i) {
+ var node = data.nodes[i];
+ if (node.disabled = true) {
+ removeClass(node.listElement, "nodedisabled");
+ if (node.type=='flow') {
+ this.changeDisabled(node);
+ }
}
else {
- $(this.listNodes[id]).removeClass("nodedisabled");
+ addClass(node.listElement, "nodedisabled");
}
}
},
src/web/js/azkaban.flow.view.js 56(+22 -34)
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index a6fd36e..d0c7e7f 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -16,19 +16,6 @@
$.namespace('azkaban');
-var statusStringMap = {
- "FAILED": "Failed",
- "SUCCEEDED": "Success",
- "FAILED_FINISHING": "Running w/Failure",
- "RUNNING": "Running",
- "WAITING": "Waiting",
- "KILLED": "Killed",
- "DISABLED": "Disabled",
- "READY": "Ready",
- "UNKNOWN": "Unknown",
- "QUEUED": "Queued"
-};
-
var handleJobMenuClick = function(action, el, pos) {
var jobid = el[0].jobid;
var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" +
@@ -302,28 +289,29 @@ azkaban.SummaryView = Backbone.View.extend({
this.model.bind('render', this.render, this);
this.fetchDetails();
- this.fetchSchedule();
+ this.fetchSchedule();
this.fetchLastRun();
this.model.trigger('render');
},
- fetchDetails: function() {
- var requestURL = contextURL + "/manager";
- var requestData = {
- 'ajax': 'fetchflowdetails',
- 'project': projectName,
- 'flow': flowId
- };
+ fetchDetails: function() {
+ var requestURL = contextURL + "/manager";
+ var requestData = {
+ 'ajax': 'fetchflowdetails',
+ 'project': projectName,
+ 'flow': flowId
+ };
+
var model = this.model;
- var successHandler = function(data) {
- console.log(data);
- model.set({
- 'jobTypes': data.jobTypes
- });
- model.trigger('render');
- };
- $.get(requestURL, requestData, successHandler, 'json');
- },
+ var successHandler = function(data) {
+ console.log(data);
+ model.set({
+ 'jobTypes': data.jobTypes
+ });
+ model.trigger('render');
+ };
+ $.get(requestURL, requestData, successHandler, 'json');
+ },
fetchSchedule: function() {
var requestURL = contextURL + "/schedule"
@@ -334,8 +322,8 @@ azkaban.SummaryView = Backbone.View.extend({
};
var model = this.model;
var successHandler = function(data) {
- model.set({'schedule': data.schedule});
- model.trigger('render');
+ model.set({'schedule': data.schedule});
+ model.trigger('render');
};
$.get(requestURL, requestData, successHandler, 'json');
},
@@ -349,9 +337,9 @@ azkaban.SummaryView = Backbone.View.extend({
render: function(evt) {
var data = {
- projectName: projectName,
+ projectName: projectName,
flowName: flowId,
- jobTypes: this.model.get('jobTypes'),
+ jobTypes: this.model.get('jobTypes'),
general: this.model.get('general'),
schedule: this.model.get('schedule'),
lastRun: this.model.get('lastRun')
src/web/js/azkaban.project.view.js 4(+2 -2)
diff --git a/src/web/js/azkaban.project.view.js b/src/web/js/azkaban.project.view.js
index ef01a0e..ccfeeb5 100644
--- a/src/web/js/azkaban.project.view.js
+++ b/src/web/js/azkaban.project.view.js
@@ -81,8 +81,8 @@ azkaban.FlowTableView = Backbone.View.extend({
var level = job.level;
var nodeId = flowId + "-" + name;
- var li = document.createElement('li');
- $(li).addClass("list-group-item");
+ var li = document.createElement('li');
+ $(li).addClass("list-group-item");
$(li).attr("id", nodeId);
li.flowId = flowId;
li.dependents = job.dependents;
src/web/js/azkaban.svg.flow.loader.js 20(+15 -5)
diff --git a/src/web/js/azkaban.svg.flow.loader.js b/src/web/js/azkaban.svg.flow.loader.js
index a3cbc71..24098fa 100644
--- a/src/web/js/azkaban.svg.flow.loader.js
+++ b/src/web/js/azkaban.svg.flow.loader.js
@@ -1,3 +1,16 @@
+var statusStringMap = {
+ "FAILED": "Failed",
+ "SUCCEEDED": "Success",
+ "FAILED_FINISHING": "Running w/Failure",
+ "RUNNING": "Running",
+ "WAITING": "Waiting",
+ "KILLED": "Killed",
+ "DISABLED": "Disabled",
+ "READY": "Ready",
+ "UNKNOWN": "Unknown",
+ "QUEUED": "Queued"
+};
+
var extendedViewPanels = {};
var extendedDataModels = {};
var openJobDisplayCallback = function(nodeId, flowId, evt) {
@@ -81,10 +94,8 @@ var processFlowData = function(data) {
for (var key in nodes) {
var node = nodes[key];
node.parent = data;
- if (node.type == "flow" && node.flowData) {
- processFlowData(node.flowData);
- // Weird cycle. Evaluate whether we can instead unwrap these things.
- node.flowData.node = node;
+ if (node.type == "flow") {
+ processFlowData(node);
}
}
@@ -92,7 +103,6 @@ var processFlowData = function(data) {
console.log("data fetched");
data.nodeMap = nodes;
data.edges = edges;
- data.disabled = {};
}
var closeAllSubDisplays = function() {
src/web/js/azkaban.svg.graph.view.js 102(+50 -52)
diff --git a/src/web/js/azkaban.svg.graph.view.js b/src/web/js/azkaban.svg.graph.view.js
index cf6d2c4..968de08 100644
--- a/src/web/js/azkaban.svg.graph.view.js
+++ b/src/web/js/azkaban.svg.graph.view.js
@@ -19,7 +19,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
events: {
},
- initialize: function(settings) {
+ initialize: function(settings) {
this.model.bind('change:selected', this.changeSelected, this);
this.model.bind('centerNode', this.centerNode, this);
this.model.bind('change:graph', this.render, this);
@@ -136,10 +136,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
bounds.maxY = bounds.maxY ? bounds.maxY + margin : margin;
this.assignInitialStatus(this, data);
-
- if (data.disabled && data.disabled.length > 0) {
- this.handleDisabledChange(self);
- }
if (self.rightClick) {
if (self.rightClick.node) {
@@ -162,24 +158,31 @@ azkaban.SvgGraphView = Backbone.View.extend({
return false;
});
}
-
};
+
+ $(".node").each(
+ function(d,i){
+ $(this).tooltip({container:"body", delay: {show: 500, hide: 100}});
+ });
return bounds;
},
-
- handleDisabledChange: function(evt) {
- var disabledMap = this.model.get("disabled");
-
- for(var id in this.nodes) {
- var g = this.gNodes[id];
- if (disabledMap[id]) {
- this.nodes[id].disabled = true;
- addClass(g, "disabled");
+ handleDisabledChange: function(evt) {
+ this.changeDisabled(this.model.get('data'));
+ },
+ changeDisabled: function(data) {
+ for (var i =0; i < data.nodes.length; ++i) {
+ var node = data.nodes[i];
+ if (node.disabled) {
+ addClass(node.gNode, "nodeDisabled");
}
else {
- this.nodes[id].disabled = false;
- removeClass(g, "disabled");
+ if (node.gNode) {
+ removeClass(node.gNode, "nodeDisabled");
+ }
+ if (node.type=='flow') {
+ this.changeDisabled(node);
+ }
}
}
},
@@ -188,9 +191,13 @@ azkaban.SvgGraphView = Backbone.View.extend({
var updateNode = data.nodes[i];
var g = updateNode.gNode;
var initialStatus = updateNode.status ? updateNode.status : "READY";
-
+
addClass(g, initialStatus);
- $(g).attr("title", initialStatus);
+ $(g).attr("title", updateNode.status + " (" + updateNode.type + ")");
+
+ if (updateNode.disabled) {
+ addClass(g, "nodeDisabled");
+ }
}
},
changeSelected: function(self) {
@@ -215,28 +222,32 @@ azkaban.SvgGraphView = Backbone.View.extend({
}
},
propagateExpansion: function(node) {
- if (node.parent) {
- if (node.parent.node) {
- this.propagateExpansion(node.parent.node);
- this.expandFlow(node.parent.node);
- }
+ if (node.parent.type) {
+ this.propagateExpansion(node.parent);
+ this.expandFlow(node.parent);
}
},
handleStatusUpdate: function(evt) {
var updateData = this.model.get("update");
- if (updateData.nodes) {
- for (var i = 0; i < updateData.nodes.length; ++i) {
- var updateNode = updateData.nodes[i];
+ this.updateStatusChanges(updatedData);
+ },
+ updateStatusChanges: function(changedData) {
+ // Assumes all changes have been applied.
+ if (changedData.nodes) {
+ var nodeMap = previousData.nodeMap;
+ for (var i = 0; i < changedData.nodes.length; ++i) {
+ var node = changedData.nodes[i];
+ var nodeToUpdate = nodeMap[updateNode.id];
- var g = this.gNodes[updateNode.id];
+ var g = nodeToUpdate.gNode;
this.handleRemoveAllStatus(g);
+ addClass(g, nodeToUpdate.status);
+ $(g).attr("title", updateNode.status + " (" + updateNode.type + ")");
- addClass(g, updateNode.status);
- $(g).attr("title", updateNode.status);
+ this.updateStatusChanges(node);
}
}
},
-
handleRemoveAllStatus: function(gNode) {
for (var j = 0; j < statusList.length; ++j) {
var status = statusList[j];
@@ -244,13 +255,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
}
},
- clickGraph: function(self) {
- console.log("click");
- if (self.currentTarget.data) {
- this.model.set({"selected": self.currentTarget.data});
- }
- },
-
handleRightClick: function(self) {
if (this.rightClick) {
var callbacks = this.rightClick;
@@ -323,7 +327,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
var innerG = gnode.innerG;
var borderRect = innerG.borderRect;
var labelG = innerG.labelG;
- var flowData = node.flowData;
var bbox;
if (!innerG.expandedFlow) {
@@ -331,7 +334,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
var hmargin = 10;
var expandedFlow = svg.group(innerG, "", {class: "expandedGraph"});
- this.renderGraph(flowData, expandedFlow);
+ this.renderGraph(node, expandedFlow);
innerG.expandedFlow = expandedFlow;
removeClass(innerG, "collapsed");
addClass(innerG, "expanded");
@@ -366,7 +369,6 @@ azkaban.SvgGraphView = Backbone.View.extend({
var innerG = gnode.innerG;
var borderRect = innerG.borderRect;
var labelG = innerG.labelG;
- var flowData = node.flowData;
removeClass(innerG, "expanded");
addClass(innerG, "collapsed");
@@ -395,15 +397,11 @@ azkaban.SvgGraphView = Backbone.View.extend({
var parent = node.parent;
if (parent) {
layoutGraph(parent.nodes, parent.edges, 10);
- if (parent.node) {
- this.relayoutFlow(parent.node);
- }
+ this.relayoutFlow(parent);
+ // Move all points again.
+ this.moveNodeEdges(parent.nodes, parent.edges);
+ this.animateExpandedFlowNode(node, 250);
}
-
- // Move all points again.
- this.moveNodeEdges(parent.nodes, parent.edges);
- this.animateExpandedFlowNode(node, 250);
-
},
moveNodeEdges: function(nodes, edges) {
var svg = this.svg;
@@ -563,7 +561,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
var labelG = innerG.labelG;
var expandedFlow = innerG.expandedFlow;
- var bound = this.calculateBounds(node.flowData.nodes);
+ var bound = this.calculateBounds(node.nodes);
node.height = bound.height + topmargin + bottommargin;
node.width = bound.width + hmargin*2;
@@ -601,9 +599,9 @@ azkaban.SvgGraphView = Backbone.View.extend({
this.panZoom({x: x, y: y, width: node.width, height: node.height});
},
globalNodePosition: function(gNode) {
- if (node.parent.node) {
+ if (node.parent) {
- var parentPos = this.globalNodePosition(node.parent.node);
+ var parentPos = this.globalNodePosition(node.parent);
return {x: parentPos.x + node.x, y: parentPos.y + node.y};
}
else {
diff --git a/unit/java/azkaban/test/executor/ExecutableFlowTest.java b/unit/java/azkaban/test/executor/ExecutableFlowTest.java
index 9031518..9f2a3c2 100644
--- a/unit/java/azkaban/test/executor/ExecutableFlowTest.java
+++ b/unit/java/azkaban/test/executor/ExecutableFlowTest.java
@@ -108,7 +108,7 @@ public class ExecutableFlowTest {
ExecutionOptions options = new ExecutionOptions();
options.setConcurrentOption("blah");
- options.setDisabledJobs(Arrays.asList(new String[] {"bee", null, "boo"}));
+ options.setDisabledJobs(Arrays.asList(new Object[] {"bee", null, "boo"}));
options.setFailureAction(FailureAction.CANCEL_ALL);
options.setFailureEmails(Arrays.asList(new String[] {"doo", null, "daa"}));
options.setSuccessEmails(Arrays.asList(new String[] {"dee", null, "dae"}));
@@ -287,7 +287,7 @@ public class ExecutableFlowTest {
Assert.assertEquals(optionsA.isFailureEmailsOverridden(), optionsB.isFailureEmailsOverridden());
Assert.assertEquals(optionsA.isSuccessEmailsOverridden(), optionsB.isSuccessEmailsOverridden());
- testEquals(optionsA.getDisabledJobs(), optionsB.getDisabledJobs());
+ testDisabledEquals(optionsA.getDisabledJobs(), optionsB.getDisabledJobs());
testEquals(optionsA.getSuccessEmails(), optionsB.getSuccessEmails());
testEquals(optionsA.getFailureEmails(), optionsB.getFailureEmails());
testEquals(optionsA.getFlowParameters(), optionsB.getFlowParameters());
@@ -329,11 +329,43 @@ public class ExecutableFlowTest {
while(iterA.hasNext()) {
String aStr = iterA.next();
String bStr = iterB.next();
-
Assert.assertEquals(aStr, bStr);
}
}
+ @SuppressWarnings("unchecked")
+ public static void testDisabledEquals(List<Object> a, List<Object> b) {
+ if (a == b) {
+ return;
+ }
+
+ if (a == null || b == null) {
+ Assert.fail();
+ }
+
+ Assert.assertEquals(a.size(), b.size());
+
+ Iterator<Object> iterA = a.iterator();
+ Iterator<Object> iterB = b.iterator();
+
+ while(iterA.hasNext()) {
+ Object aStr = iterA.next();
+ Object bStr = iterB.next();
+
+ if (aStr instanceof Map && bStr instanceof Map) {
+ Map<String, Object> aMap = (Map<String, Object>)aStr;
+ Map<String, Object> bMap = (Map<String, Object>)bStr;
+
+ Assert.assertEquals((String)aMap.get("id"), (String)bMap.get("id"));
+ testDisabledEquals((List<Object>)aMap.get("children"), (List<Object>)bMap.get("children"));
+ }
+ else {
+ Assert.assertEquals(aStr, bStr);
+ }
+ }
+ }
+
+
public static void testEquals(Map<String, String> a, Map<String, String> b) {
if (a == b) {
return;
diff --git a/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java b/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java
index 905ca3d..4c49dab 100644
--- a/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java
+++ b/unit/java/azkaban/test/trigger/ExecuteFlowActionTest.java
@@ -21,7 +21,7 @@ public class ExecuteFlowActionTest {
loader.init(new Props());
ExecutionOptions options = new ExecutionOptions();
- List<String> disabledJobs = new ArrayList<String>();
+ List<Object> disabledJobs = new ArrayList<Object>();
options.setDisabledJobs(disabledJobs);
ExecuteFlowAction executeFlowAction = new ExecuteFlowAction("ExecuteFlowAction", 1, "testproject", "testflow", "azkaban", options, null);