Remove sample tracking manual external service transfer due to security concerns

This commit is contained in:
Nate Coraor
2016-02-24 11:18:24 -05:00
parent 7c4b8ff747
commit ffcd4fe192
6 changed files with 3 additions and 520 deletions
-32
View File
@@ -25,11 +25,6 @@ from sqlalchemy.ext import hybrid
from sqlalchemy import types
from sqlalchemy import type_coerce
try:
import pexpect
except ImportError:
pexpect = None
import galaxy.model.orm.now
import galaxy.model.metadata
import galaxy.security.passwords
@@ -60,9 +55,6 @@ _datatypes_registry = None
# this be unlimited - filter in Python if over this limit.
MAX_IN_FILTER_LENGTH = 100
PEXPECT_IMPORT_MESSAGE = ('The Python pexpect package is required to use this '
'feature, please install it')
class NoConverterException(Exception):
def __init__(self, value):
@@ -4621,30 +4613,6 @@ class Sample( object, Dictifiable ):
untransferred_datasets.append( dataset )
return untransferred_datasets
def get_untransferred_dataset_size( self, filepath, scp_configs ):
def print_ticks( d ):
pass
if pexpect is None:
return PEXPECT_IMPORT_MESSAGE
error_msg = 'Error encountered in determining the file size of %s on the external_service.' % filepath
if not scp_configs['host'] or not scp_configs['user_name'] or not scp_configs['password']:
return error_msg
login_str = '%s@%s' % ( scp_configs['user_name'], scp_configs['host'] )
cmd = 'ssh %s "du -sh \'%s\'"' % ( login_str, filepath )
try:
output = pexpect.run( cmd,
events={ '.ssword:*': scp_configs['password'] + '\r\n',
pexpect.TIMEOUT: print_ticks},
timeout=10 )
except Exception:
return error_msg
# cleanup the output to get just the file size
return output.replace( filepath, '' )\
.replace( 'Password:', '' )\
.replace( "'s password:", '' )\
.replace( login_str, '' )\
.strip()
@property
def run_details( self ):
# self.runs is a list of SampleRunAssociations ordered descending on update_time.
@@ -9,15 +9,7 @@ from galaxy.web.form_builder import build_select_field
from galaxy.web.framework.helpers import time_ago, grids
from .requests_common import RequestsGrid, invalid_id_redirect
from markupsafe import escape
import amqp
try:
import pexpect
except ImportError:
pexpect = None
PEXPECT_IMPORT_MESSAGE = ('The Python pexpect package is required to use this '
'feature, please install it')
log = logging.getLogger( __name__ )
@@ -371,207 +363,6 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitionsMixin ):
action='manage_datasets',
sample_id=sample_id ) )
@web.expose
@web.require_admin
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 )
external_service_id = kwd.get( 'external_service_id', None )
request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
external_service = trans.sa_session.query( trans.model.ExternalService ).get( trans.security.decode_id( external_service_id ) )
# Load the data transfer settings
external_service.load_data_transfer_settings( trans )
scp_configs = external_service.data_transfer[ trans.model.ExternalService.data_transfer_protocol.SCP ]
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_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_datasets_to_transfer_button', False ):
# Get the sample that was sequenced to produce these datasets.
if sample_id == 'none':
del kwd[ 'select_datasets_to_transfer_button' ]
message = 'Select the sample that was sequenced to produce the datasets you want to transfer.'
kwd[ 'message' ] = message
kwd[ 'status' ] = 'error'
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='select_datasets_to_transfer',
**kwd ) )
if not sample.library:
# 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'
return trans.response.send_redirect( web.url_for( controller='requests_common',
action='edit_samples',
cntrller='requests_admin',
id=trans.security.encode_id( request.id ),
status=status,
message=message ) )
# Save the sample datasets
sample_dataset_file_names = self.__create_sample_datasets( trans, sample, selected_datasets_to_transfer, external_service )
if sample_dataset_file_names:
message = 'Datasets (%s) have been selected for sample (%s)' % \
( str( sample_dataset_file_names )[1:-1].replace( "'", "" ), sample.name )
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,
external_service=external_service,
scp_configs=scp_configs,
sample=sample,
sample_id_select_field=sample_id_select_field,
status=status,
message=message )
@web.json
def get_file_details( self, trans, request_id, external_service_id, folder_path ):
def print_ticks( d ):
# pexpect timeout method
pass
# Avoid caching
trans.response.headers['Pragma'] = 'no-cache'
trans.response.headers['Expires'] = '0'
if pexpect is None:
return PEXPECT_IMPORT_MESSAGE
external_service = trans.sa_session.query( trans.model.ExternalService ).get( trans.security.decode_id( external_service_id ) )
external_service.load_data_transfer_settings( trans )
scp_configs = external_service.data_transfer[ trans.model.ExternalService.data_transfer_protocol.SCP ]
cmd = 'ssh %s@%s "ls -oghp \'%s\'"' % ( scp_configs[ 'user_name' ],
scp_configs[ 'host' ],
folder_path )
# Handle the authentication message if ssh keys are not set - the message is
# something like: "Are you sure you want to continue connecting (yes/no)."
output = pexpect.run( cmd,
events={ '\(yes\/no\)\.*' : 'yes\r\n',
'.ssword:*' : scp_configs[ 'password' ] + '\r\n',
pexpect.TIMEOUT : print_ticks },
timeout=10 )
for password_str in [ 'Password:\r\n', 'password:\r\n' ]:
# Eliminate the output created using ssh from the tree
if password_str in output:
output = output.replace( password_str, '' )
return text_type( output.replace( '\r\n', '<br/>' ) )
@web.json
def open_folder( self, trans, request_id, external_service_id, key ):
# Avoid caching
trans.response.headers['Pragma'] = 'no-cache'
trans.response.headers['Expires'] = '0'
request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
external_service = trans.sa_session.query( trans.model.ExternalService ).get( trans.security.decode_id( external_service_id ) )
folder_path = key
files_list = self.__get_files( trans, request, external_service, folder_path )
folder_contents = []
for filename in files_list:
is_folder = False
if filename and filename[-1] == os.sep:
is_folder = True
if filename:
full_path = os.path.join( folder_path, filename )
node = { "title": filename,
"isFolder": is_folder,
"isLazy": is_folder,
"tooltip": full_path,
"key": full_path }
folder_contents.append( node )
return folder_contents
def __get_files( self, trans, request, external_service, folder_path ):
# Retrieves the filenames to be transferred from the remote host.
ok = True
external_service.load_data_transfer_settings( trans )
scp_configs = external_service.data_transfer[ trans.model.ExternalService.data_transfer_protocol.SCP ]
if not scp_configs[ 'host' ] or not scp_configs[ 'user_name' ] or not scp_configs[ 'password' ]:
status = 'error'
message = "Error in external service login information."
ok = False
def print_ticks( d ):
pass
cmd = 'ssh %s@%s "ls -p \'%s\'"' % ( scp_configs[ 'user_name' ], scp_configs[ 'host' ], folder_path )
# Handle the authentication message if keys are not set - the message is
# something like: "Are you sure you want to continue connecting (yes/no)."
if pexpect is not None:
output = pexpect.run( cmd,
events={ '\(yes\/no\)\.*' : 'yes\r\n',
'.ssword:*' : scp_configs[ 'password' ] + '\r\n',
pexpect.TIMEOUT : print_ticks },
timeout=10 )
if 'No such file or directory' in output:
status = 'error'
message = "No folder named (%s) exists on the external service." % folder_path
ok = False
else:
status = 'error'
message = PEXPECT_IMPORT_MESSAGE
ok = False
if ok:
if 'assword:' in output:
# Eliminate the output created using ssh from the tree
output_as_list = output.splitlines()[ 1: ]
else:
output_as_list = output.splitlines()
return output_as_list
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='select_datasets_to_transfer',
request_id=trans.security.encode_id( request.id ),
external_service_id=trans.security.encode_id( external_service.id ),
status=status,
message=message ) )
def __create_sample_datasets( self, trans, sample, selected_datasets_to_transfer, external_service ):
external_service.load_data_transfer_settings( trans )
scp_configs = external_service.data_transfer[ trans.model.ExternalService.data_transfer_protocol.SCP ]
sample_dataset_file_names = []
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.__rename_dataset( sample, filepath.split( '/' )[-1], scp_configs )
status = trans.app.model.SampleDataset.transfer_status.NOT_STARTED
size = sample.get_untransferred_dataset_size( filepath, scp_configs )
sample_dataset = trans.model.SampleDataset( sample=sample,
file_path=filepath,
status=status,
name=name,
error_msg='',
size=size,
external_service=external_service )
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 __rename_dataset( self, sample, filepath, scp_configs ):
name = filepath.split( '/' )[-1]
options = sample.request.type.rename_dataset_options
option = scp_configs.get( 'rename_dataset', options.NO )
if option == options.SAMPLE_NAME:
new_name = sample.name + '_' + name
if option == options.EXPERIMENT_AND_SAMPLE_NAME:
new_name = sample.request.name + '_' + sample.name + '_' + name
if option == options.EXPERIMENT_NAME:
new_name = sample.request.name + '_' + name
else:
new_name = name
return util.sanitize_for_filename( new_name )
def __ensure_library_add_permission( self, trans, target_library, target_folder ):
"""
Ensures the current admin user has ADD_LIBRARY permission on the target data library and folder.
@@ -594,92 +385,6 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitionsMixin ):
if flush_needed:
trans.sa_session.flush()
def __create_data_transfer_messages( self, trans, sample, selected_sample_datasets ):
"""
Creates the xml messages to send to the rabbitmq server. It returns a dictionary of messages
keyed by the external service used to transfer the datasets
"""
# Create the xml message based on the following template
xml = \
''' <data_transfer>
<galaxy_host>%(GALAXY_HOST)s</galaxy_host>
<api_key>%(API_KEY)s</api_key>
<data_host>%(DATA_HOST)s</data_host>
<data_user>%(DATA_USER)s</data_user>
<data_password>%(DATA_PASSWORD)s</data_password>
<request_id>%(REQUEST_ID)s</request_id>
<sample_id>%(SAMPLE_ID)s</sample_id>
<library_id>%(LIBRARY_ID)s</library_id>
<folder_id>%(FOLDER_ID)s</folder_id>
%(DATASETS)s
</data_transfer>'''
dataset_xml = \
'''<dataset>
<dataset_id>%(ID)s</dataset_id>
<name>%(NAME)s</name>
<file>%(FILE)s</file>
</dataset>'''
# Here we group all the sample_datasets by the external service used to transfer them.
# The idea is to bundle up the sample_datasets which uses the same external service and
# send a single AMQP message to the galaxy_listener
dataset_elements = {}
for sample_dataset in selected_sample_datasets:
external_service = sample_dataset.external_service
if sample_dataset.status == trans.app.model.SampleDataset.transfer_status.NOT_STARTED:
if external_service not in dataset_elements:
dataset_elements[ external_service ] = ''
dataset_elements[ external_service ] += dataset_xml % dict( ID=str( sample_dataset.id ),
NAME=sample_dataset.name,
FILE=sample_dataset.file_path )
# update the dataset transfer status
sample_dataset.status = trans.app.model.SampleDataset.transfer_status.IN_QUEUE
trans.sa_session.add( sample_dataset )
trans.sa_session.flush()
# Finally prepend the external service info to the sets of sample datasets
messages = []
for external_service, dataset_elem in dataset_elements.items():
external_service.load_data_transfer_settings( trans )
scp_configs = external_service.data_transfer[ trans.model.ExternalService.data_transfer_protocol.SCP ]
# Check data transfer settings
err_msg = self.__validate_data_transfer_settings( trans, sample.request.type, scp_configs )
if err_msg:
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='manage_datasets',
sample_id=trans.security.encode_id( sample.id ),
status='error',
message=err_msg ) )
message = xml % dict( GALAXY_HOST=trans.request.host,
API_KEY=trans.user.api_keys[0].key,
DATA_HOST=scp_configs[ 'host' ],
DATA_USER=scp_configs[ 'user_name' ],
DATA_PASSWORD=scp_configs[ 'password' ],
REQUEST_ID=str( sample.request.id ),
SAMPLE_ID=str( sample.id ),
LIBRARY_ID=str( sample.library.id ),
FOLDER_ID=str( sample.folder.id ),
DATASETS=dataset_elem )
messages.append( message.replace( '\n', '' ).replace( '\r', '' ) )
return messages
def __validate_data_transfer_settings( self, trans, request_type, scp_configs ):
err_msg = ''
# check the external service login info
if not scp_configs.get( 'host', '' ) or \
not scp_configs.get( 'user_name', '' ) or \
not scp_configs.get( 'password', '' ):
err_msg += "Error in external service login information. "
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 = "'The library_import_dir' setting is not correctly 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, specifically "%s" is not set.' % k
break
return err_msg
@web.expose
@web.require_admin
def initiate_data_transfer( self, trans, sample_id, sample_datasets=[], sample_dataset_id='' ):
@@ -721,35 +426,8 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitionsMixin ):
external_service=external_service,
external_service_type=external_service_type )
else:
# TODO: Using RabbitMq for now, but eliminate this entire block when we replace RabbitMq with Galaxy's
# own messaging engine. We're holding off on using the new way to transfer files manually until we
# implement a Galaxy-proprietary messaging engine because the deferred job plugins currently perform
# constant db hits to check for deferred jobs that are not in a finished state.
# Create the message
messages = self.__create_data_transfer_messages( trans, sample, sample_datasets )
# Send the messages
for rmq_msg in messages:
try:
conn = amqp.Connection( host=trans.app.config.amqp[ 'host' ] + ":" + trans.app.config.amqp[ 'port' ],
userid=trans.app.config.amqp[ 'userid' ],
password=trans.app.config.amqp[ 'password' ],
virtual_host=trans.app.config.amqp[ 'virtual_host' ])
chan = conn.channel()
msg = amqp.Message( rmq_msg,
content_type='text/plain',
application_headers={ 'msg_type': 'data_transfer' } )
msg.properties[ "delivery_mode" ] = 2
chan.basic_publish( msg,
exchange=trans.app.config.amqp[ 'exchange' ],
routing_key=trans.app.config.amqp[ 'routing_key' ] )
chan.close()
conn.close()
except Exception, e:
message = "Error sending the data transfer message to the Galaxy AMQP message queue:<br/>%s" % str(e)
status = "error"
if not message:
message = "%i datasets have been queued for transfer from the external service." % len( sample_datasets )
status = "done"
message = "Message queue transfer is no longer supported, please set enable_beta_job_managers = True in galaxy.ini"
status = "error"
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='manage_datasets',
sample_id=trans.security.encode_id( sample.id ),
@@ -1,148 +0,0 @@
<%inherit file="/base.mako"/>
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/requests/common/common.mako" import="render_sample_datasets" />
<%namespace file="/requests/common/common.mako" import="common_javascripts" />
<%def name="javascripts()">
${parent.javascripts()}
${common_javascripts()}
</%def>
${h.js( "libs/jquery/jquery-ui", "libs/jquery/jquery.cookie", "libs/jquery/jquery.dynatree" )}
${h.css( "dynatree_skin/ui.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: "${scp_configs['data_location']}",
minExpandLevel: 1, // 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: { request_id: "${trans.security.encode_id( request.id )}", external_service_id: "${trans.security.encode_id( external_service.id )}", key: "${scp_configs['data_location']}" },
},
onLazyRead: function(dtnode){
dtnode.appendAjax({
url: "${h.url_for( controller='requests_admin', action='open_folder' )}",
dataType: "json",
data: { request_id: "${trans.security.encode_id( request.id )}", external_service_id: "${trans.security.encode_id( external_service.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;
if (dtnode.data.key == 'root') {
selected_value = "${scp_configs['data_location']}/";
} else {
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: { request_id: "${trans.security.encode_id(request.id)}", external_service_id: "${trans.security.encode_id(external_service.id)}", folder_path: selected_value },
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 and sample.library and sample.folder
%>
<br/><br/>
<ul class="manage-table-actions">
<li><a class="action-button" href="${h.url_for( controller='external_service', action='view_external_service', id=trans.security.encode_id( external_service.id ) )}">External service 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:
<br/>
<font color="red"><b><i>Select a sample before selecting datasets to transfer</i></b></font>
<br/>
%endif
%if request.samples_without_library_destinations:
<p>
<font color="red"><b><i>Select a target data library and folder for a sample before selecting its datasets to transfer from the external service</i></b></font>
</p>
%endif
%if message:
${render_msg( message, status )}
%endif
<div class="toolForm">
<div class="toolFormTitle">Select datasets to transfer from data directory configured for the external service</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, external_service_id=trans.security.encode_id( external_service.id ), 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 external service 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>external service 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 = 'All selected datasets for "%s"' % sample.name %>
<p/>
${render_sample_datasets( 'requests_admin', sample, sample.datasets, title )}
%endif
@@ -333,7 +333,6 @@
can_add_samples = is_unsubmitted
can_delete_samples = not adding_new_samples and request.samples and ( ( is_admin and not is_complete ) or is_unsubmitted )
can_edit_samples = request.samples and ( is_admin or not is_complete )
can_select_datasets = is_admin and displayable_sample_widgets and ( is_submitted or is_complete )
can_transfer_datasets = is_admin and request.samples and not request.is_rejected
display_checkboxes = not adding_new_samples and ( is_complete or is_rejected or is_submitted )
display_bar_code = request.samples and ( is_complete or is_rejected or is_submitted )
@@ -407,7 +406,7 @@
%elif sample:
<tr>
<td>
%if sample.state and ( can_select_datasets or can_transfer_datasets ):
%if sample.state and can_transfer_datasets:
## A sample will have a state only after the request has been submitted.
<%
encoded_id = trans.security.encode_id( sample.id )
@@ -419,14 +418,6 @@
<a class="view-info" href="${h.url_for( controller='requests_common', action='view_sample', cntrller=cntrller, id=trans.security.encode_id( sample.id ) )}">${sample.name | h}</a>
</div>
<div popupmenu="sample-${sample.id}-popup">
%if can_select_datasets:
%for external_service in sample.request.type.get_external_services_for_manual_data_transfer( trans ):
<%
menu_item_label = "Select datasets to transfer using %s" % external_service.name
%>
<li><a class="action-button" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', external_service_id=trans.security.encode_id( external_service.id ), request_id=trans.security.encode_id( request.id ), sample_id=trans.security.encode_id( sample.id ) )}">${menu_item_label}</a></li>
%endfor
%endif
%if sample.datasets and len( sample.datasets ) > len( transferred_dataset_files ) and sample.library and sample.folder:
<li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=trans.security.encode_id( sample.id ) )}">Manage selected datasets</a></li>
%elif sample.datasets and len( sample.datasets ) == len( transferred_dataset_files ):
@@ -665,7 +656,6 @@
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
%>
## The transfer status should update only when the request has been submitted or complete
@@ -11,7 +11,6 @@
can_add_samples = is_unsubmitted
can_edit_request = ( is_admin and not is_complete ) or is_unsubmitted
can_reject = is_admin and is_submitted
can_select_datasets = is_admin and ( is_complete or is_submitted )
can_submit_request = request.samples and is_unsubmitted
%>
@@ -12,7 +12,6 @@
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 and sample.library and sample.folder
%>
@@ -24,9 +23,6 @@
%endif
<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, external_service_id=trans.security.encode_id( external_service.id ), 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>