浏览代码

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

Romain Rigaux 11 年之前
父节点
当前提交
d3da396

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

@@ -1603,12 +1603,18 @@ class Bundle(Job):
   def uuid(self):
   def uuid(self):
     return self.document.uuid
     return self.document.uuid
 
 
-  def json_for_html(self):
+  def get_data_for_json(self):
     _data = self.data.copy()
     _data = self.data.copy()
 
 
     _data['properties']['kickoff'] = _data['properties']['kickoff'].strftime('%Y-%m-%dT%H:%M:%S')
     _data['properties']['kickoff'] = _data['properties']['kickoff'].strftime('%Y-%m-%dT%H:%M:%S')
 
 
-    return json.dumps(_data, cls=JSONEncoderForHTML)
+    return _data
+
+  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
   @property
   def data(self):
   def data(self):

+ 235 - 8
apps/oozie/src/oozie/templates/editor/list_editor_bundles.mako

@@ -15,7 +15,7 @@
 ## limitations under the License.
 ## limitations under the License.
 
 
 <%!
 <%!
-  from desktop.views import commonheader, commonfooter
+  from desktop.views import commonheader, commonfooter, commonshare
   from django.utils.translation import ugettext as _
   from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="actionbar" file="../actionbar.mako" />
 <%namespace name="actionbar" file="../actionbar.mako" />
@@ -26,19 +26,246 @@ ${ commonheader(_("Bundles"), "oozie", user) | n,unicode }
 ${ layout.menubar(section='Bundles', is_editor=True) }
 ${ layout.menubar(section='Bundles', is_editor=True) }
 
 
 
 
+
+<div id="editor">
+
 <div class="container-fluid">
 <div class="container-fluid">
   <div class="card card-small">
   <div class="card card-small">
   <h1 class="card-heading simple">${ _('Bundle Editor') }</h1>
   <h1 class="card-heading simple">${ _('Bundle Editor') }</h1>
 
 
-  % for bundle in bundles:
-    <div>
-      <a href="${ url('oozie:edit_bundle') }?bundle=${ bundle.id }">
-        ${ bundle.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_workflow') }" 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_bundle') }?bundle=' + 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>
 </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 workflow(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(${ bundles_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/bundle/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_bundle') }", {
+        "selection": ko.mapping.toJSON(self.selectedJobs)
+      }, function() {
+        window.location.reload();
+        $('#deleteWf').modal('hide');
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
+
+    self.copy = function() {
+      $.post("${ url('oozie:copy_bundle') }", {
+        "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}
 ${commonfooter(messages) | n,unicode}

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

@@ -31,7 +31,7 @@ ${ layout.menubar(section='Coordinators', is_editor=True) }
 
 
 <div class="container-fluid">
 <div class="container-fluid">
   <div class="card card-small">
   <div class="card card-small">
-  <h1 class="card-heading simple">${ _('Coordinator Manager') }</h1>
+  <h1 class="card-heading simple">${ _('Coordinator Editor') }</h1>
 
 
   <%actionbar:render>
   <%actionbar:render>
     <%def name="search()">
     <%def name="search()">

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

@@ -30,7 +30,7 @@ ${ layout.menubar(section='workflows', is_editor=True) }
 
 
 <div class="container-fluid">
 <div class="container-fluid">
   <div class="card card-small">
   <div class="card card-small">
-  <h1 class="card-heading simple">${ _('Workflow Manager') }</h1>
+  <h1 class="card-heading simple">${ _('Workflow Editor') }</h1>
 
 
   <%actionbar:render>
   <%actionbar:render>
     <%def name="search()">
     <%def name="search()">

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

@@ -74,7 +74,7 @@ urlpatterns += patterns(
   url(r'^editor/workflow/edit/$', 'edit_workflow', name='edit_workflow'),
   url(r'^editor/workflow/edit/$', 'edit_workflow', name='edit_workflow'),
   url(r'^editor/workflow/new/$', 'new_workflow', name='new_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/delete/$', 'delete_job', name='delete_editor_workflow'),
-  url(r'^editor/workflow/copy_workflow/$', 'copy_workflow', name='copy_workflow'),
+  url(r'^editor/workflow/copy/$', 'copy_workflow', name='copy_workflow'),
   url(r'^editor/workflow/save/$', 'save_workflow', name='save_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'),
   url(r'^editor/workflow/submit/(?P<doc_id>\d+)$', 'submit_workflow', name='editor_submit_workflow'),
   url(r'^editor/workflow/new_node/$', 'new_node', name='new_node'),
   url(r'^editor/workflow/new_node/$', 'new_node', name='new_node'),
@@ -87,7 +87,7 @@ urlpatterns += patterns(
   url(r'^editor/coordinator/edit/$', 'edit_coordinator', name='edit_coordinator'),
   url(r'^editor/coordinator/edit/$', 'edit_coordinator', name='edit_coordinator'),
   url(r'^editor/coordinator/new/$', 'new_coordinator', name='new_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/delete/$', 'delete_job', name='delete_editor_coordinator'),
-  url(r'^editor/coordinator/copy_workflow/$', 'copy_coordinator', name='copy_coordinator'),
+  url(r'^editor/coordinator/copy/$', 'copy_coordinator', name='copy_coordinator'),
   url(r'^editor/coordinator/save/$', 'save_coordinator', name='save_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/submit/(?P<doc_id>\d+)$', 'submit_coordinator', name='editor_submit_coordinator'),
   url(r'^editor/coordinator/gen_xml/$', 'gen_xml_coordinator', name='gen_xml_coordinator'),
   url(r'^editor/coordinator/gen_xml/$', 'gen_xml_coordinator', name='gen_xml_coordinator'),
@@ -95,6 +95,8 @@ urlpatterns += patterns(
   url(r'^editor/bundle/list/$', 'list_editor_bundles', name='list_editor_bundles'),
   url(r'^editor/bundle/list/$', 'list_editor_bundles', name='list_editor_bundles'),
   url(r'^editor/bundle/edit/$', 'edit_bundle', name='edit_bundle'),
   url(r'^editor/bundle/edit/$', 'edit_bundle', name='edit_bundle'),
   url(r'^editor/bundle/new/$', 'new_bundle', name='new_bundle'),
   url(r'^editor/bundle/new/$', 'new_bundle', name='new_bundle'),
+  url(r'^editor/bundle/delete/$', 'delete_job', name='delete_editor_bundle'),
+  url(r'^editor/bundle/copy/$', 'copy_bundle', name='copy_bundle'),
   url(r'^editor/bundle/save/$', 'save_bundle', name='save_bundle'),
   url(r'^editor/bundle/save/$', 'save_bundle', name='save_bundle'),
   url(r'^editor/bundle/submit/(?P<doc_id>\d+)$', 'submit_bundle', name='editor_submit_bundle')
   url(r'^editor/bundle/submit/(?P<doc_id>\d+)$', 'submit_bundle', name='editor_submit_bundle')
 )
 )

+ 37 - 4
apps/oozie/src/oozie/views/editor2.py

@@ -420,7 +420,6 @@ def copy_coordinator(request):
   return HttpResponse(json.dumps(response), mimetype="application/json")
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
 
 
-
 @check_document_modify_permission()
 @check_document_modify_permission()
 def save_coordinator(request):
 def save_coordinator(request):
   response = {'status': -1}
   response = {'status': -1}
@@ -511,10 +510,10 @@ def _submit_coordinator(request, coordinator, mapping):
     
     
 
 
 def list_editor_bundles(request):
 def list_editor_bundles(request):
-  bundles = [d.content_object for d in Document.objects.get_docs(request.user, Document2, extra='bundle2')]
+  bundles = [d.content_object.to_dict() for d in Document.objects.get_docs(request.user, Document2, extra='bundle2')]
 
 
   return render('editor/list_editor_bundles.mako', request, {
   return render('editor/list_editor_bundles.mako', request, {
-      'bundles': bundles
+      'bundles_json': json.dumps(bundles, cls=JSONEncoderForHTML)
   })
   })
 
 
 
 
@@ -533,7 +532,7 @@ def edit_bundle(request):
                       for d in Document.objects.get_docs(request.user, Document2, extra='coordinator2')]
                       for d in Document.objects.get_docs(request.user, Document2, extra='coordinator2')]
 
 
   return render('editor/bundle_editor.mako', request, {
   return render('editor/bundle_editor.mako', request, {
-      'bundle_json': bundle.json_for_html(),
+      'bundle_json': bundle.to_json_for_html(),
       'coordinators_json': json.dumps(coordinators, cls=JSONEncoderForHTML),
       'coordinators_json': json.dumps(coordinators, cls=JSONEncoderForHTML),
       'doc1_id': doc.doc.get().id if doc else -1,
       'doc1_id': doc.doc.get().id if doc else -1,
       'can_edit_json': json.dumps(doc is None or doc.doc.get().is_editable(request.user))      
       'can_edit_json': json.dumps(doc is None or doc.doc.get().is_editable(request.user))      
@@ -573,6 +572,40 @@ def save_bundle(request):
   return HttpResponse(json.dumps(response), mimetype="application/json")
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
 
 
+@check_document_access_permission()
+def copy_bundle(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-bundle2', 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)
+    
+    bundle_data = Bundle(document=doc2).get_data_for_json()
+    bundle_data['name'] = name
+    doc2.update_data(bundle_data)
+    doc2.save()
+
+  response = {}  
+  request.info(_('Bundle copied.') if len(jobs) > 1 else _('Bundle copied.'))
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+
 @check_document_access_permission()
 @check_document_access_permission()
 def submit_bundle(request, doc_id):
 def submit_bundle(request, doc_id):
   bundle = Bundle(document=Document2.objects.get(id=doc_id))  
   bundle = Bundle(document=Document2.objects.get(id=doc_id))