diff --git a/lib/galaxy/jobs/actions/__init__.py b/lib/galaxy/jobs/actions/__init__.py new file mode 100644 index 00000000000..9750741c2a6 --- /dev/null +++ b/lib/galaxy/jobs/actions/__init__.py @@ -0,0 +1,4 @@ +""" +This package contains job action classes. + +""" \ No newline at end of file diff --git a/lib/galaxy/jobs/actions/post.py b/lib/galaxy/jobs/actions/post.py new file mode 100644 index 00000000000..2a96cae7cb7 --- /dev/null +++ b/lib/galaxy/jobs/actions/post.py @@ -0,0 +1,196 @@ +import logging, threading, time +from Queue import Queue, Empty + +from galaxy.util.json import from_json_string, to_json_string + +from galaxy.web.form_builder import * + +log = logging.getLogger( __name__ ) + +# DBTODO This still needs refactoring and general cleanup. + +class DefaultJobAction(object): + name = "DefaultJobAction" + + @classmethod + def execute(cls, job): + pass + + @classmethod + def get_config_form(cls, trans): + return "

Default Job Action Config Form

" + + @classmethod + def get_short_str(cls, pja): + if pja.action_arguments: + return "%s -> %s" % (pja.action_type, pja.action_arguments) + else: + return "%s" % pja.action_type + + +class ChangeDatatypeAction(DefaultJobAction): + name = "ChangeDatatypeAction" + + @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: + trans.app.datatypes_registry.change_datatype( dataset_assoc.dataset, action.action_arguments['newtype']) + + @classmethod + def get_config_form(cls, trans): + dt_list = "" + dtnames = [ dtype_name for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems()] + dtnames.sort() + for dt_name in dtnames: + dt_list += """""" % (dt_name, dt_name, dt_name) + ps = """ + if (pja.action_type == "ChangeDatatypeAction"){ + p_str = "
" + pja.action_type + "
on " + pja.output_name + "\ +
\ +
"; + if (pja.action_arguments != undefined && pja.action_arguments.newtype != undefined){ + p_str += "$('#pja__" + pja.output_name + "__ChangeDatatypeAction__newtype').val('" + pja.action_arguments.newtype + "');" + } + p_str += "
This action will change the datatype of the output to the indicated value.
"; + } + """ % dt_list + # Note the scrip + t hack above. Is there a better way? + return ps + +class RenameDatasetAction(DefaultJobAction): + name = "RenameDatasetAction" + + @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.name = action.action_arguments['newname'] + + @classmethod + def get_config_form(cls, trans): + return """ + if (pja.action_type == "RenameDatasetAction"){ + p_str = "
"+ pja.action_type + "
on " + pja.output_name + "\ +
"; + if ((pja.action_arguments != undefined) && (pja.action_arguments.newname != undefined)){ + p_str += ""; + } + else{ + p_str += ""; + } + p_str += "
This action will rename the result dataset.
"; + } + """ + +class HideDatasetAction(DefaultJobAction): + name = "HideDatasetAction" + + @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.visible=False + + @classmethod + def get_config_form(cls, trans): + return """ + if (pja.action_type == "HideDatasetAction"){ + p_str = "
"+ pja.action_type + "
on " + pja.output_name + "\ +
"; + p_str += ""; + p_str += "
This action will *hide* the result dataset from your history.
"; + } + """ + +class SetMetadataAction(DefaultJobAction): + name = "SetMetadataAction" + # DBTODO Setting of Metadata is currently broken and disabled. It should not be used (yet). + + @classmethod + def execute(cls, trans, action, job): + for data in self.job.output_datasets: + data.set_metadata( action.action_arguments['newtype'] ) + + @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 += "

" + k + ":
" + v + "

" + return """ + if (pja.action_type == "SetMetadataAction"){ + p_str = "
"+ pja.action_type + "
on " + pja.output_name + "\ +
\ +
\ + %s\ +
This tool sets metadata in output.
"; + } + """ % dt_list + +ACTIONS = { "RenameDatasetAction" : RenameDatasetAction, + "HideDatasetAction" : HideDatasetAction, + "ChangeDatatypeAction": ChangeDatatypeAction, + # "SetMetadataAction" : SetMetadataAction, + } + +class ActionBox(object): + @classmethod + def execute(cls, action, job): + if action.action_type in ACTIONS: + 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" + + @classmethod + def handle_incoming(cls, incoming): + npd = {} + for key, val in incoming.iteritems(): + if key.startswith('pja'): + sp = key.split('__') + # flag / output_name / pjatype / desc + if not sp[2] in npd: + npd[sp[2]] = {'action_type' : sp[2], + 'output_name' : sp[1], + 'action_arguments' : {}} + if len(sp) > 3: + if sp[3] == 'output_name': + npd[sp[2]]['output_name'] = val + else: + npd[sp[2]]['action_arguments'][sp[3]] = val + else: + # Not pja stuff. + pass + return to_json_string(npd) + + @classmethod + def get_add_list(cls): + addlist = "" + return addlist + + @classmethod + def get_forms(cls, trans): + forms = "" + for action in ACTIONS: + forms += ACTIONS[action].get_config_form(trans) + return forms + + @classmethod + def execute(cls, trans, pja, job): + ACTIONS[pja.action_type].execute(trans, pja, job) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 5f77a398281..5ac3d9780af 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -113,6 +113,8 @@ class Job( object ): self.info = None self.job_runner_name = None self.job_runner_external_id = None + self.post_job_actions = None + def add_parameter( self, name, value ): self.parameters.append( JobParameter( name, value ) ) def add_input_dataset( self, name, dataset ): @@ -184,6 +186,13 @@ class JobToOutputLibraryDatasetAssociation( object ): self.name = name self.dataset = dataset +class PostJobAction( object ): + def __init__( self, action_type, workflow_step, output_name = None, action_arguments = None): + self.action_type = action_type + self.output_name = output_name + self.action_arguments = action_arguments + self.workflow_step = workflow_step + class JobExternalOutputMetadata( object ): def __init__( self, job = None, dataset = None ): self.job = job diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 1278a058fe8..1047865c780 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -369,6 +369,13 @@ JobExternalOutputMetadata.table = Table( "job_external_output_metadata", metadat Column( "filename_override_metadata", String( 255 ) ), Column( "job_runner_external_pid", String( 255 ) ) ) +PostJobAction.table = Table("post_job_action", metadata, + Column("id", Integer, primary_key=True), + Column("workflow_step_id", Integer, ForeignKey( "workflow_step.id" ), index=True, nullable=False), + Column("action_type", String(255), nullable=False), + Column("output_name", String(255), nullable=True), + Column("action_arguments", JSONType, nullable=True)) + Event.table = Table( "event", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), @@ -1237,6 +1244,9 @@ assign_mapper( context, JobExternalOutputMetadata, JobExternalOutputMetadata.tab history_dataset_association = relation( HistoryDatasetAssociation, lazy = False ), library_dataset_dataset_association = relation( LibraryDatasetDatasetAssociation, lazy = False ) ) ) +assign_mapper( context, PostJobAction, PostJobAction.table, + properties=dict(workflow_step = relation( WorkflowStep, backref='post_job_actions', primaryjoin=(WorkflowStep.table.c.id == PostJobAction.table.c.workflow_step_id)))) + assign_mapper( context, Job, Job.table, properties=dict( galaxy_session=relation( GalaxySession ), history=relation( History ), @@ -1309,7 +1319,7 @@ assign_mapper( context, Workflow, Workflow.table, assign_mapper( context, WorkflowStep, WorkflowStep.table, properties=dict( tags=relation(WorkflowStepTagAssociation, order_by=WorkflowStepTagAssociation.table.c.id, backref="workflow_steps"), - annotations=relation( WorkflowStepAnnotationAssociation, order_by=WorkflowStepAnnotationAssociation.table.c.id, backref="workflow_steps" ) ) + annotations=relation( WorkflowStepAnnotationAssociation, order_by=WorkflowStepAnnotationAssociation.table.c.id, backref="workflow_steps" ) ) ) assign_mapper( context, WorkflowStepConnection, WorkflowStepConnection.table, diff --git a/lib/galaxy/model/migrate/versions/0046_post_job_actions.py b/lib/galaxy/model/migrate/versions/0046_post_job_actions.py new file mode 100644 index 00000000000..9c4fcd93242 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0046_post_job_actions.py @@ -0,0 +1,49 @@ +""" +Migration script to create tables for handling post-job actions. +""" + +from sqlalchemy import * +from sqlalchemy.orm import * +from migrate import * +from migrate.changeset import * + +import logging +logging.basicConfig( level=logging.DEBUG ) +log = logging.getLogger( __name__ ) + +# Need our custom types, but don't import anything else from model +from galaxy.model.custom_types import * + +import datetime +now = datetime.datetime.utcnow + +metadata = MetaData( migrate_engine ) +db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) + +PostJobAction_table = Table("post_job_action", metadata, + Column("id", Integer, primary_key=True), + Column("workflow_step_id", Integer, ForeignKey( "workflow_step.id" ), index=True, nullable=False), + Column("action_type", String(255), nullable=False), + Column("output_name", String(255), nullable=True), + Column("action_arguments", JSONType, nullable=True)) + +# PostJobActionAssociation_table = Table("post_job_action_association", metadata, +# Column("id", Integer, primary_key=True), +# Column("post_job_action_id", Integer, ForeignKey("post_job_action.id"), index=True, nullable=False), +# Column("job_id", Integer, ForeignKey("job.id"), index=True, nullable=False)) + +tables = [PostJobAction_table]#, PostJobActionAssociation_table] + +def upgrade(): + print __doc__ + metadata.reflect() + for table in tables: + try: + table.create() + except: + log.warn( "Failed to create table '%s', ignoring (might result in wrong schema)" % table.name ) + +def downgrade(): + metadata.reflect() + for table in tables: + table.drop() \ No newline at end of file diff --git a/lib/galaxy/web/controllers/workflow.py b/lib/galaxy/web/controllers/workflow.py index 4253483b126..ad9e7bd42a1 100644 --- a/lib/galaxy/web/controllers/workflow.py +++ b/lib/galaxy/web/controllers/workflow.py @@ -17,6 +17,7 @@ from galaxy.workflow.modules import * from galaxy import model from galaxy.model.mapping import desc from galaxy.model.orm import * +from galaxy.jobs.actions.post import * import urllib2 @@ -387,7 +388,6 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno else: stored.importable = importable trans.sa_session.flush() - return @web.expose @@ -514,6 +514,7 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno This is used for the form shown in the right pane when a node is selected. """ + trans.workflow_building_mode = True module = module_factory.from_dict( trans, { 'type': type, @@ -521,14 +522,26 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno 'tool_state': incoming.pop("tool_state") } ) module.update_state( incoming ) - return { - 'tool_state': module.get_state(), - 'data_inputs': module.get_data_inputs(), - 'data_outputs': module.get_data_outputs(), - 'tool_errors': module.get_errors(), - 'form_html': module.get_config_form(), - 'annotation': annotation - } + + if type=='tool': + return { + 'tool_state': module.get_state(), + 'data_inputs': module.get_data_inputs(), + 'data_outputs': module.get_data_outputs(), + 'tool_errors': module.get_errors(), + 'form_html': module.get_config_form(), + 'annotation': annotation, + 'post_job_actions': module.get_post_job_actions() + } + else: + return { + 'tool_state': module.get_state(), + 'data_inputs': module.get_data_inputs(), + 'data_outputs': module.get_data_outputs(), + 'tool_errors': module.get_errors(), + 'form_html': module.get_config_form(), + 'annotation': annotation + } @web.json def get_new_module_info( self, trans, type, **kwargs ): @@ -552,7 +565,7 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno 'form_html': module.get_config_form(), 'annotation': "" } - + @web.json def load_workflow( self, trans, id ): """ @@ -613,6 +626,13 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno # Filter # FIXME: this removes connection without displaying a message currently! input_connections = [ conn for conn in input_connections if conn.input_name in data_input_names ] + # post_job_actions + pja_dict = {} + for pja in step.post_job_actions: + pja_dict[pja.action_type+pja.output_name] = dict(action_type = pja.action_type, + output_name = pja.output_name, + action_arguments = pja.action_arguments) + step_dict['post_job_actions'] = pja_dict # Encode input connections as dictionary input_conn_dict = {} for conn in input_connections: @@ -665,7 +685,7 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno annotation = step_dict[ 'annotation' ] if annotation: annotation = sanitize_html( annotation, 'utf-8', 'text/html' ) - self.add_item_annotation( trans, step, annotation ) + self.add_item_annotation( trans, step, annotation ) # Second pass to deal with connections between steps for step in steps: # Input connections @@ -815,7 +835,7 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno annotation = step_dict[ 'annotation' ] if annotation: annotation = sanitize_html( annotation, 'utf-8', 'text/html' ) - self.add_item_annotation( trans, step, annotation ) + self.add_item_annotation( trans, step, annotation ) # Second pass to deal with connections between steps for step in steps: # Input connections @@ -1044,6 +1064,8 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno # Execute it job, out_data = tool.execute( trans, step.state.inputs ) outputs[ step.id ] = out_data + for pja in step.post_job_actions: + ActionBox.execute(trans, pja, job) else: job, out_data = step.module.execute( trans, step.state ) outputs[ step.id ] = out_data diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index e4881f052d5..15aa9676a21 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -6,7 +6,8 @@ from galaxy.tools import DefaultToolState from galaxy.tools.parameters.grouping import Repeat, Conditional from galaxy.util.bunch import Bunch from galaxy.util.json import from_json_string, to_json_string - +from galaxy.jobs.actions.post import ActionBox +from galaxy.model import PostJobAction class WorkflowModule( object ): @@ -163,6 +164,7 @@ class ToolModule( WorkflowModule ): self.trans = trans self.tool_id = tool_id self.tool = trans.app.toolbox.tools_by_id[ tool_id ] + self.post_job_actions = {} self.state = None self.errors = None @@ -171,6 +173,7 @@ class ToolModule( WorkflowModule ): module = Class( trans, tool_id ) module.state = module.tool.new_state( trans, all_pages=True ) return module + @classmethod def from_dict( Class, trans, d, secure=True ): tool_id = d['tool_id'] @@ -178,6 +181,10 @@ class ToolModule( WorkflowModule ): module.state = DefaultToolState() module.state.decode( d["tool_state"], module.tool, module.trans.app, secure=secure ) module.errors = d.get( "tool_errors", None ) + if 'post_job_actions' in d and d["post_job_actions"] != []: + module.post_job_actions = d["post_job_actions"] + else: + module.post_job_actions = {} return module @classmethod @@ -187,6 +194,11 @@ class ToolModule( WorkflowModule ): module.state = DefaultToolState() module.state.inputs = module.tool.params_from_strings( step.tool_inputs, trans.app, ignore_errors=True ) module.errors = step.tool_errors + # module.post_job_actions = step.post_job_actions + pjadict = {} + for pja in step.post_job_actions: + pjadict[pja.action_type] = pja + module.post_job_actions = pjadict return module def save_to_step( self, step ): @@ -194,6 +206,17 @@ class ToolModule( WorkflowModule ): step.tool_id = self.tool_id step.tool_inputs = self.tool.params_to_strings( self.state.inputs, self.trans.app ) step.tool_errors = self.errors + for k, v in self.post_job_actions.iteritems(): + # Must have action_type, step. output and a_args are optional. + if 'output_name' in v: + output_name = v['output_name'] + else: + output_name = None + if 'action_arguments' in v: + action_arguments = v['action_arguments'] + else: + action_arguments = None + n_p = PostJobAction(v['action_type'], step, output_name, action_arguments) def get_name( self ): return self.tool.name @@ -221,15 +244,23 @@ class ToolModule( WorkflowModule ): for name, ( format, metadata_source, parent ) in self.tool.outputs.iteritems(): data_outputs.append( dict( name=name, extension=format ) ) return data_outputs + + def get_post_job_actions( self ): + return self.post_job_actions + def get_config_form( self ): self.add_dummy_datasets() return self.trans.fill_template( "workflow/editor_tool_form.mako", tool=self.tool, values=self.state.inputs, errors=( self.errors or {} ) ) + def update_state( self, incoming ): # Build a callback that handles setting an input to be required at # runtime. We still process all other parameters the user might have # set. We also need to make sure all datasets have a dummy value # for dependencies to see + + self.post_job_actions = ActionBox.handle_incoming(incoming) + make_runtime_key = incoming.get( 'make_runtime', None ) make_buildtime_key = incoming.get( 'make_buildtime', None ) def item_callback( trans, key, input, value, error, old_value, context ): diff --git a/static/scripts/galaxy.workflow_editor.canvas.js b/static/scripts/galaxy.workflow_editor.canvas.js index 3f7e84aa8aa..97a587603e4 100644 --- a/static/scripts/galaxy.workflow_editor.canvas.js +++ b/static/scripts/galaxy.workflow_editor.canvas.js @@ -273,6 +273,7 @@ $.extend( Node.prototype, { this.tool_errors = data.tool_errors; this.tooltip = data.tooltip ? data.tooltip : "" this.annotation = data.annotation; + this.post_job_actions = data.post_job_actions; if ( this.tool_errors ) { f.addClass( "tool-node-error" ); @@ -309,6 +310,7 @@ $.extend( Node.prototype, { this.form_html = data.form_html; this.tool_errors = data.tool_errors; this.annotation = data['annotation']; + this.post_job_actions = $.parseJSON(data.post_job_actions); if ( this.tool_errors ) { el.addClass( "tool-node-error" ); } else { @@ -400,6 +402,19 @@ $.extend( Workflow.prototype, { input_connections[ t.name ] = { id: c.handle1.node.id, output_name: c.handle1.name }; }); }); + var post_job_actions = {}; + if (node.post_job_actions){ + $.each( node.post_job_actions, function ( i, act ) { + var pja = { + job_id : act.id, + action_type : act.action_type, + output_name : act.output_name, + action_arguments : act.action_arguments + } + post_job_actions[ act.type + act.output_name ] = null; + post_job_actions[ act.type + act.output_name ] = pja; + }); + } var node_data = { id : node.id, type : node.type, @@ -408,7 +423,8 @@ $.extend( Workflow.prototype, { tool_errors : node.tool_errors, input_connections : input_connections, position : $(node.element).position(), - annotation: node.annotation + annotation: node.annotation, + post_job_actions: node.post_job_actions }; nodes[ node.id ] = node_data; }); diff --git a/templates/workflow/editor.mako b/templates/workflow/editor.mako index a5769109207..d8a1cdc17c1 100644 --- a/templates/workflow/editor.mako +++ b/templates/workflow/editor.mako @@ -128,7 +128,7 @@ show_modal( "Server error", message, { "Ignore error" : hide_modal } ); return false; }); - + make_popupmenu( $("#workflow-options-button"), { ##"Create New" : create_new_workflow_dialog, "Edit Attributes" : edit_workflow_attributes, @@ -306,13 +306,63 @@ }); }; +<% + from galaxy.jobs.actions.post import ActionBox +%> + + // 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. + p_str = ''; + ${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(); + $(this).closest(".toolForm").remove(); + delete workflow.active_node.post_job_actions[action_to_rem]; + workflow.active_form_has_changes = true; + }); + } + + function display_pja_list(){ + return "${ActionBox.get_add_list()}"; + } + + function display_file_list(node){ + addlist = ""; + return addlist; + } + + function new_pja(action_type, target, node){ + if (node.post_job_actions == undefined){ + //New tool node, set up dict. + node.post_job_actions = {}; + } + if (node.post_job_actions[action_type+target] == undefined){ + var new_pja = new Object(); + new_pja.action_type = action_type; + new_pja.output_name = target; + node.post_job_actions[action_type+target] = null; + node.post_job_actions[action_type+target] = new_pja; + display_pja(new_pja, node); + workflow.active_form_has_changes = true; + return true; + }else{ + return false; + } + } + function show_form_for_tool( text, node ) { $("#edit-attributes").hide(); $("#right-content").show().html( text ); - // Add metadata form to tool. if (node) { - $("#right-content").find(".toolForm").after( "

\ + $("#right-content").find(".toolForm:first").after( "

\
Edit Step Attributes
\
\ \ @@ -323,7 +373,23 @@
\
" ); } - + // Add step actions. + if (node && node.type=='tool'){ + pjastr = "

Edit Step Actions
\ +
" + display_pja_list() + display_file_list(node) + "
Create
\ +
\ +
"; + pjastr += "
Add actions to this step; actions are applied when this workflow step completes.
"; + $("#right-content").find(".toolForm").after( pjastr ); + for (var key in node.post_job_actions){ + if (key != "undefined"){ //To make sure we haven't just deleted it. + display_pja(node.post_job_actions[key], node); + } + } + $("#add_pja").click(function (){ + new_pja($("#new_pja_list").val(),$("#node_data_list").val(), node); + }); + } $("#right-content").find( "form" ).ajaxForm( { type: 'POST', dataType: 'json', @@ -355,7 +421,6 @@ $(this).remove(); make_popupmenu( b, options ); }); - // Implements auto-saving based on whether the inputs change. We consider // "changed" to be when a field is accessed and not necessarily modified // because of an issue where "onchange" is not triggered when activating @@ -612,6 +677,7 @@ .form-row { } + div.toolFormInCanvas div.toolFormBody { padding: 0; } @@ -630,7 +696,19 @@ position: absolute; z-index: 10000; } - + + .pjaForm { + margin-bottom:10px; + } + + .pjaForm .toolFormBody{ + padding:10px; + } + + .pjaForm .toolParamHelp{ + padding:5px; + } + .panel-header-button-group { margin-right: 5px; padding-right: 5px; diff --git a/templates/workflow/run.mako b/templates/workflow/run.mako index 93f464abbcc..8a84064404b 100644 --- a/templates/workflow/run.mako +++ b/templates/workflow/run.mako @@ -23,6 +23,7 @@ <% from galaxy.tools.parameters import DataToolParameter, RuntimeValue +from galaxy.jobs.actions.post import ActionBox %> <%def name="do_inputs( inputs, values, errors, prefix, step, other_values = None )"> @@ -136,6 +137,23 @@ from galaxy.tools.parameters import DataToolParameter, RuntimeValue
Step ${int(step.order_index)+1}: ${tool.name}
${do_inputs( tool.inputs, step.state.inputs, errors.get( step.id, dict() ), "", step )} + % if step.post_job_actions: +
+
+ % if len(step.post_job_actions) > 1: + + % else: + + % endif + ${', '.join([ActionBox.get_short_str(pja) for pja in step.post_job_actions])} +
+ % endif + % if step.annotations: +
+
+ ${step.annotations[0].annotation} +
+ % endif
%else: @@ -145,6 +163,12 @@ from galaxy.tools.parameters import DataToolParameter, RuntimeValue
Step ${int(step.order_index)+1}: ${module.name}
${do_inputs( module.get_runtime_inputs(), step.state.inputs, errors.get( step.id, dict() ), "", step )} + % if step.annotations: +
+
+ ${step.annotations[0].annotation} +
+ % endif
%endif