Details
diff --git a/azkaban-web-server/src/main/java/azkaban/flowtrigger/quartz/FlowTriggerScheduler.java b/azkaban-web-server/src/main/java/azkaban/flowtrigger/quartz/FlowTriggerScheduler.java
index c794213..04fae28 100644
--- a/azkaban-web-server/src/main/java/azkaban/flowtrigger/quartz/FlowTriggerScheduler.java
+++ b/azkaban-web-server/src/main/java/azkaban/flowtrigger/quartz/FlowTriggerScheduler.java
@@ -29,6 +29,7 @@ import azkaban.scheduler.QuartzJobDescription;
import azkaban.scheduler.QuartzScheduler;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
+import com.google.gson.GsonBuilder;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -203,6 +204,11 @@ public class FlowTriggerScheduler {
return this.flowTrigger;
}
+ public String getDependencyListJson() {
+ return new GsonBuilder().setPrettyPrinting().create()
+ .toJson(this.flowTrigger.getDependencies());
+ }
+
public Trigger getQuartzTrigger() {
return this.quartzTrigger;
}
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java b/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
index 52d831c..6d527ae 100644
--- a/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
@@ -55,6 +55,7 @@ import azkaban.webapp.plugin.ViewerPlugin;
import azkaban.webapp.servlet.AbstractAzkabanServlet;
import azkaban.webapp.servlet.ExecutorServlet;
import azkaban.webapp.servlet.FlowTriggerInstanceServlet;
+import azkaban.webapp.servlet.FlowTriggerServlet;
import azkaban.webapp.servlet.HistoryServlet;
import azkaban.webapp.servlet.IndexRedirectServlet;
import azkaban.webapp.servlet.JMXHttpServlet;
@@ -508,6 +509,7 @@ public class AzkabanWebServer extends AzkabanServer {
root.addServlet(new ServletHolder(new StatusServlet(this.statusService)), "/status");
root.addServlet(new ServletHolder(new NoteServlet()), "/notes");
root.addServlet(new ServletHolder(new FlowTriggerInstanceServlet()), "/flowtriggerinstance");
+ root.addServlet(new ServletHolder(new FlowTriggerServlet()), "/flowtrigger");
final ServletHolder restliHolder = new ServletHolder(new RestliServlet());
restliHolder.setInitParameter("resourcePackages", "azkaban.restli");
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/FlowTriggerServlet.java b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/FlowTriggerServlet.java
new file mode 100644
index 0000000..4aef3bc
--- /dev/null
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/FlowTriggerServlet.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 LinkedIn Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package azkaban.webapp.servlet;
+
+import azkaban.flowtrigger.quartz.FlowTriggerScheduler;
+import azkaban.server.session.Session;
+import azkaban.webapp.AzkabanWebServer;
+import java.io.IOException;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class FlowTriggerServlet extends LoginAbstractAzkabanServlet {
+
+ private static final long serialVersionUID = 1L;
+ private FlowTriggerScheduler scheduler;
+
+ @Override
+ public void init(final ServletConfig config) throws ServletException {
+ super.init(config);
+ final AzkabanWebServer server = (AzkabanWebServer) getApplication();
+ this.scheduler = server.getScheduler();
+ }
+
+ @Override
+ protected void handleGet(final HttpServletRequest req, final HttpServletResponse resp,
+ final Session session) throws ServletException, IOException {
+ handlePage(req, resp, session);
+ }
+
+ private void handlePage(final HttpServletRequest req,
+ final HttpServletResponse resp, final Session session) {
+ final Page page =
+ newPage(req, resp, session,
+ "azkaban/webapp/servlet/velocity/flowtriggerspage.vm");
+
+ page.add("flowTriggers", this.scheduler.getScheduledFlowTriggerJobs());
+ page.render();
+ }
+
+ @Override
+ protected void handlePost(final HttpServletRequest req, final HttpServletResponse resp,
+ final Session session) throws ServletException, IOException {
+ }
+}
diff --git a/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowtriggerspage.vm b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowtriggerspage.vm
new file mode 100644
index 0000000..383d17e
--- /dev/null
+++ b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowtriggerspage.vm
@@ -0,0 +1,168 @@
+#*
+ * Copyright 2018 LinkedIn Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+
+ #parse("azkaban/webapp/servlet/velocity/style.vm")
+ #parse("azkaban/webapp/servlet/velocity/javascript.vm")
+ <script type="text/javascript" src="${context}/js/azkaban/util/ajax.js"></script>
+ #
+ <script type="text/javascript" src="${context}/js/azkaban/view/executions.js"></script>
+ <script type="text/javascript" src="${context}/js/jquery/jquery.tablesorter.js"></script>
+ <script type="text/javascript">
+ var contextURL = "${context}";
+ var currentTime = ${currentTime};
+ var timezone = "${timezone}";
+ var errorMessage = null;
+ var successMessage = null;
+
+ $(document).ready(function () {
+ var jobTable = $("#scheduleFlowTriggers");
+ jobTable.tablesorter();
+ });
+ </script>
+</head>
+<body>
+
+ #set ($current_page="executing")
+ #parse ("azkaban/webapp/servlet/velocity/nav.vm")
+
+## Page header.
+
+<div class="az-page-header">
+ <div class="container-full">
+ <h1><a href="${context}/executor">Scheduled Flow Triggers</a></h1>
+ </div>
+</div>
+
+<div class="container-full">
+
+ #parse ("azkaban/webapp/servlet/velocity/alerts.vm")
+
+## Page Content
+
+ <ul class="nav nav-tabs nav-sm" id="header-tabs">
+ <li id="currently-running-view-link"><a href="#currently-running">Flow Triggers</a></li>
+ </ul>
+
+ <div class="row" id="currently-running-view">
+ <div class="col-xs-12">
+ <table id="scheduleFlowTriggers"
+ class="table table-striped table-bordered table-hover table-condensed executions-table">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Flow</th>
+ <th>Project</th>
+ <th>Submitted by</th>
+ <th>First Schedule to Run</th>
+ <th>Next Execution Time</th>
+ <th>Schedule</th>
+ <th>Max Wait Mins</th>
+ <th>Dependencies</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ #if ( !$null.isNull(${flowTriggers}))
+ #foreach ($trigger in $flowTriggers)
+ <tr class="parent" id=${trigger.getId()}>
+ <td class="tb-name">
+ $velocityCount
+ </td>
+ #*todo chengren311: keep result of vmutils.getProjectName as a variable *#
+ <td><a
+ href="${context}/manager?project=${trigger.getProjectName()}&flow=${trigger.getFlowId()}">${trigger.getFlowId()}</a>
+ </td>
+ <td>
+ <a href="${context}/manager?project=${trigger.getProjectName()}">${trigger.getProjectName()}</a>
+ </td>
+
+ <td>${trigger.getSubmitUser()}</td>
+
+ <td>$utils.formatDate(${trigger.getQuartzTrigger().getStartTime().getTime()})</td>
+ <td>$utils.formatDate(${trigger.getQuartzTrigger().getNextFireTime().getTime()})</td>
+
+ <td>${trigger.getFlowTrigger().getSchedule().getCronExpression()}</td>
+ <td>${trigger.getFlowTrigger().getMaxWaitDuration().toMinutes()}</td>
+
+
+ ## adopted from template to show executionOption in scheduledflowpage.vm
+ <td>
+ ## Changed the style of "Show" button to be consistent with other buttons.
+ <button type="button" class="btn btn-sm btn-info" data-toggle="modal"
+ data-target="#dependencyList-${velocityCount}">Show
+ </button>
+ </td>
+
+ <div class="modal fade" id="dependencyList-${velocityCount}" tabindex="-1"
+ role="dialog"
+ aria-labelledby="dependencyLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span></button>
+ <h4 class="modal-title" id="dependencyLabel">Dependencies</h4>
+ </div>
+ <div class="modal-body">
+ ## Used <pre> to display text with code format
+ <pre>${trigger.getDependencyListJson()}</pre>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </tr>
+ #end
+ #else
+ <tr>
+ <td colspan="10">No Scheduled Flow Trigger</td>
+ </tr>
+ #end
+ </tbody>
+ </table>
+ </div><!-- /col-xs-12 -->
+ </div><!-- /row -->
+
+ <div class="modal" id="messageDialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header" id="messageTitle">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×
+ </button>
+ <h4 class="modal-title">Error</h4>
+ </div>
+ <div class="modal-body" id="messageDiv">
+ <p id="messageBox"></p>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal"
+ onclick="window.location.reload(true);">Dismiss
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</div><!-- /container-full -->
+</body>
+</html>