Bläddra i källkod

HUE-1837 [oozie] SLA dashboard it is not pretty

Restyled the SLA dashboard
Added charting capabilities
Enrico Berti 12 år sedan
förälder
incheckning
95d90669fa

+ 325 - 74
apps/oozie/src/oozie/templates/dashboard/list_oozie_sla.mako

@@ -25,104 +25,355 @@
 ${ commonheader(_("SLA"), "sla", user) | n,unicode }
 ${ layout.menubar(section='sla', dashboard=True) }
 
+<style type="text/css">
+  .label-with-margin {
+    margin-right: 20px;
+  }
 
-<div class="container-fluid">
+  .checkbox {
+    margin-bottom: 2px !important;
+  }
 
-  <div class="card card-small">
-  <div class="card-body">
-  <p>
+  input[type='checkbox'] {
+    margin-right: 5px !important;
+    margin-top: 3px;
+  }
+
+  th {
+    vertical-align: middle !important;
+  }
 
-  <h1 class="card-heading card-heading-noborder simple pull-right" style="margin-top: -4px;">
-    Import / Export
-  </h1>
+  #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;
+  }
 
-  <ul class="nav nav-tabs">
-      <form class="form-search" id="searchForm" method="GET" action=".">
+</style>
+
+<div class="container-fluid">
+  <div class="card card-small">
+    <h1 class="card-heading simple">
+    <div class="pull-left" style="margin-right: 20px;margin-top: 2px">${_('Search SLA')}</div>
+    <form class="form-inline" id="searchForm" method="GET" action="." style="margin-bottom: 4px">
+      <label>
+        ${_('Name or Id')}
         <input type="text" name="job_name" class="searchFilter input-xlarge search-query" placeholder="${_('Job Name or Id (required)')}">
-        <input type="checkbox" name="isParent" class="searchFilter" placeholder="${_('Text Filter')}">
-        ${ _('Parent ID') }
-        Start
-        <input type="text" name="start" class="searchFilter input-xlarge search-query" placeholder="${_('Start in GMT')}">
-        End
-        <input type="text" name="end" class="searchFilter input-xlarge search-query" placeholder="${_('End')}">
-      </form>
-    <li class="active"><a href="#slaListTab" data-toggle="tab">${ _('List') }</a></li>
-    <li><a href="#graphTab" data-toggle="tab">${ _('Graph') }</a></li>
-  </ul>
-
-  <div class="tab-content" style="padding-bottom:200px">
-    <div class="tab-pane active" id="slaListTab">
-      <form class="form-search">
-        <input type="text" class="searchFilter input-xlarge search-query" placeholder="${_('Text Filter')}">
-      </form>
-      <div class="tabbable">
-
-        <div class="tab-content">
-
-            <table id="slaTable" class="table table-striped table-condensed">
-              <thead>
-                % for col in columns:
-                  <th>${ col }</th>
-                % endfor
-              </thead>
-              <tbody>
-                % for sla in oozie_slas:
-                <tr>
-                  % for col in columns:
-                    <td>${ sla.get(col, '') }</td>
-                  % endfor
-                </tr>
-                % endfor
-              </tbody>
-              </table>
+      </label>
+      <label class="checkbox label-with-margin">
+        <input type="checkbox" name="isParent" class="searchFilter">
+        ${ _('This is the parent ID') }
+      </label>
+      <label class="label-with-margin">
+        ${ _('Start') }
+        <input type="text" name="start_0" class="input-small date" value="" placeholder="${_('Date in GMT')}"><input type="text" name="start_1" class="input-small time" value="">
+      </label>
+      <label>
+        ${ _('End') }
+        <input type="text" name="end_0" class="input-small date" value="" placeholder="${_('Date in GMT')}"><input type="text" name="end_1" class="input-small time" value="">
+      </label>
+    </form>
+    </h1>
+    <div class="card-body">
+      <p>
+        <div class="loader hide" style="text-align: center;margin-top: 20px">
+          <!--[if lte IE 9]>
+              <img src="/static/art/spinner-big.gif" />
+          <![endif]-->
+          <!--[if !IE]> -->
+            <i class="fa fa-spinner fa-spin" style="font-size: 60px; color: #DDD"></i>
+          <!-- <![endif]-->
+        </div>
 
+        <div class="search-something center empty-wrapper">
+          <i class="fa fa-search"></i>
+          <h1>${_('Use the form above to search for SLAs.')}</h1>
+          <br/>
         </div>
-      </div>
 
-    </div>
+        <div class="no-results center empty-wrapper hide">
+          <i class="fa fa-frown-o"></i>
+          <h1>${_('The server returned no results.')}</h1>
+          <br/>
+        </div>
 
-    <div class="tab-pane" id="graphTab">
-      MY GRAPH
-    </div>
+        <div class="results hide">
+          <ul class="nav nav-tabs">
+            <li class="active"><a href="#slaListTab" data-toggle="tab">${ _('List') }</a></li>
+            <li><a href="#chartTab" data-toggle="tab">${ _('Chart') }</a></li>
+          </ul>
 
-    </p>
-    </div>
+          <div class="tab-content" style="padding-bottom:200px">
+            <div class="tab-pane active" id="slaListTab">
+              <div class="tabbable">
+                <div class="tab-content">
+                  <table id="slaTable" class="table table-striped table-condensed">
+                    <thead>
+                      <th>${_('Status')}</th>
+                      <th>${_('Name')}</th>
+                      <th>${_('Type')}</th>
+                      <th>${_('ID')}</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>${_('Job Status')}</th>
+                      <th>${_('User')}</th>
+                      <th>${_('Last Modified')}</th>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                  </table>
+                </div>
+              </div>
+
+            </div>
+
+            <div class="tab-pane" id="chartTab">
+              <div id="yAxisLabel" class="hide">${_('Time since Nominal Time')}</div>
+              <div id="slaChart"></div>
+            </div>
+          </div>
+        </div>
+      </p>
     </div>
   </div>
+</div>
+
+<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) {
+      $(".results").addClass("hide");
+      $(".loader").removeClass("hide");
+      $(".search-something").addClass("hide");
+      var IN_DATETIME_FORMAT = "MM/DD/YYYY hh:mm A";
+      var OUT_DATETIME_FORMAT = "YYYY-MM-DD[T]HH:mm[Z]";
+      var _postObj = {
+        "job_name": id != null ? id : $("input[name='job_name']").val()
+      };
+
+      if ($("input[name='isParent']").is("checked")) {
+        _postObj.isParent = true;
+      }
+      if ($("input[name='start_0']").val() != "" && $("input[name='start_1']").val() != "") {
+        _postObj.start = moment($("input[name='start_0']").val() + " " + $("input[name='start_1']").val(), IN_DATETIME_FORMAT).format(OUT_DATETIME_FORMAT);
+      }
+      if ($("input[name='end_0']").val() != "" && $("input[name='end_1']").val() != "") {
+        _postObj.end = moment($("input[name='end_0']").val() + " " + $("input[name='end_1']").val(), IN_DATETIME_FORMAT).format(OUT_DATETIME_FORMAT)
+      }
+
+      $.post("${ url('oozie:list_oozie_sla') }?format=json", _postObj, function (data) {
+        $(".loader").addClass("hide");
+        slaTable.fnClearTable();
+        if (data['oozie_slas'] && data['oozie_slas'].length > 0) {
+          $(".no-results").addClass("hide");
+          $(".results").removeClass("hide");
+          for (var i = 0; i < data['oozie_slas'].length; i++) {
+            slaTable.fnAddData(getSLArow(data['oozie_slas'][i]));
+          }
+        }
+        else {
+          $(".results").addClass("hide");
+          $(".no-results").removeClass("hide");
+        }
+      });
+    }
+  }
+
+  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>'
+  }
 
-<script>
-  $(document).ready(function(){
+  function emptyStringIfNull(obj) {
+    if (obj != null && obj != undefined) {
+      return obj;
+    }
+    return "";
+  }
+
+  $(document).ready(function () {
     $("a[data-row-selector='true']").jHueRowSelector();
 
     $("*[rel=tooltip]").tooltip();
 
-    var slaTable = $("#slaTable").dataTable({
-        "bPaginate": false,
-        "bLengthChange": false,
-        "bInfo": false,
-        "bAutoWidth": false,
-        "oLanguage": {
-            "sEmptyTable": "${_('No data available')}",
-            "sZeroRecords": "${_('No matching records')}"
+    $("input[name='start_0']").val(moment().subtract('days', 7).format("MM/DD/YYYY"));
+    $("input[name='start_1']").val(moment().subtract('days', 7).format("hh:mm A"));
+    $("input[name='end_0']").val(moment().format("MM/DD/YYYY"));
+    $("input[name='end_1']").val(moment().format("hh:mm A"));
+
+
+    $.getJSON("${url('oozie:list_oozie_workflows')}?format=json&justsla=true", function (data) {
+      var _autocomplete = [];
+      $(data).each(function (iWf, item) {
+        _autocomplete.push(item.id);
+      });
+      $("input[name='job_name']").typeahead({
+        source: _autocomplete,
+        updater: function (item) {
+          performSearch(item);
+          return item;
         }
+      });
     });
 
-    var _filterTimeout = -1;
-    $(".searchFilter").keyup(function() {
-      window.clearTimeout(_filterTimeout);
-      _filterTimeout = window.setTimeout(function () {
-        $.post("${ url('oozie:list_oozie_sla') }?format=json", $("#searchForm").serialize(), function(data) {
-          slaTable.fnClearTable();
-          if (data['oozie_slas']) {
-            slaTable.fnAddData(data['oozie_slas']); ## Should put a class on status, links to oozie job ids...
-          }
-        });
-      }, 300);
+    $("input[name='job_name']").on("keydown", function (e) {
+      if (e.keyCode == 13) {
+        performSearch();
+      }
+    });
+
+    slaTable = $("#slaTable").dataTable({
+      "bPaginate": false,
+      "bLengthChange": false,
+      "bInfo": false,
+      "bAutoWidth": false,
+      "oLanguage": {
+        "sEmptyTable": "${_('No data available')}",
+        "sZeroRecords": "${_('No matching records')}"
+      },
+      "fnDrawCallback": function (oSettings) {
+        $("a[data-row-selector='true']").jHueRowSelector();
+      }
     });
 
-    $(".dataTables_wrapper").css("min-height","0");
+    $(".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");
+          }
+        }, 300)
+      }
+    });
+  });
+</script>
+
+${ utils.decorate_datetime_fields() }
+
+<script type="text/javascript" charset="utf-8">
+  $(document).ready(function () {
+    $("input[name='start_0']").parent().datepicker().on("changeDate", function () {
+      performSearch();
+    });
+
+    $("input[name='end_0']").parent().datepicker().on("changeDate", function () {
+      performSearch();
+    });
+    $("input[name='start_1']").on("change", function (e) {
+      // the timepicker plugin doesn't have a change event handler
+      // so we need to wait a bit to handle in with the default field event
+      window.setTimeout(function () {
+        performSearch();
+      }, 200);
+    });
+
+    $("input[name='end_1']").on("change", function () {
+      window.setTimeout(function () {
+        performSearch();
+      }, 200);
+    });
   });
 </script>
 

+ 51 - 49
apps/oozie/src/oozie/views/dashboard.py

@@ -103,11 +103,12 @@ def list_oozie_workflows(request):
 
   if request.GET.get('format') == 'json':
     json_jobs = workflows.jobs
+    just_sla = request.GET.get('justsla') == 'true'
     if request.GET.get('type') == 'running':
       json_jobs = split_oozie_jobs(request.user, workflows.jobs)['running_jobs']
     if request.GET.get('type') == 'completed':
       json_jobs = split_oozie_jobs(request.user, workflows.jobs)['completed_jobs']
-    return HttpResponse(encode_json_for_js(massaged_oozie_jobs_for_json(json_jobs, request.user)), mimetype="application/json")
+    return HttpResponse(encode_json_for_js(massaged_oozie_jobs_for_json(json_jobs, request.user, just_sla)), mimetype="application/json")
 
   return render('dashboard/list_oozie_workflows.mako', request, {
     'user': request.user,
@@ -334,7 +335,6 @@ def list_oozie_sla(request):
   if request.method == 'POST':
     # filter=nominal_start=2013-06-18T00:01Z;nominal_end=2013-06-23T00:01Z;app_name=my-sla-app
     params = {}
-    print request.POST
 
     job_name = request.POST.get('job_name')
     if job_name.endswith('-oozie-oozi-W'):
@@ -351,40 +351,42 @@ def list_oozie_sla(request):
       params['nominal_end'] = request.POST.get('end')
 
     oozie_slas = api.get_oozie_slas(**params)
-    print oozie_slas
 
   else:
     oozie_slas = [] # or get latest?
 
-  columns = [
-    'slaStatus',
-    'id',
-    'appType',
-    'appName',
-    'user',
-    'nominalTime',
-    'expectedStart',
-    'actualStart',
-    'expectedEnd',
-    'actualEnd',
-    'jobStatus',
-    #'expectedDuration',
-    #'actualDuration',
-    'lastModified'
-  ]
-
   if request.REQUEST.get('format') == 'json':
     massaged_slas = []
     for sla in oozie_slas:
-      massaged_slas.append([sla[key] for key in columns])
+      massaged_slas.append(massaged_sla_for_json(sla))
 
     return HttpResponse(json.dumps({'oozie_slas': massaged_slas}), content_type="text/json")
 
   return render('dashboard/list_oozie_sla.mako', request, {
-    'oozie_slas': oozie_slas,
-    'columns': columns,
+    'oozie_slas': oozie_slas
   })
 
+def massaged_sla_for_json(sla):
+  massaged_sla = {
+    'slaStatus': sla['slaStatus'],
+    'id': sla['id'],
+    'appType': sla['appType'],
+    'appName': sla['appName'],
+    'appUrl': reverse(sla['appType'] == 'WORKFLOW_JOB' and 'oozie:list_oozie_workflow' or 'oozie:list_oozie_coordinator', kwargs={'job_id': sla['id']}),
+    'user': sla['user'],
+    'nominalTime': sla['nominalTime'],
+    'expectedStart': sla['expectedStart'],
+    'actualStart': sla['actualStart'],
+    'expectedEnd': sla['expectedEnd'],
+    'actualEnd': sla['actualEnd'],
+    'jobStatus': sla['jobStatus'],
+    'expectedDuration': sla['expectedDuration'],
+    'actualDuration': sla['actualDuration'],
+    'lastModified': sla['lastModified']
+  }
+
+  return massaged_sla
+
 
 @show_oozie_error
 def rerun_oozie_job(request, job_id, app_path):
@@ -695,7 +697,7 @@ def format_time(st_time):
     return time.strftime("%a, %d %b %Y %H:%M:%S", st_time)
 
 
-def massaged_oozie_jobs_for_json(oozie_jobs, user):
+def massaged_oozie_jobs_for_json(oozie_jobs, user, just_sla=False):
   jobs = []
 
   for job in oozie_jobs:
@@ -706,31 +708,31 @@ def massaged_oozie_jobs_for_json(oozie_jobs, user):
         job = get_oozie(user).get_coordinator(job.id)
       else:
         job = get_oozie(user).get_bundle(job.id)
-
-    massaged_job = {
-      'id': job.id,
-      'lastModTime': hasattr(job, 'lastModTime') and job.lastModTime and format_time(job.lastModTime) or None,
-      'kickoffTime': hasattr(job, 'kickoffTime') and job.kickoffTime or '',
-      'timeOut': hasattr(job, 'timeOut') and job.timeOut or None,
-      'endTime': job.endTime and format_time(job.endTime) or None,
-      'status': job.status,
-      'isRunning': job.is_running(),
-      'duration': job.endTime and job.startTime and format_duration_in_millis(( time.mktime(job.endTime) - time.mktime(job.startTime) ) * 1000) or None,
-      'appName': job.appName,
-      'progress': job.get_progress(),
-      'user': job.user,
-      'absoluteUrl': job.get_absolute_url(),
-      'canEdit': has_job_edition_permission(job, user),
-      'killUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'kill'}),
-      'suspendUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'suspend'}),
-      'resumeUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'resume'}),
-      'created': hasattr(job, 'createdTime') and job.createdTime and job.createdTime and ((job.type == 'Bundle' and job.createdTime) or format_time(job.createdTime)),
-      'startTime': hasattr(job, 'startTime') and format_time(job.startTime) or None,
-      'run': hasattr(job, 'run') and job.run or 0,
-      'frequency': hasattr(job, 'frequency') and job.frequency or None,
-      'timeUnit': hasattr(job, 'timeUnit') and job.timeUnit or None,
-    }
-    jobs.append(massaged_job)
+    if not just_sla or (just_sla and job.has_sla):
+      massaged_job = {
+        'id': job.id,
+        'lastModTime': hasattr(job, 'lastModTime') and job.lastModTime and format_time(job.lastModTime) or None,
+        'kickoffTime': hasattr(job, 'kickoffTime') and job.kickoffTime or '',
+        'timeOut': hasattr(job, 'timeOut') and job.timeOut or None,
+        'endTime': job.endTime and format_time(job.endTime) or None,
+        'status': job.status,
+        'isRunning': job.is_running(),
+        'duration': job.endTime and job.startTime and format_duration_in_millis(( time.mktime(job.endTime) - time.mktime(job.startTime) ) * 1000) or None,
+        'appName': job.appName,
+        'progress': job.get_progress(),
+        'user': job.user,
+        'absoluteUrl': job.get_absolute_url(),
+        'canEdit': has_job_edition_permission(job, user),
+        'killUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'kill'}),
+        'suspendUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'suspend'}),
+        'resumeUrl': reverse('oozie:manage_oozie_jobs', kwargs={'job_id':job.id, 'action':'resume'}),
+        'created': hasattr(job, 'createdTime') and job.createdTime and job.createdTime and ((job.type == 'Bundle' and job.createdTime) or format_time(job.createdTime)),
+        'startTime': hasattr(job, 'startTime') and format_time(job.startTime) or None,
+        'run': hasattr(job, 'run') and job.run or 0,
+        'frequency': hasattr(job, 'frequency') and job.frequency or None,
+        'timeUnit': hasattr(job, 'timeUnit') and job.timeUnit or None,
+      }
+      jobs.append(massaged_job)
 
   return jobs
 

+ 48 - 7
desktop/core/static/js/jquery.blueprint.js

@@ -23,7 +23,11 @@
       COLORS = {
         ORANGE: "#FB950D",
         GREEN: "#419E08",
-        BLUE: "#338BB8"
+        BLUE: "#338BB8",
+        RED: "#CE151D",
+        PURPLE: "#572B91",
+        TURQUOISE: "#049D84",
+        FALAFEL: "#774400"
       },
       TYPES = {
         LINECHART: "lines",
@@ -42,8 +46,13 @@
         tooltips: true,
         enableSelection: false,
         isDateTime: false,
+        timeFormat: null,
         isCategories: false,
         useCanvas: false,
+        xAxisFormatter: null,
+        yAxisFormatter: null,
+        xTooltipFormatter: null,
+        yTooltipFormatter: null,
         onSelect: function () {
         },
         onItemClick: function () {
@@ -81,7 +90,7 @@
     }
     else {
       _series.push(getSerie(serie));
-      var _plot = $.plot(element, _series, { grid: { borderWidth: element.data('plugin_' + pluginName).options.borderWidth } });
+      var _plot = $.plot(element, _series, $(element).data("plotOptions"));
       $(element).data("plotObj", _plot);
       $(element).data("plotSeries", _series);
     }
@@ -117,12 +126,30 @@
       _options.xaxis = {
         mode: "time"
       }
+      if (_this.options.timeFormat) {
+         _options.xaxis.timeformat = _this.options.timeFormat;
+      }
     }
     if (_this.options.isCategories) {
       _options.xaxis = {
         mode: "categories"
       }
     }
+    if (_this.options.xAxisFormatter != null) {
+      if (_options.xaxis == null){
+        _options.xaxis = {
+          tickFormatter: _this.options.yAxisFormatter
+        }
+      }
+      else {
+        _options.xaxis.tickFormatter = _this.options.xAxisFormatter;
+      }
+    }
+    if (_this.options.yAxisFormatter != null) {
+      _options.yaxis = {
+        tickFormatter: _this.options.yAxisFormatter
+      }
+    }
     if (_this.options.useCanvas) {
       _options.canvas = true;
     }
@@ -154,7 +181,7 @@
               x = item.series.data[item.dataIndex][0];
             }
 
-            showTooltip(item.pageX, item.pageY, "X: "+x+", 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));
           }
         } else {
           $("#jHueBlueprintTooltip").remove();
@@ -166,6 +193,7 @@
       _this.options.onItemClick(pos, item);
     });
     $(_this.element).data("plotObj", _plot);
+    $(_this.element).data("plotOptions", _options);
     $(_this.element).data("plotSeries", [_serie]);
   }
 
@@ -230,11 +258,24 @@
   $.fn[pluginName] = function (options) {
     var _args = Array.prototype.slice.call(arguments);
     if (_args.length == 1) {
-      return this.each(function () {
-        if (!$.data(this, 'plugin_' + pluginName)) {
-          $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
+      if (_args[0] == "reset") { // resets the graph
+        if (this.data("plotObj")){
+          this.data("plotObj").setData([]);
+          this.data("plotObj").setupGrid();
+          this.data("plotObj").draw();
+          this.data("plotObj", null);
+          this.data("plotSeries", null);
+          this.data("plotOptions", null);
+          this.data('plugin_' + pluginName, null);
         }
-      });
+      }
+      else {
+        return this.each(function () {
+          if (!$.data(this, 'plugin_' + pluginName)) {
+            $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
+          }
+        });
+      }
     }
     else {
       if (_args[0] == "add") { // add a serie to the graph