Changes to the 'list histories' interface. This incorporates the changes from Assaf Gordon to display datasets by state, as well as changing how switch / delete/ undelete work to make the interface less disruptive. Share and rename still work as before. There is now a history controller, and some fixes to tests to deal with that. New grid styles used here will eventually be propogated to other data grid tables.

This commit is contained in:
James Taylor
2009-01-12 18:25:32 -05:00
parent f2c1abdd85
commit c245b5b6ca
14 changed files with 384 additions and 520 deletions
+2
View File
@@ -57,6 +57,8 @@ def app_factory( global_conf, **kwargs ):
# Create the universe WSGI application
webapp = galaxy.web.framework.WebApplication( app, session_cookie='galaxysession' )
add_controllers( webapp, app )
# Force /history to go to /root/history -- needed since the tests assume this
webapp.add_route( '/history', controller='root', action='history' )
# These two routes handle our simple needs at the moment
webapp.add_route( '/async/:tool_id/:data_id/:data_secret', controller='async', action='index', tool_id=None, data_id=None, data_secret=None )
webapp.add_route( '/:controller/:action', action='index' )
+6 -307
View File
@@ -371,128 +371,13 @@ class RootController( BaseController ):
"""Displays a list of history related actions"""
return trans.fill_template( "/history/options.mako",
user = trans.get_user(), history = trans.get_history() )
@web.expose
def history_delete( self, trans, id=None, **kwd):
"""Deletes a list of histories, ensures that histories are owned by current user"""
if trans.app.memory_usage:
# Keep track of memory usage
m0 = self.app.memory_usage.memory()
history_names = []
if id:
if isinstance( id, list ):
history_ids = id
else:
history_ids = [ id ]
user = trans.get_user()
for hid in history_ids:
try:
int( hid )
except:
return trans.show_message( "Invalid history: %s" % str( hid ) )
history = self.app.model.History.get( hid )
if history:
if history.user_id != None and user:
assert user.id == history.user_id, "History does not belong to current user"
# Delete DefaultHistoryPermissions
for dhp in history.default_permissions:
dhp.delete()
dhp.flush()
# Mark history as deleted in db
history.deleted = True
history_names.append(history.name)
# If deleting the current history, make a new current.
if history == trans.get_history():
trans.new_history()
else:
return trans.show_message( "Not able to find history %s" % str( hid ) )
self.app.model.flush()
trans.log_event( "History id %s marked as deleted" % str(hid) )
else:
return trans.show_message( "You must select at least one history to delete." )
if self.app.memory_usage:
m1 = trans.app.memory_usage.memory( m0, pretty=True )
log.info( "End of root/history_delete, memory used increased by %s" % m1 )
return trans.show_message( "History deleted: %s" % ",".join(history_names),
refresh_frames=['history'])
@web.expose
def history_undelete( self, trans, id=[], **kwd):
"""Undeletes a list of histories, ensures that histories are owned by current user"""
history_names = []
errors = []
ok_msg = ""
if id:
if not isinstance( id, list ):
id = id.split( "," )
user = trans.get_user()
for hid in id:
try:
int( hid )
except:
errors.append( "Invalid history: %s" % str( hid ) )
continue
history = self.app.model.History.get( hid )
if history:
if history.user != user:
errors.append( "History does not belong to current user." )
continue
if history.purged:
errors.append( "History has already been purged and can not be undeleted." )
continue
history_names.append( history.name )
history.deleted = False
else:
errors.append( "Not able to find history %s." % str( hid ) )
trans.log_event( "History id %s marked as undeleted" % str(hid) )
self.app.model.flush()
if history_names:
ok_msg = "Histories (%s) have been undeleted." % ", ".join( history_names )
else:
errors.append( "You must select at least one history to undelete." )
return self.history_available( trans, id=','.join( id ), show_deleted=True, ok_msg = ok_msg, error_msg = " ".join( errors ) )
@web.expose
def history_undelete( self, trans, id=[], **kwd):
"""Undeletes a list of histories, ensures that histories are owned by current user"""
if trans.app.memory_usage:
# Keep track of memory usage
m0 = self.app.memory_usage.memory()
history_names = []
errors = []
ok_msg = ""
if id:
if not isinstance( id, list ):
id = id.split( "," )
user = trans.get_user()
for hid in id:
try:
int( hid )
except:
errors.append( "Invalid history: %s" % str( hid ) )
continue
history = self.app.model.History.get( hid )
if history:
if history.user != user:
errors.append( "History does not belong to current user." )
continue
if history.purged:
errors.append( "History has already been purged and can not be undeleted." )
continue
history_names.append( history.name )
history.deleted = False
else:
errors.append( "Not able to find history %s." % str( hid ) )
trans.log_event( "History id %s marked as undeleted" % str(hid) )
self.app.model.flush()
if history_names:
ok_msg = "Histories (%s) have been undeleted." % ", ".join( history_names )
else:
errors.append( "You must select at least one history to undelete." )
if self.app.memory_usage:
m1 = trans.app.memory_usage.memory( m0, pretty=True )
log.info( "End of root/history_undelete, memory used increased by %s" % m1 )
return self.history_available( trans, id=','.join( id ), show_deleted=True, ok_msg = ok_msg, error_msg = " ".join( errors ) )
def history_delete( self, trans, id ):
"""
Backward compatibility with check_galaxy script.
"""
return trans.webapp.controllers['history'].list( trans, id, operation='delete' )
@web.expose
def clear_history( self, trans ):
@@ -510,117 +395,6 @@ class RootController( BaseController ):
m1 = trans.app.memory_usage.memory( m0, pretty=True )
log.info( "End of root/clear_history, memory used increased by %s" % m1 )
trans.response.send_redirect( url_for("/index" ) )
@web.expose
@web.require_login( "share histories with other users" )
def history_share( self, trans, id=None, email="", **kwd ):
send_to_err = ""
if not id:
id = trans.get_history().id
if not isinstance( id, list ):
id = [ id ]
histories = []
history_names = []
for hid in id:
histories.append( trans.app.model.History.get( hid ) )
history_names.append(histories[-1].name)
if not email:
return trans.fill_template("/history/share.mako", histories=histories, email=email, send_to_err=send_to_err)
user = trans.get_user()
send_to_user = trans.app.model.User.filter( trans.app.model.User.table.c.email==email ).first()
params = util.Params( kwd )
action = params.get( 'action', None )
if action == "no_share":
trans.response.send_redirect( url_for( action='history_options' ) )
if not send_to_user:
send_to_err = "No such user"
elif user.email == email:
send_to_err = "You can't send histories to yourself"
else:
if 'history_share_btn' in kwd or action != 'share':
# The user is attempting to share a history whose datasets cannot all be accessed by the other user. In this case,
# the user sharing the history can chose to make the datasets public ( action == 'public' ) if he has the authority
# to do so, or automatically create a new "sharing role" that allows the user to share his private datasets only with the
# desired user ( action == 'private' ).
can_change = {}
cannot_change = {}
for history in histories:
for hda in history.activatable_datasets:
# Only deal with datasets that have not been purged
if not trans.app.security_agent.allow_action( send_to_user,
trans.app.security_agent.permitted_actions.DATASET_ACCESS,
dataset=hda ):
# The user with which we are sharing the history does not have access permission on the current dataset
if trans.app.security_agent.allow_action( user,
trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS,
dataset=hda ) and not hda.dataset.library_associations:
# The current user has authority to change permissions on the current dataset because
# they have permission to manage permissions on the dataset and the dataset is not associated
# with a library.
if action == "private":
trans.app.security_agent.privately_share_dataset( hda.dataset, users=[ user, send_to_user ] )
elif action == "public":
trans.app.security_agent.make_dataset_public( hda.dataset )
elif history not in can_change:
# Build the set of histories / datasets on which the current user has authority
# to "manage permissions". This is used in /history/share.mako
can_change[ history ] = [ hda ]
else:
can_change[ history ].append( hda )
else:
if action in [ "private", "public" ]:
# Don't change stuff that the user doesn't have permission to change
continue
elif history not in cannot_change:
# Build the set of histories / datasets on which the current user does
# not have authority to "manage permissions". This is used in /history/share.mako
cannot_change[ history ] = [ hda ]
else:
cannot_change[ history ].append( hda )
if can_change or cannot_change:
return trans.fill_template( "/history/share.mako",
histories=histories,
email=email,
send_to_err=send_to_err,
can_change=can_change,
cannot_change=cannot_change )
for history in histories:
new_history = history.copy( target_user=send_to_user )
new_history.name = history.name + " from " + user.email
new_history.user_id = send_to_user.id
trans.log_event( "History share, id: %s, name: '%s': to new id: %s" % ( str( history.id ), history.name, str( new_history.id ) ) )
self.app.model.flush()
return trans.show_message( "History (%s) has been shared with: %s" % ( ",".join( history_names ),email ) )
return trans.fill_template( "/history/share.mako", histories=histories, email=email, send_to_err=send_to_err )
@web.expose
@web.require_login( "work with multiple histories" )
def history_available( self, trans, id=[], do_operation = "view", show_deleted = False, ok_msg = "", error_msg="", as_xml=False, **kwd ):
"""
List all available histories
"""
if trans.app.memory_usage:
# Keep track of memory usage
m0 = self.app.memory_usage.memory()
if as_xml:
trans.response.set_content_type('text/xml')
return trans.fill_template( "/history/list_as_xml.mako" )
if not isinstance( id, list ):
id = id.split( "," )
trans.log_event( "History id %s available" % str( id ) )
history_operations = dict( share=self.history_share, rename=self.history_rename, delete=self.history_delete, undelete=self.history_undelete )
if self.app.memory_usage:
m1 = trans.app.memory_usage.memory( m0, pretty=True )
log.info( "End of root/history_available, memory used increased by %s" % m1 )
if do_operation in history_operations:
return history_operations[do_operation]( trans, id=id, show_deleted=show_deleted, ok_msg=ok_msg, error_msg=error_msg, **kwd )
return trans.fill_template( "/history/list.mako", ids=id,
user=trans.get_user(),
current_history=trans.get_history(),
show_deleted=util.string_as_bool( show_deleted ),
ok_msg=ok_msg, error_msg=error_msg )
@web.expose
def history_import( self, trans, id=None, confirm=False, **kwd ):
@@ -677,34 +451,6 @@ class RootController( BaseController ):
Warning! If you import this history, you will lose your current
history. Click <a href="%s">here</a> to confirm.
""" % web.url_for( id=id, confirm=True ) )
@web.expose
@web.require_login( "switch histories" )
def history_switch( self, trans, id=None ):
if not id:
return trans.response.send_redirect( web.url_for( action='history_available' ) )
else:
if trans.app.memory_usage:
# Keep track of memory usage
m0 = self.app.memory_usage.memory()
new_history = trans.app.model.History.get( id )
if new_history:
galaxy_session = trans.get_galaxy_session()
try:
association = trans.app.model.GalaxySessionToHistoryAssociation.filter_by( session_id=galaxy_session.id, history_id=new_history.id ).first()
except:
association = None
new_history.add_galaxy_session( galaxy_session, association=association )
new_history.flush()
trans.set_history( new_history )
trans.log_event( "History switched to id: %s, name: '%s'" % (str(new_history.id), new_history.name ) )
if self.app.memory_usage:
m1 = trans.app.memory_usage.memory( m0, pretty=True )
log.info( "End of root/history_switch, memory used increased by %s" % m1 )
return trans.show_message( "History switched to: %s" % new_history.name,
refresh_frames=['history'])
else:
return trans.show_error_message( "History not found" )
@web.expose
def history_new( self, trans ):
@@ -718,53 +464,6 @@ class RootController( BaseController ):
log.info( "End of root/history_new, memory used increased by %s" % m1 )
return trans.show_message( "New history created", refresh_frames = ['history'] )
@web.expose
@web.require_login( "renames histories" )
def history_rename( self, trans, id=None, name=None, **kwd ):
if trans.app.memory_usage:
# Keep track of memory usage
m0 = self.app.memory_usage.memory()
user = trans.get_user()
if not isinstance( id, list ):
if id != None:
id = [ id ]
if not isinstance( name, list ):
if name != None:
name = [ name ]
histories = []
cur_names = []
if not id:
if not trans.get_history().user:
return trans.show_error_message( "You must save your history before renaming it." )
id = [trans.get_history().id]
for history_id in id:
history = trans.app.model.History.get( history_id )
if history and history.user_id == user.id:
histories.append(history)
cur_names.append(history.name)
if not name or len(histories)!=len(name):
return trans.fill_template( "/history/rename.mako",histories=histories )
change_msg = ""
for i in range(len(histories)):
if histories[i].user_id == user.id:
if name[i] == histories[i].name:
change_msg = change_msg + "<p>History: "+cur_names[i]+" is already named: "+name[i]+"</p>"
elif name[i] not in [None,'',' ']:
name[i] = escape(name[i])
histories[i].name = name[i]
histories[i].flush()
change_msg = change_msg + "<p>History: "+cur_names[i]+" renamed to: "+name[i]+"</p>"
trans.log_event( "History renamed: id: %s, renamed to: '%s'" % (str(histories[i].id), name[i] ) )
else:
change_msg = change_msg + "<p>You must specify a valid name for History: "+cur_names[i]+"</p>"
else:
change_msg = change_msg + "<p>History: "+cur_names[i]+" does not appear to belong to you.</p>"
if self.app.memory_usage:
m1 = trans.app.memory_usage.memory( m0, pretty=True )
log.info( "End of root/history_rename, memory used increased by %s" % m1 )
return trans.show_message( "<p>%s" % change_msg, refresh_frames=['history'] )
@web.expose
def history_add_to( self, trans, history_id=None, file_data=None, name="Data Added to History",info=None,ext="txt",dbkey="?",copy_access_from=None,**kwd ):
"""Adds a POSTed file to a History"""
+66 -1
View File
@@ -142,6 +142,7 @@ div.form-row label
select, input, textarea
{
font: inherit;
font-size: 115%;
}
@@ -444,6 +445,30 @@ ul.manage-table-actions li {
margin-left: 0.5em;
}
## State colors
.state-color-queued {
border-color: $history_queued_border;
background: $history_queued_bg;
}
.state-color-ok {
border-color: $history_ok_border;
background: $history_ok_bg;
}
.state-color-error {
border-color: $history_error_border;
background: $history_error_bg;
}
.state-color-running {
border-color: $history_running_border;
background: $history_running_bg;
}
## Button styles
.action-button {
background: #eeeeee;
color: #333;
@@ -459,6 +484,9 @@ ul.manage-table-actions li {
-moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em;
border-radius: 0.5em;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.action-button > * {
@@ -467,13 +495,15 @@ ul.manage-table-actions li {
.action-button:hover {
color: black;
background: #aaaaaa;
background: #dddddd;
}
.action-button:active {
color: white;
background: #aaaaaa;
}
## Popup menu styles
div.popupmenu {
display: none;
background: #eeeeee;
@@ -488,6 +518,9 @@ div.popupmenu {
-moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em;
border-radius: 0.5em;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
div.popupmenu-item {
@@ -515,3 +548,35 @@ div.popupmenu-item:hover {
div.permissionContainer {
padding-left: 20px;
}
## Data grid style
.grid {
padding-top: 1em;
border-collapse: collapse;
width: 100%;
}
.grid tbody td {
border-top: solid #DDDDDD 1px;
border-bottom: solid #DDDDDD 1px;
padding: 0.5em 1em;
}
.grid thead th {
background: $table_header_bg;
background-image: url(form_title_bg.png);
background-repeat: repeat-x;
background-position: top;
border-top: solid $table_border 1px;
border-bottom: solid $table_border 1px;
padding: 0.5em 1em;
text-align: left;
}
.grid tfoot td {
background-color: #F8F8F8;
border-top: solid #DDDDDD 1px;
border-bottom: solid #DDDDDD 1px;
padding: 0.5em 1em;
}
.grid .current {
background-color: #EEEEFF;
}
+63 -2
View File
@@ -142,6 +142,7 @@ div.form-row label
select, input, textarea
{
font: inherit;
font-size: 115%;
}
@@ -152,7 +153,7 @@ select, textarea, input[type="text"], input[type="file"]
/* Messages */
.errormessage, .warningmessage, .donemessage, .infomessage, .welcomeBlue, .welcomeRed, .screencastBox, .yellowbox, .redbox, .bluebox, .greenbox
.errormessage, .warningmessage, .donemessage, .infomessage, .welcomeBlue, .welcomeRed , .screencastBox, .yellowbox, .redbox, .bluebox, .greenbox
{
padding: 10px;
padding-left: 52px;
@@ -443,6 +444,28 @@ ul.manage-table-actions li {
margin-left: 0.5em;
}
.state-color-queued {
border-color: #888888;
background: #EEEEEE;
}
.state-color-ok {
border-color: #66AA66;
background: #CCFFCC;
}
.state-color-error {
border-color: #AA6666;
background: #FFCCCC;
}
.state-color-running {
border-color: #AAAA66;
background: #FFFFCC;
}
.action-button {
background: #eeeeee;
color: #333;
@@ -458,6 +481,9 @@ ul.manage-table-actions li {
-moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em;
border-radius: 0.5em;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.action-button > * {
@@ -466,13 +492,14 @@ ul.manage-table-actions li {
.action-button:hover {
color: black;
background: #aaaaaa;
background: #dddddd;
}
.action-button:active {
color: white;
background: #aaaaaa;
}
div.popupmenu {
display: none;
background: #eeeeee;
@@ -487,6 +514,9 @@ div.popupmenu {
-moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em;
border-radius: 0.5em;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
div.popupmenu-item {
@@ -514,3 +544,34 @@ div.popupmenu-item:hover {
div.permissionContainer {
padding-left: 20px;
}
.grid {
padding-top: 1em;
border-collapse: collapse;
width: 100%;
}
.grid tbody td {
border-top: solid #DDDDDD 1px;
border-bottom: solid #DDDDDD 1px;
padding: 0.5em 1em;
}
.grid thead th {
background: #ebd9b2;
background-image: url(form_title_bg.png);
background-repeat: repeat-x;
background-position: top;
border-top: solid #d8b365 1px;
border-bottom: solid #d8b365 1px;
padding: 0.5em 1em;
text-align: left;
}
.grid tfoot td {
background-color: #F8F8F8;
border-top: solid #DDDDDD 1px;
border-bottom: solid #DDDDDD 1px;
padding: 0.5em 1em;
}
.grid .current {
background-color: #EEEEFF;
}
+3 -62
View File
@@ -131,31 +131,6 @@ body
right: 0px;
z-index: 200;
}
td.panel-header
{
background: #f5f5f5;
background-image: url(panel_header_bg.png);
background-position: top center;
background-repeat: repeat-x;
padding: 0;
border-bottom: solid #999 1px;
}
div.panel-header
{
margin: 0;
padding: 4px;
padding-right: 10px;
padding-left: 10px;
color: #333;
font-weight: bold;
}
table.column-layout
{
position: absolute;
width: 100%;
height: 100%;
}
.unified-panel-header {
height: 2em;
@@ -171,6 +146,9 @@ table.column-layout
padding-left: 10px;
color: #333;
font-weight: bold;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.unified-panel-header-inner {
@@ -214,43 +192,6 @@ div.unified-panel-body {
background: #aaaaaa;
}
div.popupmenu {
display: none;
background: #eee;
position: fixed;
z-index: 20000;
background: #cccccc;
background-image: url(panel_header_bg.png);
background-position: top center;
background-repeat: repeat-x;
border: solid #999 1px;
padding-top: 3px;
padding-bottom: 3px;
}
div.popupmenu-top {
padding-bottom: 3px;
}
div.popupmenu-top-inner {
height: 10px;
width: 20px;
margin-top: -13px;
margin-left: auto;
margin-right: 20px;
background: url(popupmenu_callout_top.png);
}
div.popupmenu-item {
padding: 3px;
padding-left: 10px;
padding-right: 10px;
cursor: pointer;
}
div.popupmenu-item:hover {
background: #AAAAEE;
}
#overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
+4 -4
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env python
from galaxy import eggs
import pkg_resources
pkg_resources.require("Cheetah")
#from galaxy import eggs
#import pkg_resources
#pkg_resources.require("Cheetah")
import sys, string, os.path
from galaxy import eggs
#from galaxy import eggs
import pkg_resources
pkg_resources.require( "Cheetah" )
+3 -62
View File
@@ -131,31 +131,6 @@ body
right: 0px;
z-index: 200;
}
td.panel-header
{
background: ${panel_header_bg_top};
background-image: url(panel_header_bg.png);
background-position: top center;
background-repeat: repeat-x;
padding: 0;
border-bottom: solid ${layout_border} 1px;
}
div.panel-header
{
margin: 0;
padding: 4px;
padding-right: 10px;
padding-left: 10px;
color: #333;
font-weight: bold;
}
table.column-layout
{
position: absolute;
width: 100%;
height: 100%;
}
.unified-panel-header {
height: 2em;
@@ -171,6 +146,9 @@ table.column-layout
padding-left: 10px;
color: #333;
font-weight: bold;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.unified-panel-header-inner {
@@ -214,43 +192,6 @@ div.unified-panel-body {
background: #aaaaaa;
}
div.popupmenu {
display: none;
background: #eee;
position: fixed;
z-index: 20000;
background: ${panel_header_bg_bottom};
background-image: url(panel_header_bg.png);
background-position: top center;
background-repeat: repeat-x;
border: solid ${layout_border} 1px;
padding-top: 3px;
padding-bottom: 3px;
}
div.popupmenu-top {
padding-bottom: 3px;
}
div.popupmenu-top-inner {
height: 10px;
width: 20px;
margin-top: -13px;
margin-left: auto;
margin-right: 20px;
background: url(popupmenu_callout_top.png);
}
div.popupmenu-item {
padding: 3px;
padding-left: 10px;
padding-right: 10px;
cursor: pointer;
}
div.popupmenu-item:hover {
background: ${layout_hover};
}
#overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
+16
View File
@@ -1,3 +1,19 @@
$.fn.makeAbsolute = function(rebase) {
return this.each(function() {
var el = $(this);
var pos = el.position();
el.css({
position: "absolute",
marginLeft: 0, marginTop: 0,
top: pos.top, left: pos.left,
right: $(window).width() - ( pos.left + el.width() )
});
if (rebase) {
el.remove().appendTo("body");
}
});
}
jQuery(document).ready( function() {
// Links with confirmation
jQuery( "a[@confirm]" ).click( function() {
+194 -58
View File
@@ -1,65 +1,201 @@
<%inherit file="/base.mako"/>
<%def name="title()">Your saved histories</%def>
%if error_msg:
<p>
<div class="errormessage">${error_msg}</div>
<div style="clear: both"></div>
</p>
%endif
%if ok_msg:
<p>
<div class="donemessage">${ok_msg}</div>
<div style="clear: both"></div>
</p>
%if message:
<p>
<div class="${message_type}message transient-message">${message}</div>
<div style="clear: both"></div>
</p>
%endif
%if user.histories:
<h1 style="margin-bottom:0px;">Stored Histories</h1>
%if show_deleted:
<div><a href="${h.url_for( action='history_available', id=','.join( ids ), show_deleted=False )}">hide deleted</a></div>
%else:
<div><a href="${h.url_for( action='history_available', id=','.join( ids ), show_deleted=True )}">show deleted</a></div>
%endif
<form name="history_actions" action="${h.url_for( action='history_available')}" method="post" >
<table class="colored" border="0" cellspacing="0" cellpadding="0" width="100%">
<tr class="header" align="center"><td>Name</td><td>Size</td><td>Last modified</td><td>Actions</td></tr>
%for history in user.histories:
%if ( show_deleted and not history.purged ) or not( history.deleted ):
<tr>
<td>
<input type=checkbox name="id" value="${history.id}"
%if str(history.id) in ids:
checked
%endif
>${history.name}
%if history == trans.get_history():
(current history)
%endif
</td>
<td>${len(history.active_datasets)}</td>
<td>${str(history.update_time)[:19]}</td>
<td>
%if not history.deleted:
<a href="${h.url_for( action='history_rename', id=history.id )}">rename</a><br />
<a href="${h.url_for( action='history_switch', id=history.id )}">switch to</a><br />
<a href="${h.url_for( action='history_delete', id=history.id )}" confirm="Are you sure you want to delete this history?">delete</a><br />
%else:
<a href="${h.url_for( action='history_undelete', id=history.id )}">undelete</a><br />
%endif
</td>
</tr>
<%def name="javascripts()">
${parent.javascripts()}
<script type="text/javascript">
%if refresh_history:
if ( parent.frames && parent.frames.galaxy_history ) {
parent.frames.galaxy_history.location.href="${h.url_for( controller='root', action='history')}";
}
%endif
%endfor
<tr><th colspan="100%">Action</th></tr>
<tr><td colspan="100%" align="center"><input type="radio" name="do_operation" value="share" checked>Share <input type="radio" name="do_operation" value="rename">Rename <input type="radio" name="do_operation" value="delete">Delete
%if show_deleted:
<input type="radio" name="do_operation" value="undelete">Undelete
%endif
</td></tr>
<tr><td colspan="100%" align="center"><input type="submit" name="submit" value="Perform Action"></td></tr>
</table>
## TODO: generalize and move into galaxy.base.js
$(function() {
$(".grid").each( function() {
var grid = this;
var checkboxes = $(this).find("input.grid-row-select-checkbox");
var update = $(this).find( "span.grid-selected-count" );
$(checkboxes).each( function() {
$(this).change( function() {
var n = $(checkboxes).filter("[checked]").size();
update.text( n );
});
})
});
});
</script>
</%def>
<%def name="stylesheets()">
<link href="${h.url_for('/static/style/base.css')}" rel="stylesheet" type="text/css" />
<style>
.count-box {
width: 1.1em;
float: left;
padding: 5px;
margin-right: 5px;
border-width: 1px;
border-style: solid;
text-align: center;
}
.count-box-dummy {
width: 1.1em;
float: left;
padding: 5px;
margin-right: 5px;
border-width: 1px;
border-style: solid;
border-color: transparent;
background: transparent;
text-align: center;
}
</style>
</%def>
<h2>Stored Histories</h2>
<ul class="manage-table-actions">
<li>
%if show_deleted:
<a href="${h.url_for( show_deleted=False )}" class="action-button">Hide deleted</a></div>
%else:
<a href="${h.url_for( show_deleted=True )}" class="action-button">Show deleted</a></div>
%endif
</li>
</ul>
<form name="history_actions" action="${h.url_for()}" method="post" >
%if show_deleted:
<input type="hidden" name="show_deleted" value="true">
%endif
<table class="grid">
<thead>
<tr>
<th width="1.5em"></th>
<th>Name (click to activate)</th>
<th>Datasets (by state)</th>
<th>Status</th>
<th>Last update</th>
<th></th>
</tr>
</thead>
<tbody>
%for i, history in enumerate( user.histories ):
%if ( show_deleted and not history.purged ) or not( history.deleted ):
<tr class="${["","current"][ history == trans.get_history() ]}">
<td><input type="checkbox" name="id" value="${history.id}" class="grid-row-select-checkbox"></td>
<td>
%if not history.deleted:
<a href="${h.url_for( operation="switch", id=history.id, show_deleted=show_deleted )}">${history.name}</a>
<a id="h-${i}-popup" class="popup-arrow" style="display: none;">&#9660;</a>
%else:
${history.name} <a id="h-${i}-popup" class="popup-arrow" style="display: none;">&#9660;</a>
%endif
</td>
<td>
<%
total_ok = sum( 1 for d in history.active_datasets if d.state == 'ok' )
total_running = sum( 1 for d in history.active_datasets if d.state == 'running' )
total_queued = sum( 1 for d in history.active_datasets if d.state == 'queued' )
total_error = sum( 1 for d in history.active_datasets if d.state == 'error' )
%>
%if total_ok:
<div class="count-box state-color-ok">${total_ok}</div>
%else:
<div class="count-box-dummy"></div>
%endif
%if total_error:
<div class="count-box state-color-error">${total_error}</div
%else:
<div class="count-box-dummy"></div>
%endif
%if total_running:
<div class="count-box state-color-running">${total_running}</div>
%else:
<div class="count-box-dummy"></div>
%endif
%if total_queued:
<div class="count-box state-color-queued">${total_queued}</div>
%else:
<div class="count-box-dummy"></div>
%endif
</td>
<td>
%if history == trans.get_history():
active
%endif
%if history.deleted:
deleted
%endif
</td>
<td>${h.date.distance_of_time_in_words( history.update_time, h.date.datetime.utcnow() )}</td>
<td>
<div popupmenu="h-${i}-popup">
%if not history.deleted:
<a class="action-button" href="${h.url_for( operation='rename', id=history.id, show_deleted=show_deleted )}">rename</a> <br />
<a class="action-button" href="${h.url_for( operation='switch', id=history.id, show_deleted=show_deleted )}">switch to</a> <br />
<a class="action-button" href="${h.url_for( operation='delete', id=history.id , show_deleted=show_deleted)}" confirm="Are you sure you want to delete this history?">delete</a> <br />
%else:
<a class="action-button" href="${h.url_for( operation='undelete', id=history.id, show_deleted=show_deleted )}">undelete</a><br />
%endif
</div>
</td>
</tr>
%endif
%endfor
</tbody>
<tfoot>
<tr>
<td></td>
<td colspan="6">
For <span class="grid-selected-count"></span> selected histories:
<input type="submit" name="operation" value="Share" class="action-button">
<input type="submit" name="operation" value="Rename" class="action-button">
<input type="submit" name="operation" value="Delete" class="action-button">
%if show_deleted:
<input type="submit" name="operation" value="Undelete" class="action-button">
%endif
</td>
</tr>
</tfoot>
</table>
</form>
%else:
You have no stored histories
%endif
+4 -4
View File
@@ -11,16 +11,16 @@
<ul>
%if user:
<li><a href="${h.url_for( controller='root', action='history_rename', id=history.id )}" target="galaxy_main">Rename</a> current history (stored as "${history.name}")</li>
<li><a href="${h.url_for( controller='root', action='history_available')}" target="galaxy_main">List</a> previously stored histories</li>
<li><a href="${h.url_for( controller='history', action='rename', id=history.id )}" target="galaxy_main">Rename</a> current history (stored as "${history.name}")</li>
<li><a href="${h.url_for( controller='history', action='list')}" target="galaxy_main">List</a> previously stored histories</li>
%if len( history.active_datasets ) > 0:
<li><a href="${h.url_for( controller='root', action='history_new' )}">Create</a> a new empty history</li>
%endif
<li><a href="${h.url_for( controller='workflow', action='build_from_current_history' )}">Construct workflow</a> from the current history</li>
<li><a href="${h.url_for( controller='root', action='history_share' )}" target="galaxy_main">Share</a> current history</div>
<li><a href="${h.url_for( controller='history', action='share' )}" target="galaxy_main">Share</a> current history</div>
<li><a href="${h.url_for( action='history_set_default_permissions' )}">Change default permissions</a> for the current history</li>
%endif
<li><a href="${h.url_for( controller='root', action='history', show_deleted=True)}" target="galaxy_history">Show deleted</a> datasets in history</li>
<li><a href="${h.url_for( controller='root', action='history_delete', id=history.id )}" confirm="Are you sure you want to delete the current history?">Delete</a> current history</div>
<li><a href="${h.url_for( controller='history', action='list', operation="delete", id=history.id )}" confirm="Are you sure you want to delete the current history?">Delete</a> current history</div>
</ul>
+1 -1
View File
@@ -4,7 +4,7 @@
<div class="toolForm">
<div class="toolFormTitle">Rename History</div>
<div class="toolFormBody">
<form action="${h.url_for( action='history_rename' )}" method="post" >
<form action="${h.url_for( controller='history', action='rename' )}" method="post" >
<table>
<tr><th>Current Name</th><th>New Name</th></tr>
%for history in histories:
+2 -2
View File
@@ -5,7 +5,7 @@
<div class="toolForm">
<div class="toolFormTitle">Share Histories</div>
<table>
<form action="${h.url_for( action='history_share' )}" method="post" >
<form action="${h.url_for( controller="history", action='share' )}" method="post" >
<tr><th>History Name:</td><th>Number of Datasets:</th><th>Share Link</th></tr>
%for history in histories:
<tr>
@@ -39,7 +39,7 @@
vertical-align: top;
}
</style>
<form action="${h.url_for( controller='root', action='history_share' )}" method="post">
<form action="${h.url_for( controller='history', action='share' )}" method="post">
%for history in histories:
<input type="hidden" name="id" value="${history.id}">
%endfor
+15 -12
View File
@@ -13,7 +13,7 @@ from elementtree import ElementTree
buffer = StringIO.StringIO()
#Force twill to log to a buffer -- FIXME: Should this go to stdout and be captured by nose?
twill.set_output(buffer)
## twill.set_output(buffer)
tc.config('use_tidy', 0)
# Dial ClientCookie logging down (very noisy)
@@ -107,10 +107,10 @@ class TwillTestCase( unittest.TestCase ):
history_list = self.get_histories()
self.assertTrue( history_list )
if id is None:
history = history_list[-1]
history = history_list[0]
id = history.get( 'id' )
id = str( id )
self.visit_page( "history_delete?id=%s" %(id) )
self.visit_page( "history/list?operation=delete&id=%s" %(id) )
def get_histories( self ):
"""Returns all histories"""
@@ -135,7 +135,7 @@ class TwillTestCase( unittest.TestCase ):
def histories_as_xml_tree( self ):
"""Returns a parsed xml object of all histories"""
self.home()
self.visit_page( 'history_available?as_xml=True' )
self.visit_page( 'history/list_as_xml' )
xml = self.last_page()
tree = ElementTree.fromstring(xml)
return tree
@@ -164,7 +164,7 @@ class TwillTestCase( unittest.TestCase ):
old_name = elem.get( 'name' )
self.assertTrue( old_name )
id = str( id )
self.visit_page( "history_rename?id=%s&name=%s" %(id, name) )
self.visit_page( "history/rename?id=%s&name=%s" %(id, name) )
return id, old_name, name
def set_history( self ):
@@ -188,25 +188,25 @@ class TwillTestCase( unittest.TestCase ):
id = str( id )
name = elem.get( 'name' )
self.assertTrue( name )
self.visit_url( "%s/history_share?id=%s&email=%s&history_share_btn=Submit" % ( self.url, id, email ) )
self.visit_url( "%s/history/share?id=%s&email=%s&history_share_btn=Submit" % ( self.url, id, email ) )
return id, name, email
def share_history_containing_private_datasets( self, history_id, email='test@bx.psu.edu' ):
"""Attempt to share a history containing private datasets with a different user"""
self.visit_url( "%s/history_share?id=%s&email=%s&history_share_btn=Submit" % ( self.url, history_id, email ) )
self.visit_url( "%s/history/share?id=%s&email=%s&history_share_btn=Submit" % ( self.url, history_id, email ) )
self.last_page()
self.check_page_for_string( "The history or histories you've chosen to share contain datasets" )
self.check_page_for_string( "How would you like to proceed?" )
self.home()
def make_datasets_public( self, history_id, email='test@bx.psu.edu' ):
"""Make private datasets public in order to share a history with a different user"""
self.visit_url( "%s/history_share?id=%s&email=%s&action=public&submit=Ok" % ( self.url, history_id, email ) )
self.visit_url( "%s/history/share?id=%s&email=%s&action=public&submit=Ok" % ( self.url, history_id, email ) )
self.last_page()
check_str = "History (Unnamed history) has been shared with: %s" % email
self.check_page_for_string( check_str )
self.home()
def privately_share_dataset( self, history_id, email='test@bx.psu.edu' ):
"""Make private datasets public in order to share a history with a different user"""
self.visit_url( "%s/history_share?id=%s&email=%s&action=private&submit=Ok" % ( self.url, history_id, email ) )
self.visit_url( "%s/history/share?id=%s&email=%s&action=private&submit=Ok" % ( self.url, history_id, email ) )
self.last_page()
check_str = "History (Unnamed history) has been shared with: %s" % email
self.check_page_for_string( check_str )
@@ -223,10 +223,10 @@ class TwillTestCase( unittest.TestCase ):
hid = str(hid)
elems = [ elem for elem in data_list if elem.get('hid') == hid ]
self.assertEqual(len(elems), 1)
self.visit_page( "history_switch?id=%s" % elems[0].get('id') )
self.visit_page( "history/list?operation=switch&id=%s" % elems[0].get('id') )
def view_stored_histories( self ):
self.visit_page( "history_available" )
self.visit_page( "history/list" )
# Functions associated with datasets (history items) and meta data
def get_job_stderr( self, id ):
@@ -554,7 +554,10 @@ class TwillTestCase( unittest.TestCase ):
tc.submit( button )
def visit_page( self, page ):
tc.go("./%s" % page)
# tc.go("./%s" % page)
if not page.startswith( "/" ):
page = "/" + page
tc.go( self.url + page )
tc.code( 200 )
def visit_url( self, url ):
+5 -5
View File
@@ -16,7 +16,7 @@ class TestHistory( TwillTestCase ):
if len(self.get_history()) > 0:
raise AssertionError("test_new_history_then_delete failed")
self.delete_history()
self.check_page_for_string( 'History deleted:' )
self.check_page_for_string( 'Deleted 1 histories' )
def test_10_history_options_when_logged_in( self ):
"""Testing history options when logged in"""
self.history_options()
@@ -32,10 +32,10 @@ class TestHistory( TwillTestCase ):
"""Testing viewing previously stored histories"""
self.view_stored_histories()
self.check_page_for_string( 'Stored Histories' )
self.check_page_for_string( '<input type=checkbox name="id" value=' )
self.check_page_for_string( 'history_rename?id' )
self.check_page_for_string( 'history_switch?id' )
self.check_page_for_string( 'history_delete?id' )
self.check_page_for_string( '<input type="checkbox" name="id" value=' )
self.check_page_for_string( 'operation=rename&id' )
self.check_page_for_string( 'operation=switch&id' )
self.check_page_for_string( 'operation=delete&id' )
def test_20_delete_history_item( self ):
"""Testing deleting history item"""
self.upload_file('1.bed', dbkey='hg15')