/lib/galaxy/jobs/actions/post.py

https://bitbucket.org/cistrome/cistrome-harvard/ · Python · 422 lines · 312 code · 55 blank · 55 comment · 69 complexity · 1dc61a4aaf4c894bea57c4bf275dfc37 MD5 · raw file

  1. import datetime
  2. import logging
  3. from galaxy.util import send_mail
  4. from galaxy.util.json import to_json_string
  5. log = logging.getLogger( __name__ )
  6. # DBTODO This still needs refactoring and general cleanup.
  7. def get_form_template(action_type, title, content, help, on_output = True ):
  8. if on_output:
  9. form = """
  10. if (pja.action_type == "%s"){
  11. 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 + "\
  12. <div style='float: right;' class='buttons'><img src='/static/images/history-buttons/delete_icon.png'></div></div><div class='toolFormBody'>";
  13. %s
  14. p_str += "</div><div class='toolParamHelp'>%s</div></div>";
  15. }""" % (action_type, title, content, help)
  16. else:
  17. form = """
  18. if (pja.action_type == "%s"){
  19. p_str = "<div class='pjaForm toolForm'><span class='action_tag' style='display:none'>"+ pja.action_type + "</span><div class='toolFormTitle'> %s \
  20. <div style='float: right;' class='buttons'><img src='/static/images/history-buttons/delete_icon.png'></div></div><div class='toolFormBody'>";
  21. %s
  22. p_str += "</div><div class='toolParamHelp'>%s</div></div>";
  23. }""" % (action_type, title, content, help)
  24. return form
  25. # def get_field(action, argument, i_type, label = None):
  26. # fstr = ''
  27. # fname = """pja__"+pja.output_name+"__%s__%s""" % (action, argument)
  28. # if label:
  29. # fstr += """<label for='pja__"+pja.output_name+"__ColumnSetAction__chromCol'>Chrom Column</label>"""
  30. # fstr += """<input type='text' value=" + chromCol + " name='pja__"+pja.output_name+"__ColumnSetAction__chromCol'/>"""
  31. class DefaultJobAction(object):
  32. name = "DefaultJobAction"
  33. verbose_name = "Default Job"
  34. @classmethod
  35. def execute(cls, app, sa_session, action, job, replacement_dict = None):
  36. pass
  37. @classmethod
  38. def get_config_form(cls, trans):
  39. return "<p>Default Job Action Config Form</p>"
  40. @classmethod
  41. def get_short_str(cls, pja):
  42. if pja.action_arguments:
  43. return "%s -> %s" % (pja.action_type, pja.action_arguments)
  44. else:
  45. return "%s" % pja.action_type
  46. class EmailAction(DefaultJobAction):
  47. name = "EmailAction"
  48. verbose_name = "Email Notification"
  49. @classmethod
  50. def execute(cls, app, sa_session, action, job, replacement_dict):
  51. if action.action_arguments and action.action_arguments.has_key('host'):
  52. host = action.action_arguments['host']
  53. else:
  54. host = 'usegalaxy.org'
  55. frm = 'galaxy-noreply@%s' % host
  56. to = job.user.email
  57. subject = "Galaxy workflow step notification '%s'" % (job.history.name)
  58. outdata = ', '.join(ds.dataset.display_name() for ds in job.output_datasets)
  59. body = "Your Galaxy job generating dataset '%s' is complete as of %s." % (outdata, datetime.datetime.now().strftime( "%I:%M" ))
  60. try:
  61. send_mail( frm, to, subject, body, app.config )
  62. except Exception, e:
  63. log.error("EmailAction PJA Failed, exception: %s" % e)
  64. @classmethod
  65. def get_config_form(cls, trans):
  66. form = """
  67. 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>\
  68. <input type='hidden' value='%s' name='pja__"+pja.output_name+"__EmailAction__host'/><input type='hidden' name='pja__"+pja.output_name+"__EmailAction'/>";
  69. """ % trans.request.host
  70. 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)
  71. @classmethod
  72. def get_short_str(cls, pja):
  73. if pja.action_arguments and pja.action_arguments.has_key('host'):
  74. return "Email the current user from server %s when this job is complete." % pja.action_arguments['host']
  75. else:
  76. return "Email the current user when this job is complete."
  77. class ChangeDatatypeAction(DefaultJobAction):
  78. name = "ChangeDatatypeAction"
  79. verbose_name = "Change Datatype"
  80. @classmethod
  81. def execute(cls, app, sa_session, action, job, replacement_dict):
  82. for dataset_assoc in job.output_datasets:
  83. if action.output_name == '' or dataset_assoc.name == action.output_name:
  84. app.datatypes_registry.change_datatype( dataset_assoc.dataset, action.action_arguments['newtype'])
  85. @classmethod
  86. def get_config_form(cls, trans):
  87. dt_list = ""
  88. dtnames = [ dtype_name for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems()]
  89. dtnames.sort()
  90. for dt_name in dtnames:
  91. dt_list += """<option id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype__%s' value='%s'>%s</option>""" % (dt_name, dt_name, dt_name)
  92. ps = """
  93. p_str += "<label for='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>New Datatype:</label>\
  94. <select id='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype' name='pja__"+pja.output_name+"__ChangeDatatypeAction__newtype'>\
  95. %s\
  96. </select>";
  97. if (pja.action_arguments !== undefined && pja.action_arguments.newtype !== undefined){
  98. p_str += "<scrip" + "t type='text/javascript'>$('#pja__" + pja.output_name + "__ChangeDatatypeAction__newtype').val('" + pja.action_arguments.newtype + "');</scrip" + "t>";
  99. }
  100. """ % dt_list
  101. # Note the scrip + t hack above. Is there a better way?
  102. return get_form_template(cls.name, cls.verbose_name, ps, 'This action will change the datatype of the output to the indicated value.')
  103. @classmethod
  104. def get_short_str(cls, pja):
  105. return "Set the datatype of output '%s' to '%s'" % (pja.output_name, pja.action_arguments['newtype'])
  106. class RenameDatasetAction(DefaultJobAction):
  107. name = "RenameDatasetAction"
  108. verbose_name = "Rename Dataset"
  109. @classmethod
  110. def execute(cls, app, sa_session, action, job, replacement_dict):
  111. # Prevent renaming a dataset to the empty string.
  112. if action.action_arguments and action.action_arguments.has_key('newname') and action.action_arguments['newname'] != '':
  113. new_name = action.action_arguments['newname']
  114. # TODO: Unify and simplify replacement options.
  115. # Add interface through workflow editor UI
  116. # The following if statement will process a request to rename
  117. # using an input file name.
  118. # TODO: Replace all matching code with regex
  119. # Proper syntax is #{input_file_variable | option 1 | option n}
  120. # where
  121. # input_file_variable = is the name of an module input variable
  122. # | = the delimiter for added options. Optional if no options.
  123. # options = basename, upper, lower
  124. # basename = keep all of the file name except the extension
  125. # (everything before the final ".")
  126. # upper = force the file name to upper case
  127. # lower = force the file name to lower case
  128. # suggested additions:
  129. # "replace" option so you can replace a portion of the name,
  130. # support multiple #{name} in one rename action...
  131. if new_name.find("#{") > -1:
  132. to_be_replaced = ""
  133. # This assumes a single instance of #{variable} will exist
  134. start_pos = new_name.find("#{") + 2
  135. end_pos = new_name.find("}")
  136. to_be_replaced = new_name[start_pos:end_pos]
  137. input_file_var = to_be_replaced
  138. # Pull out the piped controls and store them for later
  139. # parsing.
  140. tokens = to_be_replaced.split("|")
  141. operations = []
  142. if len(tokens) > 1:
  143. input_file_var = tokens[0].strip()
  144. for i in range(1, len(tokens)):
  145. operations.append(tokens[i].strip())
  146. replacement = ""
  147. # Lookp through inputs find one with "to_be_replaced" input
  148. # variable name, and get the replacement name
  149. for input_assoc in job.input_datasets:
  150. if input_assoc.name == input_file_var:
  151. replacement = input_assoc.dataset.name
  152. # Do operations on replacement
  153. # Any control that is not defined will be ignored.
  154. # This should be moved out to a class or module function
  155. for operation in operations:
  156. # Basename returns everything prior to the final '.'
  157. if operation == "basename":
  158. fields = replacement.split(".")
  159. replacement = fields[0]
  160. if len(fields) > 1:
  161. temp = ""
  162. for i in range(1, len(fields) - 1):
  163. temp += "." + fields[i]
  164. replacement += temp
  165. elif operation == "upper":
  166. replacement = replacement.upper()
  167. elif operation == "lower":
  168. replacement = replacement.lower()
  169. new_name = new_name.replace("#{%s}" % to_be_replaced, replacement)
  170. if replacement_dict:
  171. for k, v in replacement_dict.iteritems():
  172. new_name = new_name.replace("${%s}" % k, v)
  173. for dataset_assoc in job.output_datasets:
  174. if action.output_name == '' or dataset_assoc.name == action.output_name:
  175. dataset_assoc.dataset.name = new_name
  176. @classmethod
  177. def get_config_form(cls, trans):
  178. form = """
  179. if (pja.action_arguments && pja.action_arguments.newname){
  180. p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\
  181. <input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value=\\"" + pja.action_arguments.newname.replace(/"/g, "&quot;") + "\\"/>";
  182. }
  183. else{
  184. p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\
  185. <input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value=''/>";
  186. }
  187. """
  188. return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.")
  189. @classmethod
  190. def get_short_str(cls, pja):
  191. # Prevent renaming a dataset to the empty string.
  192. if pja.action_arguments and pja.action_arguments.has_key('newname') and pja.action_arguments['newname'] != '':
  193. return "Rename output '%s' to '%s'." % (pja.output_name, pja.action_arguments['newname'])
  194. else:
  195. return "Rename action used without a new name specified. Output name will be unchanged."
  196. class HideDatasetAction(DefaultJobAction):
  197. name = "HideDatasetAction"
  198. verbose_name = "Hide Dataset"
  199. @classmethod
  200. def execute(cls, app, sa_session, action, job, replacement_dict):
  201. for dataset_assoc in job.output_datasets:
  202. if dataset_assoc.dataset.state != dataset_assoc.dataset.states.ERROR and ( action.output_name == '' or dataset_assoc.name == action.output_name ):
  203. dataset_assoc.dataset.visible=False
  204. @classmethod
  205. def get_config_form(cls, trans):
  206. return """
  207. if (pja.action_type == "HideDatasetAction"){
  208. p_str += "<input type='hidden' name='pja__"+pja.output_name+"__HideDatasetAction'/>";
  209. }
  210. """
  211. @classmethod
  212. def get_short_str(cls, pja):
  213. return "Hide output '%s'." % pja.output_name
  214. class DeleteDatasetAction(DefaultJobAction):
  215. # 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.
  216. name = "DeleteDatasetAction"
  217. verbose_name = "Delete Dataset"
  218. @classmethod
  219. def execute(cls, app, sa_session, action, job, replacement_dict):
  220. for dataset_assoc in job.output_datasets:
  221. if action.output_name == '' or dataset_assoc.name == action.output_name:
  222. dataset_assoc.dataset.deleted=True
  223. @classmethod
  224. def get_config_form(cls, trans):
  225. form = """
  226. p_str += "<label for='pja__"+pja.output_name+"__DeleteDatasetAction'>There are no additional options for this action. This dataset will be marked deleted.</label>\
  227. <input type='hidden' name='pja__"+pja.output_name+"__DeleteDatasetAction'/>";
  228. """
  229. return get_form_template(cls.name, cls.verbose_name, form, "This action will rename the result dataset.")
  230. @classmethod
  231. def get_short_str(cls, pja):
  232. return "Delete this dataset after creation."
  233. class ColumnSetAction(DefaultJobAction):
  234. name = "ColumnSetAction"
  235. verbose_name = "Assign Columns"
  236. @classmethod
  237. def execute(cls, app, sa_session, action, job, replacement_dict):
  238. for dataset_assoc in job.output_datasets:
  239. if action.output_name == '' or dataset_assoc.name == action.output_name:
  240. for k, v in action.action_arguments.items():
  241. if v != '':
  242. # Try to use both pure integer and 'cX' format.
  243. if v[0] == 'c':
  244. v = v[1:]
  245. v = int(v)
  246. if v != 0:
  247. setattr(dataset_assoc.dataset.metadata, k, v)
  248. @classmethod
  249. def get_config_form(cls, trans):
  250. form = """
  251. if (pja.action_arguments !== undefined){
  252. (pja.action_arguments.chromCol === undefined) ? chromCol = "" : chromCol=pja.action_arguments.chromCol;
  253. (pja.action_arguments.startCol === undefined) ? startCol = "" : startCol=pja.action_arguments.startCol;
  254. (pja.action_arguments.endCol === undefined) ? endCol = "" : endCol=pja.action_arguments.endCol;
  255. (pja.action_arguments.strandCol === undefined) ? strandCol = "" : strandCol=pja.action_arguments.strandCol;
  256. (pja.action_arguments.nameCol === undefined) ? nameCol = "" : nameCol=pja.action_arguments.nameCol;
  257. }else{
  258. chromCol = '';
  259. startCol = '';
  260. endCol = '';
  261. strandCol = '';
  262. nameCol = '';
  263. }
  264. p_str += "<p>Leave any of these fields blank if they do not need to be set.</p>\
  265. <label for='pja__"+pja.output_name+"__ColumnSetAction__chromCol'>Chrom Column</label>\
  266. <input type='text' value='" + chromCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__chromCol'/>\
  267. <label for='pja__"+pja.output_name+"__ColumnSetAction__startCol'>Start Column</label>\
  268. <input type='text' value='" + startCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__startCol'/>\
  269. <label for='pja__"+pja.output_name+"__ColumnSetAction__endCol'>End Column</label>\
  270. <input type='text' value='" + endCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__endCol'/>\
  271. <label for='pja__"+pja.output_name+"__ColumnSetAction__strandCol'>Strand Column</label>\
  272. <input type='text' value='" + strandCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__strandCol'/>\
  273. <label for='pja__"+pja.output_name+"__ColumnSetAction__nameCol'>Name Column</label>\
  274. <input type='text' value='" + nameCol + "' name='pja__"+pja.output_name+"__ColumnSetAction__nameCol'/>\";
  275. """
  276. return get_form_template(cls.name, cls.verbose_name, form, "This action will set column assignments in the output dataset. Blank fields are ignored.")
  277. @classmethod
  278. def get_short_str(cls, pja):
  279. return "Set the following metadata values:<br/>" + "<br/>".join(['%s : %s' % (k, v) for k, v in pja.action_arguments.iteritems()])
  280. class SetMetadataAction(DefaultJobAction):
  281. name = "SetMetadataAction"
  282. # DBTODO Setting of Metadata is currently broken and disabled. It should not be used (yet).
  283. @classmethod
  284. def execute(cls, app, sa_session, action, job, replacement_dict):
  285. for data in job.output_datasets:
  286. data.set_metadata( action.action_arguments['newtype'] )
  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. class ActionBox(object):
  314. actions = { "RenameDatasetAction" : RenameDatasetAction,
  315. "HideDatasetAction" : HideDatasetAction,
  316. "ChangeDatatypeAction": ChangeDatatypeAction,
  317. "ColumnSetAction" : ColumnSetAction,
  318. "EmailAction" : EmailAction,
  319. # "SetMetadataAction" : SetMetadataAction,
  320. # "DeleteDatasetAction" : DeleteDatasetAction,
  321. }
  322. public_actions = ['RenameDatasetAction', 'ChangeDatatypeAction', 'ColumnSetAction', 'EmailAction']
  323. immediate_actions = ['ChangeDatatypeAction', 'RenameDatasetAction']
  324. @classmethod
  325. def get_short_str(cls, action):
  326. if action.action_type in ActionBox.actions:
  327. return ActionBox.actions[action.action_type].get_short_str(action)
  328. else:
  329. return "Unknown Action"
  330. @classmethod
  331. def handle_incoming(cls, incoming):
  332. npd = {}
  333. for key, val in incoming.iteritems():
  334. if key.startswith('pja'):
  335. sp = key.split('__')
  336. ao_key = sp[2] + sp[1]
  337. # flag / output_name / pjatype / desc
  338. if not ao_key in npd:
  339. npd[ao_key] = {'action_type' : sp[2],
  340. 'output_name' : sp[1],
  341. 'action_arguments' : {}}
  342. if len(sp) > 3:
  343. if sp[3] == 'output_name':
  344. npd[ao_key]['output_name'] = val
  345. else:
  346. npd[ao_key]['action_arguments'][sp[3]] = val
  347. else:
  348. # Not pja stuff.
  349. pass
  350. return to_json_string(npd)
  351. @classmethod
  352. def get_add_list(cls):
  353. addlist = "<select id='new_pja_list' name='new_pja_list'>"
  354. for action in ActionBox.public_actions:
  355. addlist += "<option value='%s'>%s</option>" % (ActionBox.actions[action].name, ActionBox.actions[action].verbose_name)
  356. addlist += "</select>"
  357. return addlist
  358. @classmethod
  359. def get_forms(cls, trans):
  360. forms = ""
  361. for action in ActionBox.actions:
  362. forms += ActionBox.actions[action].get_config_form(trans)
  363. return forms
  364. @classmethod
  365. def execute(cls, app, sa_session, pja, job, replacement_dict = None):
  366. if ActionBox.actions.has_key(pja.action_type):
  367. ActionBox.actions[pja.action_type].execute(app, sa_session, pja, job, replacement_dict)