Browse Source

HUE-1838 [oozie] Add SLA tab on single workflow and coordinator dashboard page

Added chart and table to the tabs
Externalized SLA js functions
Enrico Berti 12 năm trước cách đây
mục cha
commit
094205ea24

+ 98 - 0
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinator.mako

@@ -275,6 +275,36 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
 
       % if oozie_coordinator.has_sla:
       <div class="tab-pane" id="sla">
+        <div id="yAxisLabel" class="hide">${_('Time since Nominal Time')}</div>
+        <div id="slaChart"></div>
+        <table id="slaTable" class="table table-striped table-condensed hide">
+          <thead>
+            <th>${_('Status')}</th>
+            <th>${_('Nominal Time')}</th>
+            <th>${_('Expected Start')}</th>
+            <th>${_('Actual Start')}</th>
+            <th>${_('Expected End')}</th>
+            <th>${_('Actual End')}</th>
+            <th>${_('Expected Duration')}</th>
+            <th>${_('Actual Duration')}</th>
+            <th>${_('SLA')}</th>
+          </thead>
+          <tbody>
+          %for sla in oozie_slas:
+            <tr>
+              <td class="slaStatus">${sla['slaStatus']}</td>
+              <td><span class="nominalTime">${sla['nominalTime']}</span></td>
+              <td><span class="expectedStart">${sla['expectedStart']}</span></td>
+              <td><span class="actualStart">${sla['actualStart']}</span></td>
+              <td><span class="expectedEnd">${sla['expectedEnd']}</span></td>
+              <td><span class="actualEnd">${sla['actualEnd']}</span></td>
+              <td>${sla['expectedDuration']}</td>
+              <td>${sla['actualDuration']}</td>
+              <td><a href="${ url('oozie:list_oozie_sla') }#${ sla['id'] }"></a></td>
+            </tr>
+          %endfor
+          </tbody>
+        </table>
       </div>
       % endif
     </div>
@@ -309,10 +339,38 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
 <link rel="stylesheet" href="/static/ext/css/codemirror.css">
 <script src="/static/ext/js/codemirror-xml.js"></script>
 
+% if oozie_coordinator.has_sla:
+<script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/oozie/static/js/sla.utils.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.selection.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.time.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/js/jquery.blueprint.js"></script>
+% endif
+
+
 <style type="text/css">
   .CodeMirror.cm-s-default {
     height:500px;
   }
+
+  #sla th {
+    vertical-align: middle !important;
+  }
+
+  #yAxisLabel {
+    -webkit-transform: rotate(270deg);
+    -moz-transform: rotate(270deg);
+    -o-transform: rotate(270deg);
+    writing-mode: lr-tb;
+    margin-left: -82px;
+    margin-top: 120px;
+    position: absolute;
+  }
+
+  #slaTable {
+    margin-top: 20px;
+  }
 </style>
 
 <script>
@@ -348,6 +406,16 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
   var viewModel = new RunningCoordinatorActionsModel([]);
   ko.applyBindings(viewModel);
 
+  var CHART_LABELS = {
+    NOMINAL_TIME: "${_('Nominal Time')}",
+    EXPECTED_START: "${_('Expected Start')}",
+    ACTUAL_START: "${_('Actual Start')}",
+    EXPECTED_END: "${_('Expected End')}",
+    ACTUAL_END: "${_('Actual End')}",
+    TOOLTIP_ADDON: "${_('click for the SLA dashboard')}"
+  }
+  var slaTable;
+
   $(document).ready(function(){
     $("a[data-row-selector='true']").jHueRowSelector();
 
@@ -360,6 +428,25 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
       $(this).removeClass("hide");
     });
 
+    % if oozie_coordinator.has_sla:
+    slaTable = $("#slaTable").dataTable({
+      "bPaginate": false,
+      "bLengthChange": false,
+      "bInfo": false,
+      "bAutoWidth": false,
+      "oLanguage": {
+        "sEmptyTable": "${_('No data available')}",
+        "sZeroRecords": "${_('No matching records')}"
+      }
+    });
+
+    $(".slaStatus").each(function () {
+      $(this).html(getSLAStatusLabel($(this).text()));
+    });
+
+    $(".dataTables_wrapper").css("min-height", "0");
+    $(".dataTables_filter").hide();
+    % endif
 
     var definitionEditor = $("#definitionEditor")[0];
 
@@ -376,6 +463,13 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
       if ($(e.target).attr("href") == "#definition") {
         codeMirror.refresh();
       }
+      % if oozie_coordinator.has_sla:
+      if ($(e.target).attr("href") == "#sla") {
+        window.setTimeout(function () {
+          updateSLAChart(slaTable, CHART_LABELS, 30); // limits to 30 results
+        }, 100)
+      }
+      % endif
     });
 
     $(".confirmationModal").click(function(){
@@ -498,6 +592,10 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
     function resizeLogs() {
       $("#log pre").css("overflow", "auto").height($(window).height() - $("#log pre").position().top - 80);
     }
+
+    if (window.location.hash == "#showSla") {
+      $("a[href='#sla']").click();
+    }
   });
 </script>
 

+ 17 - 100
apps/oozie/src/oozie/templates/dashboard/list_oozie_sla.mako

@@ -146,16 +146,17 @@ ${ layout.menubar(section='sla', dashboard=True) }
     </div>
   </div>
 </div>
-
+<script src="/oozie/static/js/bundles.utils.js" type="text/javascript" charset="utf-8"></script>
+<script src="/oozie/static/js/sla.utils.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/jquery/plugins/jquery.flot.min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/jquery/plugins/jquery.flot.selection.min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/jquery/plugins/jquery.flot.time.min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/js/jquery.blueprint.js"></script>
 
 <script type="text/javascript" charset="utf-8">
-  var slaTable;
   function performSearch(id) {
     if ((id != null || $("input[name='job_name']").val().trim()) != "" && slaTable) {
+      window.location.hash = (id != null ? id : $("input[name='job_name']").val().trim());
       $(".results").addClass("hide");
       $(".loader").removeClass("hide");
       $(".search-something").addClass("hide");
@@ -193,46 +194,16 @@ ${ layout.menubar(section='sla', dashboard=True) }
     }
   }
 
-  function getSLArow(row) {
-    return [
-      getStatusClass(row.slaStatus),
-      emptyStringIfNull(row.appName),
-      emptyStringIfNull(row.appType),
-      '<a href="' + row.appUrl + '" data-row-selector="true">' + row.id + '</a>',
-      emptyStringIfNull(row.nominalTime),
-      emptyStringIfNull(row.expectedStart),
-      emptyStringIfNull(row.actualStart),
-      emptyStringIfNull(row.expectedEnd),
-      emptyStringIfNull(row.actualEnd),
-      emptyStringIfNull(row.expectedDuration),
-      emptyStringIfNull(row.actualDuration),
-      emptyStringIfNull(row.jobStatus),
-      emptyStringIfNull(row.user),
-      emptyStringIfNull(row.lastModified)
-    ];
-  }
-
-  function getStatusClass(status) {
-    var klass = "";
-    if (['MET'].indexOf(status.toUpperCase()) > -1) {
-      klass = "label-success";
-    }
-    else if (['NOT_STARTED', 'IN_PROCESS'].indexOf(status.toUpperCase()) > -1) {
-      klass = "label-warning";
-    }
-    else {
-      klass = "label-important";
-    }
-    return '<span class="label ' + klass + '">' + status + '</span>'
-  }
-
-  function emptyStringIfNull(obj) {
-    if (obj != null && obj != undefined) {
-      return obj;
-    }
-    return "";
+  var CHART_LABELS = {
+    NOMINAL_TIME: "${_('Nominal Time')}",
+    EXPECTED_START: "${_('Expected Start')}",
+    ACTUAL_START: "${_('Actual Start')}",
+    EXPECTED_END: "${_('Expected End')}",
+    ACTUAL_END: "${_('Actual End')}",
+    TOOLTIP_ADDON: "${_('click for more details')}"
   }
 
+  var slaTable;
   $(document).ready(function () {
     $("a[data-row-selector='true']").jHueRowSelector();
 
@@ -281,72 +252,18 @@ ${ layout.menubar(section='sla', dashboard=True) }
     $(".dataTables_wrapper").css("min-height", "0");
     $(".dataTables_filter").hide();
 
-    function msToTime(millis) {
-      var s = Math.abs(millis);
-      var ms = s % 1000;
-      s = (s - ms) / 1000;
-      var secs = s % 60;
-      s = (s - secs) / 60;
-      var mins = s % 60;
-      var hrs = (s - mins) / 60;
-      return (millis < 0 ? "-" : "") + (hrs < 10 ? "0" : "") + hrs + ":" + (mins < 10 ? "0" : "") + mins + ":" + (secs < 10 ? "0" : "") + secs;
-    }
-
     $("a[data-toggle='tab']").on("shown", function (e) {
       if ($(e.target).attr("href") == "#chartTab") {
         window.setTimeout(function () {
-          $("#slaChart").jHueBlueprint("reset");
-          var rows = slaTable.fnGetNodes();
-          for (var i = 0; i < rows.length; i++) {
-            function getOptions(differenceCellValue, label, color) {
-              return {
-                data: [
-                  [moment($(rows[i]).find("td:eq(4)").html()).valueOf(), (moment(differenceCellValue).valueOf() - moment($(rows[i]).find("td:eq(4)").html()).valueOf())]
-                ],
-                label: label,
-                yAxisFormatter: function (val, axis) {
-                  return msToTime(val);
-                },
-                type: $.jHueBlueprint.TYPES.POINTCHART,
-                color: color,
-                isDateTime: true,
-                timeFormat: "<span style='color:" + ($(rows[i]).find("td:eq(4)").text().toUpperCase() == "MET" ? $.jHueBlueprint.COLORS.GREEN : $.jHueBlueprint.COLORS.RED) + "'>%Y-%m-%d %H:%M:%S</span>",
-                fill: false,
-                height: 300,
-                yTooltipFormatter: function (val) {
-                  return msToTime(val);
-                },
-                onItemClick: function (pos, item) {
-                  if (item) {
-                    location.href = $(slaTable.fnGetNodes()[item.dataIndex]).find("a").attr("href");
-                  }
-                }
-              }
-            }
-
-            if ($("#slaChart").data('plugin_jHueBlueprint') == null) {
-              $("#slaChart").jHueBlueprint(getOptions($(rows[i]).find("td:eq(4)").html(), (i == 0 ? "${_('Nominal Time')}" : null), $.jHueBlueprint.COLORS.ORANGE));
-            }
-            else {
-              $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("td:eq(4)").html(), (i == 0 ? "${_('Nominal Time')}" : null), $.jHueBlueprint.COLORS.ORANGE));
-            }
-            if ($(rows[i]).find("td:eq(5)").html().trim() != "") {
-              $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("td:eq(5)").html(), (i == 0 ? "${_('Expected Start')}" : null), $.jHueBlueprint.COLORS.BLUE));
-            }
-            if ($(rows[i]).find("td:eq(6)").html().trim() != "") {
-              $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("td:eq(6)").html(), (i == 0 ? "${_('Actual Start')}" : null), $.jHueBlueprint.COLORS.GREEN));
-            }
-            if ($(rows[i]).find("td:eq(7)").html().trim() != "") {
-              $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("td:eq(7)").html(), (i == 0 ? "${_('Expected End')}" : null), $.jHueBlueprint.COLORS.FALAFEL));
-            }
-            if ($(rows[i]).find("td:eq(8)").html().trim() != "") {
-              $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("td:eq(8)").html(), (i == 0 ? "${_('Actual End')}" : null), $.jHueBlueprint.COLORS.TURQUOISE));
-            }
-            $("#yAxisLabel").removeClass("hide");
-          }
+          updateSLAChart(slaTable, CHART_LABELS);
         }, 300)
       }
     });
+
+    if (window.location.hash != "") {
+      $("input[name='job_name']").val(window.location.hash.substr(1));
+      performSearch(window.location.hash.substr(1));
+    }
   });
 </script>
 

+ 101 - 3
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako

@@ -288,6 +288,36 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 
         % if oozie_workflow.has_sla:
         <div class="tab-pane" id="sla">
+          <div id="yAxisLabel" class="hide">${_('Time since Nominal Time')}</div>
+          <div id="slaChart"></div>
+          <table id="slaTable" class="table table-striped table-condensed hide">
+            <thead>
+              <th>${_('Status')}</th>
+              <th>${_('Nominal Time')}</th>
+              <th>${_('Expected Start')}</th>
+              <th>${_('Actual Start')}</th>
+              <th>${_('Expected End')}</th>
+              <th>${_('Actual End')}</th>
+              <th>${_('Expected Duration')}</th>
+              <th>${_('Actual Duration')}</th>
+              <th>${_('SLA')}</th>
+            </thead>
+            <tbody>
+            %for sla in oozie_slas:
+              <tr>
+                <td class="slaStatus">${sla['slaStatus']}</td>
+                <td><span class="nominalTime">${sla['nominalTime']}</span></td>
+                <td><span class="expectedStart">${sla['expectedStart']}</span></td>
+                <td><span class="actualStart">${sla['actualStart']}</span></td>
+                <td><span class="expectedEnd">${sla['expectedEnd']}</span></td>
+                <td><span class="actualEnd">${sla['actualEnd']}</span></td>
+                <td>${sla['expectedDuration']}</td>
+                <td>${sla['actualDuration']}</td>
+                <td><a href="${ url('oozie:list_oozie_sla') }#${ sla['id'] }"></a></td>
+              </tr>
+            %endfor
+            </tbody>
+          </table>
         </div>
         % endif
       </div>
@@ -324,10 +354,37 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 <link rel="stylesheet" href="/static/ext/css/codemirror.css">
 <script src="/static/ext/js/codemirror-xml.js"></script>
 
+% if oozie_workflow.has_sla:
+<script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/oozie/static/js/sla.utils.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.selection.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.time.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/js/jquery.blueprint.js"></script>
+%endif
+
 <style type="text/css">
-.CodeMirror.cm-s-default {
-   height:700px;
-}
+  .CodeMirror.cm-s-default {
+    height: 700px;
+  }
+
+  #sla th {
+    vertical-align: middle !important;
+  }
+
+  #yAxisLabel {
+    -webkit-transform: rotate(270deg);
+    -moz-transform: rotate(270deg);
+    -o-transform: rotate(270deg);
+    writing-mode: lr-tb;
+    margin-left: -82px;
+    margin-top: 120px;
+    position: absolute;
+  }
+
+  #slaTable {
+    margin-top: 20px;
+  }
 </style>
 
 <script type="text/javascript">
@@ -365,6 +422,16 @@ ${ layout.menubar(section='workflows', dashboard=True) }
   var viewModel = new RunningWorkflowActionsModel([]);
   ko.applyBindings(viewModel);
 
+  var CHART_LABELS = {
+    NOMINAL_TIME: "${_('Nominal Time')}",
+    EXPECTED_START: "${_('Expected Start')}",
+    ACTUAL_START: "${_('Actual Start')}",
+    EXPECTED_END: "${_('Expected End')}",
+    ACTUAL_END: "${_('Actual End')}",
+    TOOLTIP_ADDON: "${_('click for the SLA dashboard')}"
+  }
+  var slaTable;
+
   $(document).ready(function() {
     var CURRENT_ZOOM = 1;
     $(document).keydown(function(e) {
@@ -391,6 +458,26 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 
     $("*[rel=tooltip]").tooltip();
 
+    % if oozie_workflow.has_sla:
+    slaTable = $("#slaTable").dataTable({
+      "bPaginate": false,
+      "bLengthChange": false,
+      "bInfo": false,
+      "bAutoWidth": false,
+      "oLanguage": {
+        "sEmptyTable": "${_('No data available')}",
+        "sZeroRecords": "${_('No matching records')}"
+      }
+    });
+
+    $(".slaStatus").each(function () {
+      $(this).html(getSLAStatusLabel($(this).text()));
+    });
+
+    $(".dataTables_wrapper").css("min-height", "0");
+    $(".dataTables_filter").hide();
+    % endif
+
     $(".variable").each(function () {
       if ($(this).text().length > 25) {
         $(this).html($(this).text().substr(0, 24) + "&hellip;");
@@ -413,6 +500,13 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       if ($(e.target).attr("href") == "#definition") {
         codeMirror.refresh();
       }
+      % if oozie_workflow.has_sla:
+      if ($(e.target).attr("href") == "#sla") {
+        window.setTimeout(function () {
+          updateSLAChart(slaTable, CHART_LABELS, 30); // limits to 30 results
+        }, 100)
+      }
+      % endif
     });
 
     $(".action-link").click(function(){
@@ -558,6 +652,10 @@ ${ layout.menubar(section='workflows', dashboard=True) }
       $("#log pre").css("overflow", "auto").height($(window).height() - $("#log pre").position().top - 80);
     }
 
+    if (window.location.hash == "#showSla") {
+      $("a[href='#sla']").click();
+    }
+
   });
 </script>
 

+ 18 - 0
apps/oozie/src/oozie/views/dashboard.py

@@ -208,11 +208,20 @@ def list_oozie_workflow(request, job_id, coordinator_job_id=None, bundle_job_id=
     }
     return HttpResponse(encode_json_for_js(return_obj), mimetype="application/json")
 
+  oozie_slas = []
+  if oozie_workflow.has_sla:
+    api = get_oozie(request.user, api_version="v2")
+    params = {
+      'id': oozie_workflow.id
+    }
+    oozie_slas = api.get_oozie_slas(**params)
+
   return render('dashboard/list_oozie_workflow.mako', request, {
     'history': history,
     'oozie_workflow': oozie_workflow,
     'oozie_coordinator': oozie_coordinator,
     'oozie_bundle': oozie_bundle,
+    'oozie_slas': oozie_slas,
     'hue_workflow': hue_workflow,
     'hue_coord': hue_coord,
     'parameters': parameters,
@@ -248,8 +257,17 @@ def list_oozie_coordinator(request, job_id, bundle_job_id=None):
     }
     return HttpResponse(encode_json_for_js(return_obj), mimetype="application/json")
 
+  oozie_slas = []
+  if oozie_coordinator.has_sla:
+    api = get_oozie(request.user, api_version="v2")
+    params = {
+      'id': oozie_coordinator.id
+    }
+    oozie_slas = api.get_oozie_slas(**params)
+
   return render('dashboard/list_oozie_coordinator.mako', request, {
     'oozie_coordinator': oozie_coordinator,
+    'oozie_slas': oozie_slas,
     'coordinator': coordinator,
     'oozie_bundle': oozie_bundle,
     'has_job_edition_permission': has_job_edition_permission,

+ 113 - 0
apps/oozie/static/js/sla.utils.js

@@ -0,0 +1,113 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you 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.
+
+function getSLAStatusLabel(status) {
+  var klass = "";
+  if (['MET'].indexOf(status.toUpperCase()) > -1) {
+    klass = "label-success";
+  }
+  else if (['NOT_STARTED', 'IN_PROCESS'].indexOf(status.toUpperCase()) > -1) {
+    klass = "label-warning";
+  }
+  else {
+    klass = "label-important";
+  }
+  return '<span class="label ' + klass + '">' + status + '</span>'
+}
+
+function getSLArow(row) {
+  return [
+    getSLAStatusLabel(row.slaStatus),
+    emptyStringIfNull(row.appName),
+    emptyStringIfNull(row.appType),
+    '<a href="' + row.appUrl + '#showSla" data-row-selector="true">' + row.id + '</a>',
+    '<span class="nominalTime">' + emptyStringIfNull(row.nominalTime) + '</span>',
+    '<span class="expectedStart">' + emptyStringIfNull(row.expectedStart) + '</span>',
+    '<span class="actualStart">' + emptyStringIfNull(row.actualStart) + '</span>',
+    '<span class="expectedEnd">' + emptyStringIfNull(row.expectedEnd) + '</span>',
+    '<span class="actualEnd">' + emptyStringIfNull(row.actualEnd) + '</span>',
+    emptyStringIfNull(row.expectedDuration),
+    emptyStringIfNull(row.actualDuration),
+    emptyStringIfNull(row.jobStatus),
+    emptyStringIfNull(row.user),
+    emptyStringIfNull(row.lastModified)
+  ];
+}
+
+function msToTime(millis) {
+  var s = Math.abs(millis);
+  var ms = s % 1000;
+  s = (s - ms) / 1000;
+  var secs = s % 60;
+  s = (s - secs) / 60;
+  var mins = s % 60;
+  var hrs = (s - mins) / 60;
+  return (millis < 0 ? "-" : "") + (hrs < 10 ? "0" : "") + hrs + ":" + (mins < 10 ? "0" : "") + mins + ":" + (secs < 10 ? "0" : "") + secs;
+}
+
+
+function updateSLAChart(slaTable, labels, limit) {
+  $("#slaChart").jHueBlueprint("reset");
+  var rows = slaTable.fnGetNodes();
+  for (var i = 0; i < (limit != null && limit < rows.length ? limit : rows.length); i++) {
+    function getOptions(differenceCellValue, label, color) {
+      return {
+        data: [
+          [moment($(rows[i]).find("span.nominalTime").html()).valueOf(), (moment(differenceCellValue).valueOf() - moment($(rows[i]).find("span.nominalTime").html()).valueOf())]
+        ],
+        label: label,
+        yAxisFormatter: function (val, axis) {
+          return msToTime(val);
+        },
+        type: $.jHueBlueprint.TYPES.POINTCHART,
+        color: color,
+        isDateTime: true,
+        timeFormat: "<span style='color:" + ($(rows[i]).find("td:eq(4)").text().toUpperCase() == "MET" ? $.jHueBlueprint.COLORS.GREEN : $.jHueBlueprint.COLORS.RED) + "'>%Y-%m-%d %H:%M:%S</span>",
+        fill: false,
+        height: 300,
+        tooltipAddon: labels.TOOLTIP_ADDON,
+        yTooltipFormatter: function (val) {
+          return msToTime(val);
+        },
+        onItemClick: function (pos, item) {
+          if (item && $(slaTable.fnGetNodes()[item.dataIndex]).find("a").length > 0) {
+            location.href = $(slaTable.fnGetNodes()[item.dataIndex]).find("a").attr("href");
+          }
+        }
+      }
+    }
+
+    if ($("#slaChart").data('plugin_jHueBlueprint') == null) {
+      $("#slaChart").jHueBlueprint(getOptions($(rows[i]).find("span.nominalTime").html(), (i == 0 ? labels.NOMINAL_TIME : null), $.jHueBlueprint.COLORS.ORANGE));
+    }
+    else {
+      $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("span.nominalTime").html(), (i == 0 ? labels.NOMINAL_TIME : null), $.jHueBlueprint.COLORS.ORANGE));
+    }
+    if ($(rows[i]).find("span.expectedStart").html().trim() != "") {
+      $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("span.expectedStart").html(), (i == 0 ? labels.EXPECTED_START : null), $.jHueBlueprint.COLORS.BLUE));
+    }
+    if ($(rows[i]).find("span.actualStart").html().trim() != "") {
+      $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("span.actualStart").html(), (i == 0 ? labels.ACTUAL_START : null), $.jHueBlueprint.COLORS.GREEN));
+    }
+    if ($(rows[i]).find("span.expectedEnd").html().trim() != "") {
+      $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("span.expectedEnd").html(), (i == 0 ? labels.EXPECTED_END : null), $.jHueBlueprint.COLORS.FALAFEL));
+    }
+    if ($(rows[i]).find("span.actualEnd").html().trim() != "") {
+      $("#slaChart").jHueBlueprint("add", getOptions($(rows[i]).find("span.actualEnd").html(), (i == 0 ? labels.ACTUAL_END : null), $.jHueBlueprint.COLORS.TURQUOISE));
+    }
+    $("#yAxisLabel").removeClass("hide");
+  }
+}

+ 2 - 1
desktop/core/static/js/jquery.blueprint.js

@@ -44,6 +44,7 @@
         height: 200,
         borderWidth: 0,
         tooltips: true,
+        tooltipAddon: "",
         enableSelection: false,
         isDateTime: false,
         timeFormat: null,
@@ -181,7 +182,7 @@
               x = item.series.data[item.dataIndex][0];
             }
 
-            showTooltip(item.pageX, item.pageY, "X: "+(_this.options.xTooltipFormatter?_this.options.xTooltipFormatter(x):x)+", Y: "+(_this.options.yTooltipFormatter?_this.options.yTooltipFormatter(y):y));
+            showTooltip(item.pageX, item.pageY, "X: " + (_this.options.xTooltipFormatter ? _this.options.xTooltipFormatter(x) : x) + ", Y: " + (_this.options.yTooltipFormatter ? _this.options.yTooltipFormatter(y) : y) + (_this.options.tooltipAddon != "" ? ", " + _this.options.tooltipAddon : ""));
           }
         } else {
           $("#jHueBlueprintTooltip").remove();