# HG changeset patch --
Bitbucket.org
# Project galaxy-dist
# URL
http://bitbucket.org/galaxy/galaxy-dist/overview
# User rc
# Date 1282835869 14400
# Node ID 7b9f46307153d0bba1b2253204ad28695d373313
# Parent d24fca10862b5b36ba3dbe8a9f318e2e77ef60f2
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
--- a/scripts/galaxy_messaging/server/galaxyweb_interface.py
+++ b/scripts/galaxy_messaging/server/galaxyweb_interface.py
@@ -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()
+
+
+
+
+
+
--- a/templates/requests/common/new_request.mako
+++ b/templates/requests/common/new_request.mako
@@ -85,7 +85,7 @@
</div><div style="clear:
both"></div></div>
- </form>
- </div>
+ </form>
+ </div></div>
%endif
--- a/scripts/galaxy_messaging/server/amqp_consumer.py
+++ b/scripts/galaxy_messaging/server/amqp_consumer.py
@@ -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'],
--- a/templates/requests/common/sample_events.mako
+++ b/templates/requests/common/sample_events.mako
@@ -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>
--- a/templates/requests/common/edit_request.mako
+++ b/templates/requests/common/edit_request.mako
@@ -37,48 +37,86 @@
<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
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -15,7 +15,7 @@ from galaxy.datatypes.metadata import Me
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 ):
--- a/lib/galaxy/web/controllers/requests_common.py
+++ b/lib/galaxy/web/controllers/requests_common.py
@@ -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, Us
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, Us
'''
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, Us
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, Us
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, Us
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, Us
**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, Us
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, Us
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, Us
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, Us
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, Us
'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, Us
# 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, Us
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, Us
dataset_files=sample.datasets,
message=message, status=status, files=[],
folder_path=folder_path )
+
+
--- a/templates/requests/common/show_request.mako
+++ b/templates/requests/common/show_request.mako
@@ -131,21 +131,35 @@ function showContent(vThis)
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 @@ function showContent(vThis)
<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='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>
- <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>
%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 @@ function showContent(vThis)
</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 @@ function showContent(vThis)
<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
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -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)
+
+
+
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -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):
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -560,7 +560,7 @@ Request.table = Table('request', metadat
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 ),
--- /dev/null
+++ b/lib/galaxy/model/migrate/versions/0057_request_notify.py
@@ -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
--- a/scripts/galaxy_messaging/server/galaxydb_interface.py
+++ b/scripts/galaxy_messaging/server/galaxydb_interface.py
@@ -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):
'''