diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 429179f7b52..8f18c71f46b 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -581,6 +581,7 @@ class WorkflowContentsManager(UsesAnnotations): source=None, add_to_menu=False, hidden=False, + is_subworkflow=False, ): data = raw_workflow_description.as_dict # Put parameters in workflow mode @@ -600,6 +601,7 @@ class WorkflowContentsManager(UsesAnnotations): raw_workflow_description, workflow_create_options, name=name, + is_subworkflow=is_subworkflow, ) if "uuid" in data: workflow.uuid = data["uuid"] @@ -689,7 +691,7 @@ class WorkflowContentsManager(UsesAnnotations): return workflow, errors def _workflow_from_raw_description( - self, trans, raw_workflow_description, workflow_state_resolution_options, name, **kwds + self, trans, raw_workflow_description, workflow_state_resolution_options, name, is_subworkflow=False, **kwds ): # don't commit the workflow or attach its part to the sa session - just build a # a transient model to operate on or render. @@ -764,8 +766,12 @@ class WorkflowContentsManager(UsesAnnotations): # Second pass to deal with connections between steps self.__connect_workflow_steps(steps, steps_by_external_id, dry_run) - # Order the steps if possible - attach_ordered_steps(workflow, steps) + workflow.has_cycles = True + workflow.steps = steps + # we can't reorder subworkflows, as step connections would become invalid + if not is_subworkflow: + # Order the steps if possible + attach_ordered_steps(workflow) return workflow, missing_tool_tups @@ -857,7 +863,7 @@ class WorkflowContentsManager(UsesAnnotations): """ if len(workflow.steps) == 0: raise exceptions.MessageException("Workflow cannot be run because it does not have any steps.") - if attach_ordered_steps(workflow, workflow.steps): + if attach_ordered_steps(workflow): raise exceptions.MessageException("Workflow cannot be run because it contains cycles.") trans.workflow_building_mode = workflow_building_modes.USE_HISTORY module_injector = WorkflowModuleInjector(trans) @@ -950,7 +956,7 @@ class WorkflowContentsManager(UsesAnnotations): """ if len(workflow.steps) == 0: raise exceptions.MessageException("Workflow cannot be run because it does not have any steps.") - if attach_ordered_steps(workflow, workflow.steps): + if attach_ordered_steps(workflow): raise exceptions.MessageException("Workflow cannot be run because it contains cycles.") # Ensure that the user has a history @@ -1652,6 +1658,7 @@ class WorkflowContentsManager(UsesAnnotations): steps.append(step) external_id = step_dict["id"] steps_by_external_id[external_id] = step + step.order_index = external_id if "workflow_outputs" in step_dict: workflow_outputs = step_dict["workflow_outputs"] found_output_names = set() @@ -1718,7 +1725,11 @@ class WorkflowContentsManager(UsesAnnotations): def __build_embedded_subworkflow(self, trans, data, workflow_state_resolution_options): raw_workflow_description = self.ensure_raw_description(data) subworkflow = self.build_workflow_from_raw_description( - trans, raw_workflow_description, workflow_state_resolution_options, hidden=True + trans, + raw_workflow_description, + workflow_state_resolution_options, + hidden=True, + is_subworkflow=True, ).workflow return subworkflow diff --git a/lib/galaxy/workflow/extract.py b/lib/galaxy/workflow/extract.py index b5f8a6e56f4..4e0f102e388 100644 --- a/lib/galaxy/workflow/extract.py +++ b/lib/galaxy/workflow/extract.py @@ -52,8 +52,9 @@ def extract_workflow( # Workflow to populate workflow = model.Workflow() workflow.name = workflow_name + workflow.steps = steps # Order the steps if possible - attach_ordered_steps(workflow, steps) + attach_ordered_steps(workflow) # And let's try to set up some reasonable locations on the canvas # (these are pretty arbitrary values) levorder = order_workflow_steps_with_levels(steps) diff --git a/lib/galaxy/workflow/steps.py b/lib/galaxy/workflow/steps.py index a87e1828728..b632504b42f 100644 --- a/lib/galaxy/workflow/steps.py +++ b/lib/galaxy/workflow/steps.py @@ -10,11 +10,11 @@ from galaxy.util.topsort import ( ) -def attach_ordered_steps(workflow, steps): +def attach_ordered_steps(workflow): """Attempt to topologically order steps and attach to workflow. If this fails - the workflow contains cycles so it mark it as such. """ - ordered_steps = order_workflow_steps(steps) + ordered_steps = order_workflow_steps(workflow.steps) workflow.has_cycles = True if ordered_steps: workflow.has_cycles = False @@ -30,6 +30,8 @@ def order_workflow_steps(steps): """ position_data_available = bool(steps) for step in steps: + if step.subworkflow: + attach_ordered_steps(step.subworkflow) if not step.position or "left" not in step.position or "top" not in step.position: position_data_available = False if position_data_available: diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 05219b82f6e..405ad60dad1 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -5059,6 +5059,65 @@ steps: run_workflow_response = self.workflow_populator.invoke_workflow_raw(uploaded_workflow_id, workflow_request) return run_workflow_response, history_id + def test_subworkflow_import_order_maintained(self): + summary = self._run_workflow( + """ +class: GalaxyWorkflow +inputs: + outer_input_1: + type: int + default: 1 + position: + left: 0 + top: 0 + outer_input_2: + type: int + default: 2 + position: + left: 100 + top: 0 +steps: + nested_workflow: + in: + inner_input_1: outer_input_1 + inner_input_2: outer_input_2 + run: + class: GalaxyWorkflow + inputs: + inner_input_1: + type: int + position: + left: 100 + top: 0 + inner_input_2: + type: int + position: + left: 0 + top: 0 + steps: [] + outputs: + - label: nested_out_1 + outputSource: inner_input_1/output + - label: nested_out_2 + outputSource: inner_input_2/output +outputs: + - label: out_1 + outputSource: nested_workflow/nested_out_1 + - label: out_2 + outputSource: nested_workflow/nested_out_2 +""", + assert_ok=False, + wait=False, + ) + self.workflow_populator.wait_for_invocation(summary.workflow_id, summary.invocation_id) + self.workflow_populator.wait_for_history_workflows( + summary.history_id, assert_ok=False, expected_invocation_count=2 + ) + invocation = self.workflow_populator.get_invocation(summary.invocation_id) + output_values = invocation["output_values"] + assert output_values["out_1"] == 1 + assert output_values["out_2"] == 2 + @skip_without_tool("random_lines1") def test_run_replace_params_by_steps(self): workflow_request, history_id, workflow_id, steps = self._setup_random_x2_workflow_steps( diff --git a/lib/tool_shed/webapp/model/migrate/check.py b/lib/tool_shed/webapp/model/migrate/check.py index 10df4878017..98ab037ccfe 100644 --- a/lib/tool_shed/webapp/model/migrate/check.py +++ b/lib/tool_shed/webapp/model/migrate/check.py @@ -27,14 +27,14 @@ migrate_repository = repository.Repository(migrate_repository_directory) def create_or_verify_database(url, engine_options=None): """ - Check that the database is use-able, possibly creating it if empty (this is + Check that the database is useable, possibly creating it if empty (this is the only time we automatically create tables, otherwise we force the user to do it using the management script so they can create backups). 1) Empty database --> initialize with latest version and return 2) Database older than migration support --> fail and require manual update 3) Database at state where migrate support introduced --> add version control information but make no changes (might still require manual update) - 4) Database versioned but out of date --> fail with informative message, user must run "sh manage_db.sh upgrade" + 4) Database versioned but out of date --> fail with informative message, user must run "sh manage_toolshed_db.sh upgrade" """ engine_options = engine_options or {} @@ -81,7 +81,7 @@ def create_or_verify_database(url, engine_options=None): migrate_repository.versions.latest, ) exception_msg += "Back up your database and then migrate the schema by running the following from your Galaxy installation directory:" - exception_msg += "\n\nsh manage_db.sh upgrade tool_shed\n" + exception_msg += "\n\nsh manage_toolshed_db.sh upgrade\n" raise Exception(exception_msg) else: log.info("At database version %d" % db_schema.version) diff --git a/templates/tagging_common.mako b/templates/tagging_common.mako index a4d95adf1cd..cbaad93c12c 100644 --- a/templates/tagging_common.mako +++ b/templates/tagging_common.mako @@ -77,7 +77,7 @@ from markupsafe import escape item_tag_names = [] for ta in item_tags: - item_tag_names.append(escape(ta.tag.name)) + item_tag_names.append(escape(f"#{ta.value}" if ta.value else ta.tag.name)) %>