Эх сурвалжийг харах

HUE-7822 [core] Unable to clone oozie workflow in Hue 4.0

Roohi 8 жил өмнө
parent
commit
05492f2

+ 5 - 0
apps/oozie/src/oozie/models2.py

@@ -477,6 +477,11 @@ class Workflow(Job):
     _data['workflow']['name'] = name
     self.data = json.dumps(_data)
 
+  def update_uuid(self, uuid):
+    _data = self.get_data()
+    _data['workflow']['uuid'] = uuid
+    self.data = json.dumps(_data)
+
   def set_workspace(self, user):
     _data = json.loads(self.data)
 

+ 63 - 0
desktop/core/src/desktop/api2.py

@@ -363,6 +363,69 @@ def delete_document(request):
       'status': 0,
   })
 
+@api_error_handler
+@require_POST
+def copy_document(request):
+  uuid = json.loads(request.POST.get('uuid'), '""')
+
+  if not uuid:
+    raise PopupException(_('copy_document requires uuid'))
+
+  document = Document2.objects.get_by_uuid(user=request.user, uuid=uuid)
+
+  if document.type == 'directory':
+    raise PopupException(_('Directory copy is not supported'))
+
+
+  name = document.name + '-copy'
+
+  # Make the copy of the new Document
+  copy_document = document.copy(name=name, owner=request.user)
+
+  # Import workspace for all oozie jobs
+  if document.type == 'oozie-workflow2' or document.type == 'oozie-bundle2' or document.type == 'oozie-coordinator2':
+    from oozie.models2 import Workflow, Coordinator, Bundle, _import_workspace
+    # Update the name field in the json 'data' field
+    if document.type == 'oozie-workflow2':
+      workflow = Workflow(document=document)
+      workflow.update_name(name)
+      workflow.update_uuid(copy_document.uuid)
+      _import_workspace(request.fs, request.user, workflow)
+      copy_document.update_data({'workflow': workflow.get_data()['workflow']})
+      copy_document.save()
+
+    if document.type == 'oozie-bundle2' or document.type == 'oozie-coordinator2':
+      if document.type == 'oozie-bundle2':
+        bundle_or_coordinator = Bundle(document=document)
+      else:
+        bundle_or_coordinator = Coordinator(document=document)
+      json_data = bundle_or_coordinator.get_data_for_json()
+      json_data['name'] = name
+      json_data['uuid'] = copy_document.uuid
+      copy_document.update_data(json_data)
+      copy_document.save()
+      _import_workspace(request.fs, request.user, bundle_or_coordinator)
+  elif document.type == 'search-dashboard':
+    copy_data = copy_document.data_dict
+    copy_data['collection']['name'] = name
+    copy_data['collection']['uuid'] = copy_document.uuid
+    copy_document.update_data(copy_data)
+    copy_document.save()
+  # Keep the document and data in sync
+  else:
+    copy_data = copy_document.data_dict
+    if 'name' in copy_data:
+      copy_data['name'] = name
+    if 'uuid' in copy_data:
+      copy_data['uuid'] = copy_document.uuid
+    copy_document.update_data(copy_data)
+    copy_document.save()
+
+  return JsonResponse({
+    'status': 0,
+    'document': copy_document.to_dict()
+  })
+
 @api_error_handler
 @require_POST
 def restore_document(request):

+ 15 - 0
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -1091,6 +1091,21 @@ var ApiHelper = (function () {
     }, options);
   };
 
+  /**
+   * @param {Object} options
+   * @param {Function} options.successCallback
+   * @param {Function} [options.errorCallback]
+   * @param {boolean} [options.silenceErrors]
+   *
+   * @param {string} options.uuid
+   */
+  ApiHelper.prototype.copyDocument = function (options) {
+    var self = this;
+    self.simplePost(DOCUMENTS_API + 'copy', {
+      uuid: ko.mapping.toJSON(options.uuid)
+    }, options);
+  };
+
   /**
    * @param {Object} options
    * @param {Function} options.successCallback

+ 31 - 0
desktop/core/src/desktop/static/desktop/js/document/hueFileEntry.js

@@ -230,6 +230,12 @@ var HueFileEntry = (function () {
       }).length > 0;
     });
 
+    self.directorySelected = ko.pureComputed(function () {
+      return self.selectedEntries().filter(function (entry) {
+        return entry.isDirectory();
+      }).length > 0;
+    });
+
     self.selectedEntry = ko.pureComputed(function () {
       if (self.selectedEntries().length === 1) {
         return self.selectedEntries()[0];
@@ -399,6 +405,31 @@ var HueFileEntry = (function () {
     }
   };
 
+  HueFileEntry.prototype.copy = function () {
+    var self = this;
+    if (self.selectedEntries().indexOf(self) !== -1) {
+      self.activeEntry(self.parent);
+    }
+    var copyNext = function () {
+      if (self.selectedEntries().length > 0) {
+        var nextUuid = self.selectedEntries().shift().definition().uuid;
+        self.apiHelper.copyDocument({
+          uuid: nextUuid,
+          successCallback: function () {
+            copyNext();
+          },
+          errorCallback: function () {
+            self.activeEntry().load();
+          }
+        });
+      } else {
+        huePubSub.publish('assist.document.refresh');
+        self.activeEntry().load();
+      }
+    };
+    copyNext();
+  };
+
   HueFileEntry.prototype.loadDocument = function () {
     var self = this;
     self.document(new HueDocument({

+ 5 - 2
desktop/core/src/desktop/templates/document_browser.mako

@@ -408,8 +408,8 @@ from desktop.views import _ko
                     <i class="fa fa-fw fa-ellipsis-v"></i>
                   </a>
                   <ul class="dropdown-menu">
-                    <li data-bind="css: { 'disabled': isTrash() || isTrashed() || selectedEntry() === null || !canModify() || (selectedEntry() != null && (!selectedEntry().isDirectory() || !selectedEntry().canModify())) }">
-                      <a href="javascript:void(0);" data-bind="click: function () { showRenameDirectoryModal() }"><i class="fa fa-fw fa-edit"></i> ${_('Rename folder')}</a>
+                    <li data-bind="css: { 'disabled': directorySelected() || selectedEntries().length < 1 || (selectedEntries().length === 1 && selectedEntries()[0].isTrashed) }">
+                      <a href="javascript:void(0);" data-bind="click: function () {  copy() }"><i class="fa fa-files-o"></i> ${_('Copy')}</a>
                     </li>
                     <!-- ko if: isTrash() -->
                     <li data-bind="css: { 'disabled': selectedEntries().length === 0 }">
@@ -419,6 +419,9 @@ from desktop.views import _ko
                     <li data-bind="css: { 'disabled': selectedEntries().length === 0 || (sharedWithMeSelected() && !superuser) }">
                       <a href="javascript:void(0);" data-bind="click: function() { getSelectedDocsWithDependents(); showDeleteConfirmation(); }"><i class="fa fa-fw fa-times"></i> <span data-bind="text:  isTrash() || isTrashed() ? '${ _ko('Delete forever') }' : '${ _ko('Move to trash') }'"></span></a>
                     </li>
+                    <li data-bind="css: { 'disabled': isTrash() || isTrashed() || selectedEntry() === null || !canModify() || (selectedEntry() != null && (!selectedEntry().isDirectory() || !selectedEntry().canModify())) }">
+                      <a href="javascript:void(0);" data-bind="click: function () { showRenameDirectoryModal() }"><i class="fa fa-fw fa-edit"></i> ${_('Rename folder')}</a>
+                    </li>
                     <li>
                       <a title="${_('Export all or selected documents')}" href="javascript:void(0);" data-bind="click: download"><i class="fa fa-fw fa-download"></i> ${_('Export')}</a>
                     </li>

+ 54 - 0
desktop/core/src/desktop/tests_doc2.py

@@ -28,6 +28,7 @@ from django.db.utils import OperationalError
 
 from desktop.converters import DocumentConverter
 from desktop.lib.django_test_util import make_logged_in_client
+from desktop.lib.fs import ProxyFS
 from desktop.lib.test_utils import grant_access
 from desktop.models import Directory, Document2
 from notebook.models import import_saved_beeswax_query
@@ -35,8 +36,13 @@ from notebook.models import import_saved_beeswax_query
 from beeswax.models import SavedQuery
 from beeswax.design import hql_query
 from useradmin.models import get_default_user_group
+from oozie.models2 import Workflow
 
 
+class MockFs():
+  def __init__(self):
+    pass
+
 class TestDocument2(object):
 
   def setUp(self):
@@ -168,6 +174,54 @@ class TestDocument2(object):
     doc = Document2.objects.get(id = doc.id)
     assert_equal(orig_last_modified.strftime('%Y-%m-%dT%H:%M:%S'), doc.last_modified.strftime('%Y-%m-%dT%H:%M:%S'))
 
+  def test_file_copy(self):
+
+    workflow_doc = Document2.objects.create(name='Copy Test', type='oozie-workflow2', owner=self.user, data={},
+                                            parent_directory=self.home_dir)
+
+    workflow = Workflow(user=self.user)
+    workflow.update_name('Copy Test')
+    workflow.set_workspace(self.user)
+
+    # Monkey patch check_workspace for both new wor
+    if not hasattr(Workflow, 'real_check_workspace'):
+      Workflow.real_check_workspace = Workflow.check_workspace
+
+    try:
+      Workflow.check_workspace = lambda a, b, c: None
+      workflow.check_workspace(MockFs(), self.user)
+      workflow_doc.update_data({'workflow': workflow.get_data()['workflow']})
+      workflow_doc.save()
+
+      def copy_remote_dir(self, src, dst, *args, **kwargs):
+        pass
+
+      # Monkey patch as we don't want to do real copy
+      if not hasattr(ProxyFS, 'real_copy_remote_dir'):
+        ProxyFS.real_copy_remote_dir = ProxyFS.copy_remote_dir
+
+      ProxyFS.copy_remote_dir = copy_remote_dir
+      response = self.client.post('/desktop/api2/doc/copy', {
+        'uuid': json.dumps(workflow_doc.uuid)
+      })
+    finally:
+      Workflow.check_workspace = Workflow.real_check_workspace
+      ProxyFS.copy_remote_dir = ProxyFS.real_copy_remote_dir
+
+    copy_doc_json = json.loads(response.content)
+    copy_doc = Document2.objects.get(type='oozie-workflow2', uuid=copy_doc_json['document']['uuid'])
+    copy_workflow = Workflow(document=copy_doc)
+
+    # Check if document2 and data are in sync
+    assert_equal(copy_doc.name, copy_workflow.get_data()['workflow']['name'])
+    assert_equal(copy_doc.uuid, copy_workflow.get_data()['workflow']['uuid'])
+
+    assert_equal(copy_workflow.name, workflow.name + "-copy")
+    assert_not_equal(copy_workflow.deployment_dir, workflow.deployment_dir)
+    assert_not_equal(copy_doc.uuid, workflow_doc.uuid)
+    assert_not_equal(copy_workflow.get_data()['workflow']['uuid'], workflow.get_data()['workflow']['uuid'])
+
+
 
   def test_directory_move(self):
     source_dir = Directory.objects.create(name='test_mv', owner=self.user, parent_directory=self.home_dir)

+ 2 - 0
desktop/core/src/desktop/urls.py

@@ -143,9 +143,11 @@ dynamic_patterns += patterns('desktop.api2',
   (r'^desktop/api2/doc/mkdir/?$', 'create_directory'),
   (r'^desktop/api2/doc/update/?$', 'update_document'),
   (r'^desktop/api2/doc/delete/?$', 'delete_document'),
+  (r'^desktop/api2/doc/copy/?$', 'copy_document'),
   (r'^desktop/api2/doc/restore/?$', 'restore_document'),
   (r'^desktop/api2/doc/share/?$', 'share_document'),
 
+
   (r'^desktop/api2/get_config/?$', 'get_config'),
   (r'^desktop/api2/user_preferences/(?P<key>\w+)?$', 'user_preferences'),