Просмотр исходного кода

HUE-1042 [oozie] Bulk delete workflows, bundles and coordinators

Enable delete button only if the perms are correct for each selected job.
Romain Rigaux 12 лет назад
Родитель
Сommit
c4ce7f4948

+ 8 - 8
apps/oozie/src/oozie/migrations/0017_auto__add_bundledcoordinator__add_bundle.py

@@ -5,9 +5,9 @@ from south.v2 import SchemaMigration
 from django.db import models
 
 class Migration(SchemaMigration):
-    
+
     def forwards(self, orm):
-        
+
         # Adding model 'BundledCoordinator'
         db.create_table('oozie_bundledcoordinator', (
             ('coordinator', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['oozie.Coordinator'])),
@@ -23,17 +23,17 @@ class Migration(SchemaMigration):
             ('job_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Job'], unique=True, primary_key=True)),
         ))
         db.send_create_signal('oozie', ['Bundle'])
-    
-    
+
+
     def backwards(self, orm):
-        
+
         # Deleting model 'BundledCoordinator'
         db.delete_table('oozie_bundledcoordinator')
 
         # Deleting model 'Bundle'
         db.delete_table('oozie_bundle')
-    
-    
+
+
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
@@ -329,5 +329,5 @@ class Migration(SchemaMigration):
             'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
         }
     }
-    
+
     complete_apps = ['oozie']

+ 1 - 1
apps/oozie/src/oozie/templates/dashboard/list_oozie_bundle.mako

@@ -412,7 +412,7 @@ ${ layout.menubar(section='dashboard') }
           $("#kill-btn").hide();
           $("#rerun-btn").show();
         }
-        
+
         if (data.id && data.status == "KILLED") {
           $("#kill-btn").hide();
         }

+ 21 - 8
apps/oozie/src/oozie/templates/editor/list_bundles.mako

@@ -66,7 +66,7 @@ ${ layout.menubar(section='bundles') }
                   data-submit-url="${ url('oozie:submit_bundle', bundle=bundle.id) }"
               % endif
               % if bundle.is_editable(currentuser):
-                  data-delete-url="${ url('oozie:delete_bundle', bundle=bundle.id) }"
+                  data-delete-id="${ bundle.id }"
               % endif
               >
             </div>
@@ -100,22 +100,32 @@ ${ layout.menubar(section='bundles') }
 <div id="submit-job-modal" class="modal hide"></div>
 
 <div id="delete-job" class="modal hide">
-  <form id="deleteWfForm" action="" method="POST">
+  <form id="deleteWfForm" action="${ url('oozie:delete_bundle') }" method="POST">
     <div class="modal-header">
       <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3 id="deleteWfMessage">${ _('Delete this bundle?') }</h3>
+      <h3 id="deleteWfMessage">${ _('Delete the selected bundle(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>
+    <div class="hide">
+      <select name="job_selection" data-bind="options: availableJobs, selectedOptions: chosenJobs" size="5" multiple="true"></select>
+    </div>
   </form>
 </div>
 
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function () {
+    var viewModel = {
+        availableJobs : ko.observableArray(${ json_jobs | n }),
+        chosenJobs : ko.observableArray([])
+    };
+
+    ko.applyBindings(viewModel);
 
     $(".selectAll").click(function () {
       if ($(this).attr("checked")) {
@@ -147,7 +157,6 @@ ${ layout.menubar(section='bundles') }
         var action_buttons = [
           ['#submit-btn', 'data-submit-url'],
           ['#bundle-btn', 'data-bundle-url'],
-          ['#delete-btn', 'data-delete-url'],
           ['#clone-btn', 'data-clone-url']
         ];
         $.each(action_buttons, function (index) {
@@ -158,13 +167,17 @@ ${ layout.menubar(section='bundles') }
           }
         });
       }
+      var can_delete = $(".hueCheckbox[checked='checked'][data-delete-id]");
+      if (can_delete.length >= 1 && can_delete.length == selector.length) {
+        $("#delete-btn").removeAttr("disabled");
+      }
     }
 
     $("#delete-btn").click(function (e) {
-      var _this = $(".hueCheckbox[checked='checked']");
-      var _action = _this.attr("data-delete-url");
-      $("#deleteWfForm").attr("action", _action);
-      $("#deleteWfMessage").text(_this.attr("alt"));
+      viewModel.chosenJobs.removeAll();
+      $(".hueCheckbox[checked='checked']").each(function( index ) {
+        viewModel.chosenJobs.push($(this).data("delete-id"));
+      });
       $("#delete-job").modal("show");
     });
 

+ 21 - 8
apps/oozie/src/oozie/templates/editor/list_coordinators.mako

@@ -65,7 +65,7 @@ ${ layout.menubar(section='coordinators') }
                   data-submit-url="${ url('oozie:submit_coordinator', coordinator=coordinator.id) }"
               % endif
               % if coordinator.is_editable(currentuser):
-                  data-delete-url="${ url('oozie:delete_coordinator', coordinator=coordinator.id) }"
+                  data-delete-id="${ coordinator.id }"
               % endif
               >
             </div>
@@ -96,22 +96,32 @@ ${ layout.menubar(section='coordinators') }
 <div id="submit-job-modal" class="modal hide"></div>
 
 <div id="delete-job" class="modal hide">
-  <form id="deleteWfForm" action="" method="POST">
+  <form id="deleteWfForm" action="${ url('oozie:delete_coordinator') }" method="POST">
     <div class="modal-header">
       <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3 id="deleteWfMessage">${ _('Delete this coordinator?') }</h3>
+      <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>
+    <div class="hide">
+      <select name="job_selection" data-bind="options: availableJobs, selectedOptions: chosenJobs" size="5" multiple="true"></select>
+    </div>
   </form>
 </div>
 
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function () {
+    var viewModel = {
+        availableJobs : ko.observableArray(${ json_jobs | n }),
+        chosenJobs : ko.observableArray([])
+    };
+
+    ko.applyBindings(viewModel);
 
     $(".selectAll").click(function () {
       if ($(this).attr("checked")) {
@@ -143,7 +153,6 @@ ${ layout.menubar(section='coordinators') }
         var action_buttons = [
           ['#submit-btn', 'data-submit-url'],
           ['#bundle-btn', 'data-bundle-url'],
-          ['#delete-btn', 'data-delete-url'],
           ['#clone-btn', 'data-clone-url']
         ];
         $.each(action_buttons, function (index) {
@@ -154,13 +163,17 @@ ${ layout.menubar(section='coordinators') }
           }
         });
       }
+      var can_delete = $(".hueCheckbox[checked='checked'][data-delete-id]");
+      if (can_delete.length >= 1 && can_delete.length == selector.length) {
+        $("#delete-btn").removeAttr("disabled");
+      }
     }
 
     $("#delete-btn").click(function (e) {
-      var _this = $(".hueCheckbox[checked='checked']");
-      var _action = _this.attr("data-delete-url");
-      $("#deleteWfForm").attr("action", _action);
-      $("#deleteWfMessage").text(_this.attr("alt"));
+      viewModel.chosenJobs.removeAll();
+      $(".hueCheckbox[checked='checked']").each(function( index ) {
+        viewModel.chosenJobs.push($(this).data("delete-id"));
+      });
       $("#delete-job").modal("show");
     });
 

+ 21 - 8
apps/oozie/src/oozie/templates/editor/list_workflows.mako

@@ -71,7 +71,7 @@ ${ layout.menubar(section='workflows') }
                   data-clone-url="${ url('oozie:clone_workflow', workflow=workflow.id) }"
               % endif
               % if workflow.is_editable(currentuser):
-                  data-delete-url="${ url('oozie:delete_workflow', workflow=workflow.id) }"
+                  data-delete-id="${ workflow.id }"
               % endif
             ></div>
             % if workflow.is_accessible(currentuser):
@@ -100,15 +100,18 @@ ${ layout.menubar(section='workflows') }
 <div id="submit-wf-modal" class="modal hide"></div>
 
 <div id="deleteWf" class="modal hide fade">
-  <form id="deleteWfForm" action="" method="POST">
+  <form id="deleteWfForm" action="${ url('oozie:delete_workflow') }" method="POST">
     <div class="modal-header">
       <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3 id="deleteWfMessage">${ _('Delete this workflow?') }</h3>
+      <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>
+    <div class="hide">
+      <select name="job_selection" data-bind="options: availableJobs, selectedOptions: chosenJobs" size="5" multiple="true"></select>
+    </div>
   </form>
 </div>
 
@@ -129,9 +132,16 @@ ${ layout.menubar(section='workflows') }
 </div>
 
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function () {
+    var viewModel = {
+        availableJobs : ko.observableArray(${ json_jobs | n }),
+        chosenJobs : ko.observableArray([])
+    };
+
+    ko.applyBindings(viewModel);
 
     $(".selectAll").click(function () {
       if ($(this).attr("checked")) {
@@ -163,7 +173,6 @@ ${ layout.menubar(section='workflows') }
         var action_buttons = [
           ['#submit-btn', 'data-submit-url'],
           ['#schedule-btn', 'data-schedule-url'],
-          ['#delete-btn', 'data-delete-url'],
           ['#clone-btn', 'data-clone-url']
         ];
         $.each(action_buttons, function (index) {
@@ -174,13 +183,17 @@ ${ layout.menubar(section='workflows') }
           }
         });
       }
+      var can_delete = $(".hueCheckbox[checked='checked'][data-delete-id]");
+      if (can_delete.length >= 1 && can_delete.length == selector.length) {
+        $("#delete-btn").removeAttr("disabled");
+      }
     }
 
     $("#delete-btn").click(function (e) {
-      var _this = $(".hueCheckbox[checked='checked']");
-      var _action = _this.attr("data-delete-url");
-      $("#deleteWfForm").attr("action", _action);
-      $("#deleteWfMessage").text(_this.attr("alt"));
+      viewModel.chosenJobs.removeAll();
+      $(".hueCheckbox[checked='checked']").each(function( index ) {
+        viewModel.chosenJobs.push($(this).data("delete-id"));
+      });
       $("#deleteWf").modal("show");
     });
 

+ 19 - 7
apps/oozie/src/oozie/tests.py

@@ -1003,6 +1003,10 @@ class TestEditor(OozieMockBase):
     assert_not_equal(coord.deployment_dir, coord2.deployment_dir)
     assert_not_equal('', coord2.deployment_dir)
 
+    # Bulk delete
+    response = self.c.post(reverse('oozie:delete_coordinator'), {'job_selection': [coord.id, coord2.id]}, follow=True)
+    assert_equal(coordinator_count - 1, Coordinator.objects.count(), response)
+
 
   def test_coordinator_workflow_access_permissions(self):
     self.wf.is_shared = True
@@ -1284,12 +1288,16 @@ class TestEditorBundle(OozieMockBase):
     assert_not_equal(bundle.deployment_dir, bundle2.deployment_dir)
     assert_not_equal('', bundle2.deployment_dir)
 
+    # Bulk delete
+    response = self.c.post(reverse('oozie:delete_bundle'), {'job_selection': [bundle.id, bundle2.id]}, follow=True)
+    assert_equal(bundle_count - 1, Bundle.objects.count(), response)
+
 
   def test_delete_bundle(self):
     bundle = create_bundle(self.c)
     bundle_count = Bundle.objects.count()
 
-    response = self.c.post(reverse('oozie:delete_bundle', args=[bundle.id]), {}, follow=True)
+    response = self.c.post(reverse('oozie:delete_bundle'), {'job_selection': [bundle.id]}, follow=True)
 
     assert_equal(bundle_count - 1, Bundle.objects.count(), response)
 
@@ -1699,12 +1707,12 @@ class TestPermissions(OozieBase):
     # Delete
     finish = SHARE_JOBS.set_for_testing(False)
     try:
-      response = client_not_me.post(reverse('oozie:delete_workflow', args=[self.wf.id]))
+      response = client_not_me.post(reverse('oozie:delete_workflow'), {'job_selection': [self.wf.id]})
       assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
 
-    response = self.c.post(reverse('oozie:delete_workflow', args=[self.wf.id]), follow=True)
+    response = self.c.post(reverse('oozie:delete_workflow'), {'job_selection': [self.wf.id]}, follow=True)
     assert_equal(200, response.status_code)
 
 
@@ -1824,12 +1832,12 @@ class TestPermissions(OozieBase):
     # Delete
     finish = SHARE_JOBS.set_for_testing(False)
     try:
-      response = client_not_me.post(reverse('oozie:delete_coordinator', args=[coord.id]))
+      response = client_not_me.post(reverse('oozie:delete_coordinator'), {'job_selection': [coord.id]})
       assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
 
-    response = self.c.post(reverse('oozie:delete_coordinator', args=[coord.id]), follow=True)
+    response = self.c.post(reverse('oozie:delete_coordinator'), {'job_selection': [coord.id]}, follow=True)
     assert_equal(200, response.status_code)
 
 
@@ -1924,12 +1932,12 @@ class TestPermissions(OozieBase):
     # Delete
     finish = SHARE_JOBS.set_for_testing(False)
     try:
-      response = client_not_me.post(reverse('oozie:delete_bundle', args=[bundle.id]))
+      response = client_not_me.post(reverse('oozie:delete_bundle'), {'job_selection': [bundle.id]})
       assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
 
-    response = self.c.post(reverse('oozie:delete_bundle', args=[bundle.id]), follow=True)
+    response = self.c.post(reverse('oozie:delete_bundle'), {'job_selection': [bundle.id]}, follow=True)
     assert_equal(200, response.status_code)
 
 
@@ -1972,6 +1980,10 @@ class TestEditorWithOozie(OozieBase):
     assert_not_equal(self.wf.deployment_dir, wf2.deployment_dir)
     assert_not_equal('', wf2.deployment_dir)
 
+    # Bulk delete
+    response = self.c.post(reverse('oozie:delete_workflow'), {'job_selection': [self.wf.id, wf2.id]}, follow=True)
+    assert_equal(workflow_count - 1, Workflow.objects.count(), response)
+
 
   def test_import_workflow(self):
     workflow_count = Workflow.objects.count()

+ 3 - 3
apps/oozie/src/oozie/urls.py

@@ -27,7 +27,7 @@ urlpatterns = patterns(
   url(r'^list_workflows/$', 'list_workflows', name='list_workflows'),
   url(r'^create_workflow/$', 'create_workflow', name='create_workflow'),
   url(r'^edit_workflow/(?P<workflow>\d+)$', 'edit_workflow', name='edit_workflow'),
-  url(r'^delete_workflow/(?P<workflow>\d+)$', 'delete_workflow', name='delete_workflow'),
+  url(r'^delete_workflow$', 'delete_workflow', name='delete_workflow'),
   url(r'^clone_workflow/(?P<workflow>\d+)$', 'clone_workflow', name='clone_workflow'),
   url(r'^submit_workflow/(?P<workflow>\d+)$', 'submit_workflow', name='submit_workflow'),
   url(r'^schedule_workflow/(?P<workflow>\d+)$', 'schedule_workflow', name='schedule_workflow'),
@@ -36,7 +36,7 @@ urlpatterns = patterns(
   url(r'^list_coordinators/(?P<workflow_id>[-\w]+)?$', 'list_coordinators', name='list_coordinators'),
   url(r'^create_coordinator/(?P<workflow>[-\w]+)?$', 'create_coordinator', name='create_coordinator'),
   url(r'^edit_coordinator/(?P<coordinator>[-\w]+)$', 'edit_coordinator', name='edit_coordinator'),
-  url(r'^delete_coordinator/(?P<coordinator>\d+)$', 'delete_coordinator', name='delete_coordinator'),
+  url(r'^delete_coordinator$', 'delete_coordinator', name='delete_coordinator'),
   url(r'^clone_coordinator/(?P<coordinator>\d+)$', 'clone_coordinator', name='clone_coordinator'),
   url(r'^create_coordinator_dataset/(?P<coordinator>[-\w]+)$', 'create_coordinator_dataset', name='create_coordinator_dataset'),
   url(r'^edit_coordinator_dataset/(?P<dataset>\d+)$', 'edit_coordinator_dataset', name='edit_coordinator_dataset'),
@@ -48,7 +48,7 @@ urlpatterns = patterns(
   url(r'^edit_bundle/(?P<bundle>\d+)$', 'edit_bundle', name='edit_bundle'),
   url(r'^submit_bundle/(?P<bundle>\d+)$', 'submit_bundle', name='submit_bundle'),
   url(r'^clone_bundle/(?P<bundle>\d+)$', 'clone_bundle', name='clone_bundle'),
-  url(r'^delete_bundle/(?P<bundle>\d+)$', 'delete_bundle', name='delete_bundle'),
+  url(r'^delete_bundle$', 'delete_bundle', name='delete_bundle'),
   url(r'^create_bundled_coordinator/(?P<bundle>\d+)$', 'create_bundled_coordinator', name='create_bundled_coordinator'),
   url(r'^edit_bundled_coordinator/(?P<bundle>\d+)/(?P<bundled_coordinator>\d+)$', 'edit_bundled_coordinator', name='edit_bundled_coordinator'),
 

+ 35 - 18
apps/oozie/src/oozie/views/editor.py

@@ -43,7 +43,7 @@ from oozie.import_workflow import import_workflow as _import_workflow
 from oozie.management.commands import oozie_setup
 from oozie.models import Workflow, History, Coordinator,\
                          Dataset, DataInput, DataOutput,\
-                         ACTION_TYPES, Bundle, BundledCoordinator
+                         ACTION_TYPES, Bundle, BundledCoordinator, Job
 from oozie.forms import WorkflowForm, CoordinatorForm, DatasetForm,\
   DataInputForm, DataOutputForm, LinkForm,\
   DefaultLinkForm, design_form_by_type, ParameterForm,\
@@ -66,6 +66,7 @@ def list_workflows(request):
 
   return render('editor/list_workflows.mako', request, {
     'jobs': list(data),
+    'json_jobs': json.dumps(list(data.values_list('id', flat=True))),
     'currentuser': request.user,
     'show_setup_app': show_setup_app,
   })
@@ -85,6 +86,7 @@ def list_coordinators(request, workflow_id=None):
 
   return render('editor/list_coordinators.mako', request, {
     'jobs': list(data),
+    'json_jobs': json.dumps(list(data.values_list('id', flat=True))),
     'currentuser': request.user,
   })
 
@@ -101,6 +103,7 @@ def list_bundles(request):
 
   return render('editor/list_bundles.mako', request, {
     'jobs': list(data),
+    'json_jobs': json.dumps(list(data.values_list('id', flat=True))),
     'currentuser': request.user,
   })
 
@@ -181,14 +184,19 @@ def edit_workflow(request, workflow):
   })
 
 
-@check_job_access_permission()
-@check_job_edition_permission()
-def delete_workflow(request, workflow):
+
+def delete_workflow(request):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
 
-  Workflow.objects.destroy(workflow, request.fs)
-  request.info(_('Workflow deleted.'))
+  job_ids = request.POST.getlist('job_selection')
+
+  for job_id in job_ids:
+    job = Job.objects.is_accessible_or_exception(request, job_id)
+    Job.objects.can_edit_or_exception(request, job)
+    Workflow.objects.destroy(job, request.fs)
+
+  request.info(_('Workflow(s) deleted.'))
 
   return redirect(reverse('oozie:list_workflows'))
 
@@ -283,15 +291,19 @@ def create_coordinator(request, workflow=None):
   })
 
 
-@check_job_access_permission()
-@check_job_edition_permission()
-def delete_coordinator(request, coordinator):
+def delete_coordinator(request):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
 
-  coordinator.delete()
-  Submission(request.user, coordinator, request.fs, {}).remove_deployment_dir()
-  request.info(_('Coordinator deleted.'))
+  job_ids = request.POST.getlist('job_selection')
+
+  for job_id in job_ids:
+    job = Job.objects.is_accessible_or_exception(request, job_id)
+    Job.objects.can_edit_or_exception(request, job)
+    Submission(request.user, job, request.fs, {}).remove_deployment_dir()
+    job.delete()
+
+  request.info(_('Coordinator(s) deleted.'))
 
   return redirect(reverse('oozie:list_coordinators'))
 
@@ -526,15 +538,20 @@ def create_bundle(request):
   })
 
 
-@check_job_access_permission()
-@check_job_edition_permission()
-def delete_bundle(request, bundle):
+
+def delete_bundle(request):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
 
-  bundle.delete()
-  Submission(request.user, bundle, request.fs, {}).remove_deployment_dir()
-  request.info(_('Bundle deleted.'))
+  job_ids = request.POST.getlist('job_selection')
+
+  for job_id in job_ids:
+    job = Job.objects.is_accessible_or_exception(request, job_id)
+    Job.objects.can_edit_or_exception(request, job)
+    Submission(request.user, job, request.fs, {}).remove_deployment_dir()
+    job.delete()
+
+  request.info(_('Bundle(s) deleted.'))
 
   return redirect(reverse('oozie:list_bundles'))