diff --git a/client/galaxy/scripts/mvc/workflow/workflow-manager.js b/client/galaxy/scripts/mvc/workflow/workflow-manager.js index 826caf8f78e..c1d7ffa5e94 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-manager.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-manager.js @@ -189,7 +189,7 @@ class Workflow { id: node.id, type: node.type, content_id: node.content_id, - tool_version: node.config_form.version, + tool_version: node.config_form ? node.config_form.version : null, tool_state: node.tool_state, errors: node.errors, input_connections: input_connections, diff --git a/lib/galaxy/exceptions/__init__.py b/lib/galaxy/exceptions/__init__.py index 3946f542db8..d04d650120c 100644 --- a/lib/galaxy/exceptions/__init__.py +++ b/lib/galaxy/exceptions/__init__.py @@ -103,6 +103,11 @@ class ToolMissingException(MessageException): status_code = 400 err_code = error_codes.USER_TOOL_MISSING_PROBLEM + @property + def tool_id(self): + if 'tool_id' in self.extra_error_info: + return self.extra_error_info['tool_id'] + class RequestParameterInvalidException(MessageException): status_code = 400 diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 86984d1b9ed..7218d619cd0 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -415,9 +415,10 @@ class WorkflowContentsManager(UsesAnnotations): for step in workflow.steps: try: module_injector.inject(step, steps=workflow.steps, exact_tools=False) - except exceptions.ToolMissingException: - if step.tool_id not in missing_tools: - missing_tools.append(step.tool_id) + except exceptions.ToolMissingException as e: + # FIXME: if a subworkflow lacks multiple tools we report only the first missing tool + if e.tool_id not in missing_tools: + missing_tools.append(e.tool_id) continue if step.upgrade_messages: has_upgrade_messages = True diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index d1170086c4c..f00721ecfaf 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -140,7 +140,7 @@ class WorkflowModule(object): else: self.state.inputs = safe_loads(state) or {} - def get_errors(self): + def get_errors(self, **kwargs): """ This returns a step related error message as string or None """ return None @@ -327,6 +327,13 @@ class SubWorkflowModule(WorkflowModule): inputs.append(input) return inputs + def get_modules(self): + return [module_factory.from_workflow_step(self.trans, step) for step in self.subworkflow.steps] + + def get_errors(self, **kwargs): + errors = (module.get_errors(include_tool_id=True) for module in self.get_modules()) + return [e for e in errors if e] + def get_data_outputs(self): outputs = [] if hasattr(self.subworkflow, 'workflow_outputs'): @@ -338,20 +345,23 @@ class SubWorkflowModule(WorkflowModule): tooltip=False) for order_index in sorted(subworkflow_dict['steps']): step = subworkflow_dict['steps'][order_index] - data_outputs = subworkflow_dict['steps'][order_index]['data_outputs'] + data_outputs = step['data_outputs'] for workflow_output in step['workflow_outputs']: label = workflow_output['label'] if not label: label = "%s:%s" % (order_index, workflow_output['output_name']) for data_output in data_outputs: - if data_output['name'] == workflow_output['output_name']: + if data_output['name'] == workflow_output['output_name'] or data_output['uuid'] == workflow_output['uuid']: data_output['label'] = label data_output['name'] = label # That's the right data_output break else: - # This hopefully can't happen, but let's be clear - raise Exception("Workflow output '%s' defined, but not listed among data outputs" % workflow_output['output_name']) + # This can happen when importing workflows with missing tools. + # We can't raise an exception here, as that would prevent loading + # the workflow. + log.error("Workflow output '%s' defined, but not listed among data outputs" % workflow_output['output_name']) + continue outputs.append(data_output) return outputs @@ -732,8 +742,12 @@ class ToolModule(WorkflowModule): # ---- Configuration time ----------------------------------------------- - def get_errors(self): - return None if self.tool else "Tool is not installed." + def get_errors(self, include_tool_id=False, **kwargs): + if not self.tool: + if include_tool_id: + return "%s is not installed" % self.tool_id + else: + return "Tool is not installed" def get_inputs(self): return self.tool.inputs if self.tool else {} @@ -841,7 +855,8 @@ class ToolModule(WorkflowModule): return RuntimeValue() visit_input_values(self.tool.inputs, self.state.inputs, callback) else: - raise ToolMissingException("Tool %s missing. Cannot add dummy datasets." % self.tool_id) + raise ToolMissingException("Tool %s missing. Cannot add dummy datasets." % self.tool_id, + tool_id=self.tool_id) def get_post_job_actions(self, incoming): return ActionBox.handle_incoming(incoming) @@ -870,7 +885,8 @@ class ToolModule(WorkflowModule): state.inputs[RUNTIME_STEP_META_STATE_KEY] = step_metadata_runtime_state return state, step_errors else: - raise ToolMissingException("Tool %s missing. Cannot compute runtime state." % self.tool_id) + raise ToolMissingException("Tool %s missing. Cannot compute runtime state." % self.tool_id, + tool_id=self.tool_id) def decode_runtime_state(self, runtime_state): """ Take runtime state from persisted invocation and convert it @@ -882,7 +898,8 @@ class ToolModule(WorkflowModule): self.__restore_step_meta_runtime_state(loads(runtime_state[RUNTIME_STEP_META_STATE_KEY])) return state else: - raise ToolMissingException("Tool %s missing. Cannot recover runtime state." % self.tool_id) + raise ToolMissingException("Tool %s missing. Cannot recover runtime state." % self.tool_id, + tool_id=self.tool_id) def execute(self, trans, progress, invocation_step, use_cached_job=False): invocation = invocation_step.workflow_invocation