# HG changeset patch -- Bitbucket.org # Project galaxy-dist # URL http://bitbucket.org/galaxy/galaxy-dist/overview # User Greg Von Kuster <greg@bx.psu.edu> # Date 1289275808 18000 # Node ID a77b2888759e94861252ca65437e11ec298b3058 # Parent 2875445df9906828bc804e9b0923160f902b3fac Streamline the sample tracking UI in selecting and transferring sample datasets. --- a/templates/requests/common/sample_datasets.mako +++ b/templates/requests/common/sample_datasets.mako @@ -1,5 +1,5 @@ <%def name="render_sample_datasets( sample )"> - ${len( sample.transferred_dataset_files )} / ${len( sample.datasets )} + ${len( sample.datasets )} / ${len( sample.transferred_dataset_files )} </%def> ${render_sample_datasets( sample )} --- /dev/null +++ b/templates/admin/requests/view_sample_dataset.mako @@ -0,0 +1,72 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +%if message: + ${render_msg( message, status )} +%endif + +<br/><br/> + +<% + sample = sample_dataset.sample + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + can_manage_datasets = is_admin and sample.untransferred_dataset_files +%> + +<ul class="manage-table-actions"> + %if can_manage_datasets: + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">Manage sample datasets</a></li> + %endif + <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a></li> +</ul> + +<div class="toolForm"> + <div class="toolFormTitle">"${sample.name}" Dataset</div> + <div class="toolFormBody"> + <div class="form-row"> + <label>Name:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + ${sample_dataset.name} + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>File path:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + ${sample_dataset.file_path} + </div> + <div class="toolParamHelp" style="clear: both;"> + This file is contained in a sub-directory of the data directory configured for the sequencer. + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Size:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + ${sample_dataset.size} + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Date this dataset was selected for this sample:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + ${sample_dataset.create_time} + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Transfer status:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + ${sample_dataset.status} + </div> + <div style="clear: both"></div> + </div> + %if sample_dataset.status == trans.app.model.SampleDataset.transfer_status.ERROR: + <div class="form-row"> + <label>Transfer error:</label> + ${sample_dataset.error_msg} + </div> + <div style="clear: both"></div> + %endif + </div> +</div> --- a/templates/admin/requests/get_data.mako +++ /dev/null @@ -1,114 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> -${h.js( "ui.core", "jquery.cookie" )} -<link href='/static/june_2007_style/blue/dynatree_skin/ui.dynatree.css' rel='stylesheet' type='text/css'> -${h.js( "jquery.dynatree" )} - -<script type="text/javascript"> - $(function(){ - $("#tree").ajaxComplete(function(event, XMLHttpRequest, ajaxOptions) { - _log("debug", "ajaxComplete: %o", this); // dom element listening - }); - // --- Initialize sample trees - $("#tree").dynatree({ - title: "${request.type.datatx_info['data_dir']}", - rootVisible: true, - minExpandLevel: 0, // 1: root node is not collapsible - persist: false, - checkbox: true, - selectMode: 3, - onPostInit: function(isReloading, isError) { -// alert("reloading: "+isReloading+", error:"+isError); - logMsg("onPostInit(%o, %o) - %o", isReloading, isError, this); - // Re-fire onActivate, so the text is updated - this.reactivate(); - }, - fx: { height: "toggle", duration: 200 }, - // initAjax is hard to fake, so we pass the children as object array: - initAjax: {url: "${h.url_for( controller='requests_admin', action='open_folder' )}", - dataType: "json", - data: { id: "${request.id}", key: "${request.type.datatx_info['data_dir']}" }, - }, - onLazyRead: function(dtnode){ - dtnode.appendAjax({ - url: "${h.url_for( controller='requests_admin', action='open_folder' )}", - dataType: "json", - data: { id: "${request.id}", key: dtnode.data.key }, - }); - }, - onSelect: function(select, dtnode) { - // Display list of selected nodes - var selNodes = dtnode.tree.getSelectedNodes(); - // convert to title/key array - var selKeys = $.map(selNodes, function(node){ - return node.data.key; - }); - document.get_data.selected_files.value = selKeys.join(",") - }, - onActivate: function(dtnode) { - var cell = $("#file_details"); - var selected_value = dtnode.data.key - if(selected_value.charAt(selected_value.length-1) != '/') { - // Make ajax call - $.ajax( { - type: "POST", - url: "${h.url_for( controller='requests_admin', action='get_file_details' )}", - dataType: "json", - data: { id: "${request.id}", folder_path: dtnode.data.key }, - success : function ( data ) { - cell.html( '<label>'+data+'</label>' ) - } - }); - } else { - cell.html( '' ) - } - }, - }); - }); - -</script> - -<br/> -<br/> -<ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='view_request_type', id=trans.security.encode_id( request.type.id ) )}">Sequencer configuration "${request.type.name}"</a> - </li> - <li> - <a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Browse this request</a> - </li> -</ul> - -%if message: - ${render_msg( message, status )} -%endif - -<div class="toolForm"> - <div class="toolFormTitle">Select files for transfer</div> - <form name="get_data" id="get_data" action="${h.url_for( controller='requests_admin', action='get_data', cntrller=cntrller, request_id=trans.security.encode_id( request.id ))}" method="post" > - <div class="form-row"> - <label>Sample:</label> - ${sample_id_select_field.get_html()} - <div class="toolParamHelp" style="clear: both;"> - Select the sample with which you want to associate the datasets - </div> - </div> - <div class="form-row" > - <label>Select dataset files in the sequencer:</label> - <div id="tree" > - Loading... - </div> - <input id="selected_files" name="selected_files" type="hidden" size=40"/> - <div class="toolParamHelp" style="clear: both;"> - To select a folder, select all the individual files in that folder. - </div> - </div> - <div class="form-row"> - <div id="file_details" class="toolParamHelp" style="clear: both;background-color:#FAFAFA;"></div> - </div> - <div class="form-row"> - <input type="submit" name="select_show_datasets_button" value="Select & show datasets"/> - <input type="submit" name="select_more_button" value="Select more"/> - </div> - </form> -</div> --- a/lib/galaxy/web/controllers/requests_common.py +++ b/lib/galaxy/web/controllers/requests_common.py @@ -58,7 +58,7 @@ class RequestsGrid( grids.Grid ): columns = [ NameColumn( "Name", key="name", - link=( lambda item: iff( item.deleted, None, dict( operation="view_request", id=item.id ) ) ), + link=( lambda item: dict( operation="view_request", id=item.id ) ), attach_popup=True, filterable="advanced" ), DescriptionColumn( "Description", @@ -318,11 +318,10 @@ class RequestsCommon( BaseController, Us trans.sa_session.flush() trans.sa_session.refresh( request ) # Create an event with state 'New' for this new request + comment = "Request created by %s" % trans.user.email if request.user != trans.user: - sample_event_comment = "Request created by user %s for user %s." % ( trans.user.email, request.user.email ) - else: - sample_event_comment = "Request created." - event = trans.model.RequestEvent( request, request.states.NEW, sample_event_comment ) + comment += " on behalf of %s." % request.user.email + event = trans.model.RequestEvent( request, request.states.NEW, comment ) trans.sa_session.add( event ) trans.sa_session.flush() else: @@ -361,11 +360,10 @@ class RequestsCommon( BaseController, Us status='error', message=message ) ) # Change the request state to 'Submitted' - if request.user.email is not trans.user: - sample_event_comment = "Request submitted by %s on behalf of %s." % ( trans.user.email, request.user.email ) - else: - sample_event_comment = "" - event = trans.model.RequestEvent( request, request.states.SUBMITTED, sample_event_comment ) + comment = "Request submitted by %s" % trans.user.email + if request.user != trans.user: + comment += " on behalf of %s." % request.user.email + event = trans.model.RequestEvent( request, request.states.SUBMITTED, comment ) trans.sa_session.add( event ) # Change the state of each of the samples of this request # request.type.states is the list of SampleState objects configured @@ -511,23 +509,39 @@ class RequestsCommon( BaseController, Us id_list = util.listify( kwd.get( 'id', '' ) ) message = util.restore_text( params.get( 'message', '' ) ) status = util.restore_text( params.get( 'status', 'done' ) ) + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() num_deleted = 0 + not_deleted = [] for id in id_list: ok_for_now = True try: + # This block will handle bots that do not send valid request ids. request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) ) except: ok_for_now = False if ok_for_now: - request.deleted = True - trans.sa_session.add( request ) - # Delete all the samples belonging to this request - for s in request.samples: - s.deleted = True - trans.sa_session.add( s ) - trans.sa_session.flush() - num_deleted += 1 + # We will only allow the request to be deleted by a non-admin user if not request.submitted + if is_admin or not request.is_submitted: + request.deleted = True + trans.sa_session.add( request ) + # Delete all the samples belonging to this request + for s in request.samples: + s.deleted = True + trans.sa_session.add( s ) + comment = "Request marked deleted by %s." % trans.user.email + # There is no DELETED state for a request, so keep the current request state + event = trans.model.RequestEvent( request, request.state, comment ) + trans.sa_session.add( event ) + trans.sa_session.flush() + num_deleted += 1 + else: + not_deleted.append( request ) message += '%i requests have been deleted.' % num_deleted + if not_deleted: + message += ' Contact the administrator to delete the following submitted requests: ' + for request in not_deleted: + message += '%s, ' % request.name + message = message.rstrip( ', ' ) return trans.response.send_redirect( web.url_for( controller=cntrller, action='browse_requests', status=status, @@ -543,6 +557,7 @@ class RequestsCommon( BaseController, Us for id in id_list: ok_for_now = True try: + # This block will handle bots that do not send valid request ids. request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) ) except: ok_for_now = False @@ -553,6 +568,9 @@ class RequestsCommon( BaseController, Us for s in request.samples: s.deleted = False trans.sa_session.add( s ) + comment = "Request marked undeleted by %s." % trans.user.email + event = trans.model.RequestEvent( request, request.state, comment ) + trans.sa_session.add( event ) trans.sa_session.flush() num_undeleted += 1 message += '%i requests have been undeleted.' % num_undeleted @@ -651,7 +669,7 @@ class RequestsCommon( BaseController, Us # If the current request state is complete and one of its samples moved from # the final sample state, then move the request state to In-progress if request.is_complete: - message = "At least 1 sample state moved from the final sample state, so now the request is in the '%s' state" % request.states.SUBMITTED + message = "At least 1 sample state moved from the final sample state, so now the request's state is (%s)" % request.states.SUBMITTED event = trans.model.RequestEvent( request, request.states.SUBMITTED, message ) trans.sa_session.add( event ) trans.sa_session.flush() @@ -668,13 +686,13 @@ class RequestsCommon( BaseController, Us request_type_state = request.type.final_sample_state if common_state.id == request_type_state.id: # since all the samples are in the final state, change the request state to 'Complete' - comments = "All samples of this request are in the last sample state (%s). " % request_type_state.name + comment = "All samples of this request are in the final sample state (%s). " % request_type_state.name state = request.states.COMPLETE final_state = True else: - comments = "All samples are in %s state. " % common_state.name + comment = "All samples of this request are in the (%s) sample state. " % common_state.name state = request.states.SUBMITTED - event = trans.model.RequestEvent( request, state, comments ) + event = trans.model.RequestEvent( request, state, comment ) trans.sa_session.add( event ) trans.sa_session.flush() # See if an email notification is configured to be sent when the samples @@ -879,7 +897,10 @@ class RequestsCommon( BaseController, Us message=message ) ) @web.expose @web.require_login( "view data transfer page" ) - def view_dataset_transfer( self, trans, cntrller, **kwd ): + def view_selected_datasets( self, trans, cntrller, **kwd ): + # The link on the number of selected datasets will only appear if there is at least 1 selected dataset. + # If there are 0 selected datasets, there is no link, so this method will only be reached from the requests + # controller if there are selected datasets. params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) @@ -890,9 +911,9 @@ class RequestsCommon( BaseController, Us except: return invalid_id_redirect( trans, cntrller, sample_id ) # See if a library and folder have been set for this sample. - if not sample.library or not sample.folder: + if is_admin and not sample.library or not sample.folder: status = 'error' - message = "Set a data library and folder for sequencing request (%s) to transfer datasets." % sample.name + message = "Select a target data library and folder for the sample before selecting the datasets." return trans.response.send_redirect( web.url_for( controller='requests_common', action='edit_samples', cntrller=cntrller, @@ -900,11 +921,6 @@ class RequestsCommon( BaseController, Us editing_samples=True, status=status, message=message ) ) - if is_admin: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_datasets', - sample_id=sample_id ) ) - folder_path = util.restore_text( params.get( 'folder_path', '' ) ) if not folder_path: if len( sample.datasets ): @@ -917,11 +933,10 @@ class RequestsCommon( BaseController, Us or not sample.request.type.datatx_info['username'] \ or not sample.request.type.datatx_info['password']: status = 'error' - message = 'The sequencer login information is incomplete. Click on sequencer information to add login details.' - return trans.fill_template( '/requests/common/dataset_transfer.mako', + message = 'The sequencer login information is incomplete. Click sequencer information to add login details.' + return trans.fill_template( '/requests/common/view_selected_datasets.mako', cntrller=cntrller, - sample=sample, - dataset_files=sample.datasets, + sample=sample, message=message, status=status, files=[], @@ -1063,9 +1078,8 @@ class RequestsCommon( BaseController, Us # See if all the samples' barcodes are in the same state, and if so send email if configured to. common_state = request.samples_have_common_state if common_state and common_state.id == request.type.states[1].id: - event = trans.model.RequestEvent( request, - request.states.SUBMITTED, - "All samples are in %s state." % common_state.name ) + comment = "All samples of this request are in the (%s) sample state. " % common_state.name + event = trans.model.RequestEvent( request, request.states.SUBMITTED, comment ) trans.sa_session.add( event ) trans.sa_session.flush() request.send_email_notification( trans, request.type.states[1] ) --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1595,7 +1595,7 @@ class Request( object ): states = Bunch( NEW = 'New', SUBMITTED = 'In Progress', REJECTED = 'Rejected', - COMPLETE = 'Complete' ) + COMPLETE = 'Complete' ) api_collection_visible_keys = ( 'id', 'name', 'state' ) def __init__( self, name=None, desc=None, request_type=None, user=None, form_values=None, notification=None ): self.name = name @@ -1813,17 +1813,21 @@ class Sample( object ): if dataset.status == SampleDataset.transfer_status.COMPLETE: transferred_datasets.append( dataset ) return transferred_datasets - def dataset_size( self, filepath ): - def print_ticks(d): + def get_untransferred_dataset_size( self, filepath ): + # TODO: RC: If rsh keys are not set, this method will return something like the following: + # greg@scofield.bx.psu.edu's password: 46M /afs/bx.psu.edu/home/greg/chr22/chr21.fa + # This method should return the number of bytes in the file. I believe du + # displays the file system block usage which may not be the number of bytes in the file. + # Would ls -l be better? + def print_ticks( d ): pass datatx_info = self.request.type.datatx_info - cmd = 'ssh %s@%s "du -sh \'%s\'"' % ( datatx_info['username'], - datatx_info['host'], - filepath) - output = pexpect.run(cmd, events={'.ssword:*': datatx_info['password']+'\r\n', - pexpect.TIMEOUT:print_ticks}, - timeout=10) - return output.replace(filepath, '').strip() + cmd = 'ssh %s@%s "du -sh \'%s\'"' % ( datatx_info['username'], datatx_info['host'], filepath ) + output = pexpect.run( cmd, + events={ '.ssword:*': datatx_info['password']+'\r\n', + pexpect.TIMEOUT:print_ticks}, + timeout=10 ) + return output.replace( filepath, '' ).strip() def get_api_value( self, view='collection' ): rval = {} try: @@ -1856,8 +1860,7 @@ class SampleDataset( object ): ADD_TO_LIBRARY = 'Adding to data library', COMPLETE = 'Complete', ERROR = 'Error' ) - def __init__(self, sample=None, name=None, file_path=None, - status=None, error_msg=None, size=None): + def __init__( self, sample=None, name=None, file_path=None, status=None, error_msg=None, size=None): self.sample = sample self.name = name self.file_path = file_path @@ -1866,9 +1869,9 @@ class SampleDataset( object ): self.size = size class UserAddress( object ): - def __init__(self, user=None, desc=None, name=None, institution=None, - address=None, city=None, state=None, postal_code=None, - country=None, phone=None): + def __init__( self, user=None, desc=None, name=None, institution=None, + address=None, city=None, state=None, postal_code=None, + country=None, phone=None ): self.user = user self.desc = desc self.name = name --- a/templates/admin/requests/rename_datasets.mako +++ b/templates/admin/requests/rename_datasets.mako @@ -6,19 +6,14 @@ <h3>Rename datasets for Sample "${sample.name}"</h3><ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=trans.security.encode_id( sample.id ) )}">Browse datasets</a> - </li> - <li> - <a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller='requests_admin', id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a> - </li> + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=trans.security.encode_id( sample.id ) )}">Browse datasets</a></li> + <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller='requests_admin', id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a></li></ul> %if message: ${render_msg( message, status )} %endif -${render_msg( 'A dataset can be renamed only if it is in <b>Not Started</b> state.', 'warning' )} <div class="toolForm"><form name="rename_datasets" id="rename_datasets" action="${h.url_for( controller='requests_admin', action='rename_datasets', id_list=id_list, sample_id=trans.security.encode_id( sample.id ) )}" method="post" ><table class="grid"> --- a/templates/requests/common/find_samples.mako +++ b/templates/requests/common/find_samples.mako @@ -71,7 +71,7 @@ %else: State: ${sample.state.name}<br/> %endif - Datasets: <a href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.transferred_dataset_files )}/${len( sample.datasets )}</a><br/> + Datasets: <a href="${h.url_for( controller='requests_common', action='view_selected_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.transferred_dataset_files )}/${len( sample.datasets )}</a><br/> %if is_admin: <i>User: ${sample.request.user.email}</i> %endif --- /dev/null +++ b/templates/requests/common/view_selected_datasets.mako @@ -0,0 +1,37 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/requests/common/common.mako" import="render_sample_datasets" /> + +<% + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + is_complete = sample.request.is_complete + is_submitted = sample.request.is_submitted + can_select_datasets = is_admin and ( is_complete or is_submitted ) + can_transfer_datasets = is_admin and sample.untransferred_dataset_files +%> + +<br/><br/> + +<ul class="manage-table-actions"> + %if can_transfer_datasets: + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">Transfer datasets</a></li> + %endif + <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_selected_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">Refresh page</a></li> + <li><a class="action-button" id="sample-${sample.id}-popup" class="menubutton">Dataset Actions</a></li> + <div popupmenu="sample-${sample.id}-popup"> + %if can_select_datasets: + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', cntrller=cntrller, request_id=trans.security.encode_id( sample.request.id ), sample_id=trans.security.encode_id( sample.id ) )}">Select more datasets</a></li> + %endif + <li><a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller=cntrller, id=trans.security.encode_id( sample.library.id ) )}">View target Data Library</a></li> + <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a></li> + </div> +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +%if sample and sample.datasets: + <% title = 'Datasets currently selected for "sample.name"' %> + ${render_sample_datasets( cntrller, sample, sample.datasets, title )} +%endif --- a/templates/requests/common/dataset_transfer.mako +++ /dev/null @@ -1,46 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -<br/><br/> - -<ul class="manage-table-actions"> - <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">Refresh</a></li> - <li><a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller=cntrller, id=trans.security.encode_id( sample.library.id ) )}">Target Data Library</a></li> - <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller=cntrller, id=trans.security.encode_id( sample.request.id ) )}">Browse this request</a></li> -</ul> - -%if message: - ${render_msg( message, status )} -%endif - -<div class="toolForm"> - <div class="toolFormTitle">Sample "${sample.name}"</div> - <div class="toolFormBody"> - %if dataset_files: - <div class="form-row"> - <table class="grid"> - <thead> - <tr> - <th>Name</th> - <th>Size</th> - <th>Status</th> - </tr> - <thead> - <tbody> - %for dataset_file in dataset_files: - <tr> - <td>${dataset_file.name}</td> - <td>${dataset_file.size}</td> - <td>${dataset_file.status}</td> - </tr> - %endfor - </tbody> - </table> - </div> - %else: - <div class="form-row"> - There are no datasets associated with this sample. - </div> - %endif - </div> -</div> --- /dev/null +++ b/templates/admin/requests/select_datasets_to_transfer.mako @@ -0,0 +1,133 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/requests/common/common.mako" import="render_sample_datasets" /> + +${h.js( "ui.core", "jquery.cookie", "jquery.dynatree" )} +<link href='/static/june_2007_style/blue/dynatree_skin/ui.dynatree.css' rel='stylesheet' type='text/css'> +##${h.js( "jquery.dynatree" )} + +<script type="text/javascript"> + $(function(){ + $("#tree").ajaxComplete(function(event, XMLHttpRequest, ajaxOptions) { + _log("debug", "ajaxComplete: %o", this); // dom element listening + }); + // --- Initialize sample trees + $("#tree").dynatree({ + title: "${request.type.datatx_info['data_dir']}", + rootVisible: true, + minExpandLevel: 0, // 1: root node is not collapsible + persist: false, + checkbox: true, + selectMode: 3, + onPostInit: function(isReloading, isError) { +// alert("reloading: "+isReloading+", error:"+isError); + logMsg("onPostInit(%o, %o) - %o", isReloading, isError, this); + // Re-fire onActivate, so the text is updated + this.reactivate(); + }, + fx: { height: "toggle", duration: 200 }, + // initAjax is hard to fake, so we pass the children as object array: + initAjax: {url: "${h.url_for( controller='requests_admin', action='open_folder' )}", + dataType: "json", + data: { id: "${request.id}", key: "${request.type.datatx_info['data_dir']}" }, + }, + onLazyRead: function(dtnode){ + dtnode.appendAjax({ + url: "${h.url_for( controller='requests_admin', action='open_folder' )}", + dataType: "json", + data: { id: "${request.id}", key: dtnode.data.key }, + }); + }, + onSelect: function(select, dtnode) { + // Display list of selected nodes + var selNodes = dtnode.tree.getSelectedNodes(); + // convert to title/key array + var selKeys = $.map(selNodes, function(node){ + return node.data.key; + }); + document.select_datasets_to_transfer.selected_datasets_to_transfer.value = selKeys.join(",") + }, + onActivate: function(dtnode) { + var cell = $("#file_details"); + var selected_value = dtnode.data.key + if(selected_value.charAt(selected_value.length-1) != '/') { + // Make ajax call + $.ajax( { + type: "POST", + url: "${h.url_for( controller='requests_admin', action='get_file_details' )}", + dataType: "json", + data: { id: "${request.id}", folder_path: dtnode.data.key }, + success : function ( data ) { + cell.html( '<label>'+data+'</label>' ) + } + }); + } else { + cell.html( '' ) + } + }, + }); + }); + +</script> + +<% + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + can_transfer_datasets = is_admin and sample.untransferred_dataset_files +%> + +<br/><br/> +<ul class="manage-table-actions"> + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='view_request_type', id=trans.security.encode_id( request.type.id ) )}">Sequencer configuration</a></li> + %if can_transfer_datasets: + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">Transfer datasets</a></li> + %endif + <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Browse this request</a></li> +</ul> + +%if not sample: + <font color="red"><b><i>Select a sample before selecting datasets to transfer</i></b></font> + <br/><br/> +%endif + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Select datasets to transfer from data directory configured for the sequencer</div> + <form name="select_datasets_to_transfer" id="select_datasets_to_transfer" action="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', cntrller=cntrller, request_id=trans.security.encode_id( request.id ))}" method="post" > + <div class="form-row"> + <label>Sample:</label> + ${sample_id_select_field.get_html()} + <div class="toolParamHelp" style="clear: both;"> + Select the sample that was sequenced to produce the datasets you want to transfer. + </div> + </div> + <div class="form-row" > + <label>Select datasets from source data location defined in the sequencer configuration:</label> + <div id="tree" > + Loading... + </div> + <input id="selected_datasets_to_transfer" name="selected_datasets_to_transfer" type="hidden" size=40"/> + <div class="toolParamHelp" style="clear: both;"> + <ul> + <li>Click the <b>Sequencer configuration</b> button and change the <b>Data directory</b> setting to redefine the source data location.</li> + <li>Select a folder to select all of the individual files within that folder.</li> + <li>Click the <b>Select datasets</b> button when desired dataset check boxes are checked.</li> + </ul> + </div> + </div> + <div class="form-row"> + <div id="file_details" class="toolParamHelp" style="clear: both;background-color:#FAFAFA;"></div> + </div> + <div class="form-row"> + <input type="submit" name="select_datasets_to_transfer_button" value="Select datasets"/> + </div> + </form> +</div> + +%if sample and sample.datasets: + <% title = 'Datasets currently selected for "sample.name"' %> + <p/> + ${render_sample_datasets( 'requests_admin', sample, sample.datasets, title )} +%endif --- a/lib/galaxy/web/controllers/requests_admin.py +++ b/lib/galaxy/web/controllers/requests_admin.py @@ -23,10 +23,6 @@ class AdminRequestsGrid( RequestsGrid ): operations.append( grids.GridOperation( "Reject", allow_multiple=False, condition=( lambda item: not item.deleted and item.is_submitted ) ) ) operations.append( grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ) ) operations.append( grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ) ) - operations.append( grids.GridOperation( "Purge", - allow_multiple=False, - confirm="This will permanently delete this sequencing request. Click OK to proceed.", - condition=( lambda item: item.deleted ) ) ) global_actions = [ grids.GridAction( "Create new request", dict( controller='requests_common', action='create_request', @@ -227,7 +223,8 @@ class RequestsAdmin( BaseController, Use status=status, message=message ) # Create an event with state 'Rejected' for this request - event = trans.model.RequestEvent( request, request.states.REJECTED, comment ) + event_comment = "Request marked rejected by %s. Reason: %s " % ( trans.user.email, comment ) + event = trans.model.RequestEvent( request, request.states.REJECTED, event_comment ) trans.sa_session.add( event ) trans.sa_session.flush() message='Request (%s) has been rejected.' % request.name @@ -240,6 +237,11 @@ class RequestsAdmin( BaseController, Use @web.expose @web.require_admin def manage_datasets( self, trans, **kwd ): + def handle_error( **kwd ): + kwd[ 'status' ] = 'error' + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_datasets', + **kwd ) ) params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) @@ -247,23 +249,28 @@ class RequestsAdmin( BaseController, Use operation = kwd[ 'operation' ].lower() sample_dataset_id = params.get( 'id', None ) if not sample_dataset_id: - return invalid_id_redirect( trans, 'requests_admin', sample_dataset_id ) + message = 'Select at least 1 dataset to %s.' % operation + kwd[ 'message' ] = message + del kwd[ 'operation' ] + handle_error( **kwd ) id_list = util.listify( sample_dataset_id ) selected_sample_datasets = [] for sample_dataset_id in id_list: - try: - selected_sample_datasets.append( trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( sample_dataset_id ) ) ) + try: + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( sample_dataset_id ) ) except: return invalid_id_redirect( trans, 'requests_admin', sample_dataset_id ) + selected_sample_datasets.append( sample_dataset ) if operation == "view": - return trans.fill_template( '/admin/requests/dataset.mako', + return trans.fill_template( '/admin/requests/view_sample_dataset.mako', + cntrller='requests_admin', sample_dataset=selected_sample_datasets[0] ) elif operation == "delete": not_deleted = [] for sample_dataset in selected_sample_datasets: # Make sure the dataset has been transferred before deleting it. if sample_dataset in sample_dataset.sample.untransferred_dataset_files: - # save the sample to which these datasets belong to + # Save the sample dataset sample = sample_dataset.sample trans.sa_session.delete( sample_dataset ) trans.sa_session.flush() @@ -299,7 +306,9 @@ class RequestsAdmin( BaseController, Use sample=selected_sample_datasets[0].sample, id_list=id_list ) elif operation == "transfer": - self.initiate_data_transfer( trans, selected_sample_datasets[0].sample, selected_sample_datasets ) + self.initiate_data_transfer( trans, + trans.security.encode_id( selected_sample_datasets[0].sample.id ), + sample_datasets=selected_sample_datasets ) # Render the grid view sample_id = params.get( 'sample_id', None ) try: @@ -308,13 +317,10 @@ class RequestsAdmin( BaseController, Use return invalid_id_redirect( trans, 'requests_admin', sample_id ) request_id = trans.security.encode_id( sample.request.id ) library_id = trans.security.encode_id( sample.library.id ) - self.datatx_grid.global_actions = [ grids.GridAction( "Refresh page", + self.datatx_grid.title = 'Manage "%s" datasets' % sample.name + self.datatx_grid.global_actions = [ grids.GridAction( "Select more datasets", dict( controller='requests_admin', - action='manage_datasets', - sample_id=sample_id ) ), - grids.GridAction( "Select datasets", - dict( controller='requests_admin', - action='get_data', + action='select_datasets_to_transfer', request_id=request_id, sample_id=sample_id ) ), grids.GridAction( "Browse target data library", @@ -326,7 +332,11 @@ class RequestsAdmin( BaseController, Use dict( controller='requests_common', action='view_request', cntrller='requests_admin', - id=request_id ) ) ] + id=request_id ) ), + grids.GridAction( "Refresh page", + dict( controller='requests_admin', + action='manage_datasets', + sample_id=sample_id ) ) ] return self.datatx_grid( trans, **kwd ) @web.expose @web.require_admin @@ -372,70 +382,68 @@ class RequestsAdmin( BaseController, Use sample_id=sample_id ) ) @web.expose @web.require_admin - def get_data( self, trans, **kwd ): + def select_datasets_to_transfer( self, trans, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) request_id = kwd.get( 'request_id', None ) files = [] + def handle_error( **kwd ): + kwd[ 'status' ] = 'error' + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='select_datasets_to_transfer', + **kwd ) ) try: request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) ) except: return invalid_id_redirect( trans, 'requests_admin', request_id ) - selected_files = util.restore_text( params.get( 'selected_files', '' ) ) - if len( selected_files ): - selected_files = selected_files.split(',') + selected_datasets_to_transfer = util.restore_text( params.get( 'selected_datasets_to_transfer', '' ) ) + if selected_datasets_to_transfer: + selected_datasets_to_transfer = selected_datasets_to_transfer.split(',') else: - selected_files = [] - selected_sample_id = kwd.get( 'sample_id', 'none' ) - sample_id_select_field = self.__build_sample_id_select_field( trans, request, selected_sample_id ) + selected_datasets_to_transfer = [] + sample_id = kwd.get( 'sample_id', 'none' ) + sample_id_select_field = self.__build_sample_id_select_field( trans, request, sample_id ) + if sample_id != 'none': + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) + else: + sample = None # The __get_files() method redirects here with a status of 'error' and a message if there # was a problem retrieving the files. - if params.get( 'select_show_datasets_button', False ) or params.get( 'select_more_button', False ): - # get the sample these datasets are associated with - try: - sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( selected_sample_id ) ) - except: - message = 'Select a sample before selecting its associated datasets.' - return trans.fill_template( '/admin/requests/get_data.mako', - cntrller='requests_admin', - request=request, - sample_id_select_field=sample_id_select_field, - status='error', - message=message ) + if params.get( 'select_datasets_to_transfer_button', False ): + # Get the sample that was sequenced to produce these datasets. + if sample_id == 'none': + message = 'Select the sample that was sequenced to produce the datasets you want to transfer.' + kwd[ 'message' ] = message + del kwd[ 'select_datasets_to_transfer_button' ] + handle_error( **kwd ) if sample in sample.request.samples_without_library_destinations: # Display an error if a sample has been selected that # has not yet been associated with a destination library. + message = 'Select a target data library and folder for the sample before selecting the datasets.' status = 'error' - message = 'Select a sample with associated data library and folder before selecting the datasets.' - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='get_data', - request_id=request_id, - sample_id=sample.id, + return trans.response.send_redirect( web.url_for( controller='requests_common', + action='edit_samples', + cntrller='requests_admin', + id=trans.security.encode_id( request.id ), + editing_samples=True, status=status, message=message ) ) # Save the sample datasets - sample_dataset_file_names = self.__save_sample_datasets( trans, sample, selected_files ) + sample_dataset_file_names = self.__save_sample_datasets( trans, sample, selected_datasets_to_transfer ) if sample_dataset_file_names: message = 'Datasets (%s) have been selected for sample (%s)' % \ ( str( sample_dataset_file_names )[1:-1].replace( "'", "" ), sample.name ) - if params.get( 'select_show_datasets_button', False ): - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='manage_datasets', - request_id=request_id, - sample_id=selected_sample_id, - message=message, - status=status ) ) - else: # 'select_more_button' was clicked - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='get_data', - request_id=request_id, - sample_id=sample.id, - message=message, - status=status ) ) - return trans.fill_template( '/admin/requests/get_data.mako', + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_datasets', + request_id=request_id, + sample_id=sample_id, + message=message, + status=status ) ) + return trans.fill_template( '/admin/requests/select_datasets_to_transfer.mako', cntrller='requests_admin', request=request, + sample=sample, sample_id_select_field=sample_id_select_field, status=status, message=message ) @@ -499,7 +507,7 @@ class RequestsAdmin( BaseController, Use if ok: return output.splitlines() return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='get_data', + action='select_datasets_to_transfer', request_id=trans.security.encode_id( request.id ), status=status, message=message ) ) @@ -508,24 +516,36 @@ class RequestsAdmin( BaseController, Use if a_path and not a_path.endswith( os.sep ): a_path += os.sep return a_path - def __save_sample_datasets( self, trans, sample, selected_files ): + def __save_sample_datasets( self, trans, sample, selected_datasets_to_transfer ): sample_dataset_file_names = [] - if selected_files: - for filepath in selected_files: + if selected_datasets_to_transfer: + for filepath in selected_datasets_to_transfer: # FIXME: handle folder selection # ignore folders for now if filepath[-1] != os.sep: name = self.__dataset_name( sample, filepath.split( '/' )[-1] ) + status = trans.app.model.SampleDataset.transfer_status.NOT_STARTED + size = sample.get_untransferred_dataset_size( filepath ) sample_dataset = trans.model.SampleDataset( sample=sample, file_path=filepath, - status=trans.app.model.SampleDataset.transfer_status.NOT_STARTED, + status=status, name=name, error_msg='', - size=sample.dataset_size( filepath ) ) + size=size ) trans.sa_session.add( sample_dataset ) trans.sa_session.flush() sample_dataset_file_names.append( str( sample_dataset.name ) ) return sample_dataset_file_names + def dataset_file_size( self, sample, filepath ): + def print_ticks(d): + pass + datatx_info = sample.request.type.datatx_info + cmd = 'ssh %s@%s "du -sh \'%s\'"' % ( datatx_info['username'], datatx_info['host'], filepath ) + output = pexpect.run( cmd, + events={ '.ssword:*': datatx_info['password']+'\r\n', + pexpect.TIMEOUT:print_ticks}, + timeout=10 ) + return output.replace( filepath, '' ).strip() def __dataset_name( self, sample, filepath ): name = filepath.split( '/' )[-1] options = sample.request.type.rename_dataset_options @@ -610,38 +630,54 @@ class RequestsAdmin( BaseController, Use if not datatx_info[ 'host' ] \ or not datatx_info[ 'username' ] \ or not datatx_info[ 'password' ]: - err_msg = "Error in sequencer login information." - # check if web API is enabled and API key exists - if not trans.user.api_keys or not trans.app.config.enable_api: - err_msg = "Could not start data transfer as Galaxy Web API is not enabled. Enable Galaxy Web API in the Galaxy config file and create an API key." + err_msg += "Error in sequencer login information. " + # Make sure web API is enabled and API key exists + if not trans.app.config.enable_api: + err_msg += "The 'enable_api = True' setting is not correctly set in the Galaxy config file. " + if not trans.user.api_keys: + err_msg += "Set your API Key in your User Preferences to transfer datasets." # check if library_import_dir is set if not trans.app.config.library_import_dir: - err_msg = "'library_import_dir' config variable is not set in the Galaxy config file." + err_msg = "'The library_import_dir' setting is not set in the Galaxy config file." # check the RabbitMQ server settings in the config file for k, v in trans.app.config.amqp.items(): if not v: - err_msg = 'Set RabbitMQ server settings in the "galaxy_amqp" section of the Galaxy config file. %s is not set.' % k + err_msg += 'Set RabbitMQ server settings in the "galaxy_amqp" section of the Galaxy config file. %s is not set.' % k break return err_msg - def initiate_data_transfer( self, trans, sample, selected_sample_datasets ): + @web.expose + @web.require_admin + def initiate_data_transfer( self, trans, sample_id, sample_datasets=[], sample_dataset_id='' ): ''' This method initiates the transfer of the datasets from the sequencer. It happens in the following steps: - - The current admin user needs to have ADD_LIBRARY_ITEM permission for the + - The current admin user needs to have LIBRARY_ADD permission for the target library and folder - Create an XML message encapsulating all the data transfer info and send it to the message queue (RabbitMQ broker) ''' - # check data transfer settings + try: + sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) ) + except: + return invalid_id_redirect( trans, 'requests_admin', sample_id ) + # Check data transfer settings err_msg = self.__validate_data_transfer_settings( trans, sample ) if not err_msg: - # check if the current user has add_library_item permission to the sample - # target library & folder + # Make sure the current user has LIBRARY_ADD + # permission on the target library and folder. self.__check_library_add_permission( trans, sample.library, sample.folder ) - # create the message + if sample_dataset_id and not sample_datasets: + # Either a list of SampleDataset objects or a comma-separated string of + # encoded SampleDataset ids can be received. If the latter, parse the + # sample_dataset_id to build the list of sample_datasets. + id_list = util.listify( sample_dataset_id ) + for sample_dataset_id in id_list: + sample_dataset = trans.sa_session.query( trans.model.SampleDataset ).get( trans.security.decode_id( sample_dataset_id ) ) + sample_datasets.append( sample_dataset ) + # Create the message message = self.__create_data_transfer_message( trans, sample, - selected_sample_datasets ) + sample_datasets ) # Send the message try: conn = amqp.Connection( host=trans.app.config.amqp[ 'host' ] + ":" + trans.app.config.amqp[ 'port' ], @@ -660,9 +696,9 @@ class RequestsAdmin( BaseController, Use chan.close() conn.close() except Exception, e: - err_msg = "Error in sending the data transfer message to the Galaxy AMQP message queue:<br/>%s" % str(e) + err_msg = "Error sending the data transfer message to the Galaxy AMQP message queue:<br/>%s" % str(e) if not err_msg: - err_msg = "%i datasets have been queued for transfer from the sequencer. Click the Refresh button above to monitor the transfer status." % len( selected_sample_datasets ) + err_msg = "%i datasets have been queued for transfer from the sequencer. Click the Refresh button above to monitor the transfer status." % len( sample_datasets ) status = "done" else: status = 'error' --- a/templates/requests/common/edit_samples.mako +++ b/templates/requests/common/edit_samples.mako @@ -19,12 +19,13 @@ is_admin = cntrller == 'requests_admin' and trans.user_is_admin() is_complete = request.is_complete + is_submitted = request.is_submitted is_unsubmitted = request.is_unsubmitted can_add_samples = is_unsubmitted can_delete_samples = request.samples and not is_complete can_edit_samples = request.samples and ( is_admin or not is_complete ) can_edit_request = ( is_admin and not request.is_complete ) or request.is_unsubmitted - can_reject_or_transfer = is_admin and request.is_submitted + can_reject = is_admin and is_submitted can_submit = request.samples and is_unsubmitted %> @@ -47,9 +48,8 @@ <a class="action-button" href="${h.url_for( controller='requests_common', action='edit_basic_request_info', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Edit</a> %endif <a class="action-button" href="${h.url_for( controller='requests_common', action='request_events', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">View history</a> - %if can_reject_or_transfer: + %if can_reject: <a class="action-button" href="${h.url_for( controller='requests_admin', action='reject_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Reject</a> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', request_id=trans.security.encode_id( request.id ) )}">Select datasets to transfer</a> %endif </div></ul> --- a/templates/requests/common/events.mako +++ b/templates/requests/common/events.mako @@ -20,7 +20,7 @@ %endif %if is_admin and request.is_submitted: <a class="action-button" href="${h.url_for( controller='requests_admin', action='reject_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Reject</a> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', request_id=trans.security.encode_id( request.id ) )}">Select datasets to transfer</a> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', request_id=trans.security.encode_id( request.id ) )}">Select datasets to transfer</a> %endif </div></ul> --- a/templates/requests/common/edit_basic_request_info.mako +++ b/templates/requests/common/edit_basic_request_info.mako @@ -17,7 +17,7 @@ <a class="action-button" href="${h.url_for( controller='requests_common', action='request_events', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">View history</a> %if is_admin and request.is_submitted: <a class="action-button" href="${h.url_for( controller='requests_admin', action='reject_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Reject</a> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', request_id=trans.security.encode_id( request.id ) )}">Select datasets to transfer</a> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', request_id=trans.security.encode_id( request.id ) )}">Select datasets to transfer</a> %endif </div></ul> --- a/templates/requests/common/view_request.mako +++ b/templates/requests/common/view_request.mako @@ -19,11 +19,13 @@ is_admin = cntrller == 'requests_admin' and trans.user_is_admin() is_complete = request.is_complete + is_submitted = request.is_submitted is_unsubmitted = request.is_unsubmitted + can_add_samples = is_unsubmitted can_edit_request = ( is_admin and not request.is_complete ) or request.is_unsubmitted - can_add_samples = is_unsubmitted can_delete_samples = request.samples and not is_complete can_edit_samples = request.samples and ( is_admin or not is_complete ) + can_reject = is_admin and is_submitted can_submit = request.samples and is_unsubmitted %> @@ -35,13 +37,15 @@ %endif <li><a class="action-button" id="request-${request.id}-popup" class="menubutton">Request actions</a></li><div popupmenu="request-${request.id}-popup"> + %if request.deleted: + <a class="action-button" href="${h.url_for( controller='requests_common', action='undelete_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Undelete</a> + %endif %if can_edit_request: <a class="action-button" href="${h.url_for( controller='requests_common', action='edit_basic_request_info', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Edit</a> %endif <a class="action-button" href="${h.url_for( controller='requests_common', action='request_events', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">View history</a> - %if is_admin and request.is_submitted: + %if can_reject: <a class="action-button" href="${h.url_for( controller='requests_admin', action='reject_request', cntrller=cntrller, id=trans.security.encode_id( request.id ) )}">Reject</a> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', request_id=trans.security.encode_id( request.id ) )}">Select datasets to transfer</a> %endif </div></ul> --- a/templates/admin/requests/dataset.mako +++ /dev/null @@ -1,69 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -%if message: - ${render_msg( message, status )} -%endif - -<br/><br/> - -<% sample = sample_dataset.sample %> - -<ul class="manage-table-actions"> - <li> - <a class="action-button" href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller='requests_admin', sample_id=trans.security.encode_id( sample.id ) )}"> - <span>Browse datasets</span></a> - </li> -</ul> - -<div class="toolForm"> - <div class="toolFormTitle">Dataset Information</div> - <div class="toolFormBody"> - <div class="form-row"> - <label>Name:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - ${sample_dataset.name} - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>File on the Sequencer:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - ${sample_dataset.file_path} - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>Size:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - ${sample_dataset.size} - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>Created on:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - ${sample_dataset.create_time} - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>Updated on:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - ${sample_dataset.update_time} - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>Transfer status:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - ${sample_dataset.status} - %if sample_dataset.status == trans.app.model.SampleDataset.transfer_status.ERROR: - <br/> - ${sample_dataset.error_msg} - %endif - </div> - <div style="clear: both"></div> - </div> - </div> -</div> --- a/templates/requests/common/common.mako +++ b/templates/requests/common/common.mako @@ -168,15 +168,25 @@ %endif <td valign="top">${current_sample['library_select_field'].get_html()}</td><td valign="top">${current_sample['folder_select_field'].get_html()}</td> - %if display_datasets: - <% - if sample: - label = str( len( sample.datasets ) ) - else: - label = 'add' - %> - <td valign="top"><a href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${label}</a></td> - <td valign="top"><a href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${label}</a></td> + %if display_datasets: + <td valign="top"> + ## An admin can select the datasets to transfer, while a non-admin can only view what has been selected + %if is_admin: + <a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', cntrller=cntrller, request_id=trans.security.encode_id( request.id ), sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.datasets )}</a> + %elif sample.datasets: + ## Only display a link if there is at least 1 selected dataset for the sample + <a href="${h.url_for( controller='requests_common', action='view_selected_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.datasets )}</a></td> + %else: + ${len( sample.datasets )} + %endif + </td> + <td valign="top"> + %if is_admin and sample.untransferred_dataset_files: + <a href="${h.url_for( controller='requests_common', action='manage_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.transferred_dataset_files )}</a> + %else: + ${len( sample.transferred_dataset_files )} + %endif + </td> %endif %if sample and ( is_admin or is_unsubmitted ) and not is_complete: ## Delete button @@ -196,6 +206,7 @@ can_add_samples = request.is_unsubmitted can_delete_samples = request.samples and not is_complete can_edit_samples = request.samples and ( is_admin or not is_complete ) + can_select_datasets = is_admin and current_samples and ( is_submitted or is_complete ) display_checkboxes = editing_samples and ( is_complete or is_rejected or is_submitted ) display_bar_code = request.samples and ( is_complete or is_rejected or is_submitted ) display_datasets = request.samples and ( is_complete or is_rejected or is_submitted ) @@ -280,9 +291,31 @@ %else: <td></td> %endif - %if is_submitted or is_complete: - <td><a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.datasets )}</a></td> - <td><a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_common', action='view_dataset_transfer', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.transferred_dataset_files )}</a></td> + %if is_submitted or is_complete: + <td> + ## An admin can select the datasets to transfer, while a non-admin can only view what has been selected + %if is_admin: + %if not sample.datasets: + ## If there are no selected datasets, display a page alowing the admin to select some. + <a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', cntrller=cntrller, request_id=trans.security.encode_id( request.id ), sample_id= trans.security.encode_id( sample.id ) )}">${len( sample.datasets )}</a> + %else: + ## If there are selected datasets, display them + <a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_common', action='view_selected_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.datasets )}</a> + %endif + %elif sample.datasets: + ## Only display a link if there is at least 1 selected dataset for the sample + <a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_common', action='view_selected_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.datasets )}</a> + %else: + ${len( sample.datasets )} + %endif + </td> + <td> + %if is_admin and sample.untransferred_dataset_files: + <a id="sampleDatasets-${sample.id}" href="${h.url_for( controller='requests_admin', action='manage_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ) )}">${len( sample.transferred_dataset_files )}</a> + %else: + ${len( sample.transferred_dataset_files )} + %endif + </td> %endif </tr> %else: @@ -395,3 +428,52 @@ </table></div></%def> + +<%def name="render_sample_datasets( cntrller, sample, sample_datasets, title )"> + %if sample_datasets: + <% + is_admin = cntrller == 'requests_admin' and trans.user_is_admin() + is_complete = sample.request.is_complete + is_submitted = sample.request.is_submitted + can_select_datasets = is_admin and ( is_complete or is_submitted ) + can_transfer_datasets = is_admin and sample.untransferred_dataset_files + %> + <h3>${title}</h3> + <table class="grid"> + <thead> + <tr> + <th>Name</th> + <th>Size</th> + <th>Status</th> + </tr> + <thead> + <tbody> + %for dataset in sample_datasets: + <tr> + <td> + %if is_admin: + <% encoded_id = trans.security.encode_id(dataset.id) %> + <span class="expandLink dataset-${dataset}-click"><span class="rowIcon"></span> + <div style="float: left; margin-left: 2px;" class="menubutton split popup" id="dataset-${dataset.id}-popup"> + <a class="dataset-${encoded_id}-click" href="${h.url_for( controller='requests_admin', action='manage_datasets', operation='view', id=trans.security.encode_id( dataset.id ) )}">${dataset.name}</a> + </div> + </span> + <div popupmenu="dataset-${dataset.id}-popup"> + %if can_transfer_datasets and dataset in sample.untransferred_dataset_files: + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='initiate_data_transfer', sample_id=trans.security.encode_id( sample.id ), sample_dataset_id=trans.security.encode_id( dataset.id ) )}">Transfer</a></li> + %endif + </div> + %else: + ${dataset.name} + %endif + </td> + <td>${dataset.size}</td> + <td>${dataset.status}</td> + </tr> + %endfor + </tbody> + </table> + %else: + No datasets for this sample. + %endif +</%def>