`;
- }
- });
- 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();
- },
- 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 += `
`;
+ }
+ });
+ 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,
@@ -530,7 +592,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 +646,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/managers/workflows.py b/lib/galaxy/managers/workflows.py
index dbb0c7c2d3c..0ae7ba3c288 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,31 @@ 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":
- return self._workflow_to_dict_editor(trans, stored)
+ wf_dict = self._workflow_to_dict_editor(trans, stored, version=version)
elif style == "legacy":
- return self._workflow_to_dict_instance(stored, legacy=True)
+ wf_dict = self._workflow_to_dict_instance(stored, legacy=True, version=version)
elif style == "instance":
- return self._workflow_to_dict_instance(stored, legacy=False)
+ wf_dict = self._workflow_to_dict_instance(stored, legacy=False, version=version)
elif style == "run":
- return self._workflow_to_dict_run(trans, stored)
+ wf_dict = self._workflow_to_dict_run(trans, stored, version=version)
else:
- return self._workflow_to_dict_export(trans, stored)
+ 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):
+ def _workflow_to_dict_run(self, trans, stored, version=None):
"""
Builds workflow dictionary used by run workflow form
"""
- workflow = stored.latest_workflow
+ 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):
@@ -476,8 +485,8 @@ 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):
+ workflow = stored.get_internal_version(version)
# Pack workflow data into a dictionary and return
data = {}
data['name'] = workflow.name
@@ -584,12 +593,12 @@ 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
+ workflow = stored.get_internal_version(version)
annotation_str = ""
tag_str = ""
@@ -755,11 +764,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.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:
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),
diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py
index 2533f26fe75..482bef16b3d 100644
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -214,7 +214,18 @@ 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 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):
@@ -400,7 +411,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/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/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py
index 095074924d3..fdcce648d47 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,10 +653,18 @@ 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 None:
+ version = len(stored.workflows) - 1
+ else:
+ 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):
+ 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 +673,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")
diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako
index c46c672bb46..854892a3381 100644
--- a/templates/webapps/galaxy/workflow/editor.mako
+++ b/templates/webapps/galaxy/workflow/editor.mako
@@ -10,25 +10,25 @@
self.active_view="workflow"
self.overlay_visible=True
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' : 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) ),
- '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'),
+ '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]
}
%>
%def>
@@ -403,6 +403,10 @@
${h.to_unicode( stored.name ) | h}
+