From 9d5146017c77f3a2c29bb4c7dec5885849f67c66 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 21 Jun 2018 12:00:40 +0200 Subject: [PATCH 01/12] Expose version parameter for workflows, allows retrieving non-latest versions --- lib/galaxy/managers/workflows.py | 37 ++++++++++++------- lib/galaxy/webapps/galaxy/api/workflows.py | 6 ++- .../webapps/galaxy/controllers/workflow.py | 4 +- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 27c13251e57..2ca5f8e4a41 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -368,7 +368,7 @@ class WorkflowContentsManager(UsesAnnotations): return workflow, missing_tool_tups - def workflow_to_dict(self, trans, stored, style="export"): + def workflow_to_dict(self, trans, stored, style="export", version=None): """ Export the workflow contents to a dictionary ready for JSON-ification and to be sent out via API for instance. There are three styles of export allowed 'export', 'instance', and 'editor'. The Galaxy team will do its best to preserve the backward compatibility of the @@ -377,22 +377,24 @@ class WorkflowContentsManager(UsesAnnotations): option describes the workflow in a context more tied to the current Galaxy instance and includes fields like 'url' and 'url' and actual unencoded step ids instead of 'order_index'. """ + if version is not None: + version = int(version) if style == "editor": - return self._workflow_to_dict_editor(trans, stored) + return self._workflow_to_dict_editor(trans, stored, version=version) elif style == "legacy": - return self._workflow_to_dict_instance(stored, legacy=True) + return self._workflow_to_dict_instance(stored, legacy=True, version=version) elif style == "instance": - return self._workflow_to_dict_instance(stored, legacy=False) + return self._workflow_to_dict_instance(stored, legacy=False, version=version) elif style == "run": - return self._workflow_to_dict_run(trans, stored) + return self._workflow_to_dict_run(trans, stored, version=version) else: - return self._workflow_to_dict_export(trans, stored) + return self._workflow_to_dict_export(trans, stored, version=version) - def _workflow_to_dict_run(self, trans, stored): + def _workflow_to_dict_run(self, trans, stored, version=None): """ Builds workflow dictionary used by run workflow form """ - workflow = stored.latest_workflow + workflow = stored.latest_workflow if version is None else stored.workflows[version] if len(workflow.steps) == 0: raise exceptions.MessageException('Workflow cannot be run because it does not have any steps.') if attach_ordered_steps(workflow, workflow.steps): @@ -475,8 +477,12 @@ class WorkflowContentsManager(UsesAnnotations): """ return self._resource_mapper_function(trans=trans, stored_workflow=stored, workflow=workflow) - def _workflow_to_dict_editor(self, trans, stored): - workflow = stored.latest_workflow + def _workflow_to_dict_editor(self, trans, stored, version=None): + if version is None: + workflow = stored.latest_workflow + else: + version = int(version) + workflow = stored.workflows[version] # Pack workflow data into a dictionary and return data = {} data['name'] = workflow.name @@ -583,12 +589,15 @@ class WorkflowContentsManager(UsesAnnotations): data['steps'][step.order_index] = step_dict return data - def _workflow_to_dict_export(self, trans, stored=None, workflow=None): + def _workflow_to_dict_export(self, trans, stored=None, workflow=None, version=None): """ Export the workflow contents to a dictionary ready for JSON-ification and export. """ if workflow is None: assert stored is not None - workflow = stored.latest_workflow + if version is None: + workflow = stored.latest_workflow + else: + workflow = stored.workflows[version] annotation_str = "" tag_str = "" @@ -754,11 +763,11 @@ class WorkflowContentsManager(UsesAnnotations): data['steps'][step.order_index] = step_dict return data - def _workflow_to_dict_instance(self, stored, legacy=True): + def _workflow_to_dict_instance(self, stored, legacy=True, version=None): encode = self.app.security.encode_id sa_session = self.app.model.context item = stored.to_dict(view='element', value_mapper={'id': encode}) - workflow = stored.latest_workflow + workflow = stored.latest_workflow if version == None else stored.workflows[version] item['url'] = url_for('workflow', id=item['id']) item['owner'] = stored.user.username inputs = {} diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 2533f26fe75..7ebcf396da2 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -214,7 +214,8 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn style = "legacy" else: style = "instance" - return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style) + version = kwd.get('version') + return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) @expose_api def create(self, trans, payload, **kwd): @@ -400,7 +401,8 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn style = kwd.get("style", "export") download_format = kwd.get('format') - ret_dict = self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style) + version = kwd.get('version') + ret_dict = self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) if download_format == 'json-download': sname = stored_workflow.name sname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in sname)[0:150] diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 095074924d3..ece80fd8c3b 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -656,7 +656,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi return trans.fill_template("workflow/editor.mako", workflows=workflows, stored=stored, annotation=self.get_item_annotation_str(trans.sa_session, trans.user, stored)) @web.json - def load_workflow(self, trans, id): + def load_workflow(self, trans, id, version=None): """ Get the latest Workflow for the StoredWorkflow identified by `id` and encode it as a json string that can be read by the workflow editor @@ -665,7 +665,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi trans.workflow_building_mode = workflow_building_modes.ENABLED stored = self.get_stored_workflow(trans, id, check_ownership=True, check_accessible=False) workflow_contents_manager = workflows.WorkflowContentsManager(trans.app) - return workflow_contents_manager.workflow_to_dict(trans, stored, style="editor") + return workflow_contents_manager.workflow_to_dict(trans, stored, style="editor", version=version) @web.expose @web.require_login("use workflows") From f49da14e45feed1273c705112a6d75a484ba9548 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 23 Jun 2018 20:06:11 +0200 Subject: [PATCH 02/12] Move recovery functionality for getting perticular workflow versions into StoredWorkflow --- lib/galaxy/managers/workflows.py | 17 ++++++----------- lib/galaxy/model/__init__.py | 7 +++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 2ca5f8e4a41..bc5ecfcbbc7 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -377,6 +377,8 @@ class WorkflowContentsManager(UsesAnnotations): option describes the workflow in a context more tied to the current Galaxy instance and includes fields like 'url' and 'url' and actual unencoded step ids instead of 'order_index'. """ + if version == '': + version = None if version is not None: version = int(version) if style == "editor": @@ -394,7 +396,7 @@ class WorkflowContentsManager(UsesAnnotations): """ Builds workflow dictionary used by run workflow form """ - workflow = stored.latest_workflow if version is None else stored.workflows[version] + workflow = stored.get_internal_version(version) if len(workflow.steps) == 0: raise exceptions.MessageException('Workflow cannot be run because it does not have any steps.') if attach_ordered_steps(workflow, workflow.steps): @@ -478,11 +480,7 @@ class WorkflowContentsManager(UsesAnnotations): return self._resource_mapper_function(trans=trans, stored_workflow=stored, workflow=workflow) def _workflow_to_dict_editor(self, trans, stored, version=None): - if version is None: - workflow = stored.latest_workflow - else: - version = int(version) - workflow = stored.workflows[version] + workflow = stored.get_internal_version(version) # Pack workflow data into a dictionary and return data = {} data['name'] = workflow.name @@ -594,10 +592,7 @@ class WorkflowContentsManager(UsesAnnotations): """ if workflow is None: assert stored is not None - if version is None: - workflow = stored.latest_workflow - else: - workflow = stored.workflows[version] + workflow = stored.get_internal_version(version) annotation_str = "" tag_str = "" @@ -767,7 +762,7 @@ class WorkflowContentsManager(UsesAnnotations): encode = self.app.security.encode_id sa_session = self.app.model.context item = stored.to_dict(view='element', value_mapper={'id': encode}) - workflow = stored.latest_workflow if version == None else stored.workflows[version] + workflow = stored.get_internal_version(version) item['url'] = url_for('workflow', id=item['id']) item['owner'] = stored.user.username inputs = {} diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 7365e727f5b..ed2c75ded0f 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -3906,6 +3906,13 @@ class StoredWorkflow(HasTags, Dictifiable): self.latest_workflow_id = None self.workflows = [] + def get_internal_version(self, version): + if version is None: + return self.latest_workflow + if len(self.workflows) <= version: + raise Exception("Version does not exist") + return list(reversed(self.workflows))[version] + def copy_tags_from(self, target_user, source_workflow): # Override to only copy owner tags. for src_swta in source_workflow.owner_tags: From e18433127f244b332325e6a7bf9e914f9e0391e6 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 23 Jun 2018 20:08:14 +0200 Subject: [PATCH 03/12] Order list of workflows related to a StoredWorkflow This will put the newest workflows first. --- lib/galaxy/model/mapping.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 8a6d6758165..1eb70efdd71 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -2224,7 +2224,8 @@ mapper(model.StoredWorkflow, model.StoredWorkflow.table, properties=dict( workflows=relation(model.Workflow, backref='stored_workflow', cascade="all, delete-orphan", - primaryjoin=(model.StoredWorkflow.table.c.id == model.Workflow.table.c.stored_workflow_id)), + primaryjoin=(model.StoredWorkflow.table.c.id == model.Workflow.table.c.stored_workflow_id), + order_by=-model.Workflow.id), latest_workflow=relation(model.Workflow, post_update=True, primaryjoin=(model.StoredWorkflow.table.c.latest_workflow_id == model.Workflow.table.c.id), From c652cf4b078ae69e36292f1ba1ef72a899ff0b4f Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 23 Jun 2018 20:12:38 +0200 Subject: [PATCH 04/12] Allow loading non-latest workflows into editor There is no UI-element so far, but you can append &version= to the editor URL to load any arbitrary version of a workflow. --- client/galaxy/scripts/mvc/workflow/workflow-view.js | 8 ++++---- lib/galaxy/webapps/galaxy/controllers/workflow.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/galaxy/scripts/mvc/workflow/workflow-view.js b/client/galaxy/scripts/mvc/workflow/workflow-view.js index 6c6be020833..ae54296084a 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-view.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-view.js @@ -271,7 +271,7 @@ export default Backbone.View.extend({ this.type_to_type = this.datatypes_mapping.class_to_classes; // Load workflow definition - this._workflowLoadAjax(self.options.id, { + this._workflowLoadAjax(self.options.id, self.options.version, { success: function(data) { self.reset(); self.workflow.from_simple(data, true); @@ -530,7 +530,7 @@ export default Backbone.View.extend({ copy_into_workflow: function(workflowId) { // Load workflow definition var self = this; - this._workflowLoadAjax(workflowId, { + this._workflowLoadAjax(workflowId, None, { success: function(data) { self.workflow.from_simple(data, false); // Determine if any parameters were 'upgraded' and provide message @@ -584,11 +584,11 @@ export default Backbone.View.extend({ cc.css({ left: left, top: top }); }, - _workflowLoadAjax: function(workflowId, options) { + _workflowLoadAjax: function(workflowId, version, options) { $.ajax( Utils.merge(options, { url: this.urls.load_workflow, - data: { id: workflowId, _: "true" }, + data: { id: workflowId, _: "true", version: version }, dataType: "json", cache: false }) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index ece80fd8c3b..b82eeb06b5c 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -637,7 +637,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi @web.expose @web.require_login("edit workflows") - def editor(self, trans, id=None): + def editor(self, trans, id=None, version=None): """ Render the main workflow editor interface. The canvas is embedded as an iframe (necessary for scrolling to work properly), which is @@ -653,7 +653,13 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi .order_by(desc(model.StoredWorkflow.table.c.update_time)) \ .options(joinedload('latest_workflow').joinedload('steps')) \ .all() - return trans.fill_template("workflow/editor.mako", workflows=workflows, stored=stored, annotation=self.get_item_annotation_str(trans.sa_session, trans.user, stored)) + if version is not None: + version = int(version) + return trans.fill_template("workflow/editor.mako", + workflows=workflows, + stored=stored, + version=version, + annotation=self.get_item_annotation_str(trans.sa_session, trans.user, stored)) @web.json def load_workflow(self, trans, id, version=None): From 3d8db3a91f9b5dff0b5940fc0f40f5d5b19f051a Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 23 Jun 2018 20:14:34 +0200 Subject: [PATCH 05/12] Inform user that workflow loading has failed This sends the user back to the worflow index page. --- client/galaxy/scripts/mvc/workflow/workflow-view.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/galaxy/scripts/mvc/workflow/workflow-view.js b/client/galaxy/scripts/mvc/workflow/workflow-view.js index ae54296084a..4221825945d 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-view.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-view.js @@ -94,6 +94,7 @@ export default Backbone.View.extend({ window.document.location = self.urls.workflow_index; } }; + var workflow_index = self.urls.workflow_index; var save_current_workflow = (eventObj, success_callback) => { show_message("Saving workflow", "progress"); self.workflow.check_changes_in_active_form(); @@ -306,6 +307,12 @@ export default Backbone.View.extend({ } self.showWorkflowParameters(); }, + error: function(response) { + window.show_modal("Loading workflow failed.", response.err_msg, { Ok: function( response ){ + window.onbeforeunload = undefined; + window.document.location = workflow_index; +}}) + }, beforeSubmit: function(data) { show_message("Loading workflow", "progress"); } From 56411c3c116d160fc4c95b718e52a27b13c5bd98 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 30 Jun 2018 09:49:19 -0700 Subject: [PATCH 06/12] Hack around routes returning 'None' for parameter --- templates/webapps/galaxy/workflow/editor.mako | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index c46c672bb46..df7c4c26a1c 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -9,12 +9,16 @@ <% self.active_view="workflow" self.overlay_visible=True + if version: + load_workflow = h.url_for( controller='workflow', action='load_workflow', version=version ) + else: + load_workflow = h.url_for( controller='workflow', action='load_workflow') self.editor_config = { 'id' : trans.security.encode_id( stored.id ), 'urls' : { 'tool_search' : h.url_for( '/api/tools' ), 'get_datatypes' : h.url_for( '/api/datatypes/mapping' ), - 'load_workflow' : h.url_for( controller='workflow', action='load_workflow' ), + 'load_workflow' : load_workflow, 'run_workflow' : h.url_for( controller='root', action='index', workflow_id=trans.security.encode_id(stored.id)), 'rename_async' : h.url_for( controller='workflow', action='rename_async', id=trans.security.encode_id(stored.id) ), 'annotate_async' : h.url_for( controller='workflow', action='annotate_async', id=trans.security.encode_id(stored.id) ), From f1e94f84739c929429c0079f245baa10afacc155 Mon Sep 17 00:00:00 2001 From: charesredhat Date: Sat, 30 Jun 2018 18:10:53 +0000 Subject: [PATCH 07/12] Added Workflow version change control to the top bar --- templates/webapps/galaxy/workflow/editor.mako | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index df7c4c26a1c..e96a7b13ea6 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -320,6 +320,7 @@ %elif isinstance( val, ToolSectionLabel ): ${render_label( val )} %endif + %endfor ## Data Manager Tools @@ -362,6 +363,15 @@
+ + +
Workflow Canvas | ${h.to_unicode( stored.name ) | h} From 98a2526cec43129e143c3201e908dee943194353 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 30 Jun 2018 16:24:45 -0700 Subject: [PATCH 08/12] Show current version in version selection --- templates/webapps/galaxy/workflow/editor.mako | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index e96a7b13ea6..23ba938405f 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -320,7 +320,6 @@ %elif isinstance( val, ToolSectionLabel ): ${render_label( val )} %endif -
%endfor ## Data Manager Tools @@ -363,15 +362,12 @@
- - - + +
Workflow Canvas | ${h.to_unicode( stored.name ) | h} From 85d80ec20ba7bf9985c3139aa47a75e974aafa5d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 30 Jun 2018 17:50:46 -0700 Subject: [PATCH 09/12] Always pass in a version when loading the wf-editor --- .../webapps/galaxy/controllers/workflow.py | 4 ++- templates/webapps/galaxy/workflow/editor.mako | 36 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index b82eeb06b5c..fdcce648d47 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -653,7 +653,9 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi .order_by(desc(model.StoredWorkflow.table.c.update_time)) \ .options(joinedload('latest_workflow').joinedload('steps')) \ .all() - if version is not None: + if version is None: + version = len(stored.workflows) - 1 + else: version = int(version) return trans.fill_template("workflow/editor.mako", workflows=workflows, diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index 23ba938405f..119096c7f6b 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -9,30 +9,26 @@ <% self.active_view="workflow" self.overlay_visible=True - if version: - load_workflow = h.url_for( controller='workflow', action='load_workflow', version=version ) - else: - load_workflow = h.url_for( controller='workflow', action='load_workflow') self.editor_config = { - 'id' : trans.security.encode_id( stored.id ), + 'id' : trans.security.encode_id(stored.id), 'urls' : { - 'tool_search' : h.url_for( '/api/tools' ), - 'get_datatypes' : h.url_for( '/api/datatypes/mapping' ), - 'load_workflow' : load_workflow, - 'run_workflow' : h.url_for( controller='root', action='index', workflow_id=trans.security.encode_id(stored.id)), - 'rename_async' : h.url_for( controller='workflow', action='rename_async', id=trans.security.encode_id(stored.id) ), - 'annotate_async' : h.url_for( controller='workflow', action='annotate_async', id=trans.security.encode_id(stored.id) ), - 'get_new_module_info' : h.url_for( controller='workflow', action='get_new_module_info' ), - 'workflow_index' : h.url_for( '/workflows/list' ), - 'save_workflow' : h.url_for( controller='workflow', action='save_workflow' ), - 'workflow_save_as' : h.url_for( controller='workflow', action='save_workflow_as') + 'tool_search' : h.url_for('/api/tools'), + 'get_datatypes' : h.url_for('/api/datatypes/mapping'), + 'load_workflow' : h.url_for(controller='workflow', action='load_workflow', version=version), + 'run_workflow' : h.url_for(controller='root', action='index', workflow_id=trans.security.encode_id(stored.id)), + 'rename_async' : h.url_for(controller='workflow', action='rename_async', id=trans.security.encode_id(stored.id)), + 'annotate_async' : h.url_for(controller='workflow', action='annotate_async', id=trans.security.encode_id(stored.id)), + 'get_new_module_info' : h.url_for(controller='workflow', action='get_new_module_info'), + 'workflow_index' : h.url_for('/workflows/list'), + 'save_workflow' : h.url_for(controller='workflow', action='save_workflow'), + 'workflow_save_as' : h.url_for(controller='workflow', action='save_workflow_as') }, 'workflows' : [{ - 'id' : trans.security.encode_id( workflow.id ), - 'latest_id' : trans.security.encode_id( workflow.latest_workflow.id ), - 'step_count' : len( workflow.latest_workflow.steps ), - 'name' : h.to_unicode( workflow.name ) - } for workflow in workflows ] + 'id' : trans.security.encode_id(workflow.id), + 'latest_id' : trans.security.encode_id(workflow.latest_workflow.id), + 'step_count' : len(workflow.latest_workflow.steps), + 'name' : h.to_unicode(workflow.name) + } for workflow in workflows] } %> From 85adb0a371b0079b929e360e7fa35e97b2cd09d8 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 10 Sep 2018 09:57:58 +0200 Subject: [PATCH 10/12] Move version selection to right-hand most location --- templates/webapps/galaxy/workflow/editor.mako | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index 119096c7f6b..ff35f09506a 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -358,12 +358,6 @@
- -
Workflow Canvas | ${h.to_unicode( stored.name ) | h} @@ -397,6 +391,12 @@
Details + +
From d377b00388f8c8060853ef77f8be6187968e78b4 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 10 Sep 2018 10:18:07 +0200 Subject: [PATCH 11/12] Indicate which version is currently on display --- templates/webapps/galaxy/workflow/editor.mako | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index ff35f09506a..8121b8ddd0b 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -394,7 +394,13 @@
From efec721676d2b27c91afb655c7c6630ed838a39e Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 10 Sep 2018 18:10:29 +0200 Subject: [PATCH 12/12] Move version selection out of editor.mako Also adds an API that lists workflow versions for a workflow id. --- .../scripts/mvc/workflow/workflow-manager.js | 2 + .../scripts/mvc/workflow/workflow-view.js | 147 ++++++++++++------ lib/galaxy/managers/workflows.py | 15 +- lib/galaxy/webapps/galaxy/api/workflows.py | 10 ++ lib/galaxy/webapps/galaxy/buildapp.py | 5 + templates/webapps/galaxy/workflow/editor.mako | 18 +-- 6 files changed, 133 insertions(+), 64 deletions(-) diff --git a/client/galaxy/scripts/mvc/workflow/workflow-manager.js b/client/galaxy/scripts/mvc/workflow/workflow-manager.js index c2a4b9d6c59..f247d2606be 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-manager.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-manager.js @@ -14,6 +14,7 @@ class Workflow { this.has_changes = false; this.active_form_has_changes = false; this.workflowOutputLabels = {}; + this.workflow_version = 0; } canLabelOutputWith(label) { if (label) { @@ -215,6 +216,7 @@ class Workflow { var max_id = offset; // First pass, nodes var using_workflow_outputs = false; + wf.workflow_version = data.version; $.each(data.steps, (id, step) => { var node = wf.app.prebuildNode(step.type, step.name, step.content_id); // If workflow being copied into another, wipe UUID and let diff --git a/client/galaxy/scripts/mvc/workflow/workflow-view.js b/client/galaxy/scripts/mvc/workflow/workflow-view.js index 4221825945d..406d8dee70a 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-view.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-view.js @@ -127,7 +127,9 @@ export default Backbone.View.extend({ self.workflow.name = data.name; self.workflow.has_changes = false; self.workflow.stored = true; + self.workflow.workflow_version = data.version; self.showWorkflowParameters(); + self.build_version_select(); if (data.errors) { window.show_modal("Saving workflow", body, { Ok: hide_modal @@ -271,53 +273,106 @@ export default Backbone.View.extend({ this.ext_to_type = this.datatypes_mapping.ext_to_class_name; this.type_to_type = this.datatypes_mapping.class_to_classes; - // Load workflow definition - this._workflowLoadAjax(self.options.id, self.options.version, { - success: function(data) { - self.reset(); - self.workflow.from_simple(data, true); - self.workflow.has_changes = false; - self.workflow.fit_canvas_to_nodes(); - self.scroll_to_nodes(); - self.canvas_manager.draw_overview(); - // Determine if any parameters were 'upgraded' and provide message - var upgrade_message = ""; - _.each(data.steps, (step, step_id) => { - var details = ""; - if (step.errors) { - details += `
  • ${step.errors}
  • `; - } - _.each(data.upgrade_messages[step_id], m => { - details += `
  • ${m}
  • `; - }); - if (details) { - upgrade_message += `
  • Step ${parseInt(step_id, 10) + 1}: ${ - self.workflow.nodes[step_id].name - }
      ${details}
  • `; - } - }); - if (upgrade_message) { - window.show_modal( - "Issues loading this workflow", - `Please review the following issues, possibly resulting from tool upgrades or changes.

      ${upgrade_message}

    `, - { Continue: hide_modal } - ); - } else { - hide_modal(); - } - self.showWorkflowParameters(); - }, - error: function(response) { - window.show_modal("Loading workflow failed.", response.err_msg, { Ok: function( response ){ - window.onbeforeunload = undefined; - window.document.location = workflow_index; -}}) - }, - beforeSubmit: function(data) { - show_message("Loading workflow", "progress"); - } - }); + this.get_workflow_versions = function() { + let _workflow_version_dropdown = {}; + let workflow_versions = JSON.parse( + $.ajax({ + url: `${Galaxy.root}api/workflows/${self.options.id}/versions`, + async: false, + }).responseText + ); + for (let i = 0; i < workflow_versions.length; i++) { + let current_wf = workflow_versions[i]; + let version_text = `Version ${current_wf['version']}, ${current_wf['steps']} steps`; + let selected = false; + if (i == self.workflow.workflow_version) { + version_text = `${version_text} (active)`; + selected = true; + }; + _workflow_version_dropdown[version_text] = { + version: i, + selected: selected, + } + } + return _workflow_version_dropdown + } + + this.build_version_select = function() { + let versions = this.get_workflow_versions(); + $("#workflow-version-switch").empty(); + $.each(versions, function(k, v) { + $('#workflow-version-switch').append($('').html(k).val(v.version).selected(v.selected)); + }); + $("#workflow-version-switch").on('change', function () { + $('#workflow-version-switch').unbind('change'); + if (this.value != self.workflow.workflow_version) { + if (self.workflow && self.workflow.has_changes) { + let r = confirm("There are unsaved changes to your workflow which will be lost. Continue ?"); + if (r == false) { + // We rebuild the version select list, to reset the selected version + self.build_version_select(); + return + } + } + self.load_workflow(self.options.id, this.value); + } + }) + }; + + this.load_workflow = function load_workflow(id, version) { + this._workflowLoadAjax(id, version, { + success: function(data) { + self.reset(); + self.workflow.from_simple(data, true); + self.workflow.has_changes = false; + self.workflow.fit_canvas_to_nodes(); + self.scroll_to_nodes(); + self.canvas_manager.draw_overview(); + // make_popupmenu($("#workflow-versions-switch"), self.get_workflow_versions()); + self.build_version_select(); + + // Determine if any parameters were 'upgraded' and provide message + var upgrade_message = ""; + _.each(data.steps, (step, step_id) => { + var details = ""; + if (step.errors) { + details += `
  • ${step.errors}
  • `; + } + _.each(data.upgrade_messages[step_id], m => { + details += `
  • ${m}
  • `; + }); + if (details) { + upgrade_message += `
  • Step ${parseInt(step_id, 10) + 1}: ${ + self.workflow.nodes[step_id].name + }
      ${details}
  • `; + } + }); + if (upgrade_message) { + window.show_modal( + "Issues loading this workflow", + `Please review the following issues, possibly resulting from tool upgrades or changes.

      ${upgrade_message}

    `, + { Continue: hide_modal } + ); + } else { + hide_modal(); + } + self.showWorkflowParameters(); + }, + error: function(response) { + window.show_modal("Loading workflow failed.", response.err_msg, { Ok: function( response ){ + window.onbeforeunload = undefined; + window.document.location = workflow_index; +}}) + }, + beforeSubmit: function(data) { + show_message("Loading workflow", "progress"); + } + }); + } + + // Load workflow definition + this.load_workflow(self.options.id, self.options.version) if (window.make_popupmenu) { make_popupmenu($("#workflow-options-button"), { Save: save_current_workflow, diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index bc5ecfcbbc7..2e78124779a 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -382,15 +382,20 @@ class WorkflowContentsManager(UsesAnnotations): if version is not None: version = int(version) if style == "editor": - return self._workflow_to_dict_editor(trans, stored, version=version) + wf_dict = self._workflow_to_dict_editor(trans, stored, version=version) elif style == "legacy": - return self._workflow_to_dict_instance(stored, legacy=True, version=version) + wf_dict = self._workflow_to_dict_instance(stored, legacy=True, version=version) elif style == "instance": - return self._workflow_to_dict_instance(stored, legacy=False, version=version) + wf_dict = self._workflow_to_dict_instance(stored, legacy=False, version=version) elif style == "run": - return self._workflow_to_dict_run(trans, stored, version=version) + wf_dict = self._workflow_to_dict_run(trans, stored, version=version) else: - return self._workflow_to_dict_export(trans, stored, version=version) + wf_dict = self._workflow_to_dict_export(trans, stored, version=version) + if version: + wf_dict['version'] = version + else: + wf_dict['version'] = len(stored.workflows) - 1 + return wf_dict def _workflow_to_dict_run(self, trans, stored, version=None): """ diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 7ebcf396da2..482bef16b3d 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -217,6 +217,16 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn version = kwd.get('version') return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) + @expose_api + def show_versions(self, trans, workflow_id, **kwds): + """ + GET /api/workflows/{encoded_workflow_id}/versions + + Lists all versions of this workflow. + """ + stored_workflow = self.workflow_manager.get_stored_accessible_workflow(trans, workflow_id) + return [{'version': i, 'update_time': str(w.update_time), 'steps': len(w.steps)} for i, w in enumerate(reversed(stored_workflow.workflows))] + @expose_api def create(self, trans, payload, **kwd): """ diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index dec0d911031..aa3c3d636d8 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -446,6 +446,11 @@ def populate_api_routes(webapp, app): controller='workflows', action='workflow_dict', conditions=dict(method=['GET'])) + webapp.mapper.connect('show_versions', + '/api/workflows/{workflow_id}/versions', + controller='workflows', + action='show_versions', + conditions=dict(method=['GET'])) # Preserve the following download route for now for dependent applications -- deprecate at some point webapp.mapper.connect('workflow_dict', '/api/workflows/download/{workflow_id}', diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index 8121b8ddd0b..854892a3381 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -14,7 +14,7 @@ 'urls' : { 'tool_search' : h.url_for('/api/tools'), 'get_datatypes' : h.url_for('/api/datatypes/mapping'), - 'load_workflow' : h.url_for(controller='workflow', action='load_workflow', version=version), + 'load_workflow' : h.url_for(controller='workflow', action='load_workflow'), 'run_workflow' : h.url_for(controller='root', action='index', workflow_id=trans.security.encode_id(stored.id)), 'rename_async' : h.url_for(controller='workflow', action='rename_async', id=trans.security.encode_id(stored.id)), 'annotate_async' : h.url_for(controller='workflow', action='annotate_async', id=trans.security.encode_id(stored.id)), @@ -391,18 +391,6 @@
    Details - -
    @@ -415,6 +403,10 @@ ${h.to_unicode( stored.name ) | h}
    +
    + +
    + ## Workflow tags. <%namespace file="/tagging_common.mako" import="render_individual_tagging_element" />