azkaban-aplcache
Changes
src/less/azkaban.less 2(+1 -1)
src/less/base.less 21(+20 -1)
src/less/callout.less 2(+1 -1)
src/less/flow.less 117(+68 -49)
src/less/Makefile 2(+1 -1)
src/less/variables.less 12(+12 -0)
src/tl/flowstats.tl 22(+22 -0)
src/tl/flowsummary.tl 10(+5 -5)
src/web/js/azkaban/model/job-log.js 80(+80 -0)
src/web/js/azkaban/view/exflow.js 163(+82 -81)
src/web/js/azkaban/view/flow.js 149(+87 -62)
src/web/js/azkaban/view/flow-stats.js 71(+56 -15)
src/web/js/azkaban/view/job-details.js 268(+3 -265)
src/web/js/azkaban/view/time-graph.js 28(+18 -10)
Details
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index 2133db8..ebb0aa0 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -999,7 +999,7 @@ public class AzkabanWebServer extends AzkabanServer {
String pluginName = pluginProps.getString("viewer.name");
String pluginWebPath = pluginProps.getString("viewer.path");
- String pluginJobType = pluginProps.getString("viewer.jobtype", null);
+ String pluginJobTypes = pluginProps.getString("viewer.jobtypes", null);
int pluginOrder = pluginProps.getInt("viewer.order", 0);
boolean pluginHidden = pluginProps.getBoolean("viewer.hidden", false);
List<String> extLibClasspath = pluginProps.getStringList("viewer.external.classpaths", (List<String>)null);
@@ -1108,7 +1108,7 @@ public class AzkabanWebServer extends AzkabanServer {
pluginWebPath,
pluginOrder,
pluginHidden,
- pluginJobType));
+ pluginJobTypes));
}
// Velocity needs the jar resource paths to be set.
diff --git a/src/java/azkaban/webapp/plugin/PluginRegistry.java b/src/java/azkaban/webapp/plugin/PluginRegistry.java
index b9382b6..a6f74d2 100644
--- a/src/java/azkaban/webapp/plugin/PluginRegistry.java
+++ b/src/java/azkaban/webapp/plugin/PluginRegistry.java
@@ -24,34 +24,37 @@ import java.util.TreeSet;
public class PluginRegistry {
- private static PluginRegistry registry;
+ private static PluginRegistry registry;
- public TreeSet<ViewerPlugin> viewerPlugins;
+ public TreeSet<ViewerPlugin> viewerPlugins;
public Map<String, TreeSet<ViewerPlugin>> jobTypeViewerPlugins;
- private PluginRegistry() {
+ private PluginRegistry() {
viewerPlugins = new TreeSet<ViewerPlugin>(ViewerPlugin.COMPARATOR);
jobTypeViewerPlugins = new HashMap<String, TreeSet<ViewerPlugin>>();
- }
+ }
- public void register(ViewerPlugin plugin) {
+ public void register(ViewerPlugin plugin) {
viewerPlugins.add(plugin);
- String jobType = plugin.getJobType();
- if (jobType == null) {
+ List<String> jobTypes = plugin.getJobTypes();
+ if (jobTypes == null) {
return;
}
- TreeSet<ViewerPlugin> plugins = null;
- if (!jobTypeViewerPlugins.containsKey(jobType)) {
- plugins = new TreeSet<ViewerPlugin>(ViewerPlugin.COMPARATOR);
- plugins.add(plugin);
- jobTypeViewerPlugins.put(jobType, plugins);
- }
- else {
- plugins = jobTypeViewerPlugins.get(jobType);
- plugins.add(plugin);
+
+ for (String jobType : jobTypes) {
+ TreeSet<ViewerPlugin> plugins = null;
+ if (!jobTypeViewerPlugins.containsKey(jobType)) {
+ plugins = new TreeSet<ViewerPlugin>(ViewerPlugin.COMPARATOR);
+ plugins.add(plugin);
+ jobTypeViewerPlugins.put(jobType, plugins);
+ }
+ else {
+ plugins = jobTypeViewerPlugins.get(jobType);
+ plugins.add(plugin);
+ }
}
- }
+ }
public List<ViewerPlugin> getViewerPlugins() {
return new ArrayList<ViewerPlugin>(viewerPlugins);
@@ -65,10 +68,10 @@ public class PluginRegistry {
return new ArrayList<ViewerPlugin>(plugins);
}
- public static PluginRegistry getRegistry() {
- if (registry == null) {
- registry = new PluginRegistry();
- }
- return registry;
- }
+ public static PluginRegistry getRegistry() {
+ if (registry == null) {
+ registry = new PluginRegistry();
+ }
+ return registry;
+ }
}
diff --git a/src/java/azkaban/webapp/plugin/ViewerPlugin.java b/src/java/azkaban/webapp/plugin/ViewerPlugin.java
index 9e167ff..b2bb851 100644
--- a/src/java/azkaban/webapp/plugin/ViewerPlugin.java
+++ b/src/java/azkaban/webapp/plugin/ViewerPlugin.java
@@ -16,14 +16,16 @@
package azkaban.webapp.plugin;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Comparator;
public class ViewerPlugin {
private final String pluginName;
private final String pluginPath;
- private final String jobType;
private final int order;
private boolean hidden;
+ private final List<String> jobTypes;
public static final Comparator<ViewerPlugin> COMPARATOR =
new Comparator<ViewerPlugin>() {
@@ -41,12 +43,12 @@ public class ViewerPlugin {
String pluginPath,
int order,
boolean hidden,
- String jobType) {
+ String jobTypes) {
this.pluginName = pluginName;
this.pluginPath = pluginPath;
this.order = order;
this.setHidden(hidden);
- this.jobType = jobType;
+ this.jobTypes = parseJobTypes(jobTypes);
}
public String getPluginName() {
@@ -69,7 +71,19 @@ public class ViewerPlugin {
this.hidden = hidden;
}
- public String getJobType() {
- return jobType;
+ protected List<String> parseJobTypes(String jobTypesStr) {
+ if (jobTypesStr == null) {
+ return null;
+ }
+ String[] parts = jobTypesStr.split(",");
+ List<String> jobTypes = new ArrayList<String>();
+ for (int i = 0; i < parts.length; ++i) {
+ jobTypes.add(parts[i].trim());
+ }
+ return jobTypes;
+ }
+
+ public List<String> getJobTypes() {
+ return jobTypes;
}
}
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 149ca0e..c8cbc5c 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -1,12 +1,12 @@
#*
* Copyright 2012 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
@@ -14,16 +14,18 @@
* the License.
*#
-<!DOCTYPE html>
+<!DOCTYPE html>
<html lang="en">
<head>
#parse("azkaban/webapp/servlet/velocity/style.vm")
#parse("azkaban/webapp/servlet/velocity/javascript.vm")
#parse("azkaban/webapp/servlet/velocity/svgflowincludes.vm")
+ <script type="text/javascript" src="${context}/js/raphael.min.js"></script>
+ <script type="text/javascript" src="${context}/js/morris.min.js"></script>
<script type="text/javascript" src="${context}/js/moment.min.js"></script>
<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
-
+
<script type="text/javascript" src="${context}/js/dust-full-2.2.3.min.js"></script>
<script type="text/javascript" src="${context}/js/flowstats.js"></script>
<script type="text/javascript" src="${context}/js/flowstats-no-data.js"></script>
@@ -43,7 +45,7 @@
var flowId = "${flowid}";
var execId = "${execid}";
</script>
-
+ <link rel="stylesheet" type="text/css" href="${context}/css/morris.css" />
<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
</head>
<body>
@@ -97,7 +99,7 @@
<div class="container-full">
#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
-
+
## Tabs and buttons.
<ul class="nav nav-tabs nav-sm" id="headertabs">
@@ -116,7 +118,7 @@
## Graph View
#parse ("azkaban/webapp/servlet/velocity/flowgraphview.vm")
-
+
## Job List View
<div class="container-full" id="jobListView">
@@ -170,7 +172,7 @@
<div id="flow-stats-container">
<div class="row">
<div class="col-lg-12">
- <div class="alert alert-default">
+ <div class="callout callout-default">
<h4>No stats available</h4>
<p>Stats for this flow execution are not available.</p>
</div>
@@ -178,7 +180,7 @@
</div>
</div><!-- /.row -->
</div><!-- /.container-fill -->
-
+
## Error message message dialog.
<div class="container-full">
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm b/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
index fdc224f..04b71a4 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowgraphview.vm
@@ -15,17 +15,20 @@
*#
## Graph view.
+
<div class="container-full container-fill" id="graphView">
- <div class="glyphicon glyphicon-th-list" id="open-joblist-btn" title="Open Job List Panel"></div>
- <div class="graph-sidebar">
+ <div class="graph-sidebar-open" id="open-joblist-btn">
+ <span class="glyphicon glyphicon-th-list" title="Open Job List Panel"></span>
+ </div>
+ <div class="graph-sidebar-float">
<div class="panel panel-default" id="joblist-panel">
<div class="panel-heading">
- <div id="close-btn" title="Close Panel"><span class="glyphicon glyphicon-remove"></span></div>
- <div id="inputbox-panel">
+ <div class="graph-sidebar-close" id="close-btn" title="Close Panel"><span class="glyphicon glyphicon-remove"></span></div>
+ <div class="graph-sidebar-search">
<input id="filter" type="text" placeholder="Job Filter" class="form-control input-sm">
</div>
</div>
- <div id="joblist"></div>
+ <div id="joblist" class="graph-sidebar-list"></div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default" id="resetPanZoomBtn">Reset Pan Zoom</button>
<button type="button" class="btn btn-sm btn-default" id="autoPanZoomBtn" data-toggle="button">Auto Pan Zoom</button>
@@ -33,9 +36,9 @@
</div><!-- /.panel -->
</div>
<div class="col-content">
- <div id="svgDiv" class="well well-clear well-sm">
+ <div id="svgDiv" class="well well-clear well-sm graph-container">
<svg id="flow-graph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed">
</svg>
</div>
</div>
- </div>
\ No newline at end of file
+ </div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index acb0573..0394da9 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -1,12 +1,12 @@
#*
* Copyright 2012 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
@@ -14,7 +14,7 @@
* the License.
*#
-<!DOCTYPE html>
+<!DOCTYPE html>
<html lang="en">
<head>
@@ -25,12 +25,12 @@
<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript" src="${context}/js/raphael.min.js"></script>
<script type="text/javascript" src="${context}/js/morris.min.js"></script>
-
+
<script type="text/javascript" src="${context}/js/dust-full-2.2.3.min.js"></script>
<script type="text/javascript" src="${context}/js/flowsummary.js"></script>
<script type="text/javascript" src="${context}/js/flowstats-no-data.js"></script>
<script type="text/javascript" src="${context}/js/flowstats.js"></script>
-
+
<script type="text/javascript" src="${context}/js/azkaban/view/time-graph.js"></script>
<script type="text/javascript" src="${context}/js/azkaban/util/schedule.js"></script>
<script type="text/javascript" src="${context}/js/azkaban/view/schedule-sla.js"></script>
@@ -43,7 +43,7 @@
var timezone = "${timezone}";
var errorMessage = null;
var successMessage = null;
-
+
var projectId = ${project.id};
var projectName = "${project.name}";
var flowId = "${flowid}";
@@ -60,7 +60,7 @@
#if ($errorMsg)
#parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
#else
-
+
## Page header.
<div class="az-page-header page-header-bare">
@@ -87,9 +87,9 @@
</div>
<div class="container-full">
-
+
#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
-
+
## Tabs
<ul class="nav nav-tabs nav-sm" id="headertabs">
@@ -98,7 +98,7 @@
<li id="summaryViewLink"><a href="#summary">Summary</a></li>
</ul>
</div>
-
+
## Graph view.
#parse ("azkaban/webapp/servlet/velocity/flowgraphview.vm")
@@ -149,7 +149,7 @@
<div class="col-xs-12">
<div class="callout callout-info">
<h4>Analyze last run</h4>
- <p>Analyze the last run for aggregate performance statistics. <strong>Note:</strong> this may take a few minutes, especially if your flow is large.</p>
+ <p>Analyze the last run for aggregate performance statistics. Note: this may take a few minutes, especially if your flow is large.</p>
<p>
<button type="button" id="analyze-btn" class="btn btn-primary">Analyze</button>
</p>
diff --git a/src/java/azkaban/webapp/servlet/velocity/index.vm b/src/java/azkaban/webapp/servlet/velocity/index.vm
index 3d020b3..a4fc481 100644
--- a/src/java/azkaban/webapp/servlet/velocity/index.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/index.vm
@@ -110,7 +110,7 @@
#end
</ul>
#else
- <div class="alert alert-default">
+ <div class="callout callout-default">
<h4>No Viewable Projects</h4>
<p>Click Create Project to create a new project.</p>
</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobdetailsheader.vm b/src/java/azkaban/webapp/servlet/velocity/jobdetailsheader.vm
index 27863ea..8d11704 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobdetailsheader.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobdetailsheader.vm
@@ -49,11 +49,9 @@
<ul class="nav nav-tabs nav-sm" id="headertabs">
#if ($current_page == "executing")
- <li id="jobLogViewLink"><a href="#logs">Job Logs</a></li>
- <li id="jobSummaryViewLink"><a href="#summary">Summary</a></li>
+ <li class="active" id="jobLogViewLink"><a href="#logs">Job Logs</a></li>
#else
<li id="jobLogViewLink"><a href="${context}/executor?execid=${execid}&job=${jobid}#logs">Job Logs</a></li>
- <li id="jobSummaryViewLink"><a href="${context}/executor?execid=${execid}&job=${jobid}#summary">Summary</a></li>
#end
#foreach ($jobViewerPlugin in $jobViewerPlugins)
<li#if($current_page == $jobViewerPlugin.pluginName) class="active"#end><a href="$!context/${jobViewerPlugin.pluginPath}?execid=${execid}&jobid=${jobid}">$jobViewerPlugin.pluginName</a></li>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm b/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
index 27f5d40..80d90e1 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobdetailspage.vm
@@ -22,7 +22,7 @@
#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/model/log-data.js"></script>
+ <script type="text/javascript" src="${context}/js/azkaban/model/job-log.js"></script>
<script type="text/javascript" src="${context}/js/azkaban/view/job-details.js"></script>
<script type="text/javascript">
var contextURL = "${context}";
@@ -68,75 +68,10 @@
</div>
</div>
</div>
- </div>
- </div>
-
- ## Job Summary
-
- <div class="container-full" id="jobSummaryView">
- <div class="row">
- <div class="col-lg-12">
- <h3>
- Job Summary
- <div class="pull-right">
- <button type="button" id="updateSummaryBtn" class="btn btn-xs btn-default">Refresh</button>
- </div>
- </h3>
-
- <div id="jobType">
- <table id="jobTypeTable" class="table table-striped table-bordered table-hover">
- </table>
- </div>
-
- <div id="command-summary">
- <h4>Command Summary</h4>
- <table id="commandTable" class="table table-striped table-bordered table-hover">
- </table>
- </div>
-
- <div id="pigJobSummary">
- <h4>Pig Job Summary</h4>
- <table class="table table-striped table-bordered table-hover">
- <thead id="summaryHeader">
- </thead>
- <tbody id="summaryBody">
- </tbody>
- </table>
- </div>
-
- <div id="pigJobStats">
- <h4>Pig Job Stats</h4>
- <div class="panel-body-stats">
- <table class="table table-striped table-bordered table-hover table-condensed">
- <thead id="statsHeader">
- </thead>
- <tbody id="statsBody">
- </tbody>
- </table>
- </div>
- </div>
-
- <div id="hiveJobSummary">
- <h4>Hive Job Summary</h4>
- <table class="table table-striped table-bordered table-hover" id="hiveTable">
- <thead id="hiveTableHeader">
- </thead>
- <tbody id="hiveTableBody">
- </tbody>
- </table>
- </div>
-
- <div id="jobIds">
- <h4>Map Reduce Jobs</h4>
- <table class="table table-striped table-bordered table-hover">
- <tbody id="jobIdsTableBody"></tbody>
- </table>
- </div>
- </div>
</div>
</div>
-
- ## Error message message dialog.
+
+ ## Error message message dialog.
<div class="container-full">
<div class="modal" id="messageDialog">
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
index b1838bb..270f8e1 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobhistorypage.vm
@@ -1,12 +1,12 @@
#*
* Copyright 2012 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
@@ -14,7 +14,7 @@
* the License.
*#
-<!DOCTYPE html>
+<!DOCTYPE html>
<html lang="en">
<head>
@@ -32,7 +32,7 @@
var timezone = "${timezone}";
var errorMessage = null;
var successMessage = null;
-
+
var projectId = "$projectId";
var projectName = "$projectName";
var jobName = "$jobid";
@@ -48,7 +48,7 @@
#if ($errorMsg)
#parse ("azkaban/webapp/servlet/velocity/errormsg.vm")
#else
-
+
## Page header
<div class="az-page-header page-header-bare">
@@ -66,7 +66,7 @@
</div>
<div class="container-full">
-
+
#parse ("azkaban/webapp/servlet/velocity/alerts.vm")
## Time graph and job history table.
@@ -84,7 +84,7 @@
<th class="flowid">Flow</th>
<th class="date">Start Time</th>
<th class="date">End Time</th>
- <th class="elapse">Elapse</th>
+ <th class="elapse">Elapse</th>
<th class="status">Status</th>
<th class="logs">Logs</th>
</tr>
src/less/azkaban.less 2(+1 -1)
diff --git a/src/less/azkaban.less b/src/less/azkaban.less
index f40468d..0f13144 100644
--- a/src/less/azkaban.less
+++ b/src/less/azkaban.less
@@ -1,3 +1,4 @@
+@import "variables.less";
@import "non-responsive.less";
@import "base.less";
@@ -13,5 +14,4 @@
@import "login.less";
@import "project.less";
@import "flow.less";
-@import "job.less";
@import "log.less";
src/less/base.less 21(+20 -1)
diff --git a/src/less/base.less b/src/less/base.less
index 7b8f11e..4c7445d 100644
--- a/src/less/base.less
+++ b/src/less/base.less
@@ -115,7 +115,7 @@
background-position: -64px -80px;
}
}
-
+
.editable {
margin: 0px;
cursor: pointer;
@@ -135,3 +135,22 @@
padding: 8px 12px;
font-size: 13px;
}
+
+.scrollable {
+ padding: 0;
+ overflow: auto;
+ margin-bottom: 20px;
+
+ table {
+ margin-bottom: 0;
+ }
+}
+
+.panel-scrollable {
+ padding: 0;
+ overflow: auto;
+
+ table {
+ margin-bottom: 0;
+ }
+}
src/less/callout.less 2(+1 -1)
diff --git a/src/less/callout.less b/src/less/callout.less
index a9f0ca2..cdf891d 100644
--- a/src/less/callout.less
+++ b/src/less/callout.less
@@ -7,7 +7,7 @@
/* Common styles for all types */
.callout {
- margin: 20px 0;
+ margin: 0 0 20px 0;
padding: 20px;
border-left: 3px solid #eee;
src/less/flow.less 117(+68 -49)
diff --git a/src/less/flow.less b/src/less/flow.less
index 9387933..565e73c 100644
--- a/src/less/flow.less
+++ b/src/less/flow.less
@@ -1,7 +1,3 @@
-#svgDiv {
- height: 100%;
-}
-
#graphView {
-moz-user-select: none;
-khtml-user-select: none;
@@ -9,11 +5,6 @@
user-select: none;
}
-#flow-graph {
- width: 100%;
- height: 100%;
-}
-
#headertabs {
-moz-user-select: none;
-khtml-user-select: none;
@@ -53,19 +44,19 @@
}
&.SUCCEEDED {
- background-color: #5cb85c;
+ background-color: @flow-succeeded-color;
}
&.FAILED {
- background-color: #d9534f;
+ background-color: @flow-failed-color;
}
&.KILLED {
- background-color: #d9534f;
+ background-color: @flow-killed-color;
}
&.RUNNING {
- background-color: #3398cc;
+ background-color: @flow-running-color;
background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
@@ -76,11 +67,11 @@
}
&.QUEUED {
- background-color: #009fc9;
+ background-color: @flow-queued-color;
}
&.CANCELLED {
- background-color: #ff9999;
+ background-color: @flow-cancelled-color;
}
}
@@ -103,82 +94,123 @@ td {
margin-top: 2px;
&.SUCCEEDED {
- background-color: #5cb85c;
+ background-color: @flow-succeeded-color;
}
&.FAILED {
- background-color: #d9534f;
+ background-color: @flow-failed-color;
}
&.KILLED {
- background-color: #d9534f;
+ background-color: @flow-killed-color;
}
&.PAUSED {
- background-color: #c82123;
+ background-color: @flow-paused-color;
}
&.READY,
- &.UNKNOWN {
- background-color: #ccc;
+ &.UNKNOWN,
+ &.PREPARING {
+ background-color: @flow-default-color;
}
&.RUNNING {
- background-color: #3398cc;
+ background-color: @flow-running-color;
}
&.FAILED_FINISHING {
- background-color: #f19153;
+ background-color: @flow-failed-finishing-color;
}
&.DISABLED,
&.SKIPPED {
- background-color: #aaa;
+ background-color: @flow-disabled-color;
}
&.CANCELLED {
- background-color: #ff9999;
+ background-color: @flow-cancelled-color;
}
}
}
#flowStatus {
&.SKIPPED {
- color: #aaa;
+ color: @flow-disabled-color;
}
&.SUCCEEDED {
- color: #4e911e;
+ color: @flow-succeeded-color;
}
&.RUNNING {
- color: #009fc9;
+ color: @flow-running-color;
}
&.PAUSED {
- color: #c92123;
+ color: @flow-paused-color;
+ }
+
+ &.FAILED {
+ color: @flow-failed-color;
+ }
+
+ &.KILLED {
+ color: @flow-killed-color;
}
- &.FAILED,
- &.FAILED_FINISHING,
- &.KILLED,
&.CANCELLED {
- color: #cc0000;
+ color: @flow-cancelled-color;
+ }
+
+ &.FAILED_FINISHING {
+ color: @flow-failed-finishing-color;
}
}
.graph-sidebar {
+ height: 100%;
+ overflow-y: auto;
+
+ .graph-sidebar-list {
+ height: 100%;
+ }
+}
+
+.graph-sidebar-float {
position: absolute;
top: 0px;
bottom: 0px;
+
+ .graph-sidebar-list {
+ overflow-y: auto;
+ height: calc(~"100% - 102px");
+ }
+
+ .panel {
+ height: 100%;
+
+ .panel-heading {
+ padding-right: 10px;
+ }
+ }
}
-#inputbox-panel {
+.graph-container {
+ height: 100%;
+
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+}
+
+.graph-sidebar-search {
width: 206px;
margin: 0px;
}
-#close-btn {
+.graph-sidebar-close {
float: right;
color: #CCC;
padding: 5px 0px;
@@ -189,15 +221,7 @@ td {
}
}
-#joblist-panel {
- height: 100%;
-
- .panel-heading {
- padding-right: 10px;
- }
-}
-
-#open-joblist-btn {
+.graph-sidebar-open {
position: absolute;
margin: 10px;
color: #CCC;
@@ -208,11 +232,6 @@ td {
}
}
-// TODO: Rename this as #job-list
-#joblist {
- overflow-y: auto;
- height: calc(~"100% - 102px");
-}
ul.tree-list {
list-style-type: none;
padding-left: 0px;
src/less/Makefile 2(+1 -1)
diff --git a/src/less/Makefile b/src/less/Makefile
index c25438a..37180b3 100644
--- a/src/less/Makefile
+++ b/src/less/Makefile
@@ -8,11 +8,11 @@ all: $(OBJ)
azkaban_css_DEPS = \
azkaban.less \
+ variables.less \
base.less \
context-menu.less \
flow.less \
header.less \
- job.less \
login.less \
log.less \
navbar.less \
src/less/variables.less 12(+12 -0)
diff --git a/src/less/variables.less b/src/less/variables.less
new file mode 100644
index 0000000..078d2b2
--- /dev/null
+++ b/src/less/variables.less
@@ -0,0 +1,12 @@
+
+// Flow colors
+@flow-succeeded-color: #5cb85c;
+@flow-failed-color: #d9534f;
+@flow-killed-color: #d9534f;
+@flow-paused-color: #c82123;
+@flow-running-color: #3398cc;
+@flow-failed-finishing-color: #f19153;
+@flow-cancelled-color: #ff9999;
+@flow-queued-color: #009fc9;
+@flow-disabled-color: #aaa;
+@flow-default-color: #ccc;
src/tl/flowstats.tl 22(+22 -0)
diff --git a/src/tl/flowstats.tl b/src/tl/flowstats.tl
index 8fffebe..5276ce8 100644
--- a/src/tl/flowstats.tl
+++ b/src/tl/flowstats.tl
@@ -1,3 +1,25 @@
+ {?histogram}
+ <div class="row">
+ <div class="col-xs-12">
+ <div class="well well-clear well-sm">
+ <div id="job-histogram"></div>
+ </div>
+ </div>
+ </div>
+ {/histogram}
+
+ {?warnings}
+ <div class="alert alert-warning">
+ <h4>Warnings</h4>
+ <p>These stats may have reduced accuracy due to the following missing information:</p>
+ <ul>
+ {#warnings}
+ <li>{.}</li>
+ {/warnings}
+ </ul>
+ </div>
+ {/warnings}
+
<div class="row">
<div class="col-xs-12">
<h4>Resources</h4>
src/tl/flowsummary.tl 10(+5 -5)
diff --git a/src/tl/flowsummary.tl b/src/tl/flowsummary.tl
index e4f525f..82627d6 100644
--- a/src/tl/flowsummary.tl
+++ b/src/tl/flowsummary.tl
@@ -14,7 +14,7 @@
</table>
</div>
</div>
-
+
<div class="row">
<div class="col-xs-12">
<h3>
@@ -46,12 +46,12 @@
<td class="property-key">SLA</td>
<td class="property-value-half">
{?schedule.slaOptions}
- true
- {:else}
- false
+ true
+ {:else}
+ false
{/schedule.slaOptions}
<div class="pull-right">
- <button type="button" id="addSlaBtn" class="btn btn-xs btn-primary" onclick="slaView.initFromSched({schedule.scheduleId}, '{flowName}')" >Set SLA</button>
+ <button type="button" id="addSlaBtn" class="btn btn-xs btn-primary" onclick="slaView.initFromSched({schedule.scheduleId}, '{flowName}')" >View/Set SLA</button>
</div>
</td>
</tr>
src/web/js/azkaban/model/job-log.js 80(+80 -0)
diff --git a/src/web/js/azkaban/model/job-log.js b/src/web/js/azkaban/model/job-log.js
new file mode 100644
index 0000000..d295578
--- /dev/null
+++ b/src/web/js/azkaban/model/job-log.js
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 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.
+ */
+
+$.namespace('azkaban');
+
+azkaban.JobLogModel = Backbone.Model.extend({
+ initialize: function() {
+ this.set("offset", 0);
+ this.set("logData", "");
+ },
+
+ refresh: function() {
+ var requestURL = contextURL + "/executor";
+ var finished = false;
+
+ var date = new Date();
+ var startTime = date.getTime();
+
+ while (!finished) {
+ var requestData = {
+ "execid": execId,
+ "jobId": jobId,
+ "ajax":"fetchExecJobLogs",
+ "offset": this.get("offset"),
+ "length": 50000,
+ "attempt": attempt
+ };
+
+ var self = this;
+
+ var successHandler = function(data) {
+ console.log("fetchLogs");
+ if (data.error) {
+ console.log(data.error);
+ finished = true;
+ }
+ else if (data.length == 0) {
+ finished = true;
+ }
+ else {
+ var date = new Date();
+ var endTime = date.getTime();
+ if ((endTime - startTime) > 10000) {
+ finished = true;
+ showDialog("Alert", "The log is taking a long time to finish loading. Azkaban has stopped loading them. Please click Refresh to restart the load.");
+ }
+
+ self.set("offset", data.offset + data.length);
+ self.set("logData", self.get("logData") + data.data);
+ }
+ }
+
+ $.ajax({
+ url: requestURL,
+ type: "get",
+ async: false,
+ data: requestData,
+ dataType: "json",
+ error: function(data) {
+ console.log(data);
+ finished = true;
+ },
+ success: successHandler
+ });
+ }
+ },
+});
src/web/js/azkaban/view/exflow.js 163(+82 -81)
diff --git a/src/web/js/azkaban/view/exflow.js b/src/web/js/azkaban/view/exflow.js
index 6129676..7c2548c 100644
--- a/src/web/js/azkaban/view/exflow.js
+++ b/src/web/js/azkaban/view/exflow.js
@@ -1,12 +1,12 @@
/*
* Copyright 2012 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
@@ -35,16 +35,16 @@ azkaban.StatusView = Backbone.View.extend({
},
render: function(evt) {
var data = this.model.get("data");
-
+
var user = data.submitUser;
$("#submitUser").text(user);
-
+
this.statusUpdate(evt);
},
-
+
statusUpdate: function(evt) {
var data = this.model.get("data");
-
+
statusItem = $("#flowStatus");
for (var j = 0; j < statusList.length; ++j) {
var status = statusList[j];
@@ -52,27 +52,27 @@ azkaban.StatusView = Backbone.View.extend({
}
$("#flowStatus").addClass(data.status);
$("#flowStatus").text(data.status);
-
+
var startTime = data.startTime;
var endTime = data.endTime;
-
+
if (!startTime || startTime == -1) {
$("#startTime").text("-");
}
else {
var date = new Date(startTime);
$("#startTime").text(getDateFormat(date));
-
+
var lastTime = endTime;
if (endTime == -1) {
var currentDate = new Date();
lastTime = currentDate.getTime();
}
-
+
var durationString = getDuration(startTime, lastTime);
$("#duration").text(durationString);
}
-
+
if (!endTime || endTime == -1) {
$("#endTime").text("-");
}
@@ -96,17 +96,17 @@ azkaban.FlowTabView = Backbone.View.extend({
"click #resumebtn": "handleResumeClick",
"click #retrybtn": "handleRetryClick"
},
-
+
initialize: function(settings) {
$("#cancelbtn").hide();
$("#executebtn").hide();
$("#pausebtn").hide();
$("#resumebtn").hide();
$("#retrybtn").hide();
-
+
this.model.bind('change:graph', this.handleFlowStatusChange, this);
this.model.bind('change:update', this.handleFlowStatusChange, this);
-
+
var selectedView = settings.selectedView;
if (selectedView == "jobslist") {
this.handleJobslistLinkClick();
@@ -115,60 +115,60 @@ azkaban.FlowTabView = Backbone.View.extend({
this.handleGraphLinkClick();
}
},
-
+
render: function() {
console.log("render graph");
},
-
+
handleGraphLinkClick: function(){
$("#jobslistViewLink").removeClass("active");
$("#graphViewLink").addClass("active");
$("#flowLogViewLink").removeClass("active");
$("#statsViewLink").removeClass("active");
-
+
$("#jobListView").hide();
$("#graphView").show();
$("#flowLogView").hide();
$("#statsView").hide();
},
-
+
handleJobslistLinkClick: function() {
$("#graphViewLink").removeClass("active");
$("#jobslistViewLink").addClass("active");
$("#flowLogViewLink").removeClass("active");
$("#statsViewLink").removeClass("active");
-
+
$("#graphView").hide();
$("#jobListView").show();
$("#flowLogView").hide();
$("#statsView").hide();
},
-
+
handleLogLinkClick: function() {
$("#graphViewLink").removeClass("active");
$("#jobslistViewLink").removeClass("active");
$("#flowLogViewLink").addClass("active");
$("#statsViewLink").removeClass("active");
-
+
$("#graphView").hide();
$("#jobListView").hide();
$("#flowLogView").show();
$("#statsView").hide();
},
-
+
handleStatsLinkClick: function() {
$("#graphViewLink").removeClass("active");
$("#jobslistViewLink").removeClass("active");
$("#flowLogViewLink").removeClass("active");
$("#statsViewLink").addClass("active");
-
+
$("#graphView").hide();
$("#jobListView").hide();
$("#flowLogView").hide();
statsView.show();
$("#statsView").show();
},
-
+
handleFlowStatusChange: function() {
var data = this.model.get("data");
$("#cancelbtn").hide();
@@ -203,7 +203,7 @@ azkaban.FlowTabView = Backbone.View.extend({
$("#executebtn").show();
}
},
-
+
handleCancelClick: function(evt) {
var requestURL = contextURL + "/executor";
var requestData = {"execid": execId, "ajax": "cancelFlow"};
@@ -219,7 +219,7 @@ azkaban.FlowTabView = Backbone.View.extend({
};
ajaxCall(requestURL, requestData, successHandler);
},
-
+
handleRetryClick: function(evt) {
var graphData = graphModel.get("data");
var requestURL = contextURL + "/executor";
@@ -236,11 +236,11 @@ azkaban.FlowTabView = Backbone.View.extend({
};
ajaxCall(requestURL, requestData, successHandler);
},
-
+
handleRestartClick: function(evt) {
console.log("handleRestartClick");
var data = graphModel.get("data");
-
+
var executingData = {
project: projectName,
ajax: "executeFlow",
@@ -250,7 +250,7 @@ azkaban.FlowTabView = Backbone.View.extend({
};
flowExecuteDialogView.show(executingData);
},
-
+
handlePauseClick: function(evt) {
var requestURL = contextURL + "/executor";
var requestData = {"execid": execId, "ajax":"pauseFlow"};
@@ -266,7 +266,7 @@ azkaban.FlowTabView = Backbone.View.extend({
};
ajaxCall(requestURL, requestData, successHandler);
},
-
+
handleResumeClick: function(evt) {
var requestURL = contextURL + "/executor";
var requestData = {"execid": execId, "ajax":"resumeFlow"};
@@ -304,17 +304,17 @@ azkaban.FlowLogView = Backbone.View.extend({
},
handleUpdate: function(evt) {
var offset = this.model.get("offset");
- var requestURL = contextURL + "/executor";
+ var requestURL = contextURL + "/executor";
var model = this.model;
console.log("fetchLogs offset is " + offset)
$.ajax({
- async: false,
+ async: false,
url: requestURL,
data: {
- "execid": execId,
- "ajax": "fetchExecFlowLogs",
- "offset": offset,
+ "execid": execId,
+ "ajax": "fetchExecFlowLogs",
+ "offset": offset,
"length": 50000
},
success: function(data) {
@@ -346,7 +346,7 @@ var statsView;
azkaban.StatsView = Backbone.View.extend({
events: {
},
-
+
initialize: function(settings) {
this.model.bind('change:graph', this.statusUpdate, this);
this.model.bind('change:update', this.statusUpdate, this);
@@ -385,22 +385,22 @@ var updateStatus = function(updateTime) {
var requestURL = contextURL + "/executor";
var oldData = graphModel.get("data");
var nodeMap = graphModel.get("nodeMap");
-
+
if (!updateTime) {
updateTime = oldData.updateTime ? oldData.updateTime : 0;
}
var requestData = {
- "execid": execId,
- "ajax": "fetchexecflowupdate",
+ "execid": execId,
+ "ajax": "fetchexecflowupdate",
"lastUpdateTime": updateTime
};
-
+
var successHandler = function(data) {
console.log("data updated");
if (data.updateTime) {
updateGraph(oldData, data);
-
+
graphModel.set({"update": data});
graphModel.trigger("change:update");
}
@@ -415,12 +415,12 @@ var updateGraph = function(data, update) {
data.updateTime = update.updateTime;
data.status = update.status;
update.changedNode = data;
-
+
if (update.nodes) {
for (var i = 0; i < update.nodes.length; ++i) {
var newNode = update.nodes[i];
var oldNode = nodeMap[newNode.id];
-
+
updateGraph(oldNode, newNode);
}
}
@@ -429,17 +429,17 @@ var updateGraph = function(data, update) {
var updateTime = -1;
var updaterFunction = function() {
var oldData = graphModel.get("data");
- var keepRunning =
- oldData.status != "SUCCEEDED" &&
- oldData.status != "FAILED" &&
+ var keepRunning =
+ oldData.status != "SUCCEEDED" &&
+ oldData.status != "FAILED" &&
oldData.status != "KILLED";
if (keepRunning) {
updateStatus();
var data = graphModel.get("data");
- if (data.status == "UNKNOWN" ||
- data.status == "WAITING" ||
+ if (data.status == "UNKNOWN" ||
+ data.status == "WAITING" ||
data.status == "PREPARING") {
setTimeout(function() {updaterFunction();}, 1000);
}
@@ -459,9 +459,9 @@ var updaterFunction = function() {
var logUpdaterFunction = function() {
var oldData = graphModel.get("data");
- var keepRunning =
- oldData.status != "SUCCEEDED" &&
- oldData.status != "FAILED" &&
+ var keepRunning =
+ oldData.status != "SUCCEEDED" &&
+ oldData.status != "FAILED" &&
oldData.status != "KILLED";
if (keepRunning) {
// update every 30 seconds for the logs until finished
@@ -479,7 +479,7 @@ var exNodeClickCallback = function(event) {
var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
var visualizerURL = contextURL + "/pigvisualizer?execid=" + execId + "&jobid=" + jobId;
- var menu = [
+ var menu = [
{title: "Open Job...", callback: function() {window.location.href = requestURL;}},
{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
{title: "Visualize Job...", callback: function() {window.location.href = visualizerURL;}}
@@ -494,7 +494,7 @@ var exJobClickCallback = function(event) {
var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId + "&job=" + jobId;
var visualizerURL = contextURL + "/pigvisualizer?execid=" + execId + "&jobid=" + jobId;
- var menu = [
+ var menu = [
{title: "Open Job...", callback: function() {window.location.href = requestURL;}},
{title: "Open Job in New Window...", callback: function() {window.open(requestURL);}},
{title: "Visualize Job...", callback: function() {window.location.href = visualizerURL;}}
@@ -511,13 +511,13 @@ var exGraphClickCallback = function(event) {
console.log("Graph clicked callback");
var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
- var menu = [
+ var menu = [
{title: "Open Flow...", callback: function() {window.location.href=requestURL;}},
{title: "Open Flow in New Window...", callback: function() {window.open(requestURL);}},
{break: 1},
{title: "Center Graph", callback: function() {graphModel.trigger("resetPanZoom");}}
];
-
+
contextMenuView.show(event, menu);
}
@@ -526,68 +526,69 @@ var flowStatsModel;
$(function() {
var selected;
-
+
graphModel = new azkaban.GraphModel();
logModel = new azkaban.LogModel();
-
+
flowTabView = new azkaban.FlowTabView({
- el: $('#headertabs'),
+ el: $('#headertabs'),
model: graphModel
});
-
+
mainSvgGraphView = new azkaban.SvgGraphView({
- el: $('#svgDiv'),
- model: graphModel,
- rightClick: {
- "node": nodeClickCallback,
- "edge": edgeClickCallback,
- "graph": graphClickCallback
+ el: $('#svgDiv'),
+ model: graphModel,
+ rightClick: {
+ "node": nodeClickCallback,
+ "edge": edgeClickCallback,
+ "graph": graphClickCallback
}
});
-
+
jobsListView = new azkaban.JobListView({
- el: $('#joblist-panel'),
- model: graphModel,
+ el: $('#joblist-panel'),
+ model: graphModel,
contextMenuCallback: jobClickCallback
});
-
+
flowLogView = new azkaban.FlowLogView({
- el: $('#flowLogView'),
+ el: $('#flowLogView'),
model: logModel
});
-
+
statusView = new azkaban.StatusView({
- el: $('#flow-status'),
+ el: $('#flow-status'),
model: graphModel
});
-
+
flowStatsModel = new azkaban.FlowStatsModel();
flowStatsView = new azkaban.FlowStatsView({
el: $('#flow-stats-container'),
- model: flowStatsModel
+ model: flowStatsModel,
+ histogram: false
});
statsView = new azkaban.StatsView({
- el: $('#statsView'),
+ el: $('#statsView'),
model: graphModel
});
-
+
executionListView = new azkaban.ExecutionListView({
- el: $('#jobListView'),
+ el: $('#jobListView'),
model: graphModel
});
-
+
var requestURL = contextURL + "/executor";
var requestData = {"execid": execId, "ajax":"fetchexecflow"};
var successHandler = function(data) {
console.log("data fetched");
graphModel.addFlow(data);
graphModel.trigger("change:graph");
-
+
updateTime = Math.max(updateTime, data.submitTime);
updateTime = Math.max(updateTime, data.startTime);
updateTime = Math.max(updateTime, data.endTime);
-
+
if (window.location.hash) {
var hash = window.location.hash;
if (hash == "#jobslist") {
src/web/js/azkaban/view/flow.js 149(+87 -62)
diff --git a/src/web/js/azkaban/view/flow.js b/src/web/js/azkaban/view/flow.js
index 3934055..6ed8d89 100644
--- a/src/web/js/azkaban/view/flow.js
+++ b/src/web/js/azkaban/view/flow.js
@@ -1,12 +1,12 @@
/*
* Copyright 2012 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
@@ -18,7 +18,7 @@ $.namespace('azkaban');
var handleJobMenuClick = function(action, el, pos) {
var jobid = el[0].jobid;
- var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" +
+ var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" +
flowId + "&job=" + jobid;
if (action == "open") {
window.location.href = requestURL;
@@ -35,7 +35,7 @@ azkaban.FlowTabView = Backbone.View.extend({
"click #executionsViewLink": "handleExecutionLinkClick",
"click #summaryViewLink": "handleSummaryLinkClick"
},
-
+
initialize: function(settings) {
var selectedView = settings.selectedView;
if (selectedView == "executions") {
@@ -45,26 +45,26 @@ azkaban.FlowTabView = Backbone.View.extend({
this.handleGraphLinkClick();
}
},
-
+
render: function() {
console.log("render graph");
},
-
+
handleGraphLinkClick: function(){
$("#executionsViewLink").removeClass("active");
$("#graphViewLink").addClass("active");
$('#summaryViewLink').removeClass('active');
-
+
$("#executionsView").hide();
$("#graphView").show();
$('#summaryView').hide();
},
-
+
handleExecutionLinkClick: function() {
$("#graphViewLink").removeClass("active");
$("#executionsViewLink").addClass("active");
$('#summaryViewLink').removeClass('active');
-
+
$("#graphView").hide();
$("#executionsView").show();
$('#summaryView').hide();
@@ -90,35 +90,35 @@ azkaban.ExecutionsView = Backbone.View.extend({
events: {
"click #pageSelection li": "handleChangePageSelection"
},
-
+
initialize: function(settings) {
this.model.bind('change:view', this.handleChangeView, this);
this.model.bind('render', this.render, this);
this.model.set({page: 1, pageSize: 16});
this.model.bind('change:page', this.handlePageChange, this);
},
-
+
render: function(evt) {
console.log("render");
// Render page selections
var tbody = $("#execTableBody");
tbody.empty();
-
+
var executions = this.model.get("executions");
for (var i = 0; i < executions.length; ++i) {
var row = document.createElement("tr");
-
+
var tdId = document.createElement("td");
var execA = document.createElement("a");
$(execA).attr("href", contextURL + "/executor?execid=" + executions[i].execId);
$(execA).text(executions[i].execId);
tdId.appendChild(execA);
row.appendChild(tdId);
-
+
var tdUser = document.createElement("td");
$(tdUser).text(executions[i].submitUser);
row.appendChild(tdUser);
-
+
var startTime = "-";
if (executions[i].startTime != -1) {
var startDateTime = new Date(executions[i].startTime);
@@ -128,7 +128,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
var tdStartTime = document.createElement("td");
$(tdStartTime).text(startTime);
row.appendChild(tdStartTime);
-
+
var endTime = "-";
var lastTime = executions[i].endTime;
if (executions[i].endTime != -1) {
@@ -142,11 +142,11 @@ azkaban.ExecutionsView = Backbone.View.extend({
var tdEndTime = document.createElement("td");
$(tdEndTime).text(endTime);
row.appendChild(tdEndTime);
-
+
var tdElapsed = document.createElement("td");
$(tdElapsed).text( getDuration(executions[i].startTime, lastTime));
row.appendChild(tdElapsed);
-
+
var tdStatus = document.createElement("td");
var status = document.createElement("div");
$(status).addClass("status");
@@ -160,22 +160,22 @@ azkaban.ExecutionsView = Backbone.View.extend({
tbody.append(row);
}
-
+
this.renderPagination(evt);
},
-
+
renderPagination: function(evt) {
var total = this.model.get("total");
total = total? total : 1;
var pageSize = this.model.get("pageSize");
var numPages = Math.ceil(total / pageSize);
-
+
this.model.set({"numPages": numPages});
var page = this.model.get("page");
-
+
//Start it off
$("#pageSelection .active").removeClass("active");
-
+
// Disable if less than 5
console.log("Num pages " + numPages)
var i = 1;
@@ -185,7 +185,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
for (; i <= 5; ++i) {
$("#page" + i).addClass("disabled");
}
-
+
// Disable prev/next if necessary.
if (page > 1) {
$("#previous").removeClass("disabled");
@@ -195,7 +195,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
else {
$("#previous").addClass("disabled");
}
-
+
if (page < numPages) {
$("#next")[0].page = page + 1;
$("#next").removeClass("disabled");
@@ -205,7 +205,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
$("#next")[0].page = page + 1;
$("#next").addClass("disabled");
}
-
+
// Selection is always in middle unless at barrier.
var startPage = 0;
var selectionPosition = 0;
@@ -235,14 +235,14 @@ azkaban.ExecutionsView = Backbone.View.extend({
for (var j = 0; j < 5; ++j) {
var realPage = startPage + j;
var elementId = "#page" + (j+1);
-
+
$(elementId)[0].page = realPage;
var a = $(elementId + " a");
a.text(realPage);
a.attr("href", "#page" + realPage);
}
},
-
+
handleChangePageSelection: function(evt) {
if ($(evt.currentTarget).hasClass("disabled")) {
return;
@@ -250,7 +250,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
var page = evt.currentTarget.page;
this.model.set({"page": page});
},
-
+
handleChangeView: function(evt) {
if (this.init) {
return;
@@ -259,23 +259,23 @@ azkaban.ExecutionsView = Backbone.View.extend({
this.handlePageChange(evt);
this.init = true;
},
-
+
handlePageChange: function(evt) {
var page = this.model.get("page") - 1;
var pageSize = this.model.get("pageSize");
var requestURL = contextURL + "/manager";
-
+
var model = this.model;
var requestData = {
- "project": projectName,
- "flow": flowId,
- "ajax": "fetchFlowExecutions",
- "start": page * pageSize,
+ "project": projectName,
+ "flow": flowId,
+ "ajax": "fetchFlowExecutions",
+ "start": page * pageSize,
"length": pageSize
};
var successHandler = function(data) {
model.set({
- "executions": data.executions,
+ "executions": data.executions,
"total": data.total
});
model.trigger("render");
@@ -289,11 +289,11 @@ azkaban.SummaryView = Backbone.View.extend({
events: {
'click #analyze-btn': 'fetchLastRun'
},
-
+
initialize: function(settings) {
this.model.bind('change:view', this.handleChangeView, this);
this.model.bind('render', this.render, this);
-
+
this.fetchDetails();
this.fetchSchedule();
this.model.trigger('render');
@@ -306,7 +306,7 @@ azkaban.SummaryView = Backbone.View.extend({
'project': projectName,
'flow': flowId
};
-
+
var model = this.model;
var successHandler = function(data) {
@@ -318,7 +318,7 @@ azkaban.SummaryView = Backbone.View.extend({
};
$.get(requestURL, requestData, successHandler, 'json');
},
-
+
fetchSchedule: function() {
var requestURL = contextURL + "/schedule"
var requestData = {
@@ -327,9 +327,34 @@ azkaban.SummaryView = Backbone.View.extend({
'flowId': flowId
};
var model = this.model;
+ var view = this;
var successHandler = function(data) {
model.set({'schedule': data.schedule});
model.trigger('render');
+ view.fetchSla();
+ };
+ $.get(requestURL, requestData, successHandler, 'json');
+ },
+
+ fetchSla: function() {
+ var schedule = this.model.get('schedule');
+ if (schedule == null || schedule.scheduleId == null) {
+ return;
+ }
+
+ var requestURL = contextURL + "/schedule"
+ var requestData = {
+ "scheduleId": schedule.scheduleId,
+ "ajax": "slaInfo"
+ };
+ var model = this.model;
+ var successHandler = function(data) {
+ if (data == null || data.settings == null || data.settings.length == 0) {
+ return;
+ }
+ schedule.slaOptions = true;
+ model.set({'schedule': schedule});
+ model.trigger('render');
};
$.get(requestURL, requestData, successHandler, 'json');
},
@@ -390,10 +415,10 @@ $(function() {
// Execution model has to be created before the window switches the tabs.
executionModel = new azkaban.ExecutionModel();
executionsView = new azkaban.ExecutionsView({
- el: $('#executionsView'),
+ el: $('#executionsView'),
model: executionModel
});
-
+
summaryModel = new azkaban.SummaryModel();
summaryView = new azkaban.SummaryView({
el: $('#summaryView'),
@@ -407,35 +432,35 @@ $(function() {
});
flowTabView = new azkaban.FlowTabView({
- el: $('#headertabs'),
- selectedView: selected
+ el: $('#headertabs'),
+ selectedView: selected
});
graphModel = new azkaban.GraphModel();
mainSvgGraphView = new azkaban.SvgGraphView({
- el: $('#svgDiv'),
- model: graphModel,
- rightClick: {
- "node": nodeClickCallback,
- "edge": edgeClickCallback,
- "graph": graphClickCallback
+ el: $('#svgDiv'),
+ model: graphModel,
+ rightClick: {
+ "node": nodeClickCallback,
+ "edge": edgeClickCallback,
+ "graph": graphClickCallback
}
});
-
+
jobsListView = new azkaban.JobListView({
- el: $('#joblist-panel'),
- model: graphModel,
+ el: $('#joblist-panel'),
+ model: graphModel,
contextMenuCallback: jobClickCallback
});
-
+
executionsTimeGraphView = new azkaban.TimeGraphView({
- el: $('#timeGraph'),
+ el: $('#timeGraph'),
model: executionModel,
modelField: 'executions'
});
-
+
slaView = new azkaban.ChangeSlaView({el:$('#sla-options')});
-
+
var requestURL = contextURL + "/manager";
// Set up the Flow options view. Create a new one every time :p
$('#executebtn').click(function() {
@@ -451,15 +476,15 @@ $(function() {
});
var requestData = {
- "project": projectName,
- "ajax": "fetchflowgraph",
+ "project": projectName,
+ "ajax": "fetchflowgraph",
"flow": flowId
};
var successHandler = function(data) {
console.log("data fetched");
graphModel.addFlow(data);
graphModel.trigger("change:graph");
-
+
// Handle the hash changes here so the graph finishes rendering first.
if (window.location.hash) {
var hash = window.location.hash;
@@ -470,7 +495,7 @@ $(function() {
flowTabView.handleSummaryLinkClick();
}
else if (hash == "#graph") {
- // Redundant, but we may want to change the default.
+ // Redundant, but we may want to change the default.
selected = "graph";
}
else {
src/web/js/azkaban/view/flow-stats.js 71(+56 -15)
diff --git a/src/web/js/azkaban/view/flow-stats.js b/src/web/js/azkaban/view/flow-stats.js
index 5f2919e..d991a4c 100644
--- a/src/web/js/azkaban/view/flow-stats.js
+++ b/src/web/js/azkaban/view/flow-stats.js
@@ -1,12 +1,12 @@
/*
* Copyright 2012 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
@@ -21,11 +21,16 @@ azkaban.FlowStatsView = Backbone.View.extend({
events: {
},
+ histogram: true,
+
initialize: function(settings) {
this.model.bind('change:view', this.handleChangeView, this);
this.model.bind('render', this.render, this);
+ if (settings.histogram != null) {
+ this.histogram = settings.histogram;
+ }
},
-
+
render: function(evt) {
},
@@ -38,10 +43,10 @@ azkaban.FlowStatsView = Backbone.View.extend({
var requestData = {"execid": execId, "ajax":"fetchexecflow"};
var jobs = [];
var successHandler = function(data) {
- for (var i = 0; i < data.nodes.length; ++i) {
- var node = data.nodes[i];
- jobs.push(node.id);
- }
+ data.nodes.sort(function(a, b) {
+ return a.startTime - b.startTime;
+ });
+ jobs = data.nodes;
};
$.ajax({
url: requestURL,
@@ -127,7 +132,7 @@ azkaban.FlowStatsView = Backbone.View.extend({
}
}
if (str.indexOf('Xms') > -1) {
- if (str.length <= 4) {
+ if (str.length <= 4) {
continue;
}
var size = str.substring(4, str.length);
@@ -179,13 +184,13 @@ azkaban.FlowStatsView = Backbone.View.extend({
stats.fileBytesWritten.max = fileBytesWritten;
stats.fileBytesWritten.job = job;
}
-
+
var hdfsBytesRead = parseInt(fileSystemCounters['HDFS_BYTES_READ']);
if (hdfsBytesRead >= stats.hdfsBytesRead.max) {
stats.hdfsBytesRead.max = hdfsBytesRead;
stats.hdfsBytesRead.job = job;
}
-
+
var hdfsBytesWritten = parseInt(fileSystemCounters['HDFS_BYTES_WRITTEN']);
if (hdfsBytesWritten >= stats.hdfsBytesWritten.max) {
stats.hdfsBytesWritten.max = hdfsBytesWritten;
@@ -219,6 +224,8 @@ azkaban.FlowStatsView = Backbone.View.extend({
success: false,
message: null,
warnings: [],
+ durations: [],
+ histogram: this.histogram,
stats: {
mapSlots: {
max: 0,
@@ -275,18 +282,36 @@ azkaban.FlowStatsView = Backbone.View.extend({
}
};
+ var jobsAnalyzed = 0;
for (var i = 0; i < jobs.length; ++i) {
var job = jobs[i];
- var jobStats = this.fetchJobStats(job, execId);
+ var duration = job.endTime - job.startTime;
+ data.durations.push({
+ job: job.id,
+ duration: duration
+ });
+
+ var jobStats = this.fetchJobStats(job.id, execId);
if (jobStats.jobStats == null) {
data.warnings.push("No job stats available for job " + job.id);
continue;
}
for (var j = 0; j < jobStats.jobStats.length; ++j) {
- this.updateStats(jobStats.jobStats[j], data, job);
+ this.updateStats(jobStats.jobStats[j], data, job.id);
}
+ ++jobsAnalyzed;
+ }
+
+ // If no jobs were analyzed, then no jobs had any job stats available. In
+ // this case, display a No Flow Stats Available message.
+ if (jobsAnalyzed == 0) {
+ data.success = false;
+ data.message = "There were no job stats provided by any job.";
+ }
+ else {
+ this.finalizeStats(data);
}
- this.finalizeStats(data);
+
this.model.set({'data': data});
this.model.trigger('render');
},
@@ -300,14 +325,30 @@ azkaban.FlowStatsView = Backbone.View.extend({
view.display(out);
});
}
- else if (data.success == "false") {
+ else if (data.success == false) {
dust.render("flowstats-no-data", data, function(err, out) {
view.display(out);
});
}
else {
+ var histogram = this.histogram;
dust.render("flowstats", data, function(err, out) {
view.display(out);
+ if (histogram == true) {
+ var yLabelFormatCallback = function(y) {
+ var seconds = y / 1000.0;
+ return seconds.toString() + " s";
+ };
+
+ Morris.Bar({
+ element: "job-histogram",
+ data: data.durations,
+ xkey: "job",
+ ykeys: ["duration"],
+ labels: ["Duration"],
+ yLabelFormat: yLabelFormatCallback
+ });
+ }
});
}
},
src/web/js/azkaban/view/job-details.js 268(+3 -265)
diff --git a/src/web/js/azkaban/view/job-details.js b/src/web/js/azkaban/view/job-details.js
index 07b823e..7620b5a 100644
--- a/src/web/js/azkaban/view/job-details.js
+++ b/src/web/js/azkaban/view/job-details.js
@@ -38,247 +38,6 @@ azkaban.JobLogView = Backbone.View.extend({
}
});
-var jobSummaryView;
-azkaban.JobSummaryView = Backbone.View.extend({
- events: {
- "click #updateSummaryBtn" : "refresh"
- },
-
- initialize: function(settings) {
- $("#jobType").hide();
- $("#commandSummary").hide();
- $("#pigJobSummary").hide();
- $("#pigJobStats").hide();
- $("#hiveJobSummary").hide();
- $("#jobIds").hide();
-
- this.listenTo(this.model, "change:jobType", this.renderJobTypeTable);
- this.listenTo(this.model, "change:commandProperties", this.renderCommandTable);
- this.listenTo(this.model, "change:pigSummary", this.renderPigSummaryTable);
- this.listenTo(this.model, "change:pigStats", this.renderPigStatsTable);
- this.listenTo(this.model, "change:hiveSummary", this.renderHiveTable);
- this.listenTo(this.model, "change:jobIds", this.renderJobIdsTable);
- },
-
- refresh: function() {
- this.model.refresh();
- },
-
- handleUpdate: function(evt) {
- renderJobTable(jobSummary.summaryTableHeaders, jobSummary.summaryTableData, "summary");
- renderJobTable(jobSummary.statTableHeaders, jobSummary.statTableData, "stats");
- renderHiveTable(jobSummary.hiveQueries, jobSummary.hiveQueryJobs);
- },
-
- renderJobTypeTable: function() {
- var jobTypeTable = $("#jobTypeTable");
- var jobType = this.model.get("jobType");
-
- var tr = document.createElement("tr");
- var td = document.createElement("td");
- $(td).html("<b>Job Type</b>");
- $(tr).append(td);
- td = document.createElement("td");
- $(td).html(jobType);
- $(tr).append(td);
-
- jobTypeTable.append(tr);
-
- $("#jobType").show();
- },
-
- renderJobIdsTable: function() {
- var oldBody = $("#jobIdsTableBody");
- var newBody = $(document.createElement("tbody")).attr("id", "jobIdsTableBody");
-
- var jobIds = this.model.get("jobIds");
- var jobUrls = this.model.get("jobTrackerUrls");
- var numJobs = jobIds.length;
- for (var i = 0; i < numJobs; i++) {
- var job = jobIds[i];
- var tr = document.createElement("tr");
- var td = document.createElement("td");
- var html = jobUrls[job] ? "<a href='" + jobUrls[job] + "'>" + job + "</a>" : job;
- $(td).html(html);
- $(tr).append(td);
- newBody.append(tr);
- }
-
- oldBody.replaceWith(newBody);
-
- $("#jobIds").show();
- },
-
- renderCommandTable: function() {
- var commandTable = $("#commandTable");
- var commandProperties = this.model.get("commandProperties");
-
- for (var key in commandProperties) {
- if (commandProperties.hasOwnProperty(key)) {
- var value = commandProperties[key];
- if (Array.isArray(value)) {
- value = value.join("<br/>");
- }
- var tr = document.createElement("tr");
- var keyTd = document.createElement("td");
- var valueTd = document.createElement("td");
- $(keyTd).html("<b>" + key + "</b>");
- $(valueTd).html(value);
- $(tr).append(keyTd);
- $(tr).append(valueTd);
- commandTable.append(tr);
- }
- }
-
- $("#commandSummary").show();
- },
- renderPigTable: function(tableName, data) {
- // Add table headers
- var header = $("#" + tableName + "Header");
- var tr = document.createElement("tr");
- var i;
- var headers = data[0];
- var numColumns = headers.length;
- for (i = 0; i < numColumns; i++) {
- var th = document.createElement("th");
- $(th).text(headers[i]);
- $(tr).append(th);
- }
- header.append(tr);
-
- // Add table body
- var body = $("#" + tableName + "Body");
- for (i = 1; i < data.length; i++) {
- tr = document.createElement("tr");
- var row = data[i];
- for (var j = 0; j < numColumns; j++) {
- var td = document.createElement("td");
- if (j == 0) {
- // first column is a link to job details page
- $(td).html(row[j]);
- } else {
- $(td).text(row[j]);
- }
- $(tr).append(td);
- }
- body.append(tr);
- }
-
- $("#pigJob" + tableName.charAt(0).toUpperCase() + tableName.substring(1)).show();
- },
- renderPigSummaryTable: function() {
- this.renderPigTable("summary", this.model.get("pigSummary"));
- },
- renderPigStatsTable: function() {
- this.renderPigTable("stats", this.model.get("pigStats"));
- },
- renderHiveTable: function() {
- var hiveSummary = this.model.get("hiveSummary");
- var queries = hiveSummary.hiveQueries;
- var queryJobs = hiveSummary.hiveQueryJobs;
-
- // Set up table column headers
- var header = $("#hiveTableHeader");
- var tr = document.createElement("tr");
-
- var headers;
- if (this.model.get("hasCumulativeCPU")) {
- headers = ["Query","Job","Map","Reduce","Cumulative CPU","HDFS Read","HDFS Write"];
- } else {
- headers = ["Query","Job","Map","Reduce","HDFS Read","HDFS Write"];
- }
-
- var i;
- for (i = 0; i < headers.length; i++) {
- var th = document.createElement("th");
- $(th).text(headers[i]);
- $(tr).append(th);
- }
- header.html(tr);
-
- // Construct table body
- var oldBody = $("#hiveTableBody");
- var newBody = $(document.createElement("tbody")).attr("id", "hiveTableBody");
- for (i = 0; i < queries.length; i++) {
- // new query
- tr = document.createElement("tr");
- var td = document.createElement("td");
- $(td).html("<b>" + queries[i] + "</b>");
- $(tr).append(td);
-
- var jobs = queryJobs[i];
- if (jobs != null) {
- // add first job for this query
- var jobValues = jobs[0];
- var j;
- for (j = 0; j < jobValues.length; j++) {
- td = document.createElement("td");
- $(td).html(jobValues[j]);
- $(tr).append(td);
- }
- newBody.append(tr);
-
- // add remaining jobs for this query
- for (j = 1; j < jobs.length; j++) {
- jobValues = jobs[j];
- tr = document.createElement("tr");
-
- // add empty cell for query column
- td = document.createElement("td");
- $(td).html(" ");
- $(tr).append(td);
-
- // add job values
- for (var k = 0; k < jobValues.length; k++) {
- td = document.createElement("td");
- $(td).html(jobValues[k]);
- $(tr).append(td);
- }
- newBody.append(tr);
- }
-
- } else {
- newBody.append(tr);
- }
- }
- oldBody.replaceWith(newBody);
-
- $("#hiveJobSummary").show();
- }
-});
-
-var jobTabView;
-azkaban.JobTabView = Backbone.View.extend({
- events: {
- 'click #jobSummaryViewLink': 'handleJobSummaryViewLinkClick',
- 'click #jobLogViewLink': 'handleJobLogViewLinkClick'
- },
-
- initialize: function(settings) {
- var selectedView = settings.selectedView;
- if (selectedView == 'summary') {
- this.handleJobSummaryViewLinkClick();
- }
- else {
- this.handleJobLogViewLinkClick();
- }
- },
-
- handleJobLogViewLinkClick: function() {
- $('#jobSummaryViewLink').removeClass('active');
- $('#jobSummaryView').hide();
- $('#jobLogViewLink').addClass('active');
- $('#jobLogView').show();
- },
-
- handleJobSummaryViewLinkClick: function() {
- $('#jobSummaryViewLink').addClass('active');
- $('#jobSummaryView').show();
- $('#jobLogViewLink').removeClass('active');
- $('#jobLogView').hide();
- },
-});
-
var showDialog = function(title, message) {
$('#messageTitle').text(title);
$('#messageBox').text(message);
@@ -296,31 +55,10 @@ var showDialog = function(title, message) {
}
$(function() {
- var logDataModel = new azkaban.LogDataModel();
-
+ var jobLogModel = new azkaban.JobLogModel();
jobLogView = new azkaban.JobLogView({
el: $('#jobLogView'),
- model: logDataModel
- });
-
- jobSummaryView = new azkaban.JobSummaryView({
- el: $('#jobSummaryView'),
- model: logDataModel
+ model: jobLogModel
});
-
- jobTabView = new azkaban.JobTabView({
- el: $('#headertabs')
- });
-
- logDataModel.refresh();
-
- if (window.location.hash) {
- var hash = window.location.hash;
- if (hash == '#logs') {
- jobTabView.handleJobLogViewLinkClick();
- }
- else if (hash == '#summary') {
- jobTabView.handleJobSummaryViewLinkClick();
- }
- }
+ jobLogModel.refresh();
});
src/web/js/azkaban/view/time-graph.js 28(+18 -10)
diff --git a/src/web/js/azkaban/view/time-graph.js b/src/web/js/azkaban/view/time-graph.js
index 7cb3f6f..0f53fa4 100644
--- a/src/web/js/azkaban/view/time-graph.js
+++ b/src/web/js/azkaban/view/time-graph.js
@@ -1,12 +1,12 @@
/*
* Copyright 2012 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
@@ -19,7 +19,7 @@ $.namespace('azkaban');
azkaban.TimeGraphView = Backbone.View.extend({
events: {
},
-
+
initialize: function(settings) {
this.model.bind('render', this.render, this);
this.model.bind('change:page', this.render, this);
@@ -36,7 +36,7 @@ azkaban.TimeGraphView = Backbone.View.extend({
// Array of points to be passed to Morris.
var data = [];
-
+
// Map of y value to index for faster look-up in the lineColorsCallback to
// get the status for each point.
var indexMap = {};
@@ -47,11 +47,19 @@ azkaban.TimeGraphView = Backbone.View.extend({
}
var startTime = series[i].startTime;
var endTime = series[i].endTime;
+ if (startTime == -1 && endTime == -1) {
+ console.log("Ignoring data point with both start and end time invalid.");
+ continue;
+ }
+
+ var duration = 0;
+ if (endTime != -1 && startTime != -1) {
+ duration = endTime - startTime;
+ }
if (endTime == -1) {
endTime = new Date().getTime();
}
- var duration = endTime - startTime;
- data.push({
+ data.push({
time: endTime,
duration: duration
});
@@ -85,8 +93,8 @@ azkaban.TimeGraphView = Backbone.View.extend({
else if (status == 'PAUSED') {
return '#c92123';
}
- else if (status == 'FAILED' ||
- status == 'FAILED_FINISHING' ||
+ else if (status == 'FAILED' ||
+ status == 'FAILED_FINISHING' ||
status == 'KILLED') {
return '#cc0000';
}
@@ -105,7 +113,7 @@ azkaban.TimeGraphView = Backbone.View.extend({
// is the index into Morris's internal array of data sorted in ascending
// x order.
var status = series[options.data.length - index - 1].status;
- return content +
+ return content +
'<div class="morris-hover-point">Status: ' + status + '</div>';
};