lims: email notification enhancements

- changed the notify field in the request table to JSONType to store recipient email address and other config details
  - notification of specific sample state changes
     - changes made to barcode scan AMQP handler to update sample state to also send email when required
  - notification to multiple recipients
  - request events history records notifications
  - UI enhancements to the edit request page to allow users to change notification configurations
 UI enhancements to the request page to show the request type state, user & notification details
This commit is contained in:
Ramkrishna Chakrabarty
2010-08-26 11:17:49 -04:00
parent 94f3489952
commit 25dd565340
13 changed files with 515 additions and 169 deletions
+73 -3
View File
@@ -15,7 +15,7 @@ from galaxy.datatypes.metadata import MetadataCollection
from galaxy.security import RBACAgent, get_permitted_actions
from galaxy.util.hash_util import *
from galaxy.web.form_builder import *
import logging
import logging, smtplib, socket
log = logging.getLogger( __name__ )
from sqlalchemy.orm import object_session
import pexpect
@@ -1520,18 +1520,27 @@ class Request( object ):
REJECTED = 'Rejected',
COMPLETE = 'Complete' )
def __init__(self, name=None, desc=None, request_type=None, user=None,
form_values=None, notify=None):
form_values=None, notification=None):
self.name = name
self.desc = desc
self.type = request_type
self.values = form_values
self.user = user
self.notify = notify
self.notification = notification
self.samples_list = []
def state(self):
if self.events:
return self.events[0].state
return None
def common_state(self):
'''
This method returns the state of this request's sample when they are all
in one common state. If not this returns None
'''
for s in self.samples:
if s.current_state().id != self.samples[0].current_state().id:
return False
return self.samples[0].current_state()
def last_comment(self):
if self.events:
if self.events[0].comment:
@@ -1560,7 +1569,66 @@ class Request( object ):
if not s.library:
samples.append(s.name)
return samples
def send_email_notification(self, trans, common_state, final_state=False):
# check if an email notification is configured to be sent when the samples
# are in this state
if common_state.id not in self.notification['sample_states']:
return
comments = ''
# send email
if self.notification['email'] and trans.app.config.smtp_server is not None:
host = trans.request.host.split(':')[0]
if host in ['localhost', '127.0.0.1']:
host = socket.getfqdn()
body = """
Galaxy Sample Tracking Notification
===================================
User: %(user)s
Sequencing request: %(request_name)s
Sequencing request type: %(request_type)s
Sequencing request state: %(request_state)s
Number of samples: %(num_samples)s
All samples in state: %(sample_state)s
"""
values = dict(user=self.user.email,
request_name=self.name,
request_type=self.type.name,
request_state=self.state(),
num_samples=str(len(self.samples)),
sample_state=common_state.name,
create_time=self.create_time,
submit_time=self.create_time)
body = body % values
# check if this is the final state of the samples
if final_state:
txt = "Sample Name -> Data Library/Folder\r\n"
for s in self.samples:
txt = txt + "%s -> %s/%s\r\n" % (s.name, s.library.name, s.folder.name)
body = body + txt
to = self.notification['email']
frm = 'galaxy-no-reply@' + host
subject = "Galaxy Sample Tracking notification: '%s' sequencing request" % self.name
message = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % (frm, ", ".join(to), subject, body)
try:
s = smtplib.SMTP()
s.connect(trans.app.config.smtp_server)
s.sendmail(frm, to, message)
s.quit()
comments = "Email notification sent to %s." % ", ".join(to).strip().strip(',')
except:
comments = "Email notification failed."
# update the request history with the email notification event
elif not trans.app.config.smtp_server:
comments = "Email notification failed as SMTP server not set in config file"
if comments:
event = trans.app.model.RequestEvent(self, self.state(), comments)
trans.sa_session.add(event)
trans.sa_session.flush()
class RequestEvent( object ):
def __init__(self, request=None, request_state=None, comment=''):
@@ -1581,6 +1649,8 @@ class RequestType( object ):
self.request_form = request_form
self.sample_form = sample_form
self.datatx_info = datatx_info
def last_state(self):
return self.states[-1]
class RequestTypePermissions( object ):
def __init__( self, action, request_type, role ):
+1 -1
View File
@@ -560,7 +560,7 @@ Request.table = Table('request', metadata,
Column( "update_time", DateTime, default=now, onupdate=now ),
Column( "name", TrimmedString( 255 ), nullable=False ),
Column( "desc", TEXT ),
Column( "notify", Boolean, default=False ),
Column( "notification", JSONType() ),
Column( "form_values_id", Integer, ForeignKey( "form_values.id" ), index=True ),
Column( "request_type_id", Integer, ForeignKey( "request_type.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
@@ -0,0 +1,69 @@
"""
Migration script to modify the 'notify' field in the 'request' table from a boolean
to a JSONType
"""
from sqlalchemy import *
from sqlalchemy.orm import *
from migrate import *
from migrate.changeset import *
from sqlalchemy.exc import *
from galaxy.model.custom_types import *
from galaxy.util.json import from_json_string, to_json_string
import datetime
now = datetime.datetime.utcnow
import logging
log = logging.getLogger( __name__ )
metadata = MetaData( migrate_engine )
db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
def upgrade():
print __doc__
metadata.reflect()
try:
Request_table = Table( "request", metadata, autoload=True )
except NoSuchTableError, e:
Request_table = None
log.debug( "Failed loading table 'request'" )
if Request_table:
# create the column again as JSONType
try:
col = Column( "notification", JSONType() )
col.create( Request_table )
assert col is Request_table.c.notification
except Exception, e:
log.debug( "Creating column 'notification' in the 'request' table failed: %s" % ( str( e ) ) )
cmd = "SELECT id, user_id, notify FROM request"
result = db_session.execute( cmd )
for r in result:
id = int(r[0])
notify_old = r[1]
notify_new = dict(email=[], sample_states=[], body='', subject='')
cmd = "update request set notification='%s' where id=%i" % (to_json_string(notify_new), id)
db_session.execute( cmd )
cmd = "SELECT id, notification FROM request"
result = db_session.execute( cmd )
for r in result:
print r, str(r[1])
rr = from_json_string(str(r[1]))
print r[0], type(rr), rr, rr['email'], rr['sample_states']
# remove the 'notify' column
try:
Request_table.c.notify.drop()
except Exception, e:
log.debug( "Deleting column 'notify' from the 'request' table failed: %s" % ( str( e ) ) )
def downgrade():
pass
+53 -43
View File
@@ -136,6 +136,7 @@ class RequestsGrid( grids.Grid ):
grids.GridOperation( "Reject", allow_multiple=False, condition=( lambda item: not item.deleted and item.submitted() ) ),
grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ),
grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ),
grids.GridOperation( "Purge", allow_multiple=False, confirm="This will permanently delete the sequencing request. Click OK to proceed.", condition=( lambda item: item.deleted ) ),
]
global_actions = [
grids.GridAction( "Create new request", dict( controller='requests_common',
@@ -317,11 +318,6 @@ class RequestsAdmin( BaseController ):
cntrller='requests_admin',
action='events',
**kwd ) )
if operation == "settings":
return trans.response.send_redirect( web.url_for( controller='requests_common',
cntrller='requests_admin',
action='settings',
**kwd ) )
elif operation == "reject":
return self.__reject_request( trans, **kwd )
elif operation == "view_type":
@@ -514,41 +510,52 @@ class RequestsAdmin( BaseController ):
id=trans.security.encode_id(request.id),
message='Bar codes have been saved for this request',
status='done'))
def __set_request_state( self, trans, request ):
# check if all the samples of the current request are in the final state
complete = True
for s in request.samples:
if s.current_state().id != request.type.states[-1].id:
complete = False
if request.complete() and not complete:
comments = "One or more samples moved back from the %s state" % request.type.states[-1].name
event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, comments)
trans.sa_session.add( event )
trans.sa_session.flush()
elif complete:
# change the request state to 'Complete'
comments = "All samples of this request are in the last sample state (%s)." % request.type.states[-1].name
event = trans.app.model.RequestEvent(request, request.states.COMPLETE, comments)
trans.sa_session.add( event )
trans.sa_session.flush()
# now that the request is complete send the email notification to the
# the user
if request.notify and trans.app.config.smtp_server is not None:
host = trans.request.host.split(':')[0]
if host == 'localhost':
host = socket.getfqdn()
body = "The '%s' sequencing request (type: %s) is now complete. Datasets from all the samples are now available for analysis or download from the respective data libraries in Galaxy." % ( request.name, request.type.name )
to = [request.user.email]
frm = 'galaxy-no-reply@' + host
subject = "Galaxy Sample Tracking: '%s' sequencing request in complete." % request.name
message = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % (frm, ", ".join(to), subject, body)
try:
s = smtplib.SMTP()
s.connect( trans.app.config.smtp_server )
s.sendmail( frm, [ to ], message )
s.close()
except:
pass
@web.expose
@web.require_admin
def update_request_state( self, trans, **kwd ):
params = util.Params( kwd )
try:
request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
sample_id = int(params.get('sample_id', False))
except:
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='list',
status='error',
message="Invalid request ID",
**kwd) )
# check if all the samples of the current request are in the sample state
common_state = request.common_state()
if not common_state:
# 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.complete():
event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, "One or more samples' state moved from the final sample state.")
trans.sa_session.add( event )
trans.sa_session.flush()
return trans.response.send_redirect( web.url_for( controller='requests_common',
cntrller='requests_admin',
action='sample_events',
sample_id=sample_id))
final_state = False
if common_state.id == request.type.last_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.last_state().name
state = request.states.COMPLETE
final_state = True
else:
comments = "All samples are in %s state." % common_state.name
state = request.states.SUBMITTED
event = trans.app.model.RequestEvent(request, state, comments)
trans.sa_session.add( event )
trans.sa_session.flush()
# check if an email notification is configured to be sent when the samples
# are in this state
# if common_state.id in request.notification['sample_states']:
request.send_email_notification(trans, common_state, final_state)
return trans.response.send_redirect( web.url_for( controller='requests_common',
cntrller='requests_admin',
action='sample_events',
sample_id=sample_id))
@web.expose
@web.require_admin
@@ -573,10 +580,10 @@ class RequestsAdmin( BaseController ):
event = trans.app.model.SampleEvent(sample, new_state, comments)
trans.sa_session.add( event )
trans.sa_session.flush()
self.__set_request_state( trans, sample.request )
return trans.response.send_redirect( web.url_for( controller='requests_common',
return trans.response.send_redirect( web.url_for( controller='requests_admin',
cntrller='requests_admin',
action='sample_events',
action='update_request_state',
request_id=sample.request.id,
sample_id=sample.id))
#
# Data transfer from sequencer
@@ -1290,3 +1297,6 @@ class RequestsAdmin( BaseController ):
roles=roles,
status=status,
message=message)
+99 -33
View File
@@ -13,7 +13,7 @@ from sqlalchemy.sql import select
import pexpect
import ConfigParser, threading, time
from amqplib import client_0_8 as amqp
import csv
import csv, smtplib, socket
log = logging.getLogger( __name__ )
@@ -151,9 +151,6 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
util.restore_text( params.get( 'desc', '' ) )),
helptext='(Optional)'))
widgets = widgets + request_type.request_form.get_widgets( user, **kwd )
widgets.append(dict(label='Send email notification when the sequencing request is complete',
widget=CheckboxField('email_notify', False),
helptext='Email would be sent to the lab admin and the user for whom this request has been created.'))
return trans.fill_template( '/requests/common/new_request.mako',
cntrller=cntrller,
select_request_type=select_request_type,
@@ -188,17 +185,18 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
'''
params = util.Params( kwd )
cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
if request:
user = request.user
request_type = request.type
else:
request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
if cntrller == 'requests_admin' and trans.user_is_admin():
user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'select_user', '' ) ) )
elif cntrller == 'requests':
user = trans.user
name = util.restore_text(params.get('name', ''))
desc = util.restore_text(params.get('desc', ''))
notify = CheckboxField.is_checked( params.get('email_notify', '') )
notification = dict(email=[user.email], sample_states=[request_type.last_state().id], body='', subject='')
# fields
values = []
for index, field in enumerate(request_type.request_form.fields):
@@ -223,7 +221,7 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
trans.sa_session.flush()
if not request:
request = trans.app.model.Request(name, desc, request_type,
user, form_values, notify)
user, form_values, notification)
trans.sa_session.add( request )
trans.sa_session.flush()
trans.sa_session.refresh( request )
@@ -240,12 +238,66 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
request.desc = desc
request.type = request_type
request.user = user
request.notify = notify
request.notification = notification
request.values = form_values
trans.sa_session.add( request )
trans.sa_session.flush()
return request
@web.expose
@web.require_login( "create/submit sequencing requests" )
def email_settings(self, trans, **kwd):
params = util.Params( kwd )
cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
try:
request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id( params.get( 'id', None ) ) )
except:
return trans.response.send_redirect( web.url_for( controller=cntrller,
action='list',
status='error',
message="Invalid request ID") )
email_user = CheckboxField.is_checked( params.get('email_user', '') )
email_additional = params.get('email_additional', '').split('\r\n')
if email_user or email_additional:
emails = []
if email_user:
emails.append(request.user.email)
for e in email_additional:
emails.append(util.restore_text(e))
# check if valid email addresses
invalid = ''
for e in emails:
if len( e ) == 0 or "@" not in e or "." not in e:
invalid = e
break
if invalid:
message = "<b>%s</b> is not a valid email address." % invalid
return trans.response.send_redirect( web.url_for( controller='requests_common',
cntrller=cntrller,
action='edit',
show=True,
id=trans.security.encode_id(request.id),
message=message ,
status='error' ) )
else:
email_states = []
for i, ss in enumerate(request.type.states):
if CheckboxField.is_checked( params.get('sample_state_%i' % ss.id, '') ):
email_states.append(ss.id)
request.notification = dict(email=emails, sample_states=email_states,
body='', subject='')
trans.sa_session.flush()
trans.sa_session.refresh( request )
message = 'The changes made to the sequencing request has been saved'
return trans.response.send_redirect( web.url_for( controller='requests_common',
cntrller=cntrller,
action='show',
id=trans.security.encode_id(request.id),
message=message ,
status='done') )
@web.expose
@web.require_login( "create/submit sequencing requests" )
def edit(self, trans, **kwd):
@@ -262,9 +314,8 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
message="Invalid request ID") )
if params.get('show', False) == 'True':
return self.__edit_request(trans, **kwd)
elif params.get('save_changes_request_button', False) == 'Save changes' \
elif params.get('save_changes_request_button', False) == 'Save' \
or params.get('edit_samples_button', False) == 'Edit samples':
request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
if not util.restore_text(params.get('name', '')):
message = 'Please enter the <b>Name</b> of the request'
kwd['status'] = 'error'
@@ -276,7 +327,7 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
**kwd) )
request = self.__save_request(trans, request, **kwd)
message = 'The changes made to the request named %s has been saved' % request.name
if params.get('save_changes_request_button', False) == 'Save changes':
if params.get('save_changes_request_button', False) == 'Save':
return trans.response.send_redirect( web.url_for( controller=cntrller,
cntrller=cntrller,
action='list',
@@ -310,7 +361,7 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
select_request_type = self.__select_request_type(trans, request.type.id)
#select_request_type = self.__select_request_type(trans, request.type.id)
# list of widgets to be rendered on the request form
widgets = []
if util.restore_text( params.get( 'name', '' ) ):
@@ -328,12 +379,9 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
widget=TextField('desc', 40, desc),
helptext='(Optional)'))
widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd )
widgets.append(dict(label='Send email notification once the sequencing request is complete',
widget=CheckboxField('email_notify', request.notify),
helptext=''))
return trans.fill_template( 'requests/common/edit_request.mako',
cntrller=cntrller,
select_request_type=select_request_type,
#select_request_type=select_request_type,
request_type=request.type,
request=request,
widgets=widgets,
@@ -399,6 +447,7 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
trans.sa_session.add( event )
trans.sa_session.add( request )
trans.sa_session.flush()
request.send_email_notification(trans, new_state)
return trans.response.send_redirect( web.url_for( controller=cntrller,
action='list',
id=trans.security.encode_id(request.id),
@@ -486,6 +535,22 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
events_list=events_list, request=request)
@web.expose
@web.require_login( "create/submit sequencing requests" )
def settings(self, trans, **kwd):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
cntrller = params.get( 'cntrller', 'requests' )
try:
request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
except:
return trans.response.send_redirect( web.url_for( controller=cntrller,
action='list',
status='error',
message="Invalid request ID") )
return trans.fill_template( '/requests/common/settings.mako',
cntrller=cntrller, request=request)
@web.expose
@web.require_login( "create/submit sequencing requests" )
def show(self, trans, **kwd):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
@@ -807,6 +872,17 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
'Sample added to the system')
trans.sa_session.add( event )
trans.sa_session.flush()
# now check if all the samples' barcode has been entered.
# If yes then send notification email if configured
common_state = request.common_state()
if common_state:
if common_state.id == request.type.states[1].id:
event = trans.app.model.RequestEvent(request,
request.states.SUBMITTED,
"All samples are in %s state." % common_state.name)
trans.sa_session.add( event )
trans.sa_session.flush()
request.send_email_notification(trans, request.type.states[1])
sample.bar_code = current_samples[sample_index]['barcode']
trans.sa_session.add( sample )
trans.sa_session.flush()
@@ -962,21 +1038,16 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
# list of widgets to be rendered on the request form
request_details = []
# main details
request_details.append(dict(label='User',
value=str(request.user.email),
helptext=''))
request_details.append(dict(label='Description',
value=request.desc,
helptext=''))
request_details.append(dict(label='Type',
value=request.type.name,
helptext=''))
request_details.append(dict(label='State',
value=request.state(),
helptext=''))
request_details.append(dict(label='Date created',
value=request.create_time,
helptext=''))
request_details.append(dict(label='Date updated',
value=request.update_time,
helptext=''))
# form fields
for index, field in enumerate(request.type.request_form.fields):
if field['required']:
@@ -996,13 +1067,6 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
request_details.append(dict(label=field['label'],
value=request.values.content[index],
helptext=field['helptext']+' ('+req+')'))
if request.notify:
notify = 'Yes'
else:
notify = 'No'
request_details.append(dict(label='Send email notification once the sequencing request is complete',
value=notify,
helptext=''))
return request_details
@web.expose
@web.require_login( "create/submit sequencing requests" )
@@ -1089,3 +1153,5 @@ class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
dataset_files=sample.datasets,
message=message, status=status, files=[],
folder_path=folder_path )
@@ -16,11 +16,18 @@ import xml.dom.minidom
import subprocess
from galaxydb_interface import GalaxyDbInterface
new_path = [ os.path.join( os.getcwd(), "scripts/galaxy_messaging/server" ) ]
new_path.extend( sys.path[1:] ) # remove scripts/ from the path
sys.path = new_path
from galaxyweb_interface import GalaxyWebInterface
assert sys.version_info[:2] >= ( 2, 4 )
new_path = [ os.path.join( os.getcwd(), "lib" ) ]
new_path.extend( sys.path[1:] ) # remove scripts/ from the path
sys.path = new_path
from galaxy import eggs
import pkg_resources
pkg_resources.require( "amqplib" )
@@ -66,6 +73,7 @@ def get_value_index(dom, tag_name, index):
def recv_callback(msg):
global config
global webconfig
# check the meesage type.
msg_type = msg.properties['application_headers'].get('msg_type')
log.debug('\nMESSAGE RECVD: '+str(msg_type))
@@ -88,12 +96,20 @@ def recv_callback(msg):
log.debug('Barcode: '+barcode)
log.debug('State: '+state)
# update the galaxy db
galaxy = GalaxyDbInterface(dbconnstr)
sample_id = galaxy.get_sample_id(field_name='bar_code', value=barcode)
galaxydb = GalaxyDbInterface(dbconnstr)
sample_id = galaxydb.get_sample_id(field_name='bar_code', value=barcode)
if sample_id == -1:
log.debug('Invalid barcode.')
return
galaxy.change_state(sample_id, state)
galaxydb.change_state(sample_id, state)
# update the request state
galaxyweb = GalaxyWebInterface(webconfig.get("universe_wsgi_config", "host"),
webconfig.get("universe_wsgi_config", "port"),
webconfig.get("data_transfer_user_login_info", "email"),
webconfig.get("data_transfer_user_login_info", "password"),
config.get("app:main", "id_secret"))
galaxyweb.update_request_state(galaxydb.get_request_id(sample_id), sample_id)
galaxyweb.logout()
def main():
if len(sys.argv) < 2:
@@ -108,6 +124,15 @@ def main():
for option in config.options("galaxy_amqp"):
amqp_config[option] = config.get("galaxy_amqp", option)
log.debug(str(amqp_config))
# web server config
global webconfig
webconfig = ConfigParser.ConfigParser()
webconfig.read('transfer_datasets.ini')
conn = amqp.Connection(host=amqp_config['host']+":"+amqp_config['port'],
userid=amqp_config['userid'],
password=amqp_config['password'],
@@ -57,6 +57,12 @@ class GalaxyDbInterface(object):
#log.debug('Sample ID: %i' % sample_id)
return sample_id
return -1
def get_request_id(self, sample_id):
query = select(columns=[self.sample_table.c.request_id],
whereclause=self.sample_table.c.id==sample_id)
request_id = query.execute().fetchall()[0][0]
return request_id
def current_state(self, sample_id):
'''
@@ -105,3 +105,19 @@ class GalaxyWebInterface(object):
s = ( "!" * ( 8 - len(s) % 8 ) ) + s
# Encrypt
return id_cipher.encrypt( s ).encode( 'hex' )
def update_request_state(self, request_id, sample_id):
params = urllib.urlencode(dict( cntrller='requests_admin',
request_id=request_id,
sample_id=sample_id))
url = self.base_url + "/requests_admin/update_request_state"
f = self.opener.open(url, params)
print url
print params
x = f.read()
+75 -37
View File
@@ -37,48 +37,86 @@ $( function() {
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(request.id))}">
<span>Browse this request</span></a>
</li>
<li>
<a class="action-button" href="${h.url_for( controller=cntrller, action='list')}">
<span>Browse all requests</span></a>
</li>
</ul>
<div class="toolForm">
<div class="toolFormTitle">Edit request "${request.name}"</div>
%if len(select_request_type.options) == 1:
There are no request types created for a new request.
%else:
<div class="toolFormBody">
<form name="edit_request" id="edit_request" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='edit', id=trans.security.encode_id(request.id))}" method="post" >
<div class="toolFormTitle">Edit sequencing request "${request.name}"</div>
<div class="toolFormBody">
<form name="edit_request" id="edit_request" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='edit', id=trans.security.encode_id(request.id))}" method="post" >
%for i, field in enumerate(widgets):
<div class="form-row">
<label>
Select Request Type:
</label>
${select_request_type.get_html()}
<label>${field['label']}</label>
${field['widget'].get_html()}
%if field['label'] == 'Data library' and new_library:
${new_library.get_html()}
%endif
<div class="toolParamHelp" style="clear: both;">
${field['helptext']}
</div>
<div style="clear: both"></div>
</div>
%if select_request_type.get_selected() != ('Select one', 'none'):
%for i, field in enumerate(widgets):
<div class="form-row">
<label>${field['label']}</label>
${field['widget'].get_html()}
%if field['label'] == 'Data library' and new_library:
${new_library.get_html()}
%endif
<div class="toolParamHelp" style="clear: both;">
${field['helptext']}
</div>
<div style="clear: both"></div>
</div>
%endfor
<div class="form-row">
<div style="float: left; width: 250px; margin-right: 10px;">
<input type="hidden" name="refresh" value="true" size="40"/>
</div>
<div style="clear: both"></div>
</div>
<div class="form-row">
<input type="submit" name="save_changes_request_button" value="Save changes"/>
##<input type="submit" name="edit_samples_button" value="Edit samples"/>
</div>
%endif
%endfor
<div class="form-row">
<div style="float: left; width: 250px; margin-right: 10px;">
<input type="hidden" name="refresh" value="true" size="40"/>
</div>
<div style="clear: both"></div>
</div>
<div class="form-row">
<input type="submit" name="save_changes_request_button" value="Save"/>
</div>
</form>
</div>
<div class="toolFormTitle">Email notification settings</div>
<div class="toolFormBody">
<form name="settings" id="settings" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='email_settings', id=trans.security.encode_id(request.id))}" method="post" >
<%
email_user = ''
email_additional = []
for e in request.notification['email']:
if e == request.user.email:
email_user = 'checked'
else:
email_additional.append(e)
emails = '\r\n'.join(email_additional)
%>
<div class="form-row">
<label>Send to:</label>
<input type="checkbox" name="email_user" value="true" ${email_user}>${request.user.email} (Sequencing request owner)<input type="hidden" name="email_user" value="true">
</div>
<div class="form-row">
<label>Additional email addresses:</label>
<textarea name="email_additional" rows="3" cols="40">${emails}</textarea>
<div class="toolParamHelp" style="clear: both;">
Enter one email address per line
</div>
</div>
<div class="form-row">
<label>Select sample state(s) to send email notification:</label>
%for ss in request.type.states:
<%
email_state = ''
if ss.id in request.notification['sample_states']:
email_state = 'checked'
%>
<input type="checkbox" name=sample_state_${ss.id} value="true" ${email_state} >${ss.name}<input type="hidden" name=sample_state_${ss.id} value="true">
<br/>
%endfor
<div class="toolParamHelp" style="clear: both;">
Email notification would be sent when all the samples of this sequencing request are in the selected state(s).
</div>
</div>
<div class="form-row">
<input type="submit" name="save_button" value="Save"/>
</div>
</form>
</div>
</div>
%endif
+2 -2
View File
@@ -85,7 +85,7 @@ $( function() {
</div>
<div style="clear: both"></div>
</div>
</form>
</div>
</form>
</div>
</div>
%endif
@@ -17,6 +17,13 @@
%endif
<div class="toolForm">
<div class="form-row">
<div class="toolParamHelp" style="clear: both;">
<b>Possible states: </b>
<% states = " > ".join([ ss.name for ss in sample.request.type.states ]) %>
${states}
</div>
</div>
<table class="grid">
<thead>
<tr>
+85 -46
View File
@@ -131,21 +131,35 @@ $(document).ready(function(){
document.onkeypress = stopRKey
</script>
%if request.submitted():
<% samples_not_ready = request.sequence_run_ready() %>
%if samples_not_ready:
${render_msg( "Select a target library and folder for all the samples before starting the sequence run", "warning" )}
%endif
<% samples_not_ready = request.sequence_run_ready() %>
%if samples_not_ready:
${render_msg( "Select a target data library and folder for all the samples before starting the sequence run", "warning" )}
%endif
%if request.rejected():
${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )}
%endif
%if message:
${render_msg( message, status )}
%endif
<div class="grid-header">
<h2>Sequencing Request "${request.name}"</h2>
<div class="toolParamHelp" style="clear: both;">
<b>Type</b>: ${request.type.name}
%if cntrller == 'requests_admin':
| <b>User</b>: ${request.user.email}
%endif
%if request.state() == request.states.SUBMITTED:
| <b>State</b>: <i>${request.state()}</i>
%else:
| <b>State</b>: ${request.state()}
%endif
</div>
</div>
<br/>
<ul class="manage-table-actions">
<li><a class="action-button" id="seqreq-${request.id}-popup" class="menubutton">Sequencing Request Actions</a></li>
<div popupmenu="seqreq-${request.id}-popup">
@@ -153,10 +167,10 @@ $(document).ready(function(){
<a class="action-button" confirm="More samples cannot be added to this request once it is submitted. Click OK to submit." href="${h.url_for( controller=cntrller, action='list', operation='Submit', id=trans.security.encode_id(request.id) )}">
<span>Submit</span></a>
%endif
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
<span>Edit</span></a>
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}">
<span>History</span></a>
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
<span>Edit</span></a>
%if cntrller == 'requests_admin' and trans.user_is_admin():
%if request.submitted():
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='reject', id=trans.security.encode_id(request.id))}">
@@ -173,41 +187,71 @@ $(document).ready(function(){
</ul>
%if message:
${render_msg( message, status )}
%endif
<div>
<h3><img src="/static/images/fugue/toggle-expand.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> Request Information</h3>
<div style="display:none;" >
%for index, rd in enumerate(request_details):
<div class="form-row">
<label>${rd['label']}</label>
%if not rd['value']:
<i>None</i>
%else:
%if rd['label'] == 'State':
<a href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a>
%else:
${rd['value']}
%endif
%endif
</div>
<div style="clear: both"></div>
%endfor
<div class="form-row">
<ul class="manage-table-actions">
<li>
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
<span>Edit request details</span></a>
</li>
</ul>
</div>
<h4><img src="/static/images/fugue/toggle-expand.png" alt="Show" onclick="showContent(this);" style="cursor:pointer;"/> Request Information</h4>
<div style="display:none;" >
<table class="grid" border="0">
<tbody>
<tr>
<td valign="top" width="50%">
%for index, rd in enumerate(request_details):
<div class="form-row">
<label>${rd['label']}:</label>
%if not rd['value']:
<i>None</i>
%else:
%if rd['label'] == 'State':
<a href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a>
%else:
${rd['value']}
%endif
%endif
</div>
<div style="clear: both"></div>
%endfor
</td>
<td valign="top" width="50%">
<div class="form-row">
<label>Email notification recipient(s):</label>
<% emails = ', '.join(request.notification['email']) %>
%if emails:
${emails}
%else:
<i>None</i>
%endif
</div>
<div style="clear: both"></div>
<div class="form-row">
<label>Email notification on sample state(s):</label>
<%
states = []
for ss in request.type.states:
if ss.id in request.notification['sample_states']:
states.append(ss.name)
states = ', '.join(states)
%>
%if states:
${states}
%else:
<i>None</i>
%endif
</div>
<div style="clear: both"></div>
</td>
</tr>
</tbody>
</table>
<div class="form-row">
<ul class="manage-table-actions">
<li>
<a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
<span>Edit request information</span></a>
</li>
</ul>
</div>
</div>
</div>
</div>
<br/>
@@ -395,12 +439,7 @@ $(document).ready(function(){
<td>${info['name']}</td>
<td>${info['barcode']}</td>
%if sample.request.unsubmitted():
##<td>Unsubmitted</td>
<td>
<div id="history-name-container">
<div id="history-name" class="tooltip editable-text" title="Click to rename history">Unsubmitted</div>
</div>
</td>
<td>Unsubmitted</td>
%else:
<td id="sampleState-${sample.id}">${render_sample_state( cntrller, sample )}</td>
%endif
+1 -1
View File
@@ -1503,7 +1503,7 @@ class TwillTestCase( unittest.TestCase ):
def edit_request( self, request_id, name, new_name, new_desc, new_fields):
self.home()
self.visit_url( "%s/requests/list?operation=Edit&id=%s" % (self.url, self.security.encode_id(request_id) ) )
self.check_page_for_string( 'Edit request "%s"' % name )
self.check_page_for_string( 'Edit sequencing request "%s"' % name )
tc.fv( "1", "name", new_name )
tc.fv( "1", "desc", new_desc )
for index, field_value in enumerate(new_fields):