mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Streamline history sharing, add a "Manage shared histories" section to the History options menu. Also use a better approach to setting peek on datasets.
This commit is contained in:
@@ -416,8 +416,9 @@ def get_file_peek( file_name, is_multi_byte=False, WIDTH=256, LINE_COUNT=5 ):
|
||||
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 @@ def get_file_peek( file_name, is_multi_byte=False, WIDTH=256, LINE_COUNT=5 ):
|
||||
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:
|
||||
|
||||
@@ -4,7 +4,7 @@ from galaxy import util
|
||||
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 @@ class HistoryListGrid( grids.Grid ):
|
||||
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 @@ class HistoryListGrid( grids.Grid ):
|
||||
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 @@ class SharedHistoryListGrid( grids.Grid ):
|
||||
]
|
||||
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 @@ class HistoryController( BaseController ):
|
||||
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 @@ class HistoryController( BaseController ):
|
||||
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 @@ class HistoryController( BaseController ):
|
||||
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 @@ class HistoryController( BaseController ):
|
||||
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 @@ class HistoryController( BaseController ):
|
||||
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 @@ class HistoryController( BaseController ):
|
||||
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 ):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
"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() {
|
||||
galaxy_history.location = "${h.url_for( controller='root', action='history_new' )}";
|
||||
@@ -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')}";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -141,14 +141,13 @@ class TestHistory( TwillTestCase ):
|
||||
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 @@ class TestHistory( TwillTestCase ):
|
||||
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 @@ class TestHistory( TwillTestCase ):
|
||||
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 @@ class TestHistory( TwillTestCase ):
|
||||
"""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 @@ class TestHistory( TwillTestCase ):
|
||||
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 @@ class TestHistory( TwillTestCase ):
|
||||
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 )
|
||||
|
||||
Reference in New Issue
Block a user