From 9133a4bfc3375ec3444087f03ee529018af669ad Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Wed, 8 Jul 2009 14:10:45 -0400 Subject: [PATCH] Enhance history cloning to allow for cloning active or activatable datasets from the original history - resolves ticket # 29. --- lib/galaxy/model/__init__.py | 14 ++-- lib/galaxy/web/controllers/history.py | 23 ++++--- templates/history/clone.mako | 25 +++++++ templates/history/list_shared.mako | 2 +- test/base/twilltestcase.py | 46 +++++-------- test/functional/test_history_functions.py | 83 +++++++++++++++++++---- 6 files changed, 137 insertions(+), 56 deletions(-) create mode 100644 templates/history/clone.mako diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 5570a100f8e..10ab332f342 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -214,17 +214,21 @@ class History( object ): if genome_build not in [None, '?']: self.genome_build = genome_build self.datasets.append( dataset ) - def copy( self, name=None, target_user=None ): + def copy( self, name=None, target_user=None, activatable=False ): if not name: name = self.name if not target_user: target_user = self.user new_history = History( name=name, user=target_user ) new_history.flush() - for data in self.datasets: - new_data = data.copy( copy_children=True, target_history=new_history ) - new_history.add_dataset( new_data, set_hid = False ) - new_data.flush() + if activatable: + hdas = self.activatable_datasets + else: + hdas = self.active_datasets + for hda in hdas: + new_hda = hda.copy( copy_children=True, target_history=new_history ) + new_history.add_dataset( new_hda, set_hid = False ) + new_hda.flush() new_history.hid_counter = self.hid_counter new_history.flush() return new_history diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py index 92506bec557..c11630099a9 100644 --- a/lib/galaxy/web/controllers/history.py +++ b/lib/galaxy/web/controllers/history.py @@ -310,8 +310,8 @@ class HistoryController( BaseController ): email=email, send_to_err=send_to_err ) if params.get( 'share_button', False ): - can_change, cannot_change, no_change_needed = \ - self._populate_restricted( trans, user, histories, send_to_users, None ) + can_change, cannot_change, no_change_needed, send_to_err = \ + self._populate_restricted( trans, user, histories, send_to_users, None, send_to_err ) if can_change or cannot_change: return trans.fill_template( "/history/share.mako", histories=histories, @@ -335,8 +335,8 @@ class HistoryController( BaseController ): user = trans.get_user() histories, send_to_users, send_to_err = self._get_histories_and_users( trans, user, id, email ) send_to_err = '' - can_change, cannot_change, no_change_needed = \ - self._populate_restricted( trans, user, histories, send_to_users, action ) + can_change, cannot_change, no_change_needed, send_to_err = \ + self._populate_restricted( trans, user, histories, send_to_users, action, send_to_err ) # Now that we've populated the can_change, cannot_change, and no_change_needed dictionaries, # we'll populate the histories_for_sharing dictionary from each of them. histories_for_sharing = {} @@ -430,7 +430,7 @@ class HistoryController( BaseController ): elif history not in histories_for_sharing[ send_to_user ]: histories_for_sharing[ send_to_user ].append( history ) return histories_for_sharing, send_to_err - def _populate_restricted( self, trans, user, histories, send_to_users, action ): + def _populate_restricted( self, trans, user, histories, send_to_users, action, send_to_err ): # The user may be attempting to share histories whose datasets cannot all be accessed by other users. # If this is the case, the user sharing the histories can: # 1) action=='public': choose to make the datasets public if he is permitted to do so @@ -494,7 +494,7 @@ class HistoryController( BaseController ): cannot_change[ send_to_user ][ history ] = [ hda ] else: cannot_change[ send_to_user ][ history ].append( hda ) - return can_change, cannot_change, no_change_needed + return can_change, cannot_change, no_change_needed, send_to_err def _share_histories( self, trans, user, send_to_err, histories={} ): # histories looks like: { userA: [ historyX, historyY ], userB: [ historyY ] } msg = "" @@ -585,8 +585,12 @@ class HistoryController( BaseController ): return trans.show_message( "

%s" % change_msg, refresh_frames=['history'] ) @web.expose @web.require_login( "clone shared Galaxy history" ) - def clone( self, trans, id ): + def clone( self, trans, id, **kwd ): history = get_history( trans, id, check_ownership=False ) + params = util.Params( kwd ) + clone_choice = params.get( 'clone_choice', None ) + if not clone_choice: + return trans.fill_template( "/history/clone.mako", history=history ) user = trans.get_user() if history.user == user: owner = True @@ -598,7 +602,10 @@ class HistoryController( BaseController ): name = "Clone of '%s'" % history.name if not owner: name += " shared by '%s'" % history.user.email - new_history = history.copy( name=name, target_user=user ) + if clone_choice == 'activatable': + new_history = history.copy( name=name, target_user=user, activatable=True ) + elif clone_choice == 'active': + new_history = history.copy( name=name, target_user=user ) # Render the list view return trans.show_ok_message( 'Clone with name "%s" is now included in your list of stored histories.' % new_history.name ) diff --git a/templates/history/clone.mako b/templates/history/clone.mako new file mode 100644 index 00000000000..ea6d999a897 --- /dev/null +++ b/templates/history/clone.mako @@ -0,0 +1,25 @@ +<% _=n_ %> +<%inherit file="/base.mako"/> +<%def name="title()">Clone History + +

+
Clone History
+
+
+
+ + You can clone the history such that the clone will include all items in the original + history, or you can eliminate the original history's deleted items from the clone. +
+
+ Clone all history items, including deleted items +
+
+ Clone only items that are not deleted +
+
+ +
+
+
+
diff --git a/templates/history/list_shared.mako b/templates/history/list_shared.mako index 280f037ecff..5cc03cd976f 100644 --- a/templates/history/list_shared.mako +++ b/templates/history/list_shared.mako @@ -18,7 +18,7 @@ ${history.name}
- Clone + Clone
${history.user.email} diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py index 5f99ec3742d..9f2cfac4037 100644 --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -121,10 +121,13 @@ class TwillTestCase( unittest.TestCase ): page = self.last_page() if page.find( 'error' ) > -1: raise AssertionError('Errors in the history for user %s' % self.user ) - def check_history_for_string( self, patt ): + def check_history_for_string( self, patt, show_deleted=False ): """Looks for 'string' in history page""" self.home() - self.visit_page( "history" ) + if show_deleted: + self.visit_page( "history?show_deleted=True" ) + else: + self.visit_page( "history" ) for subpatt in patt.split(): tc.find(subpatt) self.home() @@ -268,11 +271,6 @@ class TwillTestCase( unittest.TestCase ): self.home() def switch_history( self, id='', name='' ): """Switches to a history in the current list of histories""" - data_list = self.get_histories_as_data_list() - self.assertTrue( data_list ) - if not id: - history = history_list[0] - id = history.get( 'id' ) self.visit_url( "%s/history/list?operation=switch&id=%s" % ( self.url, id ) ) if name: self.check_history_for_string( name ) @@ -305,11 +303,15 @@ class TwillTestCase( unittest.TestCase ): if check_str2: self.check_page_for_string( check_str2 ) self.home() - def clone_history( self, history_id, check_str1='' ): + def clone_history( self, history_id, clone_choice, check_str1='', check_str_after_submit='' ): self.home() self.visit_page( "history/clone?id=%s" % history_id ) if check_str1: self.check_page_for_string( check_str1 ) + tc.fv( '1', 'clone_choice', clone_choice ) + tc.submit( 'clone_choice_button' ) + if check_str_after_submit: + self.check_page_for_string( check_str_after_submit ) self.home() def enable_import_via_link( self, history_id, check_str='', check_str_after_submit='' ): self.home() @@ -366,34 +368,22 @@ class TwillTestCase( unittest.TestCase ): self.visit_page( "edit?hid=%d" % hid ) for subpatt in patt.split(): tc.find(subpatt) - def delete_history_item( self, hid, check_str='' ): + def delete_history_item( self, hda_id, check_str='' ): """Deletes an item from a history""" try: - hid = int( hid ) + hda_id = int( hda_id ) except: - raise AssertionError, "Invalid hid '%s' - must be int" % hid - hid = str(hid) - data_list = self.get_history_as_data_list() - self.assertTrue( data_list ) - elems = [ elem for elem in data_list if elem.get( 'hid' ) == hid ] - self.assertEqual( len( elems ), 1 ) - self.home() - self.visit_page( "delete?id=%s" % elems[0].get( 'id' ) ) + raise AssertionError, "Invalid hda_id '%s' - must be int" % hda_id + self.visit_url( "%s/root/delete?show_deleted_on_refresh=False&id=%s" % ( self.url, hda_id ) ) if check_str: self.check_page_for_string( check_str ) - def undelete_history_item( self, hid, show_deleted=False, check_str='' ): + def undelete_history_item( self, hda_id, check_str='' ): """Un-deletes a deleted item in a history""" try: - hid = int( hid ) + hda_id = int( hda_id ) except: - raise AssertionError, "Invalid hid '%s' - must be int" % hid - hid = str( hid ) - data_list = self.get_history_as_data_list( show_deleted=show_deleted ) - self.assertTrue( data_list ) - elems = [ elem for elem in data_list if elem.get( 'hid' ) == hid ] - self.assertEqual( len( elems ), 1 ) - self.home() - self.visit_url( "%s/dataset/undelete?id=%s" % ( self.url, elems[0].get( 'id' ) ) ) + raise AssertionError, "Invalid hda_id '%s' - must be int" % hda_id + self.visit_url( "%s/dataset/undelete?id=%s" % ( self.url, hda_id ) ) if check_str: self.check_page_for_string( check_str ) def display_history_item( self, id, check_str='' ): diff --git a/test/functional/test_history_functions.py b/test/functional/test_history_functions.py index c2a4364d44b..5c2e12123f3 100644 --- a/test/functional/test_history_functions.py +++ b/test/functional/test_history_functions.py @@ -168,11 +168,6 @@ class TestHistory( TwillTestCase ): # Test sharing history3 with an invalid user self.share_current_history( 'jack@jill.com', check_str_after_submit='jack@jill.com is not a valid Galaxy user.' ) - - - - - def test_025_delete_shared_current_history( self ): """Testing deleting the current history after it was shared""" # Logged in as admin_user @@ -187,7 +182,8 @@ class TestHistory( TwillTestCase ): # Shared history3 should be in regular_user1's list of shared histories self.view_shared_histories( check_str=history3.name, check_str2=admin_user.email ) self.clone_history( self.security.encode_id( history3.id ), - check_str1='is now included in your list of stored histories.' ) + 'activatable', + check_str_after_submit='is now included in your list of stored histories.' ) global history3_clone1 history3_clone1 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.user_id==regular_user1.id ) ) \ @@ -201,8 +197,26 @@ class TestHistory( TwillTestCase ): # logged in as regular_user1 self.logout() self.login( email=admin_user.email ) + # Current history should be history3, add more datasets to history3, then delete them so we can + # test cloning activatable datasets as well as only the active datasets + self.upload_file( '2.bed', dbkey='hg18' ) + hda_2_bed = galaxy.model.HistoryDatasetAssociation \ + .filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3.id, + galaxy.model.HistoryDatasetAssociation.table.c.name=='2.bed' ) ) \ + .first() + assert hda_2_bed is not None, "Problem retrieving hda_2_bed from database" + self.delete_history_item( str( hda_2_bed.id ) ) + self.upload_file( '3.bed', dbkey='hg18' ) + hda_3_bed = galaxy.model.HistoryDatasetAssociation \ + .filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3.id, + galaxy.model.HistoryDatasetAssociation.table.c.name=='3.bed' ) ) \ + .first() + assert hda_3_bed is not None, "Problem retrieving hda_3_bed from database" + self.delete_history_item( str( hda_3_bed.id ) ) + # Test cloning activatable datasets self.clone_history( self.security.encode_id( history3.id ), - check_str1='is now included in your list of stored histories.' ) + 'activatable', + check_str_after_submit='is now included in your list of stored histories.' ) global history3_clone2 history3_clone2 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.user_id==admin_user.id ) ) \ @@ -210,6 +224,42 @@ class TestHistory( TwillTestCase ): assert history3_clone2 is not None, "Problem retrieving history3_clone2 from database" # Check list of histories to make sure shared history3 was cloned self.view_stored_active_histories( check_str="Clone of '%s'" % history3.name ) + # Switch to the cloned history to make sure activatable datasets were cloned + self.switch_history( id=self.security.encode_id( history3_clone2.id ), name=history3_clone2.name ) + hda_2_bed = galaxy.model.HistoryDatasetAssociation \ + .filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3_clone2.id, + galaxy.model.HistoryDatasetAssociation.table.c.name=='2.bed' ) ) \ + .first() + assert hda_2_bed is not None, "Problem retrieving hda_2_bed from database" + hda_3_bed = galaxy.model.HistoryDatasetAssociation \ + .filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3_clone2.id, + galaxy.model.HistoryDatasetAssociation.table.c.name=='3.bed' ) ) \ + .first() + assert hda_3_bed is not None, "Problem retrieving hda_3_bed from database" + # Make sure the deleted datasets are included in the cloned history + check_str = 'This dataset has been deleted. Click undelete id=%d"' % hda_2_bed.id + self.check_history_for_string( check_str, show_deleted=True ) + check_str = 'This dataset has been deleted. Click undelete id=%d"' % hda_3_bed.id + self.check_history_for_string( check_str, show_deleted=True ) + # Test cloning only active datasets + self.clone_history( self.security.encode_id( history3.id ), + 'active', + check_str_after_submit='is now included in your list of stored histories.' ) + global history3_clone3 + history3_clone3 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, + galaxy.model.History.table.c.user_id==admin_user.id ) ) \ + .order_by( desc( galaxy.model.History.table.c.create_time ) ).first() + assert history3_clone3 is not None, "Problem retrieving history3_clone3 from database" + # Check list of histories to make sure shared history3 was cloned + self.view_stored_active_histories( check_str="Clone of '%s'" % history3.name ) + # Switch to the cloned history to make sure activatable datasets were cloned + self.switch_history( id=self.security.encode_id( history3_clone3.id ), name=history3_clone3.name ) + # Make sure the deleted datasets are NOT included in the cloned history + try: + self.check_history_for_string( 'This dataset has been deleted.', show_deleted=True ) + raise AssertionError, "Deleted datasets incorrectly included in cloned history history3_clone3" + except: + pass def test_040_sharing_mulitple_histories_with_multiple_users( self ): """Testing sharing multiple histories containing only public datasets with multiple users""" # Logged in as admin_user @@ -304,7 +354,8 @@ class TestHistory( TwillTestCase ): self.view_shared_histories( check_str=history5.name, check_str2=admin_user.email ) # Clone restricted history5 self.clone_history( self.security.encode_id( history5.id ), - check_str1='is now included in your list of stored histories.' ) + 'activatable', + check_str_after_submit='is now included in your list of stored histories.' ) global history5_clone1 history5_clone1 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.user_id==regular_user1.id ) ) \ @@ -359,7 +410,8 @@ class TestHistory( TwillTestCase ): self.view_shared_histories( check_str=history5.name, check_str2=admin_user.email ) # Clone restricted history5 self.clone_history( self.security.encode_id( history5.id ), - check_str1='is now included in your list of stored histories.' ) + 'activatable', + check_str_after_submit='is now included in your list of stored histories.' ) global history5_clone2 history5_clone2 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.user_id==regular_user2.id ) ) \ @@ -431,7 +483,8 @@ class TestHistory( TwillTestCase ): self.view_shared_histories( check_str=history5.name, check_str2=admin_user.email ) # Clone restricted history5 self.clone_history( self.security.encode_id( history5.id ), - check_str1='is now included in your list of stored histories.' ) + 'activatable', + check_str_after_submit='is now included in your list of stored histories.' ) global history5_clone3 history5_clone3 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.user_id==regular_user2.id ) ) \ @@ -468,7 +521,8 @@ class TestHistory( TwillTestCase ): self.view_shared_histories( check_str=history5.name, check_str2=admin_user.email ) # Clone restricted history5 self.clone_history( self.security.encode_id( history5.id ), - check_str1='is now included in your list of stored histories.' ) + 'activatable', + check_str_after_submit='is now included in your list of stored histories.' ) global history5_clone4 history5_clone4 = galaxy.model.History.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.user_id==regular_user3.id ) ) \ @@ -587,16 +641,16 @@ class TestHistory( TwillTestCase ): self.check_page_for_string( 'hg15' ) self.assertEqual ( len( self.get_history_as_data_list() ), 1 ) # Delete the history item - self.delete_history_item( latest_hda.hid, check_str="Your history is empty" ) + self.delete_history_item( str( latest_hda.id ), check_str="Your history is empty" ) self.assertEqual ( len( self.get_history_as_data_list() ), 0 ) # Try deleting an invalid hid try: self.delete_history_item( 'XXX' ) - raise AssertionError, "Inproperly able to delete hid 'XXX' which is not an integer" + raise AssertionError, "Inproperly able to delete hda_id 'XXX' which is not an integer" except: pass # Undelete the history item - self.undelete_history_item( latest_hda.hid, show_deleted=True ) + self.undelete_history_item( str( latest_hda.id ) ) self.home() self.visit_url( "%s/history/?show_deleted=False" % self.url ) self.check_page_for_string( '1.bed' ) @@ -673,6 +727,7 @@ class TestHistory( TwillTestCase ): # Delete histories self.delete_history( id=self.security.encode_id( history3.id ) ) self.delete_history( id=self.security.encode_id( history3_clone2.id ) ) + self.delete_history( id=self.security.encode_id( history3_clone3.id ) ) self.delete_history( id=self.security.encode_id( history4.id ) ) self.delete_history( id=self.security.encode_id( history5.id ) ) # Eliminate Sharing role for: test@bx.psu.edu, test2@bx.psu.edu