# HG changeset patch -- Bitbucket.org # Project galaxy-dist # URL http://bitbucket.org/galaxy/galaxy-dist/overview # User Dannon Baker <dannon.baker@emory.edu> # Date 1277238844 14400 # Node ID f2f31fe4cf08f9500a886184b5c5f15fa4184e4d # Parent 1f8035729ccd73f31522e816cf5467409e841b91 New: Send Email Action, Delete Action. Note that delete action is dangerous and will break a workflow if you delete datasets that are required in later steps. Partial refactoring to make addition of actions simpler. --- a/lib/galaxy/jobs/actions/post.py +++ b/lib/galaxy/jobs/actions/post.py @@ -5,18 +5,50 @@ from galaxy.util.json import from_json_s from galaxy.web.form_builder import * + +# For email notification PJA +from email.MIMEText import MIMEText +import smtplib + log = logging.getLogger( __name__ ) # DBTODO This still needs refactoring and general cleanup. +def get_form_template(action_type, title, content, help, on_output = True ): + if on_output: + form = """ + if (pja.action_type == "%s"){ + p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + pja.output_name + "</span><div class='toolFormTitle'> %s <br/> on " + pja.output_name + "\ + <div style='float: right;' class='buttons'><img src='../images/delete_icon.png'></div></div><div class='toolFormBody'>"; + %s + p_str += "</div><div class='toolParamHelp'>%s</div></div>"; + }""" % (action_type, title, content, help) + else: + form = """ + if (pja.action_type == "%s"){ + p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + "</span><div class='toolFormTitle'> %s \ + <div style='float: right;' class='buttons'><img src='../images/delete_icon.png'></div></div><div class='toolFormBody'>"; + %s + p_str += "</div><div class='toolParamHelp'>%s</div></div>"; + }""" % (action_type, title, content, help) + return form + +# def get_field(action, argument, i_type, label = None): +# fstr = '' +# fname = """pja__"+pja.output_name+"__%s__%s""" % (action, argument) +# if label: +# fstr += """<label for='pja__"+pja.output_name+"__ColumnSetAction__chromCol'>Chrom Column</label>""" +# fstr += """<input type='text' value=" + chromCol + " name='pja__"+pja.output_name+"__ColumnSetAction__chromCol'/>""" + class DefaultJobAction(object): name = "DefaultJobAction" + verbose_name = "Default Job" @classmethod def execute(cls, job): pass - @classmethod + @classmethod def get_config_form(cls, trans): return "<p>Default Job Action Config Form</p>" @@ -26,11 +58,42 @@ class DefaultJobAction(object): return "%s -> %s" % (pja.action_type, pja.action_arguments) else: return "%s" % pja.action_type - + + +class EmailAction(DefaultJobAction): + name = "EmailAction" + verbose_name = "Email Notification" + @classmethod + def execute(cls, trans, action, job): + smtp_server = trans.app.config.smtp_server + if smtp_server is None: + return trans.show_error_message( "Mail is not configured for this galaxy instance, workflow action aborted." ) + # Build the email message + msg = MIMEText( "Your job '%s' at Galaxy instance %s is complete as of %s." % (job.history.name, trans.request.host, job.update_time)) + msg[ 'To' ] = job.user.email + msg[ 'From' ] = job.user.email + msg[ 'Subject' ] = "Galaxy workflow step notification '%s'" + try: + s = smtplib.SMTP() + s.connect( smtp_server ) + s.sendmail( frm, [ to ], msg.as_string() ) + s.close() + return trans.show_ok_message( "Your error report has been sent" ) + except Exception, e: + return trans.show_error_message( "An error occurred sending the report by email: %s" % str( e ) ) + + @classmethod + def get_config_form(cls, trans): + form = """ + p_str += "<label for='pja__"+pja.output_name+"__EmailAction'>There are no additional options for this action. You will be emailed upon job completion.</label>\ + <input type='hidden' name='pja__"+pja.output_name+"__EmailAction'/>"; + """ + return get_form_template(cls.name, cls.verbose_name, form, "This action will send an email notifying you when the job is done.", on_output = False) + class ChangeDatatypeAction(DefaultJobAction): name = "ChangeDatatypeAction" - + verbose_name = "Change Datatype" @classmethod def execute(cls, trans, action, job): for dataset_assoc in job.output_datasets: @@ -45,24 +108,21 @@ class ChangeDatatypeAction(DefaultJobAct for dt_name in dtnames: dt_list += """<option id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype__%s' value='%s'>%s</option>""" % (dt_name, dt_name, dt_name) ps = """ - if (pja.action_type == "ChangeDatatypeAction"){ - p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + pja.output_name + "</span><div class='toolFormTitle'>" + pja.action_type + " <br/> on " + pja.output_name + "\ - <div style='float: right;' class='buttons'><img src='../images/delete_icon.png'></div></div>\ - <div class='toolFormBody'><label for='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>New Datatype:</label><select id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype' name='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>\ + p_str += "<label for='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>New Datatype:</label>\ + <select id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype' name='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>\ %s\ </select>"; - if (pja.action_arguments != undefined && pja.action_arguments.newtype != undefined){ - p_str += "<scrip" + "t type='text/javascript'>$('#pja__" + pja.output_name + "__ChangeDatatypeAction__newtype').val('" + pja.action_arguments.newtype + "');</scrip" + "t>" - } - p_str += "</div><div class='toolParamHelp'>This action will change the datatype of the output to the indicated value.</div></div>"; - } + if (pja.action_arguments != undefined && pja.action_arguments.newtype != undefined){ + p_str += "<scrip" + "t type='text/javascript'>$('#pja__" + pja.output_name + "__ChangeDatatypeAction__newtype').val('" + pja.action_arguments.newtype + "');</scrip" + "t>"; + } """ % dt_list # Note the scrip + t hack above. Is there a better way? - return ps + return get_form_template(cls.name, cls.verbose_name, ps, 'This action will change the datatype of the output to the indicated value.') class RenameDatasetAction(DefaultJobAction): name = "RenameDatasetAction" - + verbose_name = "Rename Dataset" + @classmethod def execute(cls, trans, action, job): for dataset_assoc in job.output_datasets: @@ -71,23 +131,22 @@ class RenameDatasetAction(DefaultJobActi @classmethod def get_config_form(cls, trans): - return """ - if (pja.action_type == "RenameDatasetAction"){ - p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + pja.output_name + "</span><div class='toolFormTitle'>"+ pja.action_type + " <br/> on " + pja.output_name + "\ - <div style='float: right;' class='buttons'><img src='../images/delete_icon.png'></div></div><div class='toolFormBody'>"; - if ((pja.action_arguments != undefined) && (pja.action_arguments.newname != undefined)){ - p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label><input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value='"+pja.action_arguments.newname + "'/>"; - } - else{ - p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label><input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value='New Name'/>"; - } - p_str += "</div><div class='toolParamHelp'>This action will rename the result dataset.</div></div>"; - } + form = """ + if ((pja.action_arguments != undefined) && (pja.action_arguments.newname != undefined)){ + p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\ + <input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value='"+pja.action_arguments.newname + "'/>"; + } + else{ + p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\ + <input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value=''/>"; + } """ + return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.") class HideDatasetAction(DefaultJobAction): name = "HideDatasetAction" - + verbose_name = "Hide Dataset" + @classmethod def execute(cls, trans, action, job): for dataset_assoc in job.output_datasets: @@ -96,14 +155,79 @@ class HideDatasetAction(DefaultJobAction @classmethod def get_config_form(cls, trans): - return """ - if (pja.action_type == "HideDatasetAction"){ - p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + pja.output_name + "</span><div class='toolFormTitle'>"+ pja.action_type + " <br/> on " + pja.output_name + "\ - <div style='float: right;' class='buttons'><img src='../images/delete_icon.png'></div></div><div class='toolFormBody'>"; - p_str += "<label for='pja__"+pja.output_name+"__HideDatasetAction'>There are no additional options for this action.</label><input type='hidden' name='pja__"+pja.output_name+"__HideDatasetAction'/>"; - p_str += "</div><div class='toolParamHelp'>This action will *hide* the result dataset from your history.</div></div>"; + form = """ + p_str += "<label for='pja__"+pja.output_name+"__HideDatasetAction'>There are no additional options for this action.</label>\ + <input type='hidden' name='pja__"+pja.output_name+"__HideDatasetAction'/>"; + """ + return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.") + +class DeleteDatasetAction(DefaultJobAction): + # This is disabled for right now. Deleting a dataset in the middle of a workflow causes errors (obviously) for the subsequent steps using the data. + name = "DeleteDatasetAction" + verbose_name = "Delete Dataset" + + @classmethod + def execute(cls, trans, action, job): + for dataset_assoc in job.output_datasets: + if action.output_name == '' or dataset_assoc.name == action.output_name: + dataset_assoc.dataset.deleted=True + + @classmethod + def get_config_form(cls, trans): + form = """ + p_str += "<label for='pja__"+pja.output_name+"__DeleteDatasetAction'>There are no additional options for this action. This dataset will be marked deleted.</label>\ + <input type='hidden' name='pja__"+pja.output_name+"__DeleteDatasetAction'/>"; + """ + return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.") + + +class ColumnSetAction(DefaultJobAction): + name = "ColumnSetAction" + verbose_name = "Assign Columns" + @classmethod + def execute(cls, trans, action, job): + for dataset_assoc in job.output_datasets: + if action.output_name == '' or dataset_assoc.name == action.output_name: + for k, v in action.action_arguments.items(): + if v != '': + # Try to use both pure integer and 'cX' format. + if v[0] == 'c': + v = v[1:] + v = int(v) + if v != 0: + setattr(dataset_assoc.dataset.metadata, k, v) + + @classmethod + def get_config_form(cls, trans): + form = """ + var chrom_col = '' + if (pja.action_arguments != undefined){ + (pja.action_arguments.chromCol == undefined) ? chromCol = "" : chromCol=pja.action_arguments.chromCol; + (pja.action_arguments.startCol == undefined) ? startCol = "" : startCol=pja.action_arguments.startCol; + (pja.action_arguments.endCol == undefined) ? endCol = "" : endCol=pja.action_arguments.endCol; + (pja.action_arguments.strandCol == undefined) ? strandCol = "" : strandCol=pja.action_arguments.strandCol; + (pja.action_arguments.nameCol == undefined) ? nameCol = "" : nameCol=pja.action_arguments.nameCol; + }else{ + chromCol = ''; + startCol = ''; + endCol = ''; + strandCol = ''; + nameCol = ''; } + p_str += "<p>Leave any of these fields blank if they do not need to be set.</p>\ + <label for='pja__"+pja.output_name+"__ColumnSetAction__chromCol'>Chrom Column</label>\ + <input type='text' value='" + chromCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__chromCol'/>\ + <label for='pja__"+pja.output_name+"__ColumnSetAction__startCol'>Start Column</label>\ + <input type='text' value='" + startCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__startCol'/>\ + <label for='pja__"+pja.output_name+"__ColumnSetAction__endCol'>End Column</label>\ + <input type='text' value='" + endCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__endCol'/>\ + <label for='pja__"+pja.output_name+"__ColumnSetAction__strandCol'>Strand Column</label>\ + <input type='text' value='" + strandCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__strandCol'/>\ + <label for='pja__"+pja.output_name+"__ColumnSetAction__nameCol'>Name Column</label>\ + <input type='text' value='" + nameCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__nameCol'/>\"; """ + return get_form_template(cls.name, cls.verbose_name, form, "This action will set column assignments in the output dataset. Blank fields are ignored.") + class SetMetadataAction(DefaultJobAction): name = "SetMetadataAction" @@ -116,28 +240,38 @@ class SetMetadataAction(DefaultJobAction @classmethod def get_config_form(cls, trans): - dt_list = "" - mdict = {} - for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems(): - for mn, mt in dtype_value.metadata_spec.items(): - if mt.visible: - mdict[mt.desc] = mt.param.get_html(value= mn).replace('"', "'").strip().replace('\n','') - for k, v in mdict.items(): - dt_list += "<p><strong>" + k + ":</strong><br/>" + v + "</p>" - return """ - if (pja.action_type == "SetMetadataAction"){ - p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + pja.output_name + "</span><div class='toolFormTitle'>"+ pja.action_type + " <br/> on " + pja.output_name + "\ - <div style='float: right;' class='buttons'><img src='../images/delete_icon.png'></div></div>\ - <div class='toolFormBody'>\ - %s\ - </div><div class='toolParamHelp'>This tool sets metadata in output.</div></div>"; - } - """ % dt_list + # dt_list = "" + # mdict = {} + # for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems(): + # for mn, mt in dtype_value.metadata_spec.items(): + # if mt.visible: + # mdict[mt.desc] = mt.param.get_html(value= mn).replace('"', "'").strip().replace('\n','') + # for k, v in mdict.items(): + # dt_list += "<p><strong>" + k + ":</strong><br/>" + v + "</p>" + # form = """ + # p_str += "%s"; + # """ % dt_list + # return get_form_template('SetMetadataAction', 'Set Metadata', form, "This action will change metadata for the dataset.") + form = """ + p_str += "<p>Leave any of these fields blank if they do not need to be set.</p><label for='pja__"+pja.output_name+"__SetMetadataAction__chromCol'>Chrom Column</label>\ + <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__chromCol'/>\ + <label for='pja__"+pja.output_name+"__SetMetadataAction__startCol'>Start Column</label>\ + <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__startCol'/>\ + <label for='pja__"+pja.output_name+"__SetMetadataAction__endCol'>End Column</label>\ + <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__endCol'/>\ + <label for='pja__"+pja.output_name+"__SetMetadataAction__comment_lines'>Comment Lines</label>\ + <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__comment_lines'/>\ + "; + """ + return get_form_template(cls.name, cls.verbose_name, form, "This action will set metadata in the output dataset.") ACTIONS = { "RenameDatasetAction" : RenameDatasetAction, "HideDatasetAction" : HideDatasetAction, "ChangeDatatypeAction": ChangeDatatypeAction, - # "SetMetadataAction" : SetMetadataAction, + "ColumnSetAction" : ColumnSetAction, + "EmailAction" : EmailAction, + # "SetMetadataAction" : SetMetadataAction, + # "DeleteDatasetAction" : DeleteDatasetAction, } class ActionBox(object): @@ -147,13 +281,13 @@ class ActionBox(object): ACTIONS[action.action_type].execute(action, job, trans) else: return False - + @classmethod def get_short_str(cls, action): if action.action_type in ACTIONS: return ACTIONS[action.action_type].get_short_str(action) else: - return "Unknown PostJobAction" + return "Unknown Action" @classmethod def handle_incoming(cls, incoming): @@ -180,7 +314,7 @@ class ActionBox(object): def get_add_list(cls): addlist = "<select id='new_pja_list' name='new_pja_list'>" for action in ACTIONS: - addlist += "<option value='%s'>%s</option>" % (ACTIONS[action].name, ACTIONS[action].name) + addlist += "<option value='%s'>%s</option>" % (ACTIONS[action].name, ACTIONS[action].verbose_name) addlist += "</select>" return addlist @@ -193,4 +327,5 @@ class ActionBox(object): @classmethod def execute(cls, trans, pja, job): - ACTIONS[pja.action_type].execute(trans, pja, job) + if ACTIONS.has_key(pja.action_type): + ACTIONS[pja.action_type].execute(trans, pja, job) --- a/templates/workflow/editor.mako +++ b/templates/workflow/editor.mako @@ -313,9 +313,9 @@ // DBTODO Refactor to the post module. // This function preloads how to display known pja's. function display_pja(pja, node){ - // DBTODO SANITIZE INPUTS. Way too easy to break the page right now with a change dataset name action. + // DBTODO SANITIZE INPUTS. p_str = ''; - ${ActionBox.get_forms(trans)}; + ${ActionBox.get_forms(trans)} $("#pja_container").append(p_str); $("#pja_container>.toolForm:last>.toolFormTitle>.buttons").click(function (){ action_to_rem = $(this).closest(".toolForm", ".action_tag").children(".action_tag:first").text(); @@ -376,7 +376,7 @@ // Add step actions. if (node && node.type=='tool'){ pjastr = "<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Actions</div><div class='form-row'> \ - <label>New Actions:</label><br/>" + display_pja_list() + display_file_list(node) + " <div class='action-button' style='border:1px solid black;display:inline;' id='add_pja'>Create</div>\ + " + display_pja_list() + " <br/> "+ display_file_list(node) + " <div class='action-button' style='border:1px solid black;display:inline;' id='add_pja'>Create</div>\ </div><div class='form-row'>\ <div style='margin-right: 10px;'><span id='pja_container'></span>"; pjastr += "<div class='toolParamHelp'>Add actions to this step; actions are applied when this workflow step completes.</div></div></div></div>";