diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 122285db6b0a..fd94a48c0535 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -178,6 +178,7 @@ import { hide_modal } from "@/layout/modal"; import { getAppRoot } from "@/onload/loadConfig"; import { useScopePointerStore } from "@/stores/scopePointerStore"; import { LastQueue } from "@/utils/promise-queue"; +import { errorMessageAsString } from "@/utils/simple-error"; import { defaultPosition } from "./composables/useDefaultStepPosition"; import { fromSimple, toSimple } from "./modules/model"; @@ -555,7 +556,15 @@ export default { this.hasChanges = false; await this.routeToWorkflow(newId); } catch (e) { - this.onWorkflowError("Saving workflow failed, please contact an administrator."); + if (create) { + throw e; + } + const errorHeading = `Saving workflow as '${rename_name}' failed`; + this.onWorkflowError(errorHeading, errorMessageAsString(e) || "Please contact an administrator.", { + Ok: () => { + this.hideModal(); + }, + }); } }, onSaveAs() { @@ -617,13 +626,15 @@ export default { ); } } catch (e) { - this.onWorkflowError("Creating workflow failed"), - e || "Please contact an administrator.", + this.onWorkflowError( + "Creating workflow failed", + errorMessageAsString(e) || "Please contact an administrator.", { Ok: () => { this.hideModal(); }, - }; + } + ); } }, nameValidate() { diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 9eb86dc3c2a6..80b399770b75 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -676,13 +676,16 @@ def update_workflow_from_raw_description( # Put parameters in workflow mode trans.workflow_building_mode = workflow_building_modes.ENABLED dry_run = workflow_update_options.dry_run - workflow, missing_tool_tups = self._workflow_from_raw_description( - trans, - raw_workflow_description, - workflow_update_options, - name=stored_workflow.name, - dry_run=dry_run, - ) + try: + workflow, missing_tool_tups = self._workflow_from_raw_description( + trans, + raw_workflow_description, + workflow_update_options, + name=stored_workflow.name, + dry_run=dry_run, + ) + except exceptions.DuplicatedIdentifierException as e: + raise e if missing_tool_tups and not workflow_update_options.allow_missing_tools: errors = [] @@ -794,29 +797,32 @@ def _workflow_from_raw_description( ) subworkflow_id_map[key] = subworkflow - # Keep track of tools required by the workflow that are not available in - # the local Galaxy instance. Each tuple in the list of missing_tool_tups - # will be (tool_id, tool_name, tool_version, step_id). - missing_tool_tups = [] - for step_dict in self.__walk_step_dicts(data): - if not dry_run: - self.__load_subworkflows( - trans, step_dict, subworkflow_id_map, workflow_state_resolution_options, dry_run=dry_run - ) + try: + # Keep track of tools required by the workflow that are not available in + # the local Galaxy instance. Each tuple in the list of missing_tool_tups + # will be (tool_id, tool_name, tool_version, step_id). + missing_tool_tups = [] + for step_dict in self.__walk_step_dicts(data): + if not dry_run: + self.__load_subworkflows( + trans, step_dict, subworkflow_id_map, workflow_state_resolution_options, dry_run=dry_run + ) - module_kwds = workflow_state_resolution_options.dict() - module_kwds.update(kwds) # TODO: maybe drop this? - for step_dict in self.__walk_step_dicts(data): - module, step = self.__module_from_dict(trans, steps, steps_by_external_id, step_dict, **module_kwds) - if isinstance(module, ToolModule) and module.tool is None: - missing_tool_tup = (module.tool_id, module.get_name(), module.tool_version, step_dict["id"]) - if missing_tool_tup not in missing_tool_tups: - missing_tool_tups.append(missing_tool_tup) - if module.get_errors(): - workflow.has_errors = True - - # Second pass to deal with connections between steps - self.__connect_workflow_steps(steps, steps_by_external_id, dry_run) + module_kwds = workflow_state_resolution_options.dict() + module_kwds.update(kwds) # TODO: maybe drop this? + for step_dict in self.__walk_step_dicts(data): + module, step = self.__module_from_dict(trans, steps, steps_by_external_id, step_dict, **module_kwds) + if isinstance(module, ToolModule) and module.tool is None: + missing_tool_tup = (module.tool_id, module.get_name(), module.tool_version, step_dict["id"]) + if missing_tool_tup not in missing_tool_tups: + missing_tool_tups.append(missing_tool_tup) + if module.get_errors(): + workflow.has_errors = True + + # Second pass to deal with connections between steps + self.__connect_workflow_steps(steps, steps_by_external_id, dry_run) + except exceptions.DuplicatedIdentifierException as e: + raise e workflow.has_cycles = True workflow.steps = steps diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 8cf8d5f629bc..f84376efc7c0 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -11,6 +11,7 @@ from sqlalchemy.sql import expression from galaxy import ( + exceptions, model, util, web, @@ -416,11 +417,6 @@ def save_workflow_as( workflow_annotation = sanitize_html(workflow_annotation) self.add_item_annotation(trans.sa_session, trans.get_user(), stored_workflow, workflow_annotation) - # Persist - session = trans.sa_session - session.add(stored_workflow) - with transaction(session): - session.commit() workflow_update_options = WorkflowUpdateOptions( update_stored_workflow_attributes=False, # taken care of above from_tool_form=from_tool_form, @@ -432,8 +428,10 @@ def save_workflow_as( workflow_data, workflow_update_options, ) + except exceptions.DuplicatedIdentifierException as e: + return self.message_exception(trans, e.err_msg, False) except MissingToolsException as e: - return dict( + missing_tools_return = dict( name=e.workflow.name, message=( "This workflow includes missing or invalid tools. " @@ -441,6 +439,18 @@ def save_workflow_as( ), errors=e.errors, ) + else: + missing_tools_return = None + + # Persist + session = trans.sa_session + session.add(stored_workflow) + with transaction(session): + session.commit() + + if missing_tools_return: + return missing_tools_return + return trans.security.encode_id(stored_workflow.id) else: # This is an error state, 'save as' must have a workflow_name