mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
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:
@@ -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 ):
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user