1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/41bad82393f1/ Changeset: 41bad82393f1 User: guerler Date: 2015-01-11 17:08:10+00:00 Summary: ToolForm/Workflow: Add modules Affected #: 6 files diff -r d9b038019163bc378b72b675ddb4e1e902384b12 -r 41bad82393f1790013b09851afd6f8914500f7e2 client/galaxy/scripts/mvc/tools/tools-form-base.js --- /dev/null +++ b/client/galaxy/scripts/mvc/tools/tools-form-base.js @@ -0,0 +1,315 @@ +/** + This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', + 'mvc/citation/citation-model', 'mvc/citation/citation-view', + 'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-content', 'mvc/tools/tools-section', 'mvc/tools/tools-tree'], + function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView, + Tools, ToolTemplate, ToolContent, ToolSection, ToolTree) { + + // create form view + return Backbone.View.extend({ + // initialize + initialize: function(options) { + // log options + console.debug(options); + + // link galaxy modal or create one + var galaxy = parent.Galaxy; + if (galaxy && galaxy.modal) { + this.modal = galaxy.modal; + } else { + this.modal = new Ui.Modal.View(); + } + + // check if the user is an admin + if (galaxy && galaxy.currUser) { + this.is_admin = galaxy.currUser.get('is_admin'); + } else { + this.is_admin = false; + } + + // link options + this.options = options; + + // link container + this.container = this.options.container || 'body'; + + // create deferred processing queue handler + // this handler reduces the number of requests to the api by filtering redundant requests + this.deferred = new Deferred(); + + // set element + this.setElement('<div/>'); + + // add to main element + $(this.container).append(this.$el); + + // build this form + this._buildForm(); + }, + + /** Shows the final message (usually upon successful job submission) + */ + reciept: function($el) { + $(this.container).empty(); + $(this.container).append($el); + }, + + /** Highlight and scroll to input element (currently only used for error notifications) + */ + highlight: function (input_id, message, silent) { + // get input field + var input_element = this.element_list[input_id]; + + // check input element + if (input_element) { + // mark error + input_element.error(message || 'Please verify this parameter.'); + + // scroll to first input element + if (!silent) { + $(this.container).animate({ + scrollTop: input_element.$el.offset().top - 20 + }, 500); + } + } + }, + + /** Main tool form build function. This function is called once a new model is available. + */ + _buildForm: function() { + // link this + var self = this; + + // reset events + this.off('refresh'); + this.off('reset'); + + // reset field list, which contains the input field elements + this.field_list = {}; + + // reset sequential input definition list, which contains the input definitions as provided from the api + this.input_list = {}; + + // reset input element list, which contains the dom elements of each input element (includes also the input field) + this.element_list = {}; + + // creates a tree/json data structure from the input form + this.tree = new ToolTree(this); + + // request history content and build form + this.content = new ToolContent(this); + + // link model options + var options = this.options; + + // create ui elements + this._renderForm(options); + + // rebuild the underlying data structure + this.tree.finalize(); + + // show errors + if (!this.workflow && options.errors) { + var error_messages = this.tree.matchResponse(options.errors); + for (var input_id in error_messages) { + this.highlight(input_id, error_messages[input_id], true); + } + } + + // add refresh listener + this.on('refresh', function() { + // by using/reseting the deferred ajax queue the number of redundant calls is reduced + self.deferred.reset(); + self.deferred.execute(function(){self._updateModel()}); + }); + + // add reset listener + this.on('reset', function() { + for (var i in this.element_list) { + this.element_list[i].reset(); + } + }); + }, + + /** Renders the UI elements required for the form + */ + _renderForm: function(options) { + // link this + var self = this; + + // create message view + this.message = new Ui.Message(); + + // button for version selection + var requirements_button = new Ui.ButtonIcon({ + icon : 'fa-info-circle', + title : 'Requirements', + tooltip : 'Display tool requirements', + onclick : function() { + if (!this.visible) { + this.visible = true; + self.message.update({ + persistent : true, + message : ToolTemplate.requirements(options), + status : 'info' + }); + } else { + this.visible = false; + self.message.update({ + message : '' + }); + } + } + }); + if (!options.requirements || options.requirements.length == 0) { + requirements_button.$el.hide(); + } + + // button for version selection + var versions_button = new Ui.ButtonMenu({ + icon : 'fa-cubes', + title : 'Versions', + tooltip : 'Select another tool version' + }); + if (options.versions && options.versions.length > 1) { + for (var i in options.versions) { + var version = options.versions[i]; + if (version != options.version) { + versions_button.addMenu({ + title : 'Switch to ' + version, + version : version, + icon : 'fa-cube', + onclick : function() { + // here we update the tool version (some tools encode the version also in the id) + options.id = options.id.replace(options.version, this.version); + options.version = this.version; + + // rebuild the model and form + self.deferred.reset(); + self.deferred.execute(function(){self._buildModel()}); + } + }); + } + } + } else { + versions_button.$el.hide(); + } + + // button menu + var menu_button = new Ui.ButtonMenu({ + icon : 'fa-caret-down', + title : 'Options', + tooltip : 'View available options' + }); + + // configure button selection + if(options.biostar_url) { + // add question option + menu_button.addMenu({ + icon : 'fa-question-circle', + title : 'Question?', + tooltip : 'Ask a question about this tool (Biostar)', + onclick : function() { + window.open(options.biostar_url + '/p/new/post/'); + } + }); + + // create search button + menu_button.addMenu({ + icon : 'fa-search', + title : 'Search', + tooltip : 'Search help for this tool (Biostar)', + onclick : function() { + window.open(options.biostar_url + '/t/' + options.id + '/'); + } + }); + }; + + // create share button + menu_button.addMenu({ + icon : 'fa-share', + title : 'Share', + tooltip : 'Share this tool', + onclick : function() { + prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id); + } + }); + + // add admin operations + if (this.is_admin) { + // create download button + menu_button.addMenu({ + icon : 'fa-download', + title : 'Download', + tooltip : 'Download this tool', + onclick : function() { + window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download'; + } + }); + } + + // create tool form section + this.section = new ToolSection.View(self, { + inputs : options.inputs, + cls : 'ui-table-plain' + }); + + // switch to classic tool form mako if the form definition is incompatible + if (this.incompatible) { + this.$el.hide(); + $('#tool-form-classic').show(); + return; + } + + // create portlet + this.portlet = new Portlet.View({ + icon : 'fa-wrench', + title : '<b>' + options.name + '</b> ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', + cls : 'ui-portlet-slim', + operations: { + requirements : requirements_button, + menu : menu_button, + versions : versions_button + }, + buttons : this.buttons + }); + + // append message + this.portlet.append(this.message.$el, true); + + // append tool section + this.portlet.append(this.section.$el); + + // start form + this.$el.empty(); + this.$el.append(this.portlet.$el); + + // append help + if (options.help != '') { + this.$el.append(ToolTemplate.help(options.help)); + } + + // append citations + if (options.citations) { + var $citations = $('<div/>'); + var citations = new CitationModel.ToolCitationCollection(); + citations.tool_id = options.id; + var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } ); + citation_list_view.render(); + citations.fetch(); + this.$el.append($citations); + } + + // show message if available in model + if (options.message) { + this.message.update({ + persistent : true, + status : 'warning', + message : options.message + }); + } + } + }); +}); diff -r d9b038019163bc378b72b675ddb4e1e902384b12 -r 41bad82393f1790013b09851afd6f8914500f7e2 client/galaxy/scripts/mvc/tools/tools-form-workflow.js --- /dev/null +++ b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js @@ -0,0 +1,66 @@ +/** + This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'mvc/tools/tools-form-base'], + function(Utils, ToolFormBase) { + + // create form view + var View = ToolFormBase.extend({ + initialize: function(options) { + this.workflow = true; + ToolFormBase.prototype.initialize.call(this, options); + }, + + /** Builds a new model through api call and recreates the entire form + */ + _buildModel: function() { + }, + + /** Request a new model for an already created tool form and updates the form inputs + */ + _updateModel: function() { + // create the request dictionary + var self = this; + var current_state = this.tree.finalize(); + + // log tool state + console.debug('tools-form-workflow::_refreshForm() - Refreshing states.'); + console.debug(current_state); + + // register process + var process_id = this.deferred.register(); + + // build model url for request + var model_url = galaxy_config.root + 'workflow/editor_form_post?tool_id=' + this.options.id; + + // post job + Utils.request({ + type : 'GET', + url : model_url, + data : current_state, + success : function(node) { + parent.update_node(node); + + // process completed + self.deferred.done(process_id); + + // log success + console.debug('tools-form::_refreshForm() - States refreshed.'); + console.debug(node); + }, + error : function(response) { + // process completed + self.deferred.done(process_id); + + // log error + console.debug('tools-form::_refreshForm() - Refresh request failed.'); + console.debug(response); + } + }); + } + }); + + return { + View: View + }; +}); diff -r d9b038019163bc378b72b675ddb4e1e902384b12 -r 41bad82393f1790013b09851afd6f8914500f7e2 static/scripts/mvc/tools/tools-form-base.js --- /dev/null +++ b/static/scripts/mvc/tools/tools-form-base.js @@ -0,0 +1,315 @@ +/** + This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', + 'mvc/citation/citation-model', 'mvc/citation/citation-view', + 'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-content', 'mvc/tools/tools-section', 'mvc/tools/tools-tree'], + function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView, + Tools, ToolTemplate, ToolContent, ToolSection, ToolTree) { + + // create form view + return Backbone.View.extend({ + // initialize + initialize: function(options) { + // log options + console.debug(options); + + // link galaxy modal or create one + var galaxy = parent.Galaxy; + if (galaxy && galaxy.modal) { + this.modal = galaxy.modal; + } else { + this.modal = new Ui.Modal.View(); + } + + // check if the user is an admin + if (galaxy && galaxy.currUser) { + this.is_admin = galaxy.currUser.get('is_admin'); + } else { + this.is_admin = false; + } + + // link options + this.options = options; + + // link container + this.container = this.options.container || 'body'; + + // create deferred processing queue handler + // this handler reduces the number of requests to the api by filtering redundant requests + this.deferred = new Deferred(); + + // set element + this.setElement('<div/>'); + + // add to main element + $(this.container).append(this.$el); + + // build this form + this._buildForm(); + }, + + /** Shows the final message (usually upon successful job submission) + */ + reciept: function($el) { + $(this.container).empty(); + $(this.container).append($el); + }, + + /** Highlight and scroll to input element (currently only used for error notifications) + */ + highlight: function (input_id, message, silent) { + // get input field + var input_element = this.element_list[input_id]; + + // check input element + if (input_element) { + // mark error + input_element.error(message || 'Please verify this parameter.'); + + // scroll to first input element + if (!silent) { + $(this.container).animate({ + scrollTop: input_element.$el.offset().top - 20 + }, 500); + } + } + }, + + /** Main tool form build function. This function is called once a new model is available. + */ + _buildForm: function() { + // link this + var self = this; + + // reset events + this.off('refresh'); + this.off('reset'); + + // reset field list, which contains the input field elements + this.field_list = {}; + + // reset sequential input definition list, which contains the input definitions as provided from the api + this.input_list = {}; + + // reset input element list, which contains the dom elements of each input element (includes also the input field) + this.element_list = {}; + + // creates a tree/json data structure from the input form + this.tree = new ToolTree(this); + + // request history content and build form + this.content = new ToolContent(this); + + // link model options + var options = this.options; + + // create ui elements + this._renderForm(options); + + // rebuild the underlying data structure + this.tree.finalize(); + + // show errors + if (!this.workflow && options.errors) { + var error_messages = this.tree.matchResponse(options.errors); + for (var input_id in error_messages) { + this.highlight(input_id, error_messages[input_id], true); + } + } + + // add refresh listener + this.on('refresh', function() { + // by using/reseting the deferred ajax queue the number of redundant calls is reduced + self.deferred.reset(); + self.deferred.execute(function(){self._updateModel()}); + }); + + // add reset listener + this.on('reset', function() { + for (var i in this.element_list) { + this.element_list[i].reset(); + } + }); + }, + + /** Renders the UI elements required for the form + */ + _renderForm: function(options) { + // link this + var self = this; + + // create message view + this.message = new Ui.Message(); + + // button for version selection + var requirements_button = new Ui.ButtonIcon({ + icon : 'fa-info-circle', + title : 'Requirements', + tooltip : 'Display tool requirements', + onclick : function() { + if (!this.visible) { + this.visible = true; + self.message.update({ + persistent : true, + message : ToolTemplate.requirements(options), + status : 'info' + }); + } else { + this.visible = false; + self.message.update({ + message : '' + }); + } + } + }); + if (!options.requirements || options.requirements.length == 0) { + requirements_button.$el.hide(); + } + + // button for version selection + var versions_button = new Ui.ButtonMenu({ + icon : 'fa-cubes', + title : 'Versions', + tooltip : 'Select another tool version' + }); + if (options.versions && options.versions.length > 1) { + for (var i in options.versions) { + var version = options.versions[i]; + if (version != options.version) { + versions_button.addMenu({ + title : 'Switch to ' + version, + version : version, + icon : 'fa-cube', + onclick : function() { + // here we update the tool version (some tools encode the version also in the id) + options.id = options.id.replace(options.version, this.version); + options.version = this.version; + + // rebuild the model and form + self.deferred.reset(); + self.deferred.execute(function(){self._buildModel()}); + } + }); + } + } + } else { + versions_button.$el.hide(); + } + + // button menu + var menu_button = new Ui.ButtonMenu({ + icon : 'fa-caret-down', + title : 'Options', + tooltip : 'View available options' + }); + + // configure button selection + if(options.biostar_url) { + // add question option + menu_button.addMenu({ + icon : 'fa-question-circle', + title : 'Question?', + tooltip : 'Ask a question about this tool (Biostar)', + onclick : function() { + window.open(options.biostar_url + '/p/new/post/'); + } + }); + + // create search button + menu_button.addMenu({ + icon : 'fa-search', + title : 'Search', + tooltip : 'Search help for this tool (Biostar)', + onclick : function() { + window.open(options.biostar_url + '/t/' + options.id + '/'); + } + }); + }; + + // create share button + menu_button.addMenu({ + icon : 'fa-share', + title : 'Share', + tooltip : 'Share this tool', + onclick : function() { + prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id); + } + }); + + // add admin operations + if (this.is_admin) { + // create download button + menu_button.addMenu({ + icon : 'fa-download', + title : 'Download', + tooltip : 'Download this tool', + onclick : function() { + window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download'; + } + }); + } + + // create tool form section + this.section = new ToolSection.View(self, { + inputs : options.inputs, + cls : 'ui-table-plain' + }); + + // switch to classic tool form mako if the form definition is incompatible + if (this.incompatible) { + this.$el.hide(); + $('#tool-form-classic').show(); + return; + } + + // create portlet + this.portlet = new Portlet.View({ + icon : 'fa-wrench', + title : '<b>' + options.name + '</b> ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', + cls : 'ui-portlet-slim', + operations: { + requirements : requirements_button, + menu : menu_button, + versions : versions_button + }, + buttons : this.buttons + }); + + // append message + this.portlet.append(this.message.$el, true); + + // append tool section + this.portlet.append(this.section.$el); + + // start form + this.$el.empty(); + this.$el.append(this.portlet.$el); + + // append help + if (options.help != '') { + this.$el.append(ToolTemplate.help(options.help)); + } + + // append citations + if (options.citations) { + var $citations = $('<div/>'); + var citations = new CitationModel.ToolCitationCollection(); + citations.tool_id = options.id; + var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } ); + citation_list_view.render(); + citations.fetch(); + this.$el.append($citations); + } + + // show message if available in model + if (options.message) { + this.message.update({ + persistent : true, + status : 'warning', + message : options.message + }); + } + } + }); +}); diff -r d9b038019163bc378b72b675ddb4e1e902384b12 -r 41bad82393f1790013b09851afd6f8914500f7e2 static/scripts/mvc/tools/tools-form-workflow.js --- /dev/null +++ b/static/scripts/mvc/tools/tools-form-workflow.js @@ -0,0 +1,66 @@ +/** + This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'mvc/tools/tools-form-base'], + function(Utils, ToolFormBase) { + + // create form view + var View = ToolFormBase.extend({ + initialize: function(options) { + this.workflow = true; + ToolFormBase.prototype.initialize.call(this, options); + }, + + /** Builds a new model through api call and recreates the entire form + */ + _buildModel: function() { + }, + + /** Request a new model for an already created tool form and updates the form inputs + */ + _updateModel: function() { + // create the request dictionary + var self = this; + var current_state = this.tree.finalize(); + + // log tool state + console.debug('tools-form-workflow::_refreshForm() - Refreshing states.'); + console.debug(current_state); + + // register process + var process_id = this.deferred.register(); + + // build model url for request + var model_url = galaxy_config.root + 'workflow/editor_form_post?tool_id=' + this.options.id; + + // post job + Utils.request({ + type : 'GET', + url : model_url, + data : current_state, + success : function(node) { + parent.update_node(node); + + // process completed + self.deferred.done(process_id); + + // log success + console.debug('tools-form::_refreshForm() - States refreshed.'); + console.debug(node); + }, + error : function(response) { + // process completed + self.deferred.done(process_id); + + // log error + console.debug('tools-form::_refreshForm() - Refresh request failed.'); + console.debug(response); + } + }); + } + }); + + return { + View: View + }; +}); diff -r d9b038019163bc378b72b675ddb4e1e902384b12 -r 41bad82393f1790013b09851afd6f8914500f7e2 static/scripts/packed/mvc/tools/tools-form-base.js --- /dev/null +++ b/static/scripts/packed/mvc/tools/tools-form-base.js @@ -0,0 +1,1 @@ +define(["utils/utils","utils/deferred","mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-content","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,h,f,k,i,a,d,c,e,j,b){return Backbone.View.extend({initialize:function(l){console.debug(l);var m=parent.Galaxy;if(m&&m.modal){this.modal=m.modal}else{this.modal=new k.Modal.View()}if(m&&m.currUser){this.is_admin=m.currUser.get("is_admin")}else{this.is_admin=false}this.options=l;this.container=this.options.container||"body";this.deferred=new h();this.setElement("<div/>");$(this.container).append(this.$el);this._buildForm()},reciept:function(l){$(this.container).empty();$(this.container).append(l)},highlight:function(m,n,l){var o=this.element_list[m];if(o){o.error(n||"Please verify this parameter.");if(!l){$(this.container).animate({scrollTop:o.$el.offset().top-20},500)}}},_buildForm:function(){var l=this;this.off("refresh");this.off("reset");this.field_list={};this.input_list={};this.element_list={};this.tree=new b(this);this.content=new e(this);var n=this.options;this._renderForm(n);this.tree.finalize();if(!this.workflow&&n.errors){var o=this.tree.matchResponse(n.errors);for(var m in o){this.highlight(m,o[m],true)}}this.on("refresh",function(){l.deferred.reset();l.deferred.execute(function(){l._updateModel()})});this.on("reset",function(){for(var p in this.element_list){this.element_list[p].reset()}})},_renderForm:function(u){var t=this;this.message=new k.Message();var q=new k.ButtonIcon({icon:"fa-info-circle",title:"Requirements",tooltip:"Display tool requirements",onclick:function(){if(!this.visible){this.visible=true;t.message.update({persistent:true,message:c.requirements(u),status:"info"})}else{this.visible=false;t.message.update({message:""})}}});if(!u.requirements||u.requirements.length==0){q.$el.hide()}var m=new k.ButtonMenu({icon:"fa-cubes",title:"Versions",tooltip:"Select another tool version"});if(u.versions&&u.versions.length>1){for(var o in u.versions){var r=u.versions[o];if(r!=u.version){m.addMenu({title:"Switch to "+r,version:r,icon:"fa-cube",onclick:function(){u.id=u.id.replace(u.version,this.version);u.version=this.version;t.deferred.reset();t.deferred.execute(function(){t._buildModel()})}})}}}else{m.$el.hide()}var p=new k.ButtonMenu({icon:"fa-caret-down",title:"Options",tooltip:"View available options"});if(u.biostar_url){p.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(u.biostar_url+"/p/new/post/")}});p.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(u.biostar_url+"/t/"+u.id+"/")}})}p.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+galaxy_config.root+"root?tool_id="+u.id)}});if(this.is_admin){p.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=galaxy_config.root+"api/tools/"+u.id+"/download"}})}this.section=new j.View(t,{inputs:u.inputs,cls:"ui-table-plain"});if(this.incompatible){this.$el.hide();$("#tool-form-classic").show();return}this.portlet=new f.View({icon:"fa-wrench",title:"<b>"+u.name+"</b> "+u.description+" (Galaxy Tool Version "+u.version+")",cls:"ui-portlet-slim",operations:{requirements:q,menu:p,versions:m},buttons:this.buttons});this.portlet.append(this.message.$el,true);this.portlet.append(this.section.$el);this.$el.empty();this.$el.append(this.portlet.$el);if(u.help!=""){this.$el.append(c.help(u.help))}if(u.citations){var s=$("<div/>");var l=new i.ToolCitationCollection();l.tool_id=u.id;var n=new a.CitationListView({el:s,collection:l});n.render();l.fetch();this.$el.append(s)}if(u.message){this.message.update({persistent:true,status:"warning",message:u.message})}}})}); \ No newline at end of file diff -r d9b038019163bc378b72b675ddb4e1e902384b12 -r 41bad82393f1790013b09851afd6f8914500f7e2 static/scripts/packed/mvc/tools/tools-form-workflow.js --- /dev/null +++ b/static/scripts/packed/mvc/tools/tools-form-workflow.js @@ -0,0 +1,1 @@ +define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(d){this.workflow=true;a.prototype.initialize.call(this,d)},_buildModel:function(){},_updateModel:function(){var d=this;var e=this.tree.finalize();console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"workflow/editor_form_post?tool_id="+this.options.id;b.request({type:"GET",url:f,data:e,success:function(h){parent.update_node(h);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.