From 2aa8ed4efbcf1224d335593e9d64966b8b173bac Mon Sep 17 00:00:00 2001 From: Anthony Bretaudeau Date: Fri, 11 Jan 2019 14:28:10 +0100 Subject: [PATCH 01/21] fix tag_using_filenames not converted to bool --- lib/galaxy/webapps/galaxy/api/library_contents.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/api/library_contents.py b/lib/galaxy/webapps/galaxy/api/library_contents.py index 81c86d15dac..4fb2c7de9d2 100644 --- a/lib/galaxy/webapps/galaxy/api/library_contents.py +++ b/lib/galaxy/webapps/galaxy/api/library_contents.py @@ -230,6 +230,8 @@ class LibraryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary # The rest of the security happens in the library_common controller. real_folder_id = trans.security.encode_id(parent.id) + payload['tag_using_filenames'] = util.string_as_bool(payload.get('tag_using_filenames', None)) + # are we copying an HDA to the library folder? # we'll need the id and any message to attach, then branch to that private function from_hda_id, from_hdca_id, ldda_message = (payload.pop('from_hda_id', None), payload.pop('from_hdca_id', None), payload.pop('ldda_message', '')) From 3fa43dfecea37a5f56e3675afad0bd65d3196f56 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 15 Jan 2019 12:12:42 -0500 Subject: [PATCH 02/21] Swap to normal event bindings for multipanel drop. get(0) isn't great, but we'll overhaul this more in the future. --- client/galaxy/scripts/mvc/history/history-view-edit.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/galaxy/scripts/mvc/history/history-view-edit.js b/client/galaxy/scripts/mvc/history/history-view-edit.js index 21c6eb1f6c8..70020d5a38c 100644 --- a/client/galaxy/scripts/mvc/history/history-view-edit.js +++ b/client/galaxy/scripts/mvc/history/history-view-edit.js @@ -481,8 +481,7 @@ var HistoryViewEdit = _super.extend( this.$list().before([this._renderDropTargetHelp(), $dropTarget]); for (var evName in dropHandlers) { if (dropHandlers.hasOwnProperty(evName)) { - //console.debug( evName, dropHandlers[ evName ] ); - $dropTarget.on(evName, dropHandlers[evName]); + $dropTarget.get(0).addEventListener(evName, dropHandlers[evName]); } } return this; @@ -549,8 +548,7 @@ var HistoryViewEdit = _super.extend( drop: function(ev) { ev.preventDefault(); //ev.stopPropagation(); - - var dataTransfer = ev.originalEvent.dataTransfer; + var dataTransfer = ev.dataTransfer; var data = dataTransfer.getData("text"); dataTransfer.dropEffect = "move"; From 54aecb3f31812c86d370034cf557537780efbcdf Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 17 Jan 2019 13:12:31 +0100 Subject: [PATCH 03/21] 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. --- .../scripts/mvc/workflow/workflow-manager.js | 2 +- lib/galaxy/exceptions/__init__.py | 5 +++ lib/galaxy/managers/workflows.py | 7 ++-- lib/galaxy/workflow/modules.py | 37 ++++++++++++++----- 4 files changed, 37 insertions(+), 14 deletions(-) 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 From 6ff84684bd577ea4610d63213cc090b86d983aa8 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 17 Jan 2019 14:17:02 +0100 Subject: [PATCH 04/21] Fix displaying tool help images on python 3 --- lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py | 2 +- lib/galaxy/webapps/tool_shed/controllers/repository.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py index da50198f54f..79aae81b5a0 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py @@ -276,7 +276,7 @@ class AdminToolshed(AdminGalaxy): mimetype = trans.app.datatypes_registry.get_mimetype_by_extension(extension) if mimetype: trans.response.set_content_type(mimetype) - return open(path_to_file, 'r') + return open(path_to_file, 'rb') return None @web.expose diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 0935c15ca5f..40eb328e4fe 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -870,7 +870,7 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): mimetype = trans.app.datatypes_registry.get_mimetype_by_extension(extension) if mimetype: trans.response.set_content_type(mimetype) - return open(path_to_file, 'r') + return open(path_to_file, 'rb') return None @web.expose From ab8ea1ee40c2a7de3a7cc6e6aaafba95f451a6c6 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 13:41:13 -0500 Subject: [PATCH 05/21] Add error listener for input elements, utilize to highlight numeric value range adjustments --- client/galaxy/scripts/mvc/form/form-section.js | 14 ++++++++++++++ client/galaxy/scripts/mvc/ui/ui-slider.js | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/client/galaxy/scripts/mvc/form/form-section.js b/client/galaxy/scripts/mvc/form/form-section.js index 75111933817..76ab42381aa 100644 --- a/client/galaxy/scripts/mvc/form/form-section.js +++ b/client/galaxy/scripts/mvc/form/form-section.js @@ -187,10 +187,24 @@ var View = Backbone.View.extend({ field: field }); this.app.element_list[id] = input_element; + this._attachWarningHandler(field, input_element); this._append(input_element.$el, input_def.id); return field; }, + /** Attach warning listeners to input elements */ + _attachWarningHandler: function(field, input_element) { + if (field.model) { + field.model.on("error", message => { + if (message) { + input_element.error(message || "Please verify this parameter."); + } else { + input_element.reset(); + } + }); + } + }, + /** Append a new element to the form i.e. input element, repeat block, conditionals etc. */ _append: function($el, id) { this.$el.append($el.addClass("section-row").attr("id", id)); diff --git a/client/galaxy/scripts/mvc/ui/ui-slider.js b/client/galaxy/scripts/mvc/ui/ui-slider.js index 7293c62a00e..49742f42bd3 100644 --- a/client/galaxy/scripts/mvc/ui/ui-slider.js +++ b/client/galaxy/scripts/mvc/ui/ui-slider.js @@ -103,7 +103,9 @@ var View = Backbone.View.extend({ /** Set and return the current value */ value: function(new_val) { - var options = this.model.attributes; + + let options = this.model.attributes; + let original_val = new_val; if (new_val !== undefined) { if (new_val !== null && new_val !== "" && !this._isParameter(new_val)) { if (isNaN(new_val)) { @@ -122,6 +124,9 @@ var View = Backbone.View.extend({ this.model.set("value", new_val); this.model.trigger("change"); options.onchange(new_val); + let has_changed = parseInt(original_val) !== parseInt(new_val); + let message = has_changed ? "Corrected value by range." : null; + this.model.trigger("error", message); } return this.model.get("value"); }, From 48c2807e2ebecf7fd01bd56e102ccaaa25947a29 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 13:43:46 -0500 Subject: [PATCH 06/21] Fix name of error handler helper function --- client/galaxy/scripts/mvc/form/form-section.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/galaxy/scripts/mvc/form/form-section.js b/client/galaxy/scripts/mvc/form/form-section.js index 76ab42381aa..4ca1b5a94e2 100644 --- a/client/galaxy/scripts/mvc/form/form-section.js +++ b/client/galaxy/scripts/mvc/form/form-section.js @@ -187,13 +187,13 @@ var View = Backbone.View.extend({ field: field }); this.app.element_list[id] = input_element; - this._attachWarningHandler(field, input_element); + this._attachErrorHandler(field, input_element); this._append(input_element.$el, input_def.id); return field; }, - /** Attach warning listeners to input elements */ - _attachWarningHandler: function(field, input_element) { + /** Attach error listeners to input elements */ + _attachErrorHandler: function(field, input_element) { if (field.model) { field.model.on("error", message => { if (message) { From d20c6f6d8c1cfdd677afa04ad70c10e11c7228f4 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 13:44:51 -0500 Subject: [PATCH 07/21] Remove unused default error message --- client/galaxy/scripts/mvc/form/form-section.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/galaxy/scripts/mvc/form/form-section.js b/client/galaxy/scripts/mvc/form/form-section.js index 4ca1b5a94e2..2fa76a9e3cb 100644 --- a/client/galaxy/scripts/mvc/form/form-section.js +++ b/client/galaxy/scripts/mvc/form/form-section.js @@ -197,7 +197,7 @@ var View = Backbone.View.extend({ if (field.model) { field.model.on("error", message => { if (message) { - input_element.error(message || "Please verify this parameter."); + input_element.error(message); } else { input_element.reset(); } From ef02c60f792a8aeadcaa58d8e0a4ac71d65aa42b Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 13:49:57 -0500 Subject: [PATCH 08/21] Move local slider helper variables into score/statement --- client/galaxy/scripts/mvc/ui/ui-slider.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/galaxy/scripts/mvc/ui/ui-slider.js b/client/galaxy/scripts/mvc/ui/ui-slider.js index 49742f42bd3..a175844587c 100644 --- a/client/galaxy/scripts/mvc/ui/ui-slider.js +++ b/client/galaxy/scripts/mvc/ui/ui-slider.js @@ -103,10 +103,9 @@ var View = Backbone.View.extend({ /** Set and return the current value */ value: function(new_val) { - - let options = this.model.attributes; - let original_val = new_val; if (new_val !== undefined) { + let options = this.model.attributes; + let original_val = new_val; if (new_val !== null && new_val !== "" && !this._isParameter(new_val)) { if (isNaN(new_val)) { new_val = 0; From 8534ef5d5cef95bad2dbb882b8285a44deb0dca6 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 13:57:53 -0500 Subject: [PATCH 09/21] Move field error binding to input element wrapper --- client/galaxy/scripts/mvc/form/form-input.js | 9 +++++++++ client/galaxy/scripts/mvc/form/form-section.js | 14 -------------- client/galaxy/scripts/mvc/ui/ui-slider.js | 2 +- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/client/galaxy/scripts/mvc/form/form-input.js b/client/galaxy/scripts/mvc/form/form-input.js index 6781e5c5086..0f7b0ff7576 100644 --- a/client/galaxy/scripts/mvc/form/form-input.js +++ b/client/galaxy/scripts/mvc/form/form-input.js @@ -69,6 +69,15 @@ export default Backbone.View.extend({ self.reset(); }); } + + // add error listener + this.field.model && this.field.model.on("error", message => { + if (message) { + this.error(message); + } else { + this.reset(); + } + }); }, /** Set backdrop for input element */ diff --git a/client/galaxy/scripts/mvc/form/form-section.js b/client/galaxy/scripts/mvc/form/form-section.js index 2fa76a9e3cb..75111933817 100644 --- a/client/galaxy/scripts/mvc/form/form-section.js +++ b/client/galaxy/scripts/mvc/form/form-section.js @@ -187,24 +187,10 @@ var View = Backbone.View.extend({ field: field }); this.app.element_list[id] = input_element; - this._attachErrorHandler(field, input_element); this._append(input_element.$el, input_def.id); return field; }, - /** Attach error listeners to input elements */ - _attachErrorHandler: function(field, input_element) { - if (field.model) { - field.model.on("error", message => { - if (message) { - input_element.error(message); - } else { - input_element.reset(); - } - }); - } - }, - /** Append a new element to the form i.e. input element, repeat block, conditionals etc. */ _append: function($el, id) { this.$el.append($el.addClass("section-row").attr("id", id)); diff --git a/client/galaxy/scripts/mvc/ui/ui-slider.js b/client/galaxy/scripts/mvc/ui/ui-slider.js index a175844587c..244dd3de72c 100644 --- a/client/galaxy/scripts/mvc/ui/ui-slider.js +++ b/client/galaxy/scripts/mvc/ui/ui-slider.js @@ -124,7 +124,7 @@ var View = Backbone.View.extend({ this.model.trigger("change"); options.onchange(new_val); let has_changed = parseInt(original_val) !== parseInt(new_val); - let message = has_changed ? "Corrected value by range." : null; + let message = has_changed ? "This value was invalid or out-of-range. It has been auto-corrected." : null; this.model.trigger("error", message); } return this.model.get("value"); From 48811d4c0c79b5dfdba2e51471f71d9d9fa0b34e Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 14:14:55 -0500 Subject: [PATCH 10/21] Use listento caller to bind input element error listener --- client/galaxy/scripts/mvc/form/form-input.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/client/galaxy/scripts/mvc/form/form-input.js b/client/galaxy/scripts/mvc/form/form-input.js index 0f7b0ff7576..aa92a5b84a5 100644 --- a/client/galaxy/scripts/mvc/form/form-input.js +++ b/client/galaxy/scripts/mvc/form/form-input.js @@ -71,13 +71,15 @@ export default Backbone.View.extend({ } // add error listener - this.field.model && this.field.model.on("error", message => { - if (message) { - this.error(message); - } else { - this.reset(); - } - }); + if (this.field.model) { + this.listenTo(this.field.model, "error", message => { + if (message) { + this.error(message); + } else { + this.reset(); + } + }); + } }, /** Set backdrop for input element */ From 97651462212b2a08a18b67b9fe2d731732b3d1c3 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 7 Jan 2019 14:21:01 -0500 Subject: [PATCH 11/21] Add additional validation to slider out-of-range error detection --- client/galaxy/scripts/mvc/ui/ui-slider.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/galaxy/scripts/mvc/ui/ui-slider.js b/client/galaxy/scripts/mvc/ui/ui-slider.js index 244dd3de72c..fa04bdcf1bd 100644 --- a/client/galaxy/scripts/mvc/ui/ui-slider.js +++ b/client/galaxy/scripts/mvc/ui/ui-slider.js @@ -106,7 +106,8 @@ var View = Backbone.View.extend({ if (new_val !== undefined) { let options = this.model.attributes; let original_val = new_val; - if (new_val !== null && new_val !== "" && !this._isParameter(new_val)) { + let is_value = new_val !== null && new_val !== "" && !this._isParameter(new_val); + if (is_value) { if (isNaN(new_val)) { new_val = 0; } @@ -123,7 +124,7 @@ var View = Backbone.View.extend({ this.model.set("value", new_val); this.model.trigger("change"); options.onchange(new_val); - let has_changed = parseInt(original_val) !== parseInt(new_val); + let has_changed = is_value && parseInt(original_val) !== parseInt(new_val); let message = has_changed ? "This value was invalid or out-of-range. It has been auto-corrected." : null; this.model.trigger("error", message); } From b1b1d848d35816cfa344aeb687c78c7a0c93e66f Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 17 Jan 2019 15:11:32 +0100 Subject: [PATCH 12/21] Fix loading subworkflows --- lib/galaxy/workflow/modules.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index f00721ecfaf..db7396136a6 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -332,7 +332,10 @@ class SubWorkflowModule(WorkflowModule): 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] + errors = [e for e in errors if e] + if any(errors): + return errors + return None def get_data_outputs(self): outputs = [] From 24f97f6917da606f8b9c1fc1fb4faef5c54f1933 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 17 Jan 2019 14:56:07 +0100 Subject: [PATCH 13/21] Open files in binary mode when sending data --- lib/galaxy/datatypes/data.py | 2 +- lib/galaxy/datatypes/genetics.py | 2 +- lib/galaxy/datatypes/interval.py | 6 +++--- lib/galaxy/datatypes/tabular.py | 4 ++-- lib/galaxy/webapps/galaxy/api/datasets.py | 2 +- lib/galaxy/webapps/galaxy/api/library_datasets.py | 2 +- lib/galaxy/webapps/galaxy/controllers/dataset.py | 4 ++-- lib/galaxy/webapps/galaxy/controllers/root.py | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index e17b6f68be5..46c74648da0 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -430,7 +430,7 @@ class Data(object): else: trans.response.set_content_type("text/html") return trans.stream_template_mako("/dataset/large_file.mako", - truncated_data=open(data.file_name).read(max_peek_size), + truncated_data=open(data.file_name, 'rb').read(max_peek_size), data=data) def _yield_user_file_content(self, trans, from_dataset, filename): diff --git a/lib/galaxy/datatypes/genetics.py b/lib/galaxy/datatypes/genetics.py index ab22d49e13d..90b16536503 100644 --- a/lib/galaxy/datatypes/genetics.py +++ b/lib/galaxy/datatypes/genetics.py @@ -68,7 +68,7 @@ class GenomeGraphs(Tabular): """ Returns file """ - return open(dataset.file_name, 'r') + return open(dataset.file_name, 'rb') def ucsc_links(self, dataset, type, app, base_url): """ diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index 63c664db2b5..e583e2fed4e 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -238,7 +238,7 @@ class Interval(Tabular): for elems in util.file_iter(dataset.file_name): tmp = [elems[c], elems[s], elems[e]] fh.write('%s\n' % '\t'.join(tmp)) - return open(fh.name) + return open(fh.name, 'rb') def display_peek(self, dataset): """Returns formated html of peek""" @@ -371,7 +371,7 @@ class BedGraph(Interval): Returns file contents as is with no modifications. TODO: this is a functional stub and will need to be enhanced moving forward to provide additional support for bedgraph. """ - return open(dataset.file_name) + return open(dataset.file_name, 'rb') def get_estimated_display_viewport(self, dataset, chrom_col=0, start_col=1, end_col=2): """ @@ -459,7 +459,7 @@ class Bed(Interval): break try: - return open(dataset.file_name) + return open(dataset.file_name, 'rb') except Exception: return "This item contains no content" diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py index 5fd331627d3..4bb7640a804 100644 --- a/lib/galaxy/datatypes/tabular.py +++ b/lib/galaxy/datatypes/tabular.py @@ -394,10 +394,10 @@ class Tabular(TabularData): dataset.metadata.delimiter = '\t' def as_gbrowse_display_file(self, dataset, **kwd): - return open(dataset.file_name) + return open(dataset.file_name, 'rb') def as_ucsc_display_file(self, dataset, **kwd): - return open(dataset.file_name) + return open(dataset.file_name, 'rb') class Taxonomy(Tabular): diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index 666960d3adf..a1147691309 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -376,7 +376,7 @@ class DatasetsController(BaseAPIController, UsesVisualizationMixin): fname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in hda.name)[0:150] trans.response.headers["Content-Type"] = "application/octet-stream" trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (hda.hid, fname, file_ext) - return open(hda.metadata.get(metadata_file).file_name) + return open(hda.metadata.get(metadata_file).file_name, 'rb') except Exception as exception: log.error("Error getting metadata_file (%s) for dataset (%s) from history (%s): %s", metadata_file, history_content_id, history_id, str(exception), exc_info=True) diff --git a/lib/galaxy/webapps/galaxy/api/library_datasets.py b/lib/galaxy/webapps/galaxy/api/library_datasets.py index 3302b9cdd9e..cb4d2eec9e2 100644 --- a/lib/galaxy/webapps/galaxy/api/library_datasets.py +++ b/lib/galaxy/webapps/galaxy/api/library_datasets.py @@ -706,7 +706,7 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin, Libra fname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in fname)[0:150] trans.response.headers["Content-Disposition"] = 'attachment; filename="%s"' % fname try: - return open(dataset.file_name) + return open(dataset.file_name, 'rb') except Exception: raise exceptions.InternalServerError("This dataset contains no content.") else: diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index 11cdd2f9b43..c16aaa96c93 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -186,7 +186,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE file_ext = data.metadata.spec.get(metadata_name).get("file_ext", metadata_name) trans.response.headers["Content-Type"] = "application/octet-stream" trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (data.hid, fname, file_ext) - return open(data.metadata.get(metadata_name).file_name) + return open(data.metadata.get(metadata_name).file_name, 'rb') def _check_dataset(self, trans, hda_id): # DEPRECATION: We still support unencoded ids for backward compatibility @@ -677,7 +677,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE # TODO: figure out a way to display images in display template. if isinstance(dataset.datatype, datatypes.binary.Binary) or isinstance(dataset.datatype, datatypes.images.Image) or isinstance(dataset.datatype, datatypes.text.Html): trans.response.set_content_type(dataset.get_mime()) - return open(dataset.file_name) + return open(dataset.file_name, 'rb') else: # Get rating data. user_item_rating = 0 diff --git a/lib/galaxy/webapps/galaxy/controllers/root.py b/lib/galaxy/webapps/galaxy/controllers/root.py index 956f01545d0..42102b16593 100644 --- a/lib/galaxy/webapps/galaxy/controllers/root.py +++ b/lib/galaxy/webapps/galaxy/controllers/root.py @@ -218,7 +218,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations): trans.response.headers["Content-Disposition"] = 'attachment; filename="GalaxyHistoryItem-%s-[%s]%s"' % (data.hid, fname, toext) trans.log_event("Display dataset id: %s" % str(id)) try: - return open(data.file_name) + return open(data.file_name, 'rb') except Exception: return "This dataset contains no content" else: From 7a9422600c76ad49515844d582bf6d5f507076e8 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Tue, 15 Jan 2019 10:35:10 +0000 Subject: [PATCH 14/21] Fix ``GalaxyInteractorBackwardCompatTestCase.test_local_test_data_download`` API test Broken in commit bf851269c30996876c0614e7a5b57da495c44ac9 . --- test/api/test_galaxy_interactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/test_galaxy_interactor.py b/test/api/test_galaxy_interactor.py index f3458e08f3b..e6084b7364e 100644 --- a/test/api/test_galaxy_interactor.py +++ b/test/api/test_galaxy_interactor.py @@ -10,4 +10,4 @@ class GalaxyInteractorBackwardCompatTestCase(api.ApiTestCase): def test_local_test_data_download(self): self.galaxy_interactor._target_galaxy_version = Version("18.09") assert self.galaxy_interactor.supports_test_data_download is False - assert self.galaxy_interactor.test_data_download(tool_id='cat1', filename='1.bed').readline().startswith('chr1\t147962192\t147962580') + assert self.galaxy_interactor.test_data_download(tool_id='cat1', filename='1.bed').readline().startswith(b'chr1\t147962192\t147962580') From 06aa30ffdca0fe092fa2d094aa23da34b10a1667 Mon Sep 17 00:00:00 2001 From: Anthony Bretaudeau Date: Fri, 18 Jan 2019 11:26:51 +0100 Subject: [PATCH 15/21] disable tag_using_filenames by default (consistent with #5115) --- lib/galaxy/actions/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/actions/library.py b/lib/galaxy/actions/library.py index aec0c500566..432ff9359bd 100644 --- a/lib/galaxy/actions/library.py +++ b/lib/galaxy/actions/library.py @@ -248,7 +248,7 @@ class LibraryActions(object): uploaded_dataset.dbkey = params.get('dbkey', None) uploaded_dataset.to_posix_lines = params.get('to_posix_lines', None) uploaded_dataset.space_to_tab = params.get('space_to_tab', None) - uploaded_dataset.tag_using_filenames = params.get('tag_using_filenames', True) + uploaded_dataset.tag_using_filenames = params.get('tag_using_filenames', False) uploaded_dataset.purge_source = getattr(trans.app.config, 'ftp_upload_purge', True) if in_folder: uploaded_dataset.in_folder = in_folder From 8d99791afb6a699e987bd1e25aaea8582f7f7065 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 18 Jan 2019 12:13:13 +0100 Subject: [PATCH 16/21] Fix data_source tools on python 3 --- tools/data_source/data_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/data_source/data_source.py b/tools/data_source/data_source.py index 8dc33aee626..eaaa1f2b66f 100644 --- a/tools/data_source/data_source.py +++ b/tools/data_source/data_source.py @@ -89,7 +89,7 @@ def __main__(): if not URL_method or URL_method == 'get': page = urlopen(cur_URL) elif URL_method == 'post': - page = urlopen(cur_URL, urlencode(params)) + page = urlopen(cur_URL, urlencode(params).encode("utf-8")) except Exception as e: stop_err('The remote data source application may be off line, please try again later. Error: %s' % str(e)) if max_file_size: From 7b14c3099876d5991ebdea85ff18c027de434be8 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 18 Jan 2019 12:16:40 +0100 Subject: [PATCH 17/21] Reduce duplicated code --- lib/galaxy/datatypes/sniff.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/galaxy/datatypes/sniff.py b/lib/galaxy/datatypes/sniff.py index 8c8c9aea3fd..0f44b2e81f4 100644 --- a/lib/galaxy/datatypes/sniff.py +++ b/lib/galaxy/datatypes/sniff.py @@ -199,27 +199,19 @@ def convert_newlines_sep2tabs(fname, in_place=True, patt=r"\s+", tmp_dir=None, t def iter_headers(fname_or_file_prefix, sep, count=60, comment_designator=None): + idx = 0 if isinstance(fname_or_file_prefix, FilePrefix): - idx = 0 - for line in fname_or_file_prefix.line_iterator(): - line = line.rstrip('\n\r') - if comment_designator is not None and comment_designator != '' and line.startswith(comment_designator): - continue - yield line.split(sep) - idx += 1 - if idx == count: - break + file_iterator = fname_or_file_prefix.line_iterator() else: - with compression_utils.get_fileobj(fname_or_file_prefix) as in_file: - idx = 0 - for line in in_file: - line = line.rstrip('\n\r') - if comment_designator is not None and comment_designator != '' and line.startswith(comment_designator): - continue - yield line.split(sep) - idx += 1 - if idx == count: - break + file_iterator = compression_utils.get_fileobj(fname_or_file_prefix) + for line in file_iterator: + line = line.rstrip('\n\r') + if comment_designator is not None and comment_designator != '' and line.startswith(comment_designator): + continue + yield line.split(sep) + idx += 1 + if idx == count: + break def get_headers(fname_or_file_prefix, sep, count=60, comment_designator=None): From e92e978ca179870d586e08f16a0542b9dd499a2a Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 18 Jan 2019 12:56:09 +0100 Subject: [PATCH 18/21] Drop use of col1_startswith Contigs may start with whatever sequence you like. Luckily this isn't essential to the datatypes. Leaving data.col1_startswith in case toolshed datatypes import this. --- lib/galaxy/datatypes/interval.py | 218 ++++++++++++++----------------- 1 file changed, 99 insertions(+), 119 deletions(-) diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index e583e2fed4e..4af4f88fca0 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -100,42 +100,38 @@ class Interval(Tabular): else: # Header lines in Interval files are optional. For example, BED is Interval but has no header. # We'll make a best guess at the location of the metadata columns. - metadata_is_set = False elems = line.split('\t') if len(elems) > 2: - for str in data.col1_startswith: - if line.lower().startswith(str): - if overwrite or not dataset.metadata.element_is_set('chromCol'): - dataset.metadata.chromCol = 1 - try: - int(elems[1]) - if overwrite or not dataset.metadata.element_is_set('startCol'): - dataset.metadata.startCol = 2 - except Exception: - pass # Metadata default will be used - try: - int(elems[2]) - if overwrite or not dataset.metadata.element_is_set('endCol'): - dataset.metadata.endCol = 3 - except Exception: - pass # Metadata default will be used - # we no longer want to guess that this column is the 'name', name must now be set manually for interval files - # we will still guess at the strand, as we can make a more educated guess - # if len( elems ) > 3: - # try: - # int( elems[3] ) - # except Exception: - # if overwrite or not dataset.metadata.element_is_set( 'nameCol' ): - # dataset.metadata.nameCol = 4 - if len(elems) < 6 or elems[5] not in data.valid_strand: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 0 - else: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 6 - metadata_is_set = True - break - if metadata_is_set or (i - empty_line_count) > num_check_lines: + if overwrite or not dataset.metadata.element_is_set('chromCol'): + dataset.metadata.chromCol = 1 + try: + int(elems[1]) + if overwrite or not dataset.metadata.element_is_set('startCol'): + dataset.metadata.startCol = 2 + except Exception: + pass # Metadata default will be used + try: + int(elems[2]) + if overwrite or not dataset.metadata.element_is_set('endCol'): + dataset.metadata.endCol = 3 + except Exception: + pass # Metadata default will be used + # we no longer want to guess that this column is the 'name', name must now be set manually for interval files + # we will still guess at the strand, as we can make a more educated guess + # if len( elems ) > 3: + # try: + # int( elems[3] ) + # except Exception: + # if overwrite or not dataset.metadata.element_is_set( 'nameCol' ): + # dataset.metadata.nameCol = 4 + if len(elems) < 6 or elems[5] not in data.valid_strand: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 0 + else: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 6 + break + if (i - empty_line_count) > num_check_lines: break # Our metadata is set or we examined 100 non-empty lines, so break out of the outer loop else: empty_line_count += 1 @@ -403,26 +399,20 @@ class Bed(Interval): i = 0 if dataset.has_data(): for i, line in enumerate(open(dataset.file_name)): - metadata_set = False line = line.rstrip('\r\n') if line and not line.startswith('#'): elems = line.split('\t') if len(elems) > 2: - for startswith in data.col1_startswith: - if line.lower().startswith(startswith): - if len(elems) > 3: - if overwrite or not dataset.metadata.element_is_set('nameCol'): - dataset.metadata.nameCol = 4 - if len(elems) < 6: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 0 - else: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 6 - metadata_set = True - break - if metadata_set: - break + if len(elems) > 3: + if overwrite or not dataset.metadata.element_is_set('nameCol'): + dataset.metadata.nameCol = 4 + if len(elems) < 6: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 0 + else: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 6 + break Tabular.set_meta(self, dataset, overwrite=overwrite, skip=i) def as_ucsc_display_file(self, dataset, **kwd): @@ -494,75 +484,67 @@ class Bed(Interval): for hdr in headers: if hdr[0] == '': continue - valid_col1 = False if len(hdr) < 3 or len(hdr) > 12: return False - for str in data.col1_startswith: - if hdr[0].lower().startswith(str): - valid_col1 = True - break - if valid_col1: + try: + int(hdr[1]) + int(hdr[2]) + except Exception: + return False + if len(hdr) > 4: + # hdr[3] is a string, 'name', which defines the name of the BED line - difficult to test for this. + # hdr[4] is an int, 'score', a score between 0 and 1000. try: - int(hdr[1]) - int(hdr[2]) + if int(hdr[4]) < 0 or int(hdr[4]) > 1000: + return False except Exception: return False - if len(hdr) > 4: - # hdr[3] is a string, 'name', which defines the name of the BED line - difficult to test for this. - # hdr[4] is an int, 'score', a score between 0 and 1000. + if len(hdr) > 5: + # hdr[5] is strand + if hdr[5] not in data.valid_strand: + return False + if len(hdr) > 6: + # hdr[6] is thickStart, the starting position at which the feature is drawn thickly. + try: + int(hdr[6]) + except Exception: + return False + if len(hdr) > 7: + # hdr[7] is thickEnd, the ending position at which the feature is drawn thickly + try: + int(hdr[7]) + except Exception: + return False + if len(hdr) > 8: + # hdr[8] is itemRgb, an RGB value of the form R,G,B (e.g. 255,0,0). However, this could also be an int (e.g., 0) + try: + int(hdr[8]) + except Exception: try: - if int(hdr[4]) < 0 or int(hdr[4]) > 1000: - return False + hdr[8].split(',') except Exception: return False - if len(hdr) > 5: - # hdr[5] is strand - if hdr[5] not in data.valid_strand: - return False - if len(hdr) > 6: - # hdr[6] is thickStart, the starting position at which the feature is drawn thickly. - try: - int(hdr[6]) - except Exception: - return False - if len(hdr) > 7: - # hdr[7] is thickEnd, the ending position at which the feature is drawn thickly - try: - int(hdr[7]) - except Exception: - return False - if len(hdr) > 8: - # hdr[8] is itemRgb, an RGB value of the form R,G,B (e.g. 255,0,0). However, this could also be an int (e.g., 0) - try: - int(hdr[8]) - except Exception: - try: - hdr[8].split(',') - except Exception: - return False - if len(hdr) > 9: - # hdr[9] is blockCount, the number of blocks (exons) in the BED line. - try: - block_count = int(hdr[9]) - except Exception: - return False - if len(hdr) > 10: - # hdr[10] is blockSizes - A comma-separated list of the block sizes. - # Sometimes the blosck_sizes and block_starts lists end in extra commas - try: - block_sizes = hdr[10].rstrip(',').split(',') - except Exception: - return False - if len(hdr) > 11: - # hdr[11] is blockStarts - A comma-separated list of block starts. - try: - block_starts = hdr[11].rstrip(',').split(',') - except Exception: - return False - if len(block_sizes) != block_count or len(block_starts) != block_count: - return False - else: - return False + if len(hdr) > 9: + # hdr[9] is blockCount, the number of blocks (exons) in the BED line. + try: + block_count = int(hdr[9]) + except Exception: + return False + if len(hdr) > 10: + # hdr[10] is blockSizes - A comma-separated list of the block sizes. + # Sometimes the blosck_sizes and block_starts lists end in extra commas + try: + block_sizes = hdr[10].rstrip(',').split(',') + except Exception: + return False + if len(hdr) > 11: + # hdr[11] is blockStarts - A comma-separated list of block starts. + try: + block_starts = hdr[11].rstrip(',').split(',') + except Exception: + return False + if len(block_sizes) != block_count or len(block_starts) != block_count: + return False return True except Exception: return False @@ -1201,16 +1183,14 @@ class Wiggle(Tabular, _RemoteCallMixin): if line and not line.startswith('#'): elems = line.split('\t') try: - float(elems[0]) # "Wiggle track data values can be integer or real, positive or negative values" + # variableStep format is nucleotide position\tvalue\n, + # fixedStep is value\n + # "Wiggle track data values can be integer or real, positive or negative values" + float(elems[0]) break except Exception: - do_break = False - for col_startswith in data.col1_startswith: - if elems[0].lower().startswith(col_startswith): - do_break = True - break - if do_break: - break + # We are either in the track definition line or in a declaration line + pass if self.max_optional_metadata_filesize >= 0 and dataset.get_size() > self.max_optional_metadata_filesize: # we'll arbitrarily only use the first 100 data lines in this wig file to calculate tabular attributes (column types) # this should be sufficient, except when we have mixed wig track types (bed, variable, fixed), From 6f861dec7e897bbc06b2cffbb33b2fdd9e6ac11a Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 18 Jan 2019 13:52:52 +0100 Subject: [PATCH 19/21] Overridde __init__ for ToolMissingException --- lib/galaxy/exceptions/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/exceptions/__init__.py b/lib/galaxy/exceptions/__init__.py index d04d650120c..d5335c9034e 100644 --- a/lib/galaxy/exceptions/__init__.py +++ b/lib/galaxy/exceptions/__init__.py @@ -103,10 +103,9 @@ 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'] + def __init__(self, err_msg=None, type="info", tool_id=None, **extra_error_info): + super(ToolMissingException, self).__init__(err_msg, type, **extra_error_info) + self.tool_id = tool_id class RequestParameterInvalidException(MessageException): From 5c90e02d3665caec50369baf0918e58f043af6e4 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 21 Jan 2019 09:26:33 +0100 Subject: [PATCH 20/21] Fix workflow save_as functionality Fixes https://github.com/galaxyproject/galaxy/issues/7240, broken in https://github.com/galaxyproject/galaxy/pull/7019, where it was renamed from `build_workflow_from_dict` to `build_workflow_from_raw_description`. --- lib/galaxy/webapps/galaxy/controllers/workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index fdcce648d47..d2f7c8dd019 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -601,7 +601,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi session.flush() try: - workflow, errors = workflow_contents_manager.update_workflow_from_dict( + workflow, errors = workflow_contents_manager.build_workflow_from_raw_description( trans, stored_workflow, workflow_data, From 8862b9311b000b83a733ed216130f0a7cfe7d4ba Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Mon, 21 Jan 2019 11:58:25 +0100 Subject: [PATCH 21/21] Use update_workflow_from_raw_description method not build_workflow_from_raw_description Co-Authored-By: mvdbeek --- lib/galaxy/webapps/galaxy/controllers/workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index d2f7c8dd019..de142ca2cb6 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -601,7 +601,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi session.flush() try: - workflow, errors = workflow_contents_manager.build_workflow_from_raw_description( + workflow, errors = workflow_contents_manager.update_workflow_from_raw_description( trans, stored_workflow, workflow_data,