diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 2c14748468d..caed4b979c5 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -617,7 +617,7 @@ class WorkflowContentsManager(UsesAnnotations): continue if step.upgrade_messages: has_upgrade_messages = True - if step.type == 'tool' or step.type is None: + if step.type in ('tool', 'subworkflow', None): if step.module.version_changes: step_version_changes.extend(step.module.version_changes) step_errors = step.module.get_errors() @@ -805,7 +805,7 @@ class WorkflowContentsManager(UsesAnnotations): # Fix any missing parameters upgrade_message_dict = module.check_and_update_state() or {} if hasattr(module, "version_changes") and module.version_changes: - upgrade_message_dict[module.tool.name] = "\n".join(module.version_changes) + upgrade_message_dict[module.get_name()] = "\n".join(module.version_changes) # Get user annotation. config_form = module.get_config_form(step=step) annotation_str = self.get_item_annotation_str(trans.sa_session, trans.user, step) or '' diff --git a/lib/galaxy/tools/toolbox/base.py b/lib/galaxy/tools/toolbox/base.py index 6ab39d1f6b8..262593afc9e 100644 --- a/lib/galaxy/tools/toolbox/base.py +++ b/lib/galaxy/tools/toolbox/base.py @@ -761,21 +761,6 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): return True return False - def get_tool_id(self, tool_id): - """ Take a tool id - potentially from a different Galaxy instance or that - is no longer loaded - and find the closest match to the currently loaded - tools (using get_tool for inexact matches which currently returns the oldest - tool shed installed tool with the same short id). - """ - if tool_id not in self._tools_by_id: - tool = self.get_tool(tool_id) - if tool: - tool_id = tool.id - else: - tool_id = None - # else exact match - leave unmodified. - return tool_id - def get_loaded_tools_by_lineage(self, tool_id): """Get all loaded tools associated by lineage to the tool whose id is tool_id.""" tool_lineage = self._lineage_map.get(tool_id) diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 8400f42114b..2463fcf1d02 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -398,6 +398,7 @@ class SubWorkflowModule(WorkflowModule): # - Second pass actually turn RuntimeInputs into inputs if possible. type = "subworkflow" name = "Subworkflow" + _modules = None @classmethod def from_dict(Class, trans, d, **kwds): @@ -455,7 +456,21 @@ class SubWorkflowModule(WorkflowModule): return inputs def get_modules(self): - return [module_factory.from_workflow_step(self.trans, step) for step in self.subworkflow.steps] + if self._modules is None: + self._modules = [module_factory.from_workflow_step(self.trans, step) for step in self.subworkflow.steps] + return self._modules + + @property + def version_changes(self): + version_changes = [] + for m in self.get_modules(): + if hasattr(m, 'version_changes'): + version_changes.extend(m.version_changes) + return version_changes + + def check_and_update_state(self): + states = (m.check_and_update_state() for m in self.get_modules()) + return [upgrade_message for upgrade_message in states if upgrade_message] or None def get_errors(self, **kwargs): errors = (module.get_errors(include_tool_id=True) for module in self.get_modules()) @@ -1244,13 +1259,15 @@ class ToolModule(WorkflowModule): def __init__(self, trans, tool_id, tool_version=None, exact_tools=True, tool_uuid=None, **kwds): super().__init__(trans, content_id=tool_id, **kwds) self.tool_id = tool_id - self.tool_version = str(tool_version) + self.tool_version = str(tool_version) if tool_version else None self.tool_uuid = tool_uuid self.tool = trans.app.toolbox.get_tool(tool_id, tool_version=tool_version, exact=exact_tools, tool_uuid=tool_uuid) if self.tool: - if tool_version and exact_tools and str(self.tool.version) != tool_version: - safe_version = WORKFLOW_SAFE_TOOL_VERSION_UPDATES.get(tool_id) - if safe_version and safe_version.current_version >= packaging.version.parse(tool_version) >= safe_version.min_version: + current_tool_id = self.tool.id + current_tool_version = str(self.tool.version) + if tool_version and exact_tools and self.tool_version != current_tool_version: + safe_version = WORKFLOW_SAFE_TOOL_VERSION_UPDATES.get(current_tool_id) + if safe_version and safe_version.current_version >= packaging.version.parse(current_tool_id) >= safe_version.min_version: self.tool = trans.app.toolbox.get_tool(tool_id, tool_version=tool_version, exact=False, tool_uuid=tool_uuid) else: log.info(f"Exact tool specified during workflow module creation for [{tool_id}] but couldn't find correct version [{tool_version}].") @@ -1299,22 +1316,19 @@ class ToolModule(WorkflowModule): @classmethod def from_workflow_step(Class, trans, step, **kwds): - if step.tool_id is not None: - tool_id = trans.app.toolbox.get_tool_id(step.tool_id) or step.tool_id - else: - tool_id = None tool_version = step.tool_version tool_uuid = step.tool_uuid - module = super().from_workflow_step(trans, step, tool_id=tool_id, tool_version=tool_version, tool_uuid=tool_uuid, **kwds) + kwds['exact_tools'] = False + module = super().from_workflow_step(trans, step, tool_id=step.tool_id, tool_version=tool_version, tool_uuid=tool_uuid, **kwds) module.workflow_outputs = step.workflow_outputs module.post_job_actions = {} for pja in step.post_job_actions: module.post_job_actions[pja.action_type] = pja if module.tool: message = "" - if step.tool_id and step.tool_id != module.tool_id: # This means the exact version of the tool is not installed. We inform the user. + if step.tool_id and step.tool_id != module.tool.id or step.tool_version and step.tool_version != module.tool.version: # This means the exact version of the tool is not installed. We inform the user. old_tool_shed = step.tool_id.split("/repos/")[0] - if old_tool_shed not in tool_id: # Only display the following warning if the tool comes from a different tool shed + if old_tool_shed not in module.tool.id: # Only display the following warning if the tool comes from a different tool shed old_tool_shed_url = get_tool_shed_url_from_tool_shed_registry(trans.app, old_tool_shed) if not old_tool_shed_url: # a tool from a different tool_shed has been found, but the original tool shed has been deactivated old_tool_shed_url = "http://" + old_tool_shed # let's just assume it's either http, or a http is forwarded to https. @@ -1324,12 +1338,12 @@ class ToolModule(WorkflowModule): message += f"The tool \'{module.tool.name}\', version {tool_version} by the owner {module.tool.repository_owner} installed from {old_tool_shed_url} is not available. " message += f"A derivation of this tool installed from {new_tool_shed_url} will be used instead. " if step.tool_version and (step.tool_version != module.tool.version): - message += f"Using version '{module.tool.version}' instead of version '{step.tool_version}' specified in this workflow. " + message += f"Using version '{module.tool.version}' instead of version '{step.tool_version}' specified in this workflow. " if message: log.debug(message) module.version_changes.append(message) else: - log.warning("The tool '%s' is missing. Cannot build workflow module." % tool_id) + log.warning("The tool '%s' is missing. Cannot build workflow module." % step.tool_id) return module # ---- Saving in various forms ------------------------------------------ diff --git a/lib/galaxy_test/selenium/test_workflow_editor.py b/lib/galaxy_test/selenium/test_workflow_editor.py index 6e476032a25..4adc6a09952 100644 --- a/lib/galaxy_test/selenium/test_workflow_editor.py +++ b/lib/galaxy_test/selenium/test_workflow_editor.py @@ -1,5 +1,6 @@ import json +import yaml from selenium.webdriver.common.keys import Keys from galaxy_test.base.workflow_fixtures import ( @@ -404,6 +405,30 @@ steps: self.components.workflow_editor.modal_button_continue.wait_for_and_click() self.assert_has_changes_and_save() + @selenium_test + def test_editor_subworkflow_tool_upgrade_message(self): + workflow_populator = self.workflow_populator + embedded_workflow = yaml.safe_load(WORKFLOW_WITH_OLD_TOOL_VERSION) + outer_workflow = yaml.safe_load(""" +class: GalaxyWorkflow +inputs: + outer_input: data +steps: + nested_workflow: + run: {} + in: + input1: outer_input + """) + outer_workflow['steps']['nested_workflow']['run'] = embedded_workflow + workflow_populator.upload_yaml_workflow(json.dumps(outer_workflow), exact_tools=True) + self.workflow_index_open() + self.workflow_index_click_option("Edit") + self.sleep_for(self.wait_types.UX_RENDER) + self.assert_modal_has_text("Using version '0.2' instead of version '0.0.1'") + self.screenshot("workflow_editor_subworkflow_tool_upgrade") + self.components.workflow_editor.modal_button_continue.wait_for_and_click() + self.assert_has_changes_and_save() + @staticmethod def set_text_element(element, value): # Try both, no harm here diff --git a/test/unit/tools/test_toolbox.py b/test/unit/tools/test_toolbox.py index dbebc07d75d..9004cfde49a 100644 --- a/test/unit/tools/test_toolbox.py +++ b/test/unit/tools/test_toolbox.py @@ -389,17 +389,17 @@ class ToolBoxTestCase(BaseToolBoxTestCase): assert section_by_label is section assert tool_panel_section_key == 'tid' - def test_get_tool_id(self): + def test_get_tool(self): self._init_tool() self._setup_two_versions_in_config() self._setup_two_versions() - assert self.toolbox.get_tool_id("test_tool") in [ + assert self.toolbox.get_tool("test_tool").id in [ "github.com/galaxyproject/example/test_tool/0.1", "github.com/galaxyproject/example/test_tool/0.2" ] - assert self.toolbox.get_tool_id("github.com/galaxyproject/example/test_tool/0.1") == "github.com/galaxyproject/example/test_tool/0.1" - assert self.toolbox.get_tool_id("github.com/galaxyproject/example/test_tool/0.2") == "github.com/galaxyproject/example/test_tool/0.2" - assert self.toolbox.get_tool_id("github.com/galaxyproject/example/test_tool/0.3") != "github.com/galaxyproject/example/test_tool/0.3" + assert self.toolbox.get_tool("github.com/galaxyproject/example/test_tool/0.1").id == "github.com/galaxyproject/example/test_tool/0.1" + assert self.toolbox.get_tool("github.com/galaxyproject/example/test_tool/0.2").id == "github.com/galaxyproject/example/test_tool/0.2" + assert self.toolbox.get_tool("github.com/galaxyproject/example/test_tool/0.3").id != "github.com/galaxyproject/example/test_tool/0.3" def test_tool_dir(self): self._init_tool() diff --git a/test/unit/workflows/workflow_support.py b/test/unit/workflows/workflow_support.py index fce3011fbba..a20fd6e361c 100644 --- a/test/unit/workflows/workflow_support.py +++ b/test/unit/workflows/workflow_support.py @@ -75,10 +75,6 @@ class TestToolbox: # Real tool box returns None of missing tool also return self.tools.get(tool_id, None) - def get_tool_id(self, tool_id): - tool = self.get_tool(tool_id) - return tool and tool.id - def yaml_to_model(has_dict, id_offset=100): if isinstance(has_dict, str):