mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Properly deal with missing tools in subworkflows
If a subworkflow is missing tools the entire parent workflow couldn't be loaded into the editor, because we raised an exception. This is now only logged as an error. On the user side we now prevent running and saving such workflows by actually implementing the `get_errors` method for the SubWorkflowModule class. This should fix https://github.com/galaxyproject/galaxy/issues/7140.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user