1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/b43eadc8cdb4/ changeset: b43eadc8cdb4 user: greg date: 2012-09-06 20:33:11 summary: Enhance the way that multiple versions of the same tool simultaneously loaded into a Galaxy instance are displayed in the tool panel. Only one link will now be displayed in the tool panel for the tool versions, but the tool page will inlcude a select list allowing the user to select a specific version of the tool. Re-running a job for a specific version of a tool (from the history item) works the same as it did before this change set. affected #: 4 files diff -r e6444e7a16851514d3efa3ef34acab8362356f8c -r b43eadc8cdb486b7bfa971ff89552ba04126ef0d lib/galaxy/tools/__init__.py --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -150,14 +150,39 @@ tool_path=tool_path, config_elems=config_elems ) self.shed_tool_confs.append( shed_tool_conf_dict ) + def __add_tool_to_tool_panel( self, tool_id, panel_component, section=False ): + # See if a version of this tool is already loaded into the tool panel. The value of panel_component + # will be a ToolSection (if the value of section=True) or self.tool_panel (if section=False). + tool = self.tools_by_id[ tool_id ] + if section: + panel_dict = panel_component.elems + else: + panel_dict = panel_component + already_loaded = False + for lineage_id in tool.lineage_ids: + if lineage_id in self.tools_by_id: + loaded_version_key = 'tool_%s' % lineage_id + if loaded_version_key in panel_dict: + already_loaded = True + break + if not already_loaded: + inserted = False + key = 'tool_%s' % tool.id + # The value of panel_component is the in-memory tool panel dictionary. + for index, integrated_panel_key in enumerate( self.integrated_tool_panel.keys() ): + if key == integrated_panel_key: + panel_dict.insert( index, key, tool ) + inserted = True + if not inserted: + # If the tool is not defined in integrated_tool_panel.xml, append it to the tool panel. + panel_dict[ key ] = tool + log.debug( "Loaded tool id: %s, version: %s." % ( tool.id, tool.version ) ) def load_tool_panel( self ): for key, val in self.integrated_tool_panel.items(): if key.startswith( 'tool_' ): tool_id = key.replace( 'tool_', '', 1 ) if tool_id in self.tools_by_id: - tool = self.tools_by_id[ tool_id ] - self.tool_panel[ key ] = tool - log.debug( "Loaded tool id: %s, version: %s." % ( tool.id, tool.version ) ) + self.__add_tool_to_tool_panel( tool_id, self.tool_panel, section=False ) elif key.startswith( 'workflow_' ): workflow_id = key.replace( 'workflow_', '', 1 ) if workflow_id in self.workflows_by_id: @@ -177,9 +202,7 @@ if section_key.startswith( 'tool_' ): tool_id = section_key.replace( 'tool_', '', 1 ) if tool_id in self.tools_by_id: - tool = self.tools_by_id[ tool_id ] - section.elems[ section_key ] = tool - log.debug( "Loaded tool id: %s, version: %s." % ( tool.id, tool.version ) ) + self.__add_tool_to_tool_panel( tool_id, section, section=True ) elif section_key.startswith( 'workflow_' ): workflow_id = section_key.replace( 'workflow_', '', 1 ) if workflow_id in self.workflows_by_id: @@ -297,6 +320,21 @@ else: return tool return None + def get_loaded_tools_by_lineage( self, tool_id ): + """Get all loaded tools associated by lineage to the tool whose id is tool_id.""" + tv = self.__get_tool_version( tool_id ) + if tv: + tool_version_ids = tv.get_version_ids( self.app ) + available_tool_versions = [] + for tool_version_id in tool_version_ids: + if tool_version_id in self.tools_by_id: + available_tool_versions.append( self.tools_by_id[ tool_version_id ] ) + return available_tool_versions + else: + if tool_id in self.tools_by_id: + tool = self.tools_by_id[ tool_id ] + return [ tool ] + return [] def __get_tool_version( self, tool_id ): """Return a ToolVersion if one exists for the tool_id""" return self.sa_session.query( self.app.model.ToolVersion ) \ @@ -334,7 +372,7 @@ # If there is not yet a tool_shed_repository record, we're in the process of installing # a new repository, so any included tools can be loaded into the tool panel. can_load_into_panel_dict = True - tool = self.load_tool( os.path.join( tool_path, path ), guid=guid ) + tool = self.load_tool( os.path.join( tool_path, path ), guid=guid ) key = 'tool_%s' % str( tool.id ) if can_load_into_panel_dict: if guid is not None: @@ -350,6 +388,8 @@ tool_version = self.app.model.ToolVersion( tool_id=tool.id, tool_shed_repository=tool_shed_repository ) self.sa_session.add( tool_version ) self.sa_session.flush() + # Load the tool's lineage ids. + tool.lineage_ids = tool.tool_version.get_version_ids( self.app ) if self.app.config.get_bool( 'enable_tool_tags', False ): tag_names = elem.get( "tags", "" ).split( "," ) for tag_name in tag_names: @@ -375,7 +415,7 @@ # Allow for the same tool to be loaded into multiple places in the tool panel. self.tools_by_id[ tool.id ] = tool if load_panel_dict: - panel_dict[ key ] = tool + self.__add_tool_to_tool_panel( tool.id, panel_dict, section=isinstance( panel_dict, galaxy.tools.ToolSection ) ) # Always load the tool into the integrated_panel_dict, or it will not be included in the integrated_tool_panel.xml file. if key in integrated_panel_dict or index is None: integrated_panel_dict[ key ] = tool @@ -789,6 +829,8 @@ self.guid = guid self.old_id = None self.version = None + # Enable easy access to this tool's version lineage. + self.lineage_ids = [] # Parse XML element containing configuration self.parse( root, guid=guid ) self.external_runJob_script = app.config.drmaa_external_runjob_script diff -r e6444e7a16851514d3efa3ef34acab8362356f8c -r b43eadc8cdb486b7bfa971ff89552ba04126ef0d lib/galaxy/util/shed_util.py --- a/lib/galaxy/util/shed_util.py +++ b/lib/galaxy/util/shed_util.py @@ -1518,15 +1518,40 @@ if tool_elem.get( 'guid' ) in guids_to_remove: tool_elems_to_remove.append( tool_elem ) for tool_elem in tool_elems_to_remove: - # Remove the tool sub-element from the section element. - config_elem.remove( tool_elem ) + if tool_elem in config_elem: + # Remove the tool sub-element from the section element. + config_elem.remove( tool_elem ) # Remove the tool from the section in the in-memory tool panel. if section_key in trans.app.toolbox.tool_panel: tool_section = trans.app.toolbox.tool_panel[ section_key ] - tool_key = 'tool_%s' % str( tool_elem.get( 'guid' ) ) - # Remove empty sections only from the in-memory config_elems, but leave the in-memory tool panel alone. + guid = tool_elem.get( 'guid' ) + tool_key = 'tool_%s' % str( guid ) + # Get the list of versions of this tool that are currently available in the toolbox. + available_tool_versions = trans.app.toolbox.get_loaded_tools_by_lineage( guid ) if tool_key in tool_section.elems: - del tool_section.elems[ tool_key ] + if available_tool_versions: + available_tool_versions.reverse() + replacement_tool_key = None + replacement_tool_version = None + # Since we are going to remove the tool from the section, replace it with the newest loaded version of the tool. + for available_tool_version in available_tool_versions: + if available_tool_version.id in tool_section.elems.keys(): + replacement_tool_key = 'tool_%s' % str( available_tool_version.id ) + replacement_tool_version = available_tool_version + break + if replacement_tool_key and replacement_tool_version: + # Get the index of the tool_key in the tool_section. + for tool_section_elems_index, key in enumerate( tool_section.elems.keys() ): + if key == tool_key: + break + # Remove the tool from the tool section. + del tool_section.elems[ tool_key ] + # Add the replacement tool at the same location in the tool section. + tool_section.elems.insert( tool_section_elems_index, replacement_tool_key, replacement_tool_version ) + else: + del tool_section.elems[ tool_key ] + else: + del tool_section.elems[ tool_key ] if uninstall: # Remove the tool from the section in the in-memory integrated tool panel. if section_key in trans.app.toolbox.integrated_tool_panel: @@ -1538,10 +1563,35 @@ # Keep a list of all empty section elements so they can be removed. config_elems_to_remove.append( config_elem ) elif config_elem.tag == 'tool': - if config_elem.get( 'guid' ) in guids_to_remove: + guid = config_elem.get( 'guid' ) + if guid in guids_to_remove: tool_key = 'tool_%s' % str( config_elem.get( 'guid' ) ) + # Get the list of versions of this tool that are currently available in the toolbox. + available_tool_versions = trans.app.toolbox.get_loaded_tools_by_lineage( guid ) if tool_key in trans.app.toolbox.tool_panel: - del trans.app.toolbox.tool_panel[ tool_key ] + if available_tool_versions: + available_tool_versions.reverse() + replacement_tool_key = None + replacement_tool_version = None + # Since we are going to remove the tool from the section, replace it with the newest loaded version of the tool. + for available_tool_version in available_tool_versions: + if available_tool_version.id in trans.app.toolbox.tool_panel.keys(): + replacement_tool_key = 'tool_%s' % str( available_tool_version.id ) + replacement_tool_version = available_tool_version + break + if replacement_tool_key and replacement_tool_version: + # Get the index of the tool_key in the tool_section. + for tool_panel_index, key in enumerate( trans.app.toolbox.tool_panel.keys() ): + if key == tool_key: + break + # Remove the tool from the tool panel. + del trans.app.toolbox.tool_panel[ tool_key ] + # Add the replacement tool at the same location in the tool panel. + trans.app.toolbox.tool_panel.insert( tool_panel_index, replacement_tool_key, replacement_tool_version ) + else: + del trans.app.toolbox.tool_panel[ tool_key ] + else: + del trans.app.toolbox.tool_panel[ tool_key ] if uninstall: if tool_key in trans.app.toolbox.integrated_tool_panel: del trans.app.toolbox.integrated_tool_panel[ tool_key ] diff -r e6444e7a16851514d3efa3ef34acab8362356f8c -r b43eadc8cdb486b7bfa971ff89552ba04126ef0d lib/galaxy/web/controllers/tool_runner.py --- a/lib/galaxy/web/controllers/tool_runner.py +++ b/lib/galaxy/web/controllers/tool_runner.py @@ -35,31 +35,57 @@ def default(self, trans, tool_id=None, **kwd): """Catches the tool id and redirects as needed""" return self.index(trans, tool_id=tool_id, **kwd) - + def __get_tool_components( self, tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=False ): + """ + Retrieve all loaded versions of a tool from the toolbox and return a select list enabling selection of a different version, the list of the tool's + loaded versions, and the specified tool. + """ + toolbox = self.get_toolbox() + tool_version_select_field = None + tools = [] + # Backwards compatibility for datasource tools that have default tool_id configured, but which are now using only GALAXY_URL. + tool_ids = util.listify( tool_id ) + for tool_id in tool_ids: + if get_loaded_tools_by_lineage: + tools = toolbox.get_loaded_tools_by_lineage( tool_id ) + else: + tools = toolbox.get_tool( tool_id, tool_version=tool_version, get_all_versions=True ) + if len( tools ) > 1: + tool_version_select_field = self.build_tool_version_select_field( tools, tool_id, set_selected ) + for tool in tools: + if tool.id == tool_id: + break + else: + tool = tools[ 0 ] + else: + tool = tools[ 0 ] + break + return tool_version_select_field, tools, tool @web.expose def index(self, trans, tool_id=None, from_noframe=None, **kwd): # No tool id passed, redirect to main page if tool_id is None: return trans.response.send_redirect( url_for( "/static/welcome.html" ) ) - # Load the tool - toolbox = self.get_toolbox() - #Backwards compatibility for datasource tools that have default tool_id configured, but which are now using only GALAXY_URL - if isinstance( tool_id, list ): - tool_ids = tool_id - else: - tool_ids = [ tool_id ] - for tool_id in tool_ids: - tool = toolbox.get_tool( tool_id ) - if tool: - break + set_selected = 'refresh' in kwd + tool_version_select_field, tools, tool = self.__get_tool_components( tool_id, + tool_version=None, + get_loaded_tools_by_lineage=True, + set_selected=set_selected ) # No tool matching the tool id, display an error (shouldn't happen) if not tool: - tool_id = ','.join( tool_ids ) log.error( "index called with tool id '%s' but no such tool exists", tool_id ) trans.log_event( "Tool id '%s' does not exist" % tool_id ) - return "Tool '%s' does not exist, kwd=%s " % (tool_id, kwd) + return "Tool '%s' does not exist, kwd=%s " % ( tool_id, kwd ) if tool.require_login and not trans.user: - return trans.response.send_redirect( url_for( controller='user', action='login', cntrller='user', message="You must be logged in to use this tool.", status="info", redirect=url_for( controller='/tool_runner', action='index', tool_id=tool_id, **kwd ) ) ) + message = "You must be logged in to use this tool." + status = "info" + redirect = url_for( controller='/tool_runner', action='index', tool_id=tool_id, **kwd ) + return trans.response.send_redirect( url_for( controller='user', + action='login', + cntrller='user', + message=message, + status=status, + redirect=redirect ) ) params = util.Params( kwd, sanitize = False ) #Sanitize parameters when substituting into command line via input wrappers #do param translation here, used by datasource tools if tool.input_translator: @@ -68,14 +94,21 @@ # so make sure to create a new history if we've never had one before. history = trans.get_history( create=True ) template, vars = tool.handle_input( trans, params.__dict__ ) - if len(params) > 0: - trans.log_event( "Tool params: %s" % (str(params)), tool_id=tool_id ) + if len( params ) > 0: + trans.log_event( "Tool params: %s" % ( str( params ) ), tool_id=tool_id ) add_frame = AddFrameData() add_frame.debug = trans.debug if from_noframe is not None: add_frame.wiki_url = trans.app.config.wiki_url add_frame.from_noframe = True - return trans.fill_template( template, history=history, toolbox=toolbox, tool=tool, util=util, add_frame=add_frame, **vars ) + return trans.fill_template( template, + history=history, + toolbox=self.get_toolbox(), + tool_version_select_field=tool_version_select_field, + tool=tool, + util=util, + add_frame=add_frame, + **vars ) @web.expose def rerun( self, trans, id=None, from_noframe=None, **kwd ): @@ -113,21 +146,10 @@ tool_id = job.tool_id tool_version = job.tool_version try: - # Load the tool - toolbox = self.get_toolbox() - tools = toolbox.get_tool( tool_id, tool_version=tool_version, get_all_versions=True ) - if len( tools ) > 1: - tool_id_select_field = self.build_tool_id_select_field( tools, tool_id ) - for tool in tools: - if tool.id == tool_id: - break - else: - tool = tools[ 0 ] - tool_id_version_message = 'This job was initially run with tool id "%s", version "%s", and multiple derivations ' % ( job.tool_id, job.tool_version ) - tool_id_version_message += 'of this tool are available. Rerun the job with the selected tool or choose another derivation of the tool.' - else: - tool_id_select_field = None - tool = tools[ 0 ] + tool_version_select_field, tools, tool = self.__get_tool_components( tool_id, + tool_version=tool_version, + get_loaded_tools_by_lineage=False, + set_selected=True ) if ( tool.id == job.tool_id or tool.old_id == job.tool_id ) and tool.version == job.tool_version: tool_id_version_message = '' elif tool.id == job.tool_id: @@ -199,27 +221,27 @@ add_frame.from_noframe = True return trans.fill_template( "tool_form.mako", history=history, - toolbox=toolbox, - tool_id_select_field=tool_id_select_field, + toolbox=self.get_toolbox(), + tool_version_select_field=tool_version_select_field, tool=tool, util=util, add_frame=add_frame, tool_id_version_message=tool_id_version_message, **vars ) - def build_tool_id_select_field( self, tools, selected_tool_id ): + def build_tool_version_select_field( self, tools, tool_id, set_selected ): """Build a SelectField whose options are the ids for the received list of tools.""" options = [] refresh_on_change_values = [] for tool in tools: - options.append( ( tool.id, tool.id ) ) + options.insert( 0, ( tool.version, tool.id ) ) refresh_on_change_values.append( tool.id ) select_field = SelectField( name='tool_id', refresh_on_change=True, refresh_on_change_values=refresh_on_change_values ) for option_tup in options: - selected = option_tup[0] == selected_tool_id + selected = set_selected and option_tup[1] == tool_id if selected: - select_field.add_option( option_tup[0], option_tup[1], selected=True ) + select_field.add_option( 'version %s' % option_tup[0], option_tup[1], selected=True ) else: - select_field.add_option( option_tup[0], option_tup[1] ) + select_field.add_option( 'version %s' % option_tup[0], option_tup[1] ) return select_field @web.expose def redirect( self, trans, redirect_url=None, **kwd ): diff -r e6444e7a16851514d3efa3ef34acab8362356f8c -r b43eadc8cdb486b7bfa971ff89552ba04126ef0d templates/tool_form.mako --- a/templates/tool_form.mako +++ b/templates/tool_form.mako @@ -287,25 +287,17 @@ %endif <div class="toolForm" id="${tool.id}"> - %if tool.has_multiple_pages: - <div class="toolFormTitle">${tool.name} (step ${tool_state.page+1} of ${tool.npages})</div> - %else: - <div class="toolFormTitle">${tool.name} (version ${tool.version})</div> - %endif - <div class="toolFormBody"> - <form id="tool_form" name="tool_form" action="${tool_url}" enctype="${tool.enctype}" target="${tool.target}" method="${tool.method}"> - %if tool_id_select_field: - <div class="form-row"> - <label>Tool id:</label> - ${tool_id_select_field.get_html()} - <div class="toolParamHelp" style="clear: both;"> - Select a different derivation of this tool to rerun the job. - </div> - </div> - <div style="clear: both"></div> - %else: - <input type="hidden" name="tool_id" value="${tool.id}"> - %endif + <form id="tool_form" name="tool_form" action="${tool_url}" enctype="${tool.enctype}" target="${tool.target}" method="${tool.method}"> + %if tool.has_multiple_pages: + <div class="toolFormTitle">${tool.name} (step ${tool_state.page+1} of ${tool.npages})</div> + %elif not tool_version_select_field: + <div class="toolFormTitle">${tool.name} (version ${tool.version})</div> + %else: + <div class="toolFormTitle">${tool.name} ${tool_version_select_field.get_html()}</div> + %endif + <div class="toolFormBody"> + <input type="hidden" name="refresh" value="refresh"> + <input type="hidden" name="tool_id" value="${tool.id}"><input type="hidden" name="tool_state" value="${util.object_to_string( tool_state.encode( tool, app ) )}"> %if tool.display_by_page[tool_state.page]: ${trans.fill_template_string( tool.display_by_page[tool_state.page], context=tool.get_param_html_map( trans, tool_state.page, tool_state.inputs ) )} @@ -320,8 +312,8 @@ %endif </div> %endif - </form> - </div> + </div> + </form></div> %if tool.help: <div class="toolHelp"> 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.