details: http://www.bx.psu.edu/hg/galaxy/rev/f0adb6152df9 changeset: 2678:f0adb6152df9 user: Greg Von Kuster <greg@bx.psu.edu> date: Thu Sep 10 21:24:06 2009 -0400 description: Streamline history sharing, add a "Manage shared histories" section to the History options menu. Also use a better approach to setting peek on datasets. 5 file(s) affected in this change: lib/galaxy/datatypes/data.py lib/galaxy/web/controllers/history.py templates/history/sharing.mako templates/root/index.mako test/functional/test_history_functions.py diffs (482 lines): diff -r 96ccd29277be -r f0adb6152df9 lib/galaxy/datatypes/data.py --- a/lib/galaxy/datatypes/data.py Thu Sep 10 17:48:52 2009 -0400 +++ b/lib/galaxy/datatypes/data.py Thu Sep 10 21:24:06 2009 -0400 @@ -416,8 +416,9 @@ count = 0 file_type = None data_checked = False - for line in file( file_name ): - line = line[:WIDTH] + temp = open( file_name, "U" ) + while count <= LINE_COUNT: + line = temp.readline( WIDTH ) if line and not is_multi_byte and not data_checked: # See if we have a compressed or binary file if line[0:2] == util.gzip_magic: @@ -432,9 +433,8 @@ if file_type in [ 'gzipped', 'binary' ]: break lines.append( line ) - if count == LINE_COUNT: - break count += 1 + temp.close() if file_type in [ 'gzipped', 'binary' ]: text = "%s file" % file_type else: diff -r 96ccd29277be -r f0adb6152df9 lib/galaxy/web/controllers/history.py --- a/lib/galaxy/web/controllers/history.py Thu Sep 10 17:48:52 2009 -0400 +++ b/lib/galaxy/web/controllers/history.py Thu Sep 10 21:24:06 2009 -0400 @@ -4,7 +4,7 @@ from galaxy.model.mapping import desc from galaxy.model.orm import * from galaxy.util.json import * -import webhelpers, logging +import webhelpers, logging, operator from datetime import datetime from cgi import escape @@ -31,10 +31,12 @@ return "deleted" elif history.users_shared_with: return "shared" + elif history.importable: + return "importable" return "" def get_link( self, trans, grid, item ): - if item.users_shared_with: - return dict( operation="sharing", id=item.id ) + if item.users_shared_with or item.importable: + return dict( operation="sharing" ) return None # Grid definition title = "Stored histories" @@ -55,9 +57,12 @@ operations = [ grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ) ), grids.GridOperation( "Share", condition=( lambda item: not item.deleted ) ), + grids.GridOperation( "Unshare", condition=( lambda item: not item.deleted ) ), grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ) ), grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ) + grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), + grids.GridOperation( "Enable import via link", condition=( lambda item: item.deleted ) ), + grids.GridOperation( "Disable import via link", condition=( lambda item: item.deleted ) ) ] standard_filters = [ grids.GridColumnFilter( "Active", args=dict( deleted=False ) ), @@ -99,7 +104,9 @@ ] operations = [ grids.GridOperation( "Clone" ), - grids.GridOperation( "Unshare" ) + grids.GridOperation( "Unshare" ), + grids.GridOperation( "Enable import via link", condition=( lambda item: item.deleted ) ), + grids.GridOperation( "Disable import via link", condition=( lambda item: item.deleted ) ) ] standard_filters = [] def build_initial_query( self, session ): @@ -126,19 +133,19 @@ current_history = trans.get_history() status = message = None if 'operation' in kwargs: - history_ids = util.listify( kwargs.get( 'id', [] ) ) - histories = [] operation = kwargs['operation'].lower() if operation == "share": return self.share( trans, **kwargs ) - elif operation == "rename": + if operation == "rename": return self.rename( trans, **kwargs ) - elif operation == 'sharing': - return self.sharing( trans, id=kwargs['id'] ) + history_ids = util.listify( kwargs.get( 'id', [] ) ) + if operation == "sharing": + return self.sharing( trans, id=history_ids ) # Display no message by default status, message = None, None refresh_history = False # Load the histories and ensure they all belong to the current user + histories = [] for history_id in history_ids: history = get_history( trans, history_id ) if history: @@ -161,6 +168,21 @@ trans.template_context['refresh_frames'] = ['history'] elif operation == "undelete": status, message = self._list_undelete( trans, histories ) + elif operation == "unshare": + for history in histories: + husas = trans.app.model.HistoryUserShareAssociation.filter_by( history=history ).all() + for husa in husas: + husa.delete() + elif operation == "enable import via link": + for history in histories: + if not history.importable: + history.importable = True + elif operation == "disable import via link": + if history_ids: + histories = [ get_history( trans, history_id ) for history_id in history_ids ] + for history in histories: + if history.importable: + history.importable = False trans.sa_session.flush() # Render the list view return self.stored_list_grid( trans, status=status, message=message, **kwargs ) @@ -237,24 +259,20 @@ msg = util.restore_text( kwargs.get( 'msg', '' ) ) status = message = None if 'operation' in kwargs: - id = kwargs.get( 'id', None ) + ids = util.listify( kwargs.get( 'id', [] ) ) operation = kwargs['operation'].lower() if operation == "clone": - if not id: + if not ids: message = "Select a history to clone" return self.shared_list_grid( trans, status='error', message=message, **kwargs ) # When cloning shared histories, only copy active datasets new_kwargs = { 'clone_choice' : 'active' } return self.clone( trans, id, **new_kwargs ) elif operation == 'unshare': - if not id: + if not ids: message = "Select a history to unshare" return self.shared_list_grid( trans, status='error', message=message, **kwargs ) - ids = util.listify( id ) - histories = [] - for history_id in ids: - history = get_history( trans, history_id, check_ownership=False ) - histories.append( history ) + histories = [ get_history( trans, history_id ) for history_id in ids ] for history in histories: # Current user is the user with which the histories were shared association = trans.app.model.HistoryUserShareAssociation.filter_by( user=trans.user, history=history ).one() @@ -262,6 +280,20 @@ association.flush() message = "Unshared %d shared histories" % len( ids ) status = 'done' + elif operation == "enable import via link": + if ids: + histories = [ get_history( trans, id ) for id in ids ] + for history in histories: + if not history.importable: + history.importable = True + history.flush() + elif operation == "disable import via link": + if ids: + histories = [ get_history( trans, id ) for id in ids ] + for history in histories: + if history.importable: + history.importable = False + history.flush() # Render the list view return self.shared_list_grid( trans, status=status, message=message, **kwargs ) @web.expose @@ -622,7 +654,9 @@ params = util.Params( kwd ) msg = util.restore_text ( params.get( 'msg', '' ) ) if id: - histories = [ get_history( trans, id ) ] + ids = util.listify( id ) + if ids: + histories = [ get_history( trans, history_id ) for history_id in ids ] for history in histories: if params.get( 'enable_import_via_link', False ): history.importable = True @@ -635,14 +669,34 @@ if not user: msg = 'History (%s) does not seem to be shared with user (%s)' % ( history.name, user.email ) return trans.fill_template( 'history/sharing.mako', histories=histories, msg=msg, messagetype='error' ) - association = trans.app.model.HistoryUserShareAssociation.filter_by( user=user, history=history ).one() - association.delete() - association.flush() - if not id: - shared_msg = "History (%s) now shared with: %d users. " % ( history.name, len( history.users_shared_with ) ) - msg = '%s%s' % ( shared_msg, msg ) + husas = trans.app.model.HistoryUserShareAssociation.filter_by( user=user, history=history ).all() + if husas: + for husa in husas: + husa.delete() + husa.flush() + histories = [] + # Get all histories that have been shared with others + husas = trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ + .join( "history" ) \ + .filter( and_( trans.app.model.History.user == trans.user, + trans.app.model.History.deleted == False ) ) \ + .order_by( trans.app.model.History.table.c.name ) \ + .all() + for husa in husas: + history = husa.history + if history not in histories: + histories.append( history ) + # Get all histories that are importable + importables = trans.sa_session.query( trans.app.model.History ) \ + .filter_by( user=trans.user, importable=True, deleted=False ) \ + .order_by( trans.app.model.History.table.c.name ) \ + .all() + for importable in importables: + if importable not in histories: + histories.append( importable ) + # Sort the list of histories by history.name + histories.sort( key=operator.attrgetter( 'name') ) return trans.fill_template( 'history/sharing.mako', histories=histories, msg=msg, messagetype='done' ) - @web.expose @web.require_login( "rename histories" ) def rename( self, trans, id=None, name=None, **kwd ): diff -r 96ccd29277be -r f0adb6152df9 templates/history/sharing.mako --- a/templates/history/sharing.mako Thu Sep 10 17:48:52 2009 -0400 +++ b/templates/history/sharing.mako Thu Sep 10 21:24:06 2009 -0400 @@ -1,75 +1,63 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> -<h2>Public access via link</h2> +##<h2>Import via link</h2> %if msg: ${render_msg( msg, messagetype )} %endif -%for history in histories: - <p> - %if history.importable: - Send the following URL to users as an easy way for them to import the history, making a copy of their own: - <% url = h.url_for( controller='history', action='imp', id=trans.security.encode_id(history.id), qualified=True ) %> - <blockquote> - <a href="${url}">${url}</a> - </blockquote> - <br/> - <form action="${h.url_for( controller='history', action='sharing', id=trans.security.encode_id( history.id ) )}" method="POST"> - <input class="action-button" type="submit" name="disable_import_via_link" value="Disable import via link"> - </form> - %else: - This history is currently restricted (only you and the users listed below - can access it). Enabling the following option will generate a URL that you - can give to a user to allow them to import this history. - <br/> - <form action="${h.url_for( action='sharing', id=trans.security.encode_id(history.id) )}" method="POST"> - <input class="action-button" type="submit" name="enable_import_via_link" value="Enable import via link"> - </form> - %endif - </p> - <h2>Sharing with specific users</h2> - %if history.users_shared_with: - <ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller='history', action='share', id=trans.security.encode_id( history.id ) )}"> - <span>Share with another user</span> - </a> - </li> - </ul> - <p> - The following users will see this history in their list of histories - shared with them by others, and they will be able to create their own copy of it: - </p> - <table class="colored" border="0" cellspacing="0" cellpadding="0" width="100%"> - <tr class="header"> - <th>History '${history.name}' currently shared with</th> - <th></th> - </tr> - %for i, association in enumerate( history.users_shared_with ): - <% user = association.user %> - <tr> - <td> - ${user.email} - <a id="user-${i}-popup" class="popup-arrow" style="display: none;">▼</a> - </td> - <td> - %if len( histories ) == 1: - ## Only allow unsharing if we're dealing with 1 history, otherwise - ## page refreshes screw things up - <div popupmenu="user-${i}-popup"> - <a class="action-button" href="${h.url_for( controller='history', action='sharing', id=trans.security.encode_id( history.id ), unshare_user=trans.security.encode_id( user.id ) )}">Unshare</a> +<h2>Histories that you've shared with others or enabled to be imported</h2> + +%if not histories: + You have no histories that you've shared with others or enabled to be imported +%else: + %for history in histories: + <div class="toolForm"> + <div class="toolFormTitle">History '${history.name}' shared with</div> + <div class="toolFormBody"> + <div class="form-row"> + <div style="float: right;"> + <a class="action-button" href="${h.url_for( controller='history', action='share', id=trans.security.encode_id( history.id ) )}"> + <span>Share with another user</span> + </a> + </div> + </div> + %if history.users_shared_with: + %for i, association in enumerate( history.users_shared_with ): + <% user = association.user %> + <div class="form-row"> + <a class="action-button" href="${h.url_for( controller='history', action='sharing', id=trans.security.encode_id( history.id ), unshare_user=trans.security.encode_id( user.id ) )}">Unshare</a> + ${user.email} + </div> + %endfor + %endif + %if history.importable: + <div class="form-row"> + <% url = h.url_for( controller='history', action='imp', id=trans.security.encode_id(history.id), qualified=True ) %> + <a href="${url}">${url}</a> + <div class="toolParamHelp" style="clear: both;"> + Send the above URL to users as an easy way for them to import the history, making a copy of their own + </div> + </div> + <div class="form-row"> + <form action="${h.url_for( controller='history', action='sharing', id=trans.security.encode_id( history.id ) )}" method="POST"> + <div class="form-row"> + <input class="action-button" type="submit" name="disable_import_via_link" value="Disable import via link"> </div> - %endif - </td> - </tr> - %endfor - </table> - %else: - <p>You have not shared this history with any users.</p> - <a class="action-button" href="${h.url_for( controller='history', action='share', id=trans.security.encode_id(history.id) )}"> - <span>Share with another user</span> - </a> - %endif -%endfor + </form> + </div> + %else: + <form action="${h.url_for( action='sharing', id=trans.security.encode_id(history.id) )}" method="POST"> + <div class="form-row"> + <input class="action-button" type="submit" name="enable_import_via_link" value="Enable import via link"> + <div class="toolParamHelp" style="clear: both;"> + Click to generate a URL that you can give to a user to allow them to import this history, making a copy of their own + </div> + </div> + </form> + %endif + </div> + </div> + %endfor +%endif diff -r 96ccd29277be -r f0adb6152df9 templates/root/index.mako --- a/templates/root/index.mako Thu Sep 10 17:48:52 2009 -0400 +++ b/templates/root/index.mako Thu Sep 10 21:24:06 2009 -0400 @@ -9,9 +9,6 @@ "List your histories": null, "Stored by you": function() { galaxy_main.location = "${h.url_for( controller='history', action='list')}"; - }, - "Shared with you": function() { - galaxy_main.location = "${h.url_for( controller='history', action='list_shared')}"; }, "Current History": null, "Create new": function() { @@ -32,10 +29,19 @@ "Show deleted datasets": function() { galaxy_history.location = "${h.url_for( controller='root', action='history', show_deleted=True)}"; }, - "Delete": function() { - if ( confirm( "Really delete the current history?" ) ) { + "Delete": function() + { + if ( confirm( "Really delete the current history?" ) ) + { galaxy_main.location = "${h.url_for( controller='history', action='delete_current' )}"; } + }, + "Manage shared histories": null, + "Shared by you": function() { + galaxy_main.location = "${h.url_for( controller='history', action='list', operation='sharing' )}"; + }, + "Shared with you": function() { + galaxy_main.location = "${h.url_for( controller='history', action='list_shared')}"; } }); }); diff -r 96ccd29277be -r f0adb6152df9 test/functional/test_history_functions.py --- a/test/functional/test_history_functions.py Thu Sep 10 17:48:52 2009 -0400 +++ b/test/functional/test_history_functions.py Thu Sep 10 21:24:06 2009 -0400 @@ -141,14 +141,13 @@ check_str_after_submit='You cannot send histories to yourself.' ) # Share history3 with 1 valid user self.share_current_history( regular_user1.email, - check_str=history3.name, - check_str_after_submit='History (%s) now shared with: 1 users' % history3.name ) + check_str=history3.name ) # Check out list of histories to make sure history3 was shared - self.view_stored_active_histories( check_str='operation=sharing&id=%s">shared' % self.security.encode_id( history3.id ) ) + self.view_stored_active_histories( check_str='operation=sharing">shared' ) # Enable importing history3 via a URL self.enable_import_via_link( self.security.encode_id( history3.id ), check_str='Unshare', - check_str_after_submit='Send the following URL to users' ) + check_str_after_submit='Send the above URL to users' ) # Make sure history3 is now import-able history3.refresh() if not history3.importable: @@ -159,7 +158,7 @@ check_str_after_submit='You cannot import your own history.' ) # Disable the import link for history3 self.disable_import_via_link( self.security.encode_id( history3.id ), - check_str='Send the following URL to users', + check_str='Send the above URL to users', check_str_after_submit='Enable import via link' ) # Try importing history3 after disabling the URL self.import_history_via_url( self.security.encode_id( history3.id ), @@ -274,12 +273,10 @@ self.upload_file( '2.bed', dbkey='hg18' ) ids = '%s,%s' % ( self.security.encode_id( history3.id ), self.security.encode_id( history4.id ) ) emails = '%s,%s' % ( regular_user2.email, regular_user3.email ) - check_str_after_submit = 'History (%s) now shared with: 3 users.' % history3.name self.share_histories_with_users( ids, emails, check_str1='Share 2 histories', - check_str2=history4.name, - check_str_after_submit=check_str_after_submit ) + check_str2=history4.name ) self.logout() self.login( email=regular_user2.email ) # Shared history3 should be in regular_user2's list of shared histories @@ -342,12 +339,10 @@ """Testing sharing a restricted history by making the datasets public""" # Logged in as admin_user action_check_str = 'The following datasets can be shared with %s by updating their permissions' % regular_user1.email - action_check_str_after_submit = 'History (%s) now shared with: 1 users.' % history5.name # Current history is history5 self.share_current_history( regular_user1.email, action='public', - action_check_str=action_check_str, - action_check_str_after_submit=action_check_str_after_submit ) + action_check_str=action_check_str ) self.logout() self.login( email=regular_user1.email ) # Shared history5 should be in regular_user1's list of shared histories @@ -375,12 +370,10 @@ self.upload_file( '2.bed', dbkey='hg18' ) check_str_after_submit = 'The following datasets can be shared with %s with no changes' % regular_user2.email check_str_after_submit2 = 'The following datasets can be shared with %s by updating their permissions' % regular_user2.email - action_check_str_after_submit = 'History (%s) now shared with: 2 users.' % history5.name self.share_current_history( regular_user2.email, check_str_after_submit=check_str_after_submit, check_str_after_submit2=check_str_after_submit2, - action='private', - action_check_str_after_submit=action_check_str_after_submit ) + action='private' ) # We should now have a new sharing role global sharing_role role_name = 'Sharing role for: %s, %s' % ( admin_user.email, regular_user2.email ) @@ -470,12 +463,10 @@ check_str_after_submit = 'The following datasets can be shared with %s with no changes' % email check_str_after_submit2 = 'The following datasets can be shared with %s by updating their permissions' % email # history5 will be shared with regular_user1, regular_user2 and regular_user3 - action_check_str_after_submit = 'History (%s) now shared with: 3 users.' % history5.name self.share_current_history( email, check_str_after_submit=check_str_after_submit, check_str_after_submit2=check_str_after_submit2, - action='share_anyway', - action_check_str_after_submit=action_check_str_after_submit ) + action='share_anyway' ) # Check security on clone of history5 for regular_user2 self.logout() self.login( email=regular_user2.email )