azkaban-developers

flow trigger UI (#1648) This PR adds an UI page to display current

2/21/2018 4:44:39 PM

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">&times;</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">&times;
+          </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>