ソースを参照

[oozie] List coordinator page with copy, delete and share actions

Romain Rigaux 11 年 前
コミット
13b68d45a1

+ 8 - 2
apps/oozie/src/oozie/models2.py

@@ -1382,13 +1382,19 @@ class Coordinator(Job):
   def uuid(self):
     return self.document.uuid
 
-  def json_for_html(self):
+  def get_data_for_json(self):
     _data = self.data.copy()
 
     _data['properties']['start'] = _data['properties']['start'].strftime('%Y-%m-%dT%H:%M:%S')
     _data['properties']['end'] = _data['properties']['end'].strftime('%Y-%m-%dT%H:%M:%S')
+    
+    return _data
 
-    return json.dumps(_data, cls=JSONEncoderForHTML)
+  def to_json(self):
+    return json.dumps(self.get_data_for_json())
+
+  def to_json_for_html(self):
+    return json.dumps(self.get_data_for_json(), cls=JSONEncoderForHTML)
  
   @property
   def data(self):

+ 235 - 9
apps/oozie/src/oozie/templates/editor/list_editor_coordinators.mako

@@ -15,7 +15,7 @@
 ## limitations under the License.
 
 <%!
-  from desktop.views import commonheader, commonfooter
+  from desktop.views import commonheader, commonfooter, commonshare
   from django.utils.translation import ugettext as _
 %>
 <%namespace name="actionbar" file="../actionbar.mako" />
@@ -26,19 +26,245 @@ ${ commonheader(_("Coordinators"), "oozie", user) | n,unicode }
 ${ layout.menubar(section='Coordinators', is_editor=True) }
 
 
+
+<div id="editor">
+
 <div class="container-fluid">
   <div class="card card-small">
-  <h1 class="card-heading simple">${ _('Coordinator Editor') }</h1>
+  <h1 class="card-heading simple">${ _('Coordinator Manager') }</h1>
 
-  % for coordinator in coordinators:
-    <div>
-      <a href="${ url('oozie:edit_coordinator') }?coordinator=${ coordinator.id }">
-        ${ coordinator.name }
-      </a>
-    </div>
-  % endfor
+  <%actionbar:render>
+    <%def name="search()">
+      <input id="filterInput" type="text" class="input-xlarge search-query" placeholder="${_('Search for name, description, etc...')}">
+    </%def>
+
+    <%def name="actions()">
+      <div class="btn-toolbar" style="display: inline; vertical-align: middle">
+        <a data-bind="click: showSubmitPopup, css: {'btn': true, 'disabled': ! oneSelected()}">
+          <i class="fa fa-play"></i> ${ _('Submit') }
+        </a>
+
+        <span style="padding-right:40px"></span>
+
+        <a class="share-link btn" rel="tooltip" data-placement="bottom" data-bind="click: prepareShareModal,
+          attr: {'data-original-title': '${ _("Share") } ' + name},
+          css: {'disabled': ! oneSelected(), 'btn': true}">
+          <i class="fa fa-users"></i> ${ _('Share') }
+        </a>
+
+        <a data-bind="click: copy, css: {'btn': true, 'disabled': ! atLeastOneSelected()}">
+          <i class="fa fa-files-o"></i> ${ _('Copy') }
+        </a>
+
+        <a data-bind="click: function() { $('#deleteWf').modal('show'); }, css: {'btn': true, 'disabled': ! atLeastOneSelected() }">
+          <i class="fa fa-times"></i> ${ _('Delete') }
+        </a>
+
+      </div>
+    </%def>
+
+    <%def name="creation()">
+      <a href="${ url('oozie:new_coordinator') }" class="btn"><i class="fa fa-plus-circle"></i> ${ _('Create') }</a>
+    </%def>
+  </%actionbar:render>
+
+  <table id="workflowTable" class="table datatables">
+    <thead>
+      <tr>
+        <th width="1%"><div data-bind="click: selectAll, css: {hueCheckbox: true, 'fa': true, 'fa-check': allSelected}" class="select-all"></div></th>
+        <th>${ _('Name') }</th>
+        <th>${ _('Description') }</th>
+        <th>${ _('Owner') }</th>
+        <th>${ _('Last Modified') }</th>
+      </tr>
+    </thead>
+    <tbody data-bind="foreach: { data: jobs }">
+      <tr>
+        <td data-bind="click: $root.handleSelect" class="center" style="cursor: default" data-row-selector-exclude="true">
+          <div data-bind="css: { 'hueCheckbox': true, 'fa': true, 'fa-check': isSelected }" data-row-selector-exclude="true"></div>
+          <a data-bind="attr: { 'href': '${ url('oozie:edit_coordinator') }?coordinator=' + id() }" data-row-selector="true"></a>
+        </td>
+        <td data-bind="text: name"></td>
+        <td data-bind="text: description"></td>
+        <td data-bind="text: owner"></td>
+        <td data-bind="text: last_modified, attr: { 'data-sort-value': last_modified_ts }" data-type="date"></td>
+      </tr>
+    </tbody>
+  </table>
 
   </div>
 </div>
 
+
+<div class="hueOverlay" data-bind="visible: isLoading">
+  <!--[if lte IE 9]>
+    <img src="/static/art/spinner-big.gif" />
+  <![endif]-->
+  <!--[if !IE]> -->
+    <i class="fa fa-spinner fa-spin"></i>
+  <!-- <![endif]-->
+</div>
+
+<div id="submit-wf-modal" class="modal hide"></div>
+
+<div id="deleteWf" class="modal hide fade">
+  <form id="deleteWfForm" method="POST" data-bind="submit: delete2">
+    ${ csrf_token(request) | n,unicode }
+    <div class="modal-header">
+      <a href="#" class="close" data-dismiss="modal">&times;</a>
+      <h3 id="deleteWfMessage">${ _('Delete the selected coordinator(s)?') }</h3>
+    </div>
+    <div class="modal-footer">
+      <a href="#" class="btn" data-dismiss="modal">${ _('No') }</a>
+      <input type="submit" class="btn btn-danger" value="${ _('Yes') }"/>
+    </div>
+  </form>
+</div>
+
+
+</div>
+
+
+${ commonshare() | n,unicode }
+
+
+<script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/js/share.vm.js"></script>
+
+
+<script type="text/javascript" charset="utf-8">
+  var Editor = function () {
+    var self = this;
+
+    self.jobs = ko.mapping.fromJS(${ coordinators_json | n });
+    self.selectedJobs = ko.computed(function() {
+      return $.grep(self.jobs(), function(job) { return job.isSelected(); });
+    });
+    self.isLoading = ko.observable(false);
+
+    self.oneSelected = ko.computed(function() {
+      return self.selectedJobs().length == 1;
+    });
+    self.atLeastOneSelected = ko.computed(function() {
+      return self.selectedJobs().length >= 1;
+    });
+    self.allSelected = ko.observable(false);
+
+    self.handleSelect = function(wf) {
+      wf.isSelected(! wf.isSelected());
+    }
+
+    self.selectAll = function() {
+      self.allSelected(! self.allSelected());
+      ko.utils.arrayForEach(self.jobs(), function (job) {
+        job.isSelected(self.allSelected());
+      });
+    }
+
+    self.datatable = null;
+
+    self.showSubmitPopup = function () {
+      $.get("/oozie/editor/coordinator/submit/" + self.selectedJobs()[0].id(), {
+      }, function (data) {
+        $(document).trigger("showSubmitPopup", data);
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
+
+    self.delete2 = function() {
+      $.post("${ url('oozie:delete_editor_coordinator') }", {
+        "selection": ko.mapping.toJSON(self.selectedJobs)
+      }, function() {
+        window.location.reload();
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
+
+    self.copy = function() {
+      $.post("${ url('oozie:copy_coordinator') }", {
+        "selection": ko.mapping.toJSON(self.selectedJobs)
+      }, function(data) {
+        window.location.reload();
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
+
+    self.prepareShareModal = function() {
+     shareViewModel.setDocId(self.selectedJobs()[0].doc1_id());
+      openShareModal();
+    };
+  }
+
+  var viewModel;
+  var shareViewModel;
+
+  $(document).ready(function () {
+    viewModel = new Editor();
+    ko.applyBindings(viewModel, $("#editor")[0]);
+
+    shareViewModel = setupSharing("#documentShareModal");
+    shareViewModel.setDocId(-1);
+
+    $(document).on("showSubmitPopup", function(event, data){
+      $('#submit-wf-modal').html(data);
+      $('#submit-wf-modal').modal('show');
+    });
+
+    var oTable = $("#workflowTable").dataTable({
+      "sPaginationType":"bootstrap",
+      'iDisplayLength':50,
+      "bLengthChange":false,
+      "sDom": "<'row'r>t<'row-fluid'<'dt-pages'p><'dt-records'i>>",
+      "aoColumns":[
+        { "bSortable":false },
+        null,
+        null,
+        null,
+        { "sSortDataType":"dom-sort-value", "sType":"numeric" }
+      ],
+      "aaSorting":[
+        [4, 'desc'],
+        [1, 'asc' ]
+      ],
+      "oLanguage":{
+        "sEmptyTable":"${_('No data available')}",
+        "sInfo":"${_('Showing _START_ to _END_ of _TOTAL_ entries')}",
+        "sInfoEmpty":"${_('Showing 0 to 0 of 0 entries')}",
+        "sInfoFiltered":"${_('(filtered from _MAX_ total entries)')}",
+        "sZeroRecords":"${_('No matching records')}",
+        "oPaginate":{
+          "sFirst":"${_('First')}",
+          "sLast":"${_('Last')}",
+          "sNext":"${_('Next')}",
+          "sPrevious":"${_('Previous')}"
+        },
+        "bDestroy": true
+      },
+      "fnDrawCallback":function (oSettings) {
+        $("a[data-row-selector='true']").jHueRowSelector();
+      }
+    });
+
+    viewModel.datatable = oTable;
+
+    $("#filterInput").keydown(function (e) {
+      if (e.which == 13) {
+        e.preventDefault();
+        return false;
+      }
+    });
+
+    $("#filterInput").keyup(function () {
+      oTable.fnFilter($(this).val());
+    });
+
+    $("a[data-row-selector='true']").jHueRowSelector();
+  });
+</script>
+
 ${commonfooter(messages) | n,unicode}

+ 1 - 1
apps/oozie/src/oozie/templates/editor/list_editor_workflows.mako

@@ -174,7 +174,7 @@ ${ commonshare() | n,unicode }
     };
 
     self.delete2 = function() {
-      $.post("${ url('oozie:delete_workflow') }", {
+      $.post("${ url('oozie:delete_editor_workflow') }", {
         "selection": ko.mapping.toJSON(self.selectedJobs)
       }, function() {
         window.location.reload();

+ 6 - 4
apps/oozie/src/oozie/urls.py

@@ -72,8 +72,8 @@ urlpatterns += patterns(
 
   url(r'^editor/workflow/list/$', 'list_editor_workflows', name='list_editor_workflows'),
   url(r'^editor/workflow/edit/$', 'edit_workflow', name='edit_workflow'),
-  url(r'^editor/workflow/new/$', 'new_workflow', name='new_workflow'),
-  url(r'^editor/workflow/delete/$', 'delete_workflow', name='delete_workflow'),
+  url(r'^editor/workflow/new/$', 'new_workflow', name='new_workflow'),  
+  url(r'^editor/workflow/delete/$', 'delete_job', name='delete_editor_workflow'),
   url(r'^editor/workflow/copy_workflow/$', 'copy_workflow', name='copy_workflow'),
   url(r'^editor/workflow/save/$', 'save_workflow', name='save_workflow'),
   url(r'^editor/workflow/submit/(?P<doc_id>\d+)$', 'submit_workflow', name='editor_submit_workflow'),
@@ -81,14 +81,16 @@ urlpatterns += patterns(
   url(r'^editor/workflow/add_node/$', 'add_node', name='add_node'),
   url(r'^editor/workflow/parameters/$', 'workflow_parameters', name='workflow_parameters'),
   url(r'^editor/workflow/action/parameters/$', 'action_parameters', name='action_parameters'),
-  url(r'^editor/workflow/gen_xml/$', 'gen_xml_workflow', name='gen_xml_workflow'), # Temporary
+  url(r'^editor/workflow/gen_xml/$', 'gen_xml_workflow', name='gen_xml_workflow'),
   
   url(r'^editor/coordinator/list/$', 'list_editor_coordinators', name='list_editor_coordinators'),
   url(r'^editor/coordinator/edit/$', 'edit_coordinator', name='edit_coordinator'),
   url(r'^editor/coordinator/new/$', 'new_coordinator', name='new_coordinator'),
+  url(r'^editor/coordinator/delete/$', 'delete_job', name='delete_editor_coordinator'),
+  url(r'^editor/coordinator/copy_workflow/$', 'copy_coordinator', name='copy_coordinator'),
   url(r'^editor/coordinator/save/$', 'save_coordinator', name='save_coordinator'),
   url(r'^editor/coordinator/submit/(?P<doc_id>\d+)$', 'submit_coordinator', name='editor_submit_coordinator'),
-  url(r'^editor/coordinator/gen_xml/$', 'gen_xml_coordinator', name='gen_xml_coordinator'), # Temporary
+  url(r'^editor/coordinator/gen_xml/$', 'gen_xml_coordinator', name='gen_xml_coordinator'),
   
   url(r'^editor/bundle/list/$', 'list_editor_bundles', name='list_editor_bundles'),
   url(r'^editor/bundle/edit/$', 'edit_bundle', name='edit_bundle'),

+ 40 - 5
apps/oozie/src/oozie/views/editor2.py

@@ -97,7 +97,7 @@ def new_workflow(request):
   return edit_workflow(request)
 
 
-def delete_workflow(request):
+def delete_job(request):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
 
@@ -112,7 +112,7 @@ def delete_workflow(request):
     doc2.delete()
 
   response = {}
-  request.info(_('Workflows deleted.') if len(jobs) > 1 else _('Workflow deleted.'))
+  request.info(_('Document deleted.') if len(jobs) > 1 else _('Document deleted.'))
   
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
@@ -341,10 +341,10 @@ def _submit_workflow(user, fs, jt, workflow, mapping):
 
 
 def list_editor_coordinators(request):
-  coordinators = [d.content_object for d in Document.objects.get_docs(request.user, Document2, extra='coordinator2')]
+  coordinators = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='coordinator2')]
 
   return render('editor/list_editor_coordinators.mako', request, {
-      'coordinators': coordinators
+      'coordinators_json': json.dumps(coordinators, cls=JSONEncoderForHTML)
   })
 
 
@@ -374,7 +374,7 @@ def edit_coordinator(request):
     raise PopupException(_('You don\'t have access to the workflow of this coordinator.'))
 
   return render('editor/coordinator_editor.mako', request, {
-      'coordinator_json': coordinator.json_for_html(),
+      'coordinator_json': coordinator.to_json_for_html(),
       'credentials_json': json.dumps(credentials.credentials.keys(), cls=JSONEncoderForHTML),
       'workflows_json': json.dumps(workflows, cls=JSONEncoderForHTML),
       'doc1_id': doc.doc.get().id if doc else -1,
@@ -386,6 +386,41 @@ def new_coordinator(request):
   return edit_coordinator(request)
 
 
+@check_document_access_permission()
+def copy_coordinator(request):
+  if request.method != 'POST':
+    raise PopupException(_('A POST request is required.'))
+
+  jobs = json.loads(request.POST.get('selection'))
+
+  for job in jobs:
+    doc2 = Document2.objects.get(type='oozie-coordinator2', id=job['id'])
+    
+    name = doc2.name + '-copy'
+    copy_doc = doc2.doc.get().copy(name=name, owner=request.user)
+  
+    doc2.pk = None
+    doc2.id = None
+    doc2.uuid = str(uuid.uuid4())
+    doc2.name = name
+    doc2.owner = request.user    
+    doc2.save()
+  
+    doc2.doc.all().delete()
+    doc2.doc.add(copy_doc)
+    
+    coordinator_data = Coordinator(document=doc2).get_data_for_json()
+    coordinator_data['name'] = name
+    doc2.update_data(coordinator_data)
+    doc2.save()
+
+  response = {}  
+  request.info(_('Coordinator copied.') if len(jobs) > 1 else _('Coordinator copied.'))
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+
+
 @check_document_modify_permission()
 def save_coordinator(request):
   response = {'status': -1}