1 | import logging, datetime |
---|
2 | |
---|
3 | from galaxy.util.json import to_json_string |
---|
4 | |
---|
5 | # For email notification PJA |
---|
6 | from email.MIMEText import MIMEText |
---|
7 | import smtplib |
---|
8 | |
---|
9 | log = logging.getLogger( __name__ ) |
---|
10 | |
---|
11 | # DBTODO This still needs refactoring and general cleanup. |
---|
12 | |
---|
13 | def get_form_template(action_type, title, content, help, on_output = True ): |
---|
14 | if on_output: |
---|
15 | form = """ |
---|
16 | if (pja.action_type == "%s"){ |
---|
17 | 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 + "\ |
---|
18 | <div style='float: right;' class='buttons'><img src='/static/images/delete_icon.png'></div></div><div class='toolFormBody'>"; |
---|
19 | %s |
---|
20 | p_str += "</div><div class='toolParamHelp'>%s</div></div>"; |
---|
21 | }""" % (action_type, title, content, help) |
---|
22 | else: |
---|
23 | form = """ |
---|
24 | if (pja.action_type == "%s"){ |
---|
25 | p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + "</span><div class='toolFormTitle'> %s \ |
---|
26 | <div style='float: right;' class='buttons'><img src='/static/images/delete_icon.png'></div></div><div class='toolFormBody'>"; |
---|
27 | %s |
---|
28 | p_str += "</div><div class='toolParamHelp'>%s</div></div>"; |
---|
29 | }""" % (action_type, title, content, help) |
---|
30 | return form |
---|
31 | |
---|
32 | # def get_field(action, argument, i_type, label = None): |
---|
33 | # fstr = '' |
---|
34 | # fname = """pja__"+pja.output_name+"__%s__%s""" % (action, argument) |
---|
35 | # if label: |
---|
36 | # fstr += """<label for='pja__"+pja.output_name+"__ColumnSetAction__chromCol'>Chrom Column</label>""" |
---|
37 | # fstr += """<input type='text' value=" + chromCol + " name='pja__"+pja.output_name+"__ColumnSetAction__chromCol'/>""" |
---|
38 | |
---|
39 | class DefaultJobAction(object): |
---|
40 | name = "DefaultJobAction" |
---|
41 | verbose_name = "Default Job" |
---|
42 | |
---|
43 | |
---|
44 | @classmethod |
---|
45 | def execute(cls, app, sa_session, action, job): |
---|
46 | pass |
---|
47 | |
---|
48 | @classmethod |
---|
49 | def get_config_form(cls, trans): |
---|
50 | return "<p>Default Job Action Config Form</p>" |
---|
51 | |
---|
52 | @classmethod |
---|
53 | def get_short_str(cls, pja): |
---|
54 | if pja.action_arguments: |
---|
55 | return "%s -> %s" % (pja.action_type, pja.action_arguments) |
---|
56 | else: |
---|
57 | return "%s" % pja.action_type |
---|
58 | |
---|
59 | |
---|
60 | class EmailAction(DefaultJobAction): |
---|
61 | name = "EmailAction" |
---|
62 | verbose_name = "Email Notification" |
---|
63 | |
---|
64 | |
---|
65 | @classmethod |
---|
66 | def execute(cls, app, sa_session, action, job): |
---|
67 | smtp_server = app.config.smtp_server |
---|
68 | if action.action_arguments: |
---|
69 | if action.action_arguments.has_key('host'): |
---|
70 | host = action.action_arguments['host'] |
---|
71 | else: |
---|
72 | host = 'usegalaxy.org' |
---|
73 | if smtp_server is None: |
---|
74 | log.error("Mail is not configured for this galaxy instance. Workflow action aborting after logging mail to info.") |
---|
75 | frm = 'galaxy-noreply@%s' % host |
---|
76 | to = job.user.email |
---|
77 | outdata = ', '.join(ds.dataset.display_name() for ds in job.output_datasets) |
---|
78 | msg = MIMEText( "Your Galaxy job generating dataset '%s' is complete as of %s." % (outdata, datetime.datetime.now().strftime( "%I:%M" ))) |
---|
79 | msg[ 'To' ] = to |
---|
80 | msg[ 'From' ] = frm |
---|
81 | msg[ 'Subject' ] = "Galaxy notification regarding history '%s'" % (job.history.name) |
---|
82 | log.info(msg) |
---|
83 | return |
---|
84 | # Build the email message |
---|
85 | frm = 'galaxy-noreply@%s' % host |
---|
86 | to = job.user.email |
---|
87 | outdata = ', '.join(ds.dataset.display_name() for ds in job.output_datasets) |
---|
88 | msg = MIMEText( "Your Galaxy job generating dataset '%s' is complete as of %s." % (outdata, datetime.datetime.now().strftime( "%I:%M" ))) |
---|
89 | msg[ 'To' ] = to |
---|
90 | msg[ 'From' ] = frm |
---|
91 | msg[ 'Subject' ] = "Galaxy workflow step notification '%s'" % (job.history.name) |
---|
92 | try: |
---|
93 | s = smtplib.SMTP() |
---|
94 | s.connect( smtp_server ) |
---|
95 | s.sendmail( frm, [ to ], msg.as_string() ) |
---|
96 | s.close() |
---|
97 | except Exception, e: |
---|
98 | log.error("EmailAction PJA Failed, exception: %s" % e) |
---|
99 | |
---|
100 | @classmethod |
---|
101 | def get_config_form(cls, trans): |
---|
102 | form = """ |
---|
103 | 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>\ |
---|
104 | <input type='hidden' value='%s' name='pja__"+pja.output_name+"__EmailAction__host'/><input type='hidden' name='pja__"+pja.output_name+"__EmailAction'/>"; |
---|
105 | """ % trans.request.host |
---|
106 | 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) |
---|
107 | |
---|
108 | @classmethod |
---|
109 | def get_short_str(cls, pja): |
---|
110 | if pja.action_arguments: |
---|
111 | if pja.action_arguments.has_key('host'): |
---|
112 | return "Email the current user from server %s when this job is complete." % pja.action_arguments['host'] |
---|
113 | else: |
---|
114 | return "Email the current user when this job is complete." |
---|
115 | |
---|
116 | |
---|
117 | class ChangeDatatypeAction(DefaultJobAction): |
---|
118 | name = "ChangeDatatypeAction" |
---|
119 | verbose_name = "Change Datatype" |
---|
120 | @classmethod |
---|
121 | def execute(cls, app, sa_session, action, job): |
---|
122 | for dataset_assoc in job.output_datasets: |
---|
123 | if action.output_name == '' or dataset_assoc.name == action.output_name: |
---|
124 | app.datatypes_registry.change_datatype( dataset_assoc.dataset, action.action_arguments['newtype']) |
---|
125 | |
---|
126 | @classmethod |
---|
127 | def get_config_form(cls, trans): |
---|
128 | dt_list = "" |
---|
129 | dtnames = [ dtype_name for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems()] |
---|
130 | dtnames.sort() |
---|
131 | for dt_name in dtnames: |
---|
132 | dt_list += """<option id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype__%s' value='%s'>%s</option>""" % (dt_name, dt_name, dt_name) |
---|
133 | ps = """ |
---|
134 | p_str += "<label for='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>New Datatype:</label>\ |
---|
135 | <select id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype' name='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>\ |
---|
136 | %s\ |
---|
137 | </select>"; |
---|
138 | if (pja.action_arguments != undefined && pja.action_arguments.newtype != undefined){ |
---|
139 | p_str += "<scrip" + "t type='text/javascript'>$('#pja__" + pja.output_name + "__ChangeDatatypeAction__newtype').val('" + pja.action_arguments.newtype + "');</scrip" + "t>"; |
---|
140 | } |
---|
141 | """ % dt_list |
---|
142 | # Note the scrip + t hack above. Is there a better way? |
---|
143 | return get_form_template(cls.name, cls.verbose_name, ps, 'This action will change the datatype of the output to the indicated value.') |
---|
144 | |
---|
145 | @classmethod |
---|
146 | def get_short_str(cls, pja): |
---|
147 | return "Set the datatype of output '%s' to '%s'" % (pja.output_name, pja.action_arguments['newtype']) |
---|
148 | |
---|
149 | |
---|
150 | class RenameDatasetAction(DefaultJobAction): |
---|
151 | name = "RenameDatasetAction" |
---|
152 | verbose_name = "Rename Dataset" |
---|
153 | |
---|
154 | @classmethod |
---|
155 | def execute(cls, app, sa_session, action, job): |
---|
156 | for dataset_assoc in job.output_datasets: |
---|
157 | if action.output_name == '' or dataset_assoc.name == action.output_name: |
---|
158 | dataset_assoc.dataset.name = action.action_arguments['newname'] |
---|
159 | |
---|
160 | @classmethod |
---|
161 | def get_config_form(cls, trans): |
---|
162 | form = """ |
---|
163 | if ((pja.action_arguments != undefined) && (pja.action_arguments.newname != undefined)){ |
---|
164 | p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\ |
---|
165 | <input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value='"+pja.action_arguments.newname + "'/>"; |
---|
166 | } |
---|
167 | else{ |
---|
168 | p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\ |
---|
169 | <input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value=''/>"; |
---|
170 | } |
---|
171 | """ |
---|
172 | return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.") |
---|
173 | |
---|
174 | @classmethod |
---|
175 | def get_short_str(cls, pja): |
---|
176 | return "Rename output '%s' to '%s'." % (pja.output_name, pja.action_arguments['newname']) |
---|
177 | |
---|
178 | |
---|
179 | class HideDatasetAction(DefaultJobAction): |
---|
180 | name = "HideDatasetAction" |
---|
181 | verbose_name = "Hide Dataset" |
---|
182 | |
---|
183 | @classmethod |
---|
184 | def execute(cls, app, sa_session, action, job): |
---|
185 | for dataset_assoc in job.output_datasets: |
---|
186 | if action.output_name == '' or dataset_assoc.name == action.output_name: |
---|
187 | dataset_assoc.dataset.visible=False |
---|
188 | |
---|
189 | @classmethod |
---|
190 | def get_config_form(cls, trans): |
---|
191 | form = """ |
---|
192 | p_str += "<label for='pja__"+pja.output_name+"__HideDatasetAction'>There are no additional options for this action.</label>\ |
---|
193 | <input type='hidden' name='pja__"+pja.output_name+"__HideDatasetAction'/>"; |
---|
194 | """ |
---|
195 | return get_form_template(cls.name, cls.verbose_name, form, "This action will hide the result dataset.") |
---|
196 | |
---|
197 | @classmethod |
---|
198 | def get_short_str(cls, trans): |
---|
199 | return "Hide this dataset." |
---|
200 | |
---|
201 | class DeleteDatasetAction(DefaultJobAction): |
---|
202 | # 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. |
---|
203 | name = "DeleteDatasetAction" |
---|
204 | verbose_name = "Delete Dataset" |
---|
205 | |
---|
206 | @classmethod |
---|
207 | def execute(cls, app, sa_session, action, job): |
---|
208 | for dataset_assoc in job.output_datasets: |
---|
209 | if action.output_name == '' or dataset_assoc.name == action.output_name: |
---|
210 | dataset_assoc.dataset.deleted=True |
---|
211 | |
---|
212 | @classmethod |
---|
213 | def get_config_form(cls, trans): |
---|
214 | form = """ |
---|
215 | p_str += "<label for='pja__"+pja.output_name+"__DeleteDatasetAction'>There are no additional options for this action. This dataset will be marked deleted.</label>\ |
---|
216 | <input type='hidden' name='pja__"+pja.output_name+"__DeleteDatasetAction'/>"; |
---|
217 | """ |
---|
218 | return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.") |
---|
219 | |
---|
220 | @classmethod |
---|
221 | def get_short_str(cls, pja): |
---|
222 | return "Delete this dataset after creation." |
---|
223 | |
---|
224 | |
---|
225 | |
---|
226 | class ColumnSetAction(DefaultJobAction): |
---|
227 | name = "ColumnSetAction" |
---|
228 | verbose_name = "Assign Columns" |
---|
229 | @classmethod |
---|
230 | def execute(cls, app, sa_session, action, job): |
---|
231 | for dataset_assoc in job.output_datasets: |
---|
232 | if action.output_name == '' or dataset_assoc.name == action.output_name: |
---|
233 | for k, v in action.action_arguments.items(): |
---|
234 | if v != '': |
---|
235 | # Try to use both pure integer and 'cX' format. |
---|
236 | if v[0] == 'c': |
---|
237 | v = v[1:] |
---|
238 | v = int(v) |
---|
239 | if v != 0: |
---|
240 | setattr(dataset_assoc.dataset.metadata, k, v) |
---|
241 | |
---|
242 | @classmethod |
---|
243 | def get_config_form(cls, trans): |
---|
244 | form = """ |
---|
245 | var chrom_col = '' |
---|
246 | if (pja.action_arguments != undefined){ |
---|
247 | (pja.action_arguments.chromCol == undefined) ? chromCol = "" : chromCol=pja.action_arguments.chromCol; |
---|
248 | (pja.action_arguments.startCol == undefined) ? startCol = "" : startCol=pja.action_arguments.startCol; |
---|
249 | (pja.action_arguments.endCol == undefined) ? endCol = "" : endCol=pja.action_arguments.endCol; |
---|
250 | (pja.action_arguments.strandCol == undefined) ? strandCol = "" : strandCol=pja.action_arguments.strandCol; |
---|
251 | (pja.action_arguments.nameCol == undefined) ? nameCol = "" : nameCol=pja.action_arguments.nameCol; |
---|
252 | }else{ |
---|
253 | chromCol = ''; |
---|
254 | startCol = ''; |
---|
255 | endCol = ''; |
---|
256 | strandCol = ''; |
---|
257 | nameCol = ''; |
---|
258 | } |
---|
259 | p_str += "<p>Leave any of these fields blank if they do not need to be set.</p>\ |
---|
260 | <label for='pja__"+pja.output_name+"__ColumnSetAction__chromCol'>Chrom Column</label>\ |
---|
261 | <input type='text' value='" + chromCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__chromCol'/>\ |
---|
262 | <label for='pja__"+pja.output_name+"__ColumnSetAction__startCol'>Start Column</label>\ |
---|
263 | <input type='text' value='" + startCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__startCol'/>\ |
---|
264 | <label for='pja__"+pja.output_name+"__ColumnSetAction__endCol'>End Column</label>\ |
---|
265 | <input type='text' value='" + endCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__endCol'/>\ |
---|
266 | <label for='pja__"+pja.output_name+"__ColumnSetAction__strandCol'>Strand Column</label>\ |
---|
267 | <input type='text' value='" + strandCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__strandCol'/>\ |
---|
268 | <label for='pja__"+pja.output_name+"__ColumnSetAction__nameCol'>Name Column</label>\ |
---|
269 | <input type='text' value='" + nameCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__nameCol'/>\"; |
---|
270 | """ |
---|
271 | return get_form_template(cls.name, cls.verbose_name, form, "This action will set column assignments in the output dataset. Blank fields are ignored.") |
---|
272 | |
---|
273 | @classmethod |
---|
274 | def get_short_str(cls, pja): |
---|
275 | return "Set the following metadata values:<br/>" + "<br/>".join(['%s : %s' % (k, v) for k, v in pja.action_arguments.iteritems()]) |
---|
276 | |
---|
277 | |
---|
278 | class SetMetadataAction(DefaultJobAction): |
---|
279 | name = "SetMetadataAction" |
---|
280 | # DBTODO Setting of Metadata is currently broken and disabled. It should not be used (yet). |
---|
281 | |
---|
282 | @classmethod |
---|
283 | def execute(cls, app, sa_session, action, job): |
---|
284 | for data in job.output_datasets: |
---|
285 | data.set_metadata( action.action_arguments['newtype'] ) |
---|
286 | |
---|
287 | @classmethod |
---|
288 | def get_config_form(cls, trans): |
---|
289 | # dt_list = "" |
---|
290 | # mdict = {} |
---|
291 | # for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems(): |
---|
292 | # for mn, mt in dtype_value.metadata_spec.items(): |
---|
293 | # if mt.visible: |
---|
294 | # mdict[mt.desc] = mt.param.get_html(value= mn).replace('"', "'").strip().replace('\n','') |
---|
295 | # for k, v in mdict.items(): |
---|
296 | # dt_list += "<p><strong>" + k + ":</strong><br/>" + v + "</p>" |
---|
297 | # form = """ |
---|
298 | # p_str += "%s"; |
---|
299 | # """ % dt_list |
---|
300 | # return get_form_template('SetMetadataAction', 'Set Metadata', form, "This action will change metadata for the dataset.") |
---|
301 | form = """ |
---|
302 | 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>\ |
---|
303 | <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__chromCol'/>\ |
---|
304 | <label for='pja__"+pja.output_name+"__SetMetadataAction__startCol'>Start Column</label>\ |
---|
305 | <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__startCol'/>\ |
---|
306 | <label for='pja__"+pja.output_name+"__SetMetadataAction__endCol'>End Column</label>\ |
---|
307 | <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__endCol'/>\ |
---|
308 | <label for='pja__"+pja.output_name+"__SetMetadataAction__comment_lines'>Comment Lines</label>\ |
---|
309 | <input type='text' name='pja__"+pja.output_name+"__SetMetadataAction__comment_lines'/>\ |
---|
310 | "; |
---|
311 | """ |
---|
312 | return get_form_template(cls.name, cls.verbose_name, form, "This action will set metadata in the output dataset.") |
---|
313 | |
---|
314 | |
---|
315 | |
---|
316 | class ActionBox(object): |
---|
317 | |
---|
318 | actions = { "RenameDatasetAction" : RenameDatasetAction, |
---|
319 | "HideDatasetAction" : HideDatasetAction, |
---|
320 | "ChangeDatatypeAction": ChangeDatatypeAction, |
---|
321 | "ColumnSetAction" : ColumnSetAction, |
---|
322 | "EmailAction" : EmailAction, |
---|
323 | # "SetMetadataAction" : SetMetadataAction, |
---|
324 | # "DeleteDatasetAction" : DeleteDatasetAction, |
---|
325 | } |
---|
326 | immediate_actions = ['ChangeDatatypeAction', 'RenameDatasetAction'] |
---|
327 | |
---|
328 | @classmethod |
---|
329 | def get_short_str(cls, action): |
---|
330 | if action.action_type in ActionBox.actions: |
---|
331 | return ActionBox.actions[action.action_type].get_short_str(action) |
---|
332 | else: |
---|
333 | return "Unknown Action" |
---|
334 | |
---|
335 | @classmethod |
---|
336 | def handle_incoming(cls, incoming): |
---|
337 | npd = {} |
---|
338 | for key, val in incoming.iteritems(): |
---|
339 | if key.startswith('pja'): |
---|
340 | sp = key.split('__') |
---|
341 | ao_key = sp[2] + sp[1] |
---|
342 | # flag / output_name / pjatype / desc |
---|
343 | if not ao_key in npd: |
---|
344 | npd[ao_key] = {'action_type' : sp[2], |
---|
345 | 'output_name' : sp[1], |
---|
346 | 'action_arguments' : {}} |
---|
347 | if len(sp) > 3: |
---|
348 | if sp[3] == 'output_name': |
---|
349 | npd[ao_key]['output_name'] = val |
---|
350 | else: |
---|
351 | npd[ao_key]['action_arguments'][sp[3]] = val |
---|
352 | else: |
---|
353 | # Not pja stuff. |
---|
354 | pass |
---|
355 | return to_json_string(npd) |
---|
356 | |
---|
357 | @classmethod |
---|
358 | def get_add_list(cls): |
---|
359 | addlist = "<select id='new_pja_list' name='new_pja_list'>" |
---|
360 | for action in ActionBox.actions: |
---|
361 | addlist += "<option value='%s'>%s</option>" % (ActionBox.actions[action].name, ActionBox.actions[action].verbose_name) |
---|
362 | addlist += "</select>" |
---|
363 | return addlist |
---|
364 | |
---|
365 | @classmethod |
---|
366 | def get_forms(cls, trans): |
---|
367 | forms = "" |
---|
368 | for action in ActionBox.actions: |
---|
369 | forms += ActionBox.actions[action].get_config_form(trans) |
---|
370 | return forms |
---|
371 | |
---|
372 | @classmethod |
---|
373 | def execute(cls, app, sa_session, pja, job): |
---|
374 | if ActionBox.actions.has_key(pja.action_type): |
---|
375 | ActionBox.actions[pja.action_type].execute(app, sa_session, pja, job) |
---|