Selaa lähdekoodia

HUE-807 [oozie] Ajax refresh of a running job

Visually restructured the workflow page
Added ajax refresh
Enrico Berti 13 vuotta sitten
vanhempi
commit
ee46c03923

+ 331 - 207
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako

@@ -27,7 +27,8 @@ ${ commonheader(_("Oozie App"), "oozie", user, "100px") | n,unicode }
 ${ layout.menubar(section='dashboard') }
 
 
-<div class="container-fluid">
+<div class="container-fluid" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
+
   ${ layout.dashboard_sub_menubar(section='workflows') }
 
   <h1>
@@ -39,241 +40,229 @@ ${ layout.menubar(section='dashboard') }
   </h1>
 
   <div class="row-fluid">
-    <div class="span3">
-      ${ _('Workflow') }
-    </div>
-    <div class="span3">
-      % if hue_workflow is not None:
-        <a title="${ _('Edit workflow') }" href="${ hue_workflow.get_absolute_url() }">${ hue_workflow }</a>
-      % else:
-        ${ oozie_workflow.appName }
-      % endif
-    </div>
-  </div>
-
-  % if hue_coord:
-  <div class="row-fluid">
-    <div class="span3">
-      ${ _('Coordinator') }
-    </div>
-    <div class="span3">
-      <a href="${ hue_coord.get_absolute_url() }">${ hue_coord.name }</a>
-    </div>
-  </div>
-  % endif
-
-  <div class="row-fluid">
-    <div class="span3">
-      ${ _('Submitter') }
-    </div>
-    <div class="span3">
-      ${ oozie_workflow.user }
-    </div>
-  </div>
-
-  <div class="row-fluid">
-    <div class="span3">
-      ${ _('Status') }
-    </div>
-    <div class="span3">
-      <span class="label ${ utils.get_status(oozie_workflow.status) }">${ oozie_workflow.status }</span>
-    </div>
-  </div>
-
-  <div class="row-fluid">
-    <div class="span3">
-      ${ _('Progress') }
-    </div>
-    <div class="span3">
-      ${ oozie_workflow.get_progress() }%
-    </div>
-  </div>
-
-  % if parameters and len(parameters) < 10:
-    <div class="row-fluid">
-      <div class="span3">
-        ${ _('Variables') }
+    <div class="span2">
+      <div class="well sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Workflow') }</li>
+          <li>
+              % if hue_workflow is not None:
+                <a title="${ _('Edit workflow') }" href="${ hue_workflow.get_absolute_url() }">${ hue_workflow }</a>
+              % else:
+              ${ oozie_workflow.appName }
+              % endif
+          </li>
+
+          % if hue_coord:
+              <li class="nav-header">${ _('Coordinator') }</li>
+              <li><a href="${ hue_coord.get_absolute_url() }">${ hue_coord.name }</a></li>
+          % endif
+
+          <li class="nav-header">${ _('Submitter') }</li>
+          <li>${ oozie_workflow.user }</li>
+
+          <li class="nav-header">${ _('Status') }</li>
+          <li id="status"><span class="label ${ utils.get_status(oozie_workflow.status) }">${ oozie_workflow.status }</span></li>
+
+          <li class="nav-header">${ _('Progress') }</li>
+          <li id="progress">
+            <div class="progress">
+              <div class="bar" style="width: 0">${ oozie_workflow.get_progress() }%</div>
+            </div>
+          </li>
+
+          % if parameters and len(parameters) < 10:
+              <li class="nav-header">${ _('Variables') }</li>
+              % for var, value in parameters.iteritems():
+                % if var not in ParameterForm.NON_PARAMETERS and var != 'oozie.use.system.libpath':
+                  <li rel="tooltip" title="${ var } : ${ str(value) }">
+                    % if utils.is_linkable(var, str(value)):
+                      <a href="${ utils.hdfs_link_js(str(value)) }"><i class="icon-eye-open"></i> <span class="variable hide">${ var }</span></a>
+                    % else:
+                    <i class="icon-eye-open"></i> <span class="variable">${ var }</span>
+                    % endif
+                  </li>
+                % endif
+              % endfor
+          % endif
+
+          % if has_job_edition_permission(oozie_workflow, user):
+              <li class="nav-header">${ _('Manage') }</li>
+              <li>
+              % if oozie_workflow.is_running():
+                <button title="${_('Kill %(workflow)s') % dict(workflow=oozie_workflow.id)}"
+                   id="kill-workflow"
+                   class="btn btn-small btn-danger confirmationModal"
+                   alt="${ _('Are you sure you want to kill workflow %s?') %  oozie_workflow.id }"
+                   href="javascript:void(0)"
+                   data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_workflow.id, action='kill') }"
+                   data-message="${ _('The workflow was killed.') }"
+                   data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
+                ${_('Kill')}
+                </button>
+              % endif
+              % if oozie_workflow.id:
+                  <button title="${ _('Rerun the same workflow') }" id="rerun-btn"
+                     data-rerun-url="${ url('oozie:rerun_oozie_job', job_id=oozie_workflow.id, app_path=oozie_workflow.appPath) }"
+                     class="btn btn-small
+                % if oozie_workflow.is_running():
+                hide
+                % endif
+                ">
+                  ${ _('Rerun') }
+                </button>
+                <div id="rerun-wf-modal" class="modal hide"></div>
+              % endif
+              </li>
+          % endif
+
+        </ul>
       </div>
     </div>
-    % for var, value in parameters.iteritems():
-      % if var not in ParameterForm.NON_PARAMETERS and var != 'oozie.use.system.libpath':
-        <div class="row-fluid">
-          <div class="span3"></div>
-          <div class="span3">
-            ${ var }
-          </div>
-          <div class="span3">
-            ${ utils.guess_hdfs_link(var, str(value)) }
-          </div>
-        </div>
-      % endif
-    % endfor
-  % endif
 
-  % if has_job_edition_permission(oozie_workflow, user):
-  <div class="row-fluid">
-    <div class="span3">
-      ${ _('Manage') }
-    </div>
-    <div class="span3">
-      % if oozie_workflow.is_running():
-        <a title="${_('Kill %(workflow)s') % dict(workflow=oozie_workflow.id)}"
-          id="kill-workflow"
-          class="btn small confirmationModal"
-          alt="${ _('Are you sure you want to kill workflow %s?') %  oozie_workflow.id }"
-          href="javascript:void(0)"
-          data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_workflow.id, action='kill') }"
-          data-message="${ _('The workflow was killed.') }"
-          data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
-            ${_('Kill')}
-        </a>
-      % else:
-        % if oozie_workflow.id:
-          <a title="${ _('Rerun the same workflow') }" class="btn" id="rerun-btn"
-            data-rerun-url="${ url('oozie:rerun_oozie_job', job_id=oozie_workflow.id, app_path=oozie_workflow.appPath) }">
-            ${ _('Rerun') }
-          </a>
-        % endif
-        <div id="rerun-wf-modal" class="modal hide"></div>
-      % endif
-    </div>
-  </div>
-  % endif
+    <div class="span9">
 
-  <br/><br/>
 
-    <ul class="nav nav-tabs">
-      % if workflow_graph:
-        <li class="active"><a href="#graph" data-toggle="tab">${ _('Graph') }</a></li>
-        <li><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
-      % else:
-        <li class="active"><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
-      % endif
+      <ul class="nav nav-tabs">
+        % if workflow_graph:
+            <li class="active"><a href="#graph" data-toggle="tab">${ _('Graph') }</a></li>
+            <li><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
+        % else:
+            <li class="active"><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
+        % endif
         <li><a href="#details" data-toggle="tab">${ _('Details') }</a></li>
         <li><a href="#configuration" data-toggle="tab">${ _('Configuration') }</a></li>
         <li><a href="#log" data-toggle="tab">${ _('Log') }</a></li>
         <li><a href="#definition" data-toggle="tab">${ _('Definition') }</a></li>
-    </ul>
+      </ul>
 
-    <div id="workflow-tab-content" class="tab-content" style="min-height:200px">
+      <div id="workflow-tab-content" class="tab-content" style="min-height:200px">
 
-     % if workflow_graph:
-       <div id="graph" class="tab-pane active">
-           ${ workflow_graph | n,unicode }
-       </div>
-     % endif
+        % if workflow_graph:
+            <div id="graph" class="tab-pane active">
+            ${ workflow_graph | n,unicode }
+            </div>
+        % endif
 
-      <div id="actions" class="tab-pane ${ utils.if_false(workflow_graph, 'active') }">
-        % if oozie_workflow.get_working_actions():
+        <div id="actions" class="tab-pane ${ utils.if_false(workflow_graph, 'active') }">
           <table class="table table-striped table-condensed selectable">
             <thead>
-              <tr>
-                <th>${ _('Logs') }</th>
-                <th>${ _('Id') }</th>
-                <th>${ _('Name') }</th>
-                <th>${ _('Type') }</th>
-                <th>${ _('Status') }</th>
-                <th>${ _('External Id') }</th>
-
-                <th>${ _('Start Time') }</th>
-                <th>${ _('End Time') }</th>
-
-                <th>${ _('Retries') }</th>
-                <th>${ _('Error Message') }</th>
-                <th>${ _('Transition') }</th>
-
-                <th>${ _('Data') }</th>
-              </tr>
+            <tr>
+              <th>${ _('Logs') }</th>
+              <th>${ _('Id') }</th>
+              <th>${ _('Name') }</th>
+              <th>${ _('Type') }</th>
+              <th>${ _('Status') }</th>
+              <th>${ _('External Id') }</th>
+
+              <th>${ _('Start Time') }</th>
+              <th>${ _('End Time') }</th>
+
+              <th>${ _('Retries') }</th>
+              <th>${ _('Error Message') }</th>
+              <th>${ _('Transition') }</th>
+
+              <th>${ _('Data') }</th>
+            </tr>
             </thead>
-            <tbody>
-              % for i, action in enumerate(oozie_workflow.get_working_actions()):
-                <tr>
-                  <td>
-                    % if action.externalId:
-                      <a href="${ url('jobbrowser.views.job_single_logs', job=action.externalId) }" data-row-selector-exclude="true"><i class="icon-tasks"></i></a>
-                    % endif
-                  </td>
-                  <td>
-                    <a href="${ url('oozie:list_oozie_workflow_action', action=action.id) }" data-row-selector='true'>${ action.id }</a>
-                  </td>
-                  <td>
-                    % if design_link:
-                      <a href="${ design_link }">${ action.name }</a>
-                    % else:
-                      ${ action.name }
-                    % endif
-                  </td>
-                  <td>${ action.type }</td>
-                  <td><span class="label ${ utils.get_status(action.status) }">${ action.status }</span></td>
-                  <td>
-                    % if action.externalId:
-                      <a href="${ url('jobbrowser.views.single_job', job=action.externalId) }">${ "_".join(action.externalId.split("_")[-2:]) }</a>
-                    % endif
-                  </td>
+            <tbody data-bind="template: {name: 'actionTemplate', foreach: actions}">
+
+            </tbody>
+            <tfoot>
+            <tr data-bind="visible: actions().length == 0">
+              <td colspan="12">
+                <div class="alert">
+                  ${ _('There are no actions to be shown.') }
+                </div>
+              </td>
+            </tr>
+            </tfoot>
+          </table>
+        </div>
 
-                  <td>${ utils.format_time(action.startTime) }</td>
-                  <td>${ utils.format_time(action.endTime) }</td>
 
-                  <td>${ action.retries }</td>
-                  <td>${ action.errorMessage }</td>
-                  <td>${ action.transition }</td>
+        <script id="actionTemplate" type="text/html">
 
-                  <td>${ action.data }</td>
-                </tr>
-              % endfor
-            <tbody>
-          </table>
-        % endif
-       </div>
+          <tr>
+            <td>
+              <a data-bind="visible:externalId !='', attr: { href: log}" data-row-selector-exclude="true"><i class="icon-tasks"></i></a>
+            </td>
+            <td>
+              <a data-bind="text: id, attr: { href: url}" data-row-selector="true"></a>
+            </td>
+            <td data-bind="text: name"></td>
+            <td data-bind="text: type"></td>
+            <td><span data-bind="text: status, attr: {'class': statusClass}"></span></td>
+            <td>
+              <a data-bind="visible:externalId !='', attr: { href: externalIdUrl}, text: externalId" data-row-selector-exclude="true"></a>
+            </td>
+
+            <td data-bind="text: startTime"></td>
+            <td data-bind="text: endTime"></td>
 
-       <div class="tab-pane" id="details">
+            <td data-bind="text: retries"></td>
+            <td data-bind="text: errorMessage"></td>
+            <td data-bind="text: transition"></td>
+
+            <td data-bind="text: data"></td>
+
+          </tr>
+
+        </script>
+
+
+        <div class="tab-pane" id="details">
           <table class="table table-condensed">
             <tbody>
-              <tr>
-                <td>${ _('Group') }</td>
-                <td>${ oozie_workflow.group or '-' }</td>
-              </tr>
-              <tr>
-                <td>${ _('External Id') }</td>
-                <td>${ oozie_workflow.externalId or '-' }</td>
-              </tr>
-              <tr>
-                <td>${ _('Start Time') }</td>
-                <td>${ utils.format_time(oozie_workflow.startTime) }</td>
-              </tr>
-              <tr>
-                <td>${ _('Created Time') }</td>
-                <td>${ utils.format_time(oozie_workflow.createdTime) }</td>
-              </tr>
-              <tr>
-                <td>${ _('End Time') }</td>
-                <td>${  utils.format_time(oozie_workflow.endTime) }</td>
-              </tr>
-              <tr>
-                <td>${ _('Application Path') }</td>
-                <td>${  utils.hdfs_link(oozie_workflow.appPath) }</td>
-              </tr>
+            <tr>
+              <td>${ _('Group') }</td>
+              <td>${ oozie_workflow.group or '-' }</td>
+            </tr>
+            <tr>
+              <td>${ _('External Id') }</td>
+              <td>${ oozie_workflow.externalId or '-' }</td>
+            </tr>
+            <tr>
+              <td>${ _('Start Time') }</td>
+              <td>${ utils.format_time(oozie_workflow.startTime) }</td>
+            </tr>
+            <tr>
+              <td>${ _('Created Time') }</td>
+              <td>${ utils.format_time(oozie_workflow.createdTime) }</td>
+            </tr>
+            <tr>
+              <td>${ _('End Time') }</td>
+              <td>${  utils.format_time(oozie_workflow.endTime) }</td>
+            </tr>
+            <tr>
+              <td>${ _('Application Path') }</td>
+              <td>${  utils.hdfs_link(oozie_workflow.appPath) }</td>
+            </tr>
             </tbody>
           </table>
-       </div>
+        </div>
 
-       <div class="tab-pane" id="configuration">
-            ${ utils.display_conf(oozie_workflow.conf_dict) }
-       </div>
+        <div class="tab-pane" id="configuration">
+          ${ utils.display_conf(oozie_workflow.conf_dict) }
+        </div>
 
-       <div class="tab-pane" id="log">
-            <pre>${ oozie_workflow.log }</pre>
-       </div>
+        <div class="tab-pane" id="log">
+          <pre>${ oozie_workflow.log }</pre>
+        </div>
+
+        <div class="tab-pane" id="definition" style="min-height:400px">
+          <textarea id="definitionEditor">${ oozie_workflow.definition }</textarea>
+        </div>
+      </div>
 
-       <div class="tab-pane" id="definition" style="min-height:400px">
-            <textarea id="definitionEditor">${ oozie_workflow.definition }</textarea>
-       </div>
+      <div style="margin-bottom: 16px">
+        <a class="btn" onclick="history.back()">${ _('Back') }</a>
+      </div>
     </div>
 
-  <div style="margin-top: 20px; margin-bottom: 20px">
-    <a class="btn" onclick="history.back()">${ _('Back') }</a>
+
   </div>
 
+
 </div>
 
 <div id="confirmation" class="modal hide">
@@ -288,7 +277,7 @@ ${ layout.menubar(section='dashboard') }
 </div>
 
 <link rel="stylesheet" href="/oozie/static/css/workflow.css">
-
+<script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/codemirror-3.0.js"></script>
 <link rel="stylesheet" href="/static/ext/css/codemirror.css">
@@ -298,13 +287,90 @@ ${ layout.menubar(section='dashboard') }
 .CodeMirror.cm-s-default {
    height:500px;
 }
+.sidebar-nav {
+  padding: 9px 0;
+}
 </style>
 
 <script type="text/javascript">
+
+  function getStatusClass(status, prefix){
+    if(!Array.prototype.indexOf) {
+      Array.prototype.indexOf = function(needle) {
+        for(var i = 0; i < this.length; i++) {
+          if(this[i] === needle) {
+            return i;
+          }
+        }
+        return -1;
+      };
+    }
+    if (prefix == null){
+      prefix = "label-";
+    }
+    var klass = "";
+    if (['SUCCEEDED', 'OK'].indexOf(status) > -1){
+      klass = prefix + "success";
+    }
+    else if (['RUNNING', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED'].indexOf(status) > -1){
+      klass = prefix + "warning";
+    }
+    else if (status == 'READY'){
+      klass = prefix + "success";
+    }
+    else {
+      klass = prefix + "important";
+      if (prefix == "bar-"){
+        klass = prefix + "danger";
+      }
+    }
+    return klass;
+  }
+
+
+  var Action = function (action) {
+    return {
+      id: action.id,
+      log: action.log,
+      url: action.url,
+      name: action.name,
+      type: action.type,
+      status: action.status,
+      statusClass: "label "+getStatusClass(action.status),
+      externalIdUrl: action.externalIdUrl,
+      externalId: action.externalId,
+      startTime: action.startTime,
+      endTime: action.endTime,
+      retries: action.retries,
+      errorMessage: action.errorMessage,
+      transition: action.transition,
+      data: action.data
+    }
+  }
+
+  var RunningWorkflowActionsModel = function (actions) {
+    var self = this;
+
+    self.actions = ko.observableArray(ko.utils.arrayMap(actions), function (action) {
+      return new Action(action);
+    });
+
+  };
+
+  var viewModel = new RunningWorkflowActionsModel([]);
+  ko.applyBindings(viewModel);
+
   $(document).ready(function() {
 
     $("*[rel=tooltip]").tooltip();
 
+    $(".variable").each(function () {
+      if ($(this).text().length > 15) {
+        $(this).html($(this).text().substr(0, 14) + "&hellip;");
+      }
+      $(this).removeClass("hide");
+    });
+
     var definitionEditor = $("#definitionEditor")[0];
 
     var codeMirror = CodeMirror(function (elt) {
@@ -361,6 +427,64 @@ ${ layout.menubar(section='dashboard') }
      });
 
     $("a[data-row-selector='true']").jHueRowSelector();
+
+    resizeLogs();
+    refreshView();
+    var logsAtEnd = true;
+
+    function refreshView() {
+      $.getJSON(window.location.href + "?format=json", function (data) {
+
+        if (data.actions){
+          viewModel.actions(ko.utils.arrayMap(data.actions, function (action) {
+            return new Action(action);
+          }));
+        }
+
+        $("#status span").attr("class", "label").addClass(getStatusClass(data.status)).text(data.status);
+
+        if (data.id && data.status != "RUNNING"){
+          $("#kill-workflow").hide();
+          $("#rerun-btn").show();
+        }
+
+        $("#progress .bar").text(data.progress+"%").css("width", data.progress+"%").attr("class", "bar "+getStatusClass(data.status, "bar-"));
+        $("#graph").html(data.graph);
+
+        var _logsEl = $("#log pre");
+        var newLines = data.log.split("\n").slice(_logsEl.text().split("\n").length);
+        _logsEl.text(_logsEl.text() + newLines.join("\n"));
+        if (logsAtEnd) {
+          _logsEl.scrollTop(_logsEl[0].scrollHeight - _logsEl.height());
+        }
+        if (data.status != "RUNNING"){
+          return;
+        }
+        window.setTimeout(refreshView, 1000);
+      });
+    }
+
+    $(window).resize(function () {
+      resizeLogs();
+    });
+
+    $("a[href='#log']").on("shown", function () {
+      resizeLogs();
+    });
+
+    $("#log pre").scroll(function () {
+      if ($(this).scrollTop() + $(this).height() + 20 >= $(this)[0].scrollHeight) {
+        logsAtEnd = true;
+      }
+      else {
+        logsAtEnd = false;
+      }
+    });
+
+    function resizeLogs() {
+      $("#log pre").css("overflow", "auto").height($(window).height() - $("#log pre").position().top - 80);
+    }
+
   });
 </script>
 

+ 29 - 0
apps/oozie/src/oozie/templates/utils.inc.mako

@@ -101,6 +101,23 @@
   % endif
 </%def>
 
+<%def name="hdfs_link_js(url)">
+  % if url:
+    <% path = Hdfs.urlsplit(url)[2] %>
+    % if path:
+      % if path.startswith(posixpath.sep):
+        /filebrowser/view${path}
+      % else:
+        /filebrowser/home_relative_view/${path}
+      % endif
+    % else:
+      javascript:void(0)
+    % endif
+  % else:
+    javascript:void(0)
+  % endif
+</%def>
+
 
 <%def name="display_conf(configs)">
   <table class="table table-condensed table-striped">
@@ -136,6 +153,18 @@
   %>
 </%def>
 
+<%def name="is_linkable(name, path)">
+  <%
+    import re
+
+    if re.search('(dir|path|output|input)', name, re.I) or path.startswith('/') or path.startswith('hdfs://'):
+      return True
+    else:
+      return False
+    endif
+  %>
+</%def>
+
 
 <%def name="if_true(cond, return_value, else_value='')">
   <%

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

@@ -20,6 +20,7 @@ try:
 except ImportError:
   import simplejson as json
 import logging
+import time
 
 from django.forms.formsets import formset_factory
 from django.http import HttpResponse
@@ -41,6 +42,8 @@ from oozie.conf import OOZIE_JOBS_COUNT
 from oozie.models import History, Job, Workflow
 from oozie.settings import DJANGO_APPS
 
+from django.template.defaultfilters import escapejs
+
 
 LOG = logging.getLogger(__name__)
 
@@ -98,6 +101,9 @@ def list_oozie_workflows(request):
 
   workflows = get_oozie().get_workflows(**kwargs)
 
+  if request.GET.get('format') == 'json':
+    return HttpResponse(json.dumps(massaged_oozie_jobs_for_json(workflows.jobs, request.user)), mimetype="application/json")
+
   return render('dashboard/list_oozie_workflows.mako', request, {
     'user': request.user,
     'jobs': split_oozie_jobs(workflows.jobs),
@@ -142,6 +148,17 @@ def list_oozie_workflow(request, job_id, coordinator_job_id=None):
   else:
     workflow_graph = Workflow.gen_status_graph_from_xml(request.user, oozie_workflow)
 
+  if request.GET.get('format') == 'json':
+    return_obj = {
+      'id': oozie_workflow.id,
+      'status':  oozie_workflow.status,
+      'progress': oozie_workflow.get_progress(),
+      'graph': workflow_graph,
+      'log': oozie_workflow.log,
+      'actions': massaged_workflow_actions_for_json(oozie_workflow.get_working_actions())
+    }
+    return HttpResponse(json.dumps(return_obj), mimetype="application/json")
+
   return render('dashboard/list_oozie_workflow.mako', request, {
     'history': history,
     'oozie_workflow': oozie_workflow,
@@ -237,6 +254,64 @@ def _rerun_workflow(request, oozie_id, run_args, mapping):
                          detail=ex._headers.get('oozie-error-message', ex))
 
 
+def massaged_workflow_actions_for_json(workflow_actions):
+  actions = []
+  for action in workflow_actions:
+    massaged_action = {
+      'id': action.id,
+      'log': action.externalId and reverse('jobbrowser.views.job_single_logs', kwargs={'job': action.externalId}) or '',
+      'url': reverse('oozie:list_oozie_workflow_action', kwargs={'action': action.id}),
+      'name': escapejs(action.name),
+      'type': action.type,
+      'status': action.status,
+      'externalIdUrl': action.externalId and reverse('jobbrowser.views.single_job', kwargs={'job': action.externalId}) or '',
+      'externalId': action.externalId and '_'.join(action.externalId.split('_')[-2:]) or '',
+      'startTime': format_time(action.startTime),
+      'endTime': format_time(action.endTime),
+      'retries': action.retries,
+      'errorMessage': escapejs(action.errorMessage),
+      'transition': action.transition,
+      'data': escapejs(action.data)
+    }
+    actions.append(massaged_action)
+
+  return actions
+
+def format_time(st_time):
+  if st_time is None:
+    return '-'
+  else:
+    return time.strftime("%a, %d %b %Y %H:%M:%S", st_time)
+
+
+def massaged_oozie_jobs_for_json(oozie_jobs, user):
+  jobs = []
+
+  for job in oozie_jobs:
+    if job.is_running():
+      if job.type == 'Workflow':
+        job = get_oozie().get_job(job.id)
+      else:
+        job = get_oozie().get_coordinator(job.id)
+
+    massaged_job = {
+      'id': job.id,
+      'lastModTime': time.mktime(job.lastModTime),
+      'endTime': time.mktime(job.endTime),
+      'status': job.status,
+      'isRunning': job.is_running(),
+      'duration': job.endTime and job.startTime and ( time.mktime(job.endTime) - time.mktime(job.startTime) ) * 1000 or None,
+      'appName': escapejs(job.appName),
+      'progress': job.get_progress(),
+      'user': job.user,
+      'absoluteUrl': job.get_absolute_url(),
+      'canEdit': has_job_edition_permission(job, user),
+      }
+    jobs.append(massaged_job)
+
+  return jobs
+
+
 def split_oozie_jobs(oozie_jobs):
   jobs = {}
   jobs_running = []