commit/galaxy-central: greg: Add the ability to view workflows contained in a tool shed repository as svg images. Add the ability to import a specific workflow from a Galaxy tool shed repository to a loacl Galaxy instance. Fix bugs when displaying or running a workflow that requires tools that use the old Galaxy tool id, but the local Galaxy instance uses tools installed from a tool shed.
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/0ba260ea43c4/ changeset: 0ba260ea43c4 user: greg date: 2011-10-26 22:11:49 summary: Add the ability to view workflows contained in a tool shed repository as svg images. Add the ability to import a specific workflow from a Galaxy tool shed repository to a loacl Galaxy instance. Fix bugs when displaying or running a workflow that requires tools that use the old Galaxy tool id, but the local Galaxy instance uses tools installed from a tool shed. affected #: 17 files diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -1,10 +1,11 @@ """ Contains functionality needed in every web interface """ -import os, time, logging, re, string, sys, glob, shutil, tempfile, subprocess +import os, time, logging, re, string, sys, glob, shutil, tempfile, subprocess, binascii from datetime import date, datetime, timedelta from time import strftime from galaxy import config, tools, web, util +from galaxy.util.hash_util import * from galaxy.web import error, form, url_for from galaxy.model.orm import * from galaxy.workflow.modules import * @@ -2484,3 +2485,32 @@ message = "The required file named tool_data_table_conf.xml does not exist in the Galaxy install directory." error = True return error, message +def tool_shed_encode( val ): + if isinstance( val, dict ): + value = simplejson.dumps( val ) + else: + value = val + a = hmac_new( 'ToolShedAndGalaxyMustHaveThisSameKey', value ) + b = binascii.hexlify( value ) + return "%s:%s" % ( a, b ) +def tool_shed_decode( value ): + # Extract and verify hash + a, b = value.split( ":" ) + value = binascii.unhexlify( b ) + test = hmac_new( 'ToolShedAndGalaxyMustHaveThisSameKey', value ) + assert a == test + # Restore from string + values = None + try: + values = simplejson.loads( value ) + except Exception, e: + log.debug( "Decoding json value from tool shed threw exception: %s" % str( e ) ) + if values is not None: + try: + return json_fix( values ) + except Exception, e: + log.debug( "Fixing decoded json value from tool shed threw exception: %s" % str( e ) ) + fixed_values = values + if values is None: + values = value + return values diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/web/controllers/admin.py --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -4,8 +4,7 @@ from galaxy.web.framework.helpers import time_ago, iff, grids from galaxy.tools.search import ToolBoxSearch from galaxy.tools import json_fix -from galaxy.util.hash_util import * -import simplejson, binascii, logging +import logging log = logging.getLogger( __name__ ) from galaxy.actions.admin import AdminActions @@ -736,7 +735,7 @@ section_key = 'section_%s' % kwd[ 'tool_panel_section' ] tool_section = trans.app.toolbox.tool_panel[ section_key ] # Decode the encoded repo_info_dict param value. - repo_info_dict = self.__decode( repo_info_dict ) + repo_info_dict = tool_shed_decode( repo_info_dict ) # Clone the repository to the configured location. current_working_dir = os.getcwd() for name, repo_info_tuple in repo_info_dict.items(): @@ -1157,15 +1156,6 @@ section_str += ' </tool>\n' section_str += ' </section>\n' return section_str - def __decode( self, value ): - # Extract and verify hash - a, b = value.split( ":" ) - value = binascii.unhexlify( b ) - test = hmac_new( 'ToolShedAndGalaxyMustHaveThisSameKey', value ) - assert a == test - # Restore from string - values = json_fix( simplejson.loads( value ) ) - return values ## ---- Utility methods ------------------------------------------------------- diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/web/controllers/workflow.py --- a/lib/galaxy/web/controllers/workflow.py +++ b/lib/galaxy/web/controllers/workflow.py @@ -4,7 +4,7 @@ pkg_resources.require( "simplejson" ) pkg_resources.require( "SVGFig" ) import simplejson -import base64, httplib, urllib2, sgmllib, svgfig +import base64, httplib, urllib2, sgmllib, svgfig, urllib, urllib2 import math from galaxy.web.framework.helpers import time_ago, grids from galaxy.tools.parameters import * @@ -1092,13 +1092,35 @@ trans.response.headers["Content-Disposition"] = "attachment; filename=Galaxy-Workflow-%s.ga" % ( sname ) trans.response.set_content_type( 'application/galaxy-archive' ) return stored_dict - @web.expose def import_workflow( self, trans, **kwd ): + """ + Import a workflow by reading an url, uploading a file, or receiving the textual + representation of a workflow. + """ url = kwd.get( 'url', '' ) + workflow_text = kwd.get( 'workflow_text', '' ) + webapp = kwd.get( 'webapp', 'galaxy' ) message = kwd.get( 'message', '' ) status = kwd.get( 'status', 'done' ) - if kwd.get( 'import_button', False ): + import_button = kwd.get( 'import_button', False ) + tool_shed_url = kwd.get( 'tool_shed_url', '' ) + repository_metadata_id = kwd.get( 'repository_metadata_id', '' ) + # The workflow_name parameter is in the request only if the import originated + # from a Galaxy tool shed, in which case the value was encoded. + workflow_name = kwd.get( 'workflow_name', '' ) + if workflow_name: + workflow_name = tool_shed_decode( workflow_name ) + if tool_shed_url and not import_button: + # Use urllib (send another request to the tool shed) to retrieve the workflow. + workflow_url = 'http://%s/workflow/import_workflow?repository_metadata_id=%s&workflow_name=%s&webapp=%s&open_for_url=true' % \ + ( tool_shed_url, repository_metadata_id, tool_shed_encode( workflow_name ), webapp ) + response = urllib2.urlopen( workflow_url ) + workflow_text = response.read() + response.close() + workflow_text = workflow_text + import_button = True + if import_button: workflow_data = None if url: # Load workflow from external URL @@ -1108,6 +1130,8 @@ except Exception, e: message = "Failed to open URL: <b>%s</b><br>Exception: %s" % ( url, str( e ) ) status = 'error' + elif workflow_text: + workflow_data = workflow_text else: # Load workflow from browsed file. file_data = kwd.get( 'file_data', '' ) @@ -1175,6 +1199,13 @@ else: # TODO: Figure out what to do here... pass + if tool_shed_url: + # We've received the textual representation of a workflow from a Galaxy tool shed. + message = "This workflow has been successfully imported into your local Galaxy instance." + # TODO: support https in the following url. + url = 'http://%s/workflow/view_workflow?repository_metadata_id=%s&workflow_name=%s&webapp=%s&message=%s' % \ + ( tool_shed_url, repository_metadata_id, tool_shed_encode( workflow_name ), webapp, message ) + return trans.response.send_redirect( url ) return self.list( trans ) return trans.fill_template( "workflow/import.mako", url=url, diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/webapps/community/app.py --- a/lib/galaxy/webapps/community/app.py +++ b/lib/galaxy/webapps/community/app.py @@ -35,6 +35,8 @@ self.tag_handler = CommunityTagHandler() # Tool data tables self.tool_data_tables = galaxy.tools.data.ToolDataTableManager( self.config.tool_data_table_config_path ) + # The tool shed has no toolbox, but this attribute is still required. + self.toolbox = None # Load security policy self.security_agent = self.model.security_agent self.quota_agent = galaxy.quota.NoQuotaAgent( self.model ) diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/webapps/community/controllers/admin.py --- a/lib/galaxy/webapps/community/controllers/admin.py +++ b/lib/galaxy/webapps/community/controllers/admin.py @@ -332,13 +332,13 @@ if 'tools' in metadata: metadata_str += '<b>Tools:</b><br/>' for tool_metadata_dict in metadata[ 'tools' ]: - metadata_str += '%s <b>%s</b><br/>' % \ - ( tool_metadata_dict[ 'id' ], tool_metadata_dict[ 'version' ] ) + metadata_str += '%s <b>%s</b><br/>' % ( tool_metadata_dict[ 'id' ], + tool_metadata_dict[ 'version' ] ) if 'workflows' in metadata: metadata_str += '<b>Workflows:</b><br/>' for workflow_metadata_dict in metadata[ 'workflows' ]: - metadata_str += '%s <b>%s</b><br/>' % \ - ( workflow_metadata_dict[ 'name' ], workflow_metadata_dict[ 'format-version' ] ) + metadata_str += '%s <b>%s</b><br/>' % ( workflow_metadata_dict[ 'name' ], + workflow_metadata_dict[ 'format-version' ] ) return metadata_str class MaliciousColumn( grids.BooleanColumn ): def get_value( self, trans, grid, repository_metadata ): diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/webapps/community/controllers/common.py --- a/lib/galaxy/webapps/community/controllers/common.py +++ b/lib/galaxy/webapps/community/controllers/common.py @@ -1,8 +1,9 @@ -import os, string, socket, logging +import os, string, socket, logging, simplejson, binascii from time import strftime from datetime import * from galaxy.tools import * from galaxy.util.json import from_json_string, to_json_string +from galaxy.util.hash_util import * from galaxy.web.base.controller import * from galaxy.webapps.community import model from galaxy.model.orm import * @@ -126,23 +127,18 @@ def generate_workflow_metadata( trans, id, changeset_revision, exported_workflow_dict, metadata_dict ): """ Update the received metadata_dict with changes that have been applied - to the received exported_workflow_dict. Store everything except the - workflow steps in the database. + to the received exported_workflow_dict. Store everything in the database. """ - workflow_dict = { 'a_galaxy_workflow' : exported_workflow_dict[ 'a_galaxy_workflow' ], - 'name' :exported_workflow_dict[ 'name' ], - 'annotation' : exported_workflow_dict[ 'annotation' ], - 'format-version' : exported_workflow_dict[ 'format-version' ] } if 'workflows' in metadata_dict: - metadata_dict[ 'workflows' ].append( workflow_dict ) + metadata_dict[ 'workflows' ].append( exported_workflow_dict ) else: - metadata_dict[ 'workflows' ] = [ workflow_dict ] + metadata_dict[ 'workflows' ] = [ exported_workflow_dict ] return metadata_dict def new_workflow_metadata_required( trans, id, metadata_dict ): """ - TODO: Currently everything about an exported workflow except the name is hard-coded, so - there's no real way to differentiate versions of exported workflows. If this changes at - some future time, this method should be enhanced accordingly... + Currently everything about an exported workflow except the name is hard-coded, so there's + no real way to differentiate versions of exported workflows. If this changes at some future + time, this method should be enhanced accordingly. """ if 'workflows' in metadata_dict: repository_metadata = get_latest_repository_metadata( trans, id ) @@ -425,18 +421,20 @@ trans.sa_session.add( repository_metadata ) trans.sa_session.flush() else: - message = "Change set revision '%s' includes no tools or exported workflows for which metadata can be set." % str( changeset_revision ) + message = "Revision '%s' includes no tools or exported workflows for which metadata can be defined " % str( changeset_revision ) + message += "so this revision cannot be automatically installed into a local Galaxy instance." status = "error" else: # change_set is None - message = "Repository does not include change set revision '%s'." % str( changeset_revision ) + message = "This repository does not include revision '%s'." % str( changeset_revision ) status = 'error' if invalid_files: if metadata_dict: - message = "Metadata was defined for some items in change set revision '%s'. " % str( changeset_revision ) + message = "Metadata was defined for some items in revision '%s'. " % str( changeset_revision ) message += "Correct the following problems if necessary and reset metadata.<br/>" else: - message = "Metadata cannot be defined for change set revision '%s'. Correct the following problems and reset metadata.<br/>" % str( changeset_revision ) + message = "Metadata cannot be defined for revision '%s' so this revision cannot be automatically " % str( changeset_revision ) + message += "installed into a local Galaxy instance. Correct the following problems and reset metadata.<br/>" for itc_tup in invalid_files: tool_file, exception_msg = itc_tup if exception_msg.find( 'No such file or directory' ) >= 0: @@ -619,3 +617,24 @@ selected = selected_value and option_tup[1] == selected_value select_field.add_option( option_tup[0], option_tup[1], selected=selected ) return select_field +def encode( val ): + if isinstance( val, dict ): + value = simplejson.dumps( val ) + else: + value = val + a = hmac_new( 'ToolShedAndGalaxyMustHaveThisSameKey', value ) + b = binascii.hexlify( value ) + return "%s:%s" % ( a, b ) +def decode( value ): + # Extract and verify hash + a, b = value.split( ":" ) + value = binascii.unhexlify( b ) + test = hmac_new( 'ToolShedAndGalaxyMustHaveThisSameKey', value ) + assert a == test + # Restore from string + try: + values = json_fix( simplejson.loads( value ) ) + except Exception, e: + # We do not have a json string + values = value + return values diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/webapps/community/controllers/repository.py --- a/lib/galaxy/webapps/community/controllers/repository.py +++ b/lib/galaxy/webapps/community/controllers/repository.py @@ -9,7 +9,6 @@ from galaxy.webapps.community.model import directory_hash_id from galaxy.web.framework.helpers import time_ago, iff, grids from galaxy.util.json import from_json_string, to_json_string -from galaxy.util.hash_util import * from galaxy.model.orm import * from common import * from mercurial import hg, ui, patch, commands @@ -458,7 +457,10 @@ ok = True for repository_metadata in trans.sa_session.query( model.RepositoryMetadata ): metadata = repository_metadata.metadata - tools = metadata[ 'tools' ] + if 'tools' in metadata: + tools = metadata[ 'tools' ] + else: + tools = [] for tool_dict in tools: if tool_ids and not tool_names and not tool_versions: for tool_id in tool_ids: @@ -588,15 +590,7 @@ changeset_revision = repository_metadata.changeset_revision repository_clone_url = generate_clone_url( trans, repository_id ) repo_info_dict[ repository.name ] = ( repository.description, repository_clone_url, changeset_revision ) - return self.__encode( repo_info_dict ) - def __encode( self, val ): - if isinstance( val, dict ): - value = simplejson.dumps( val ) - else: - value = val - a = hmac_new( 'ToolShedAndGalaxyMustHaveThisSameKey', value ) - b = binascii.hexlify( value ) - return "%s:%s" % ( a, b ) + return encode( repo_info_dict ) @web.expose def preview_tools_in_changeset( self, trans, repository_id, **kwd ): params = util.Params( kwd ) @@ -607,8 +601,10 @@ changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) ) repository_metadata = get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision ) if repository_metadata: + repository_metadata_id = trans.security.encode_id( repository_metadata.id ), metadata = repository_metadata.metadata else: + repository_metadata_id = None metadata = None revision_label = get_revision_label( trans, repository, changeset_revision ) changeset_revision_select_field = build_changeset_revision_select_field( trans, @@ -617,6 +613,7 @@ add_id_to_name=False ) return trans.fill_template( '/webapps/community/repository/preview_tools_in_changeset.mako', repository=repository, + repository_metadata_id=repository_metadata_id, changeset_revision=changeset_revision, revision_label=revision_label, changeset_revision_select_field=changeset_revision_select_field, @@ -636,7 +633,7 @@ changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) ) repo_info_dict = {} repo_info_dict[ repository.name ] = ( repository.description, repository_clone_url, changeset_revision ) - encoded_repo_info_dict = self.__encode( repo_info_dict ) + encoded_repo_info_dict = encode( repo_info_dict ) # Redirect back to local Galaxy to perform install. # TODO: support https in the following url. url = 'http://%s/admin/install_tool_shed_repository?tool_shed_url=%s&repo_info_dict=%s' % \ @@ -1159,8 +1156,10 @@ revision_label = get_revision_label( trans, repository, changeset_revision ) repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision ) if repository_metadata: + repository_metadata_id = trans.security.encode_id( repository_metadata.id ), metadata = repository_metadata.metadata else: + repository_metadata_id = None metadata = None is_malicious = change_set_is_malicious( trans, id, repository.tip ) if is_malicious: @@ -1172,6 +1171,7 @@ return trans.fill_template( '/webapps/community/repository/view_repository.mako', repo=repo, repository=repository, + repository_metadata_id=repository_metadata_id, metadata=metadata, avg_rating=avg_rating, display_reviews=display_reviews, @@ -1299,9 +1299,11 @@ revision_label = get_revision_label( trans, repository, changeset_revision ) repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision ) if repository_metadata: + repository_metadata_id = trans.security.encode_id( repository_metadata.id ) metadata = repository_metadata.metadata is_malicious = repository_metadata.malicious else: + repository_metadata_id = None metadata = None is_malicious = False if is_malicious: @@ -1321,6 +1323,7 @@ allow_push_select_field=allow_push_select_field, repo=repo, repository=repository, + repository_metadata_id=repository_metadata_id, changeset_revision=changeset_revision, changeset_revision_select_field=changeset_revision_select_field, revision_label=revision_label, diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/webapps/community/controllers/workflow.py --- /dev/null +++ b/lib/galaxy/webapps/community/controllers/workflow.py @@ -0,0 +1,395 @@ +import pkg_resources +pkg_resources.require( "simplejson" ) +pkg_resources.require( "SVGFig" ) +import os, logging, ConfigParser, tempfile, shutil, svgfig +from galaxy.webapps.community import model +from galaxy.web.framework.helpers import time_ago, iff, grids +from galaxy.util.json import from_json_string, to_json_string +from galaxy.workflow.modules import InputDataModule, ToolModule, WorkflowModuleFactory +from galaxy.tools import DefaultToolState +from galaxy.web.controllers.workflow import attach_ordered_steps +from galaxy.model.orm import * +from common import * + +class RepoInputDataModule( InputDataModule ): + + type = "data_input" + name = "Input dataset" + + @classmethod + def new( Class, trans, tools_metadata=None, tool_id=None ): + module = Class( trans ) + module.state = dict( name="Input Dataset" ) + return module + @classmethod + def from_dict( Class, trans, d, tools_metadata=None, secure=True ): + module = Class( trans ) + state = from_json_string( d[ "tool_state" ] ) + module.state = dict( name=state.get( "name", "Input Dataset" ) ) + return module + @classmethod + def from_workflow_step( Class, trans, tools_metadata, step ): + module = Class( trans ) + module.state = dict( name="Input Dataset" ) + if step.tool_inputs and "name" in step.tool_inputs: + module.state[ 'name' ] = step.tool_inputs[ 'name' ] + return module + +class RepoToolModule( ToolModule ): + + type = "tool" + + def __init__( self, trans, tools_metadata, tool_id ): + self.trans = trans + self.tools_metadata = tools_metadata + self.tool_id = tool_id + self.tool = None + for tool_dict in tools_metadata: + if self.tool_id in [ tool_dict[ 'id' ], tool_dict[ 'guid' ] ]: + self.tool = load_tool( trans, os.path.abspath( tool_dict[ 'tool_config' ] ) ) + self.post_job_actions = {} + self.workflow_outputs = [] + self.state = None + self.errors = None + @classmethod + def new( Class, trans, tools_metadata, tool_id=None ): + module = Class( trans, tools_metadata, tool_id ) + module.state = module.tool.new_state( trans, all_pages=True ) + return module + @classmethod + def from_dict( Class, trans, d, tools_metadata, secure=True ): + tool_id = d[ 'tool_id' ] + module = Class( trans, tools_metadata, tool_id ) + module.state = DefaultToolState() + if module.tool is not None: + module.state.decode( d[ "tool_state" ], module.tool, module.trans.app, secure=secure ) + module.errors = d.get( "tool_errors", None ) + return module + @classmethod + def from_workflow_step( Class, trans, tools_metadata, step ): + module = Class( trans, tools_metadata, step.tool_id ) + module.state = DefaultToolState() + if module.tool: + module.state.inputs = module.tool.params_from_strings( step.tool_inputs, trans.app, ignore_errors=True ) + else: + module.state.inputs = {} + module.errors = step.tool_errors + return module + def get_data_inputs( self ): + data_inputs = [] + def callback( input, value, prefixed_name, prefixed_label ): + if isinstance( input, DataToolParameter ): + data_inputs.append( dict( name=prefixed_name, + label=prefixed_label, + extensions=input.extensions ) ) + if self.tool: + visit_input_values( self.tool.inputs, self.state.inputs, callback ) + return data_inputs + def get_data_outputs( self ): + data_outputs = [] + if self.tool: + data_inputs = None + for name, tool_output in self.tool.outputs.iteritems(): + if tool_output.format_source != None: + # Default to special name "input" which remove restrictions on connections + formats = [ 'input' ] + if data_inputs == None: + data_inputs = self.get_data_inputs() + # Find the input parameter referenced by format_source + for di in data_inputs: + # Input names come prefixed with conditional and repeat names separated by '|', + # so remove prefixes when comparing with format_source. + if di[ 'name' ] != None and di[ 'name' ].split( '|' )[ -1 ] == tool_output.format_source: + formats = di[ 'extensions' ] + else: + formats = [ tool_output.format ] + for change_elem in tool_output.change_format: + for when_elem in change_elem.findall( 'when' ): + format = when_elem.get( 'format', None ) + if format and format not in formats: + formats.append( format ) + data_outputs.append( dict( name=name, extensions=formats ) ) + return data_outputs + +class RepoWorkflowModuleFactory( WorkflowModuleFactory ): + def __init__( self, module_types ): + self.module_types = module_types + def new( self, trans, type, tools_metadata=None, tool_id=None ): + """Return module for type and (optional) tool_id intialized with new / default state.""" + assert type in self.module_types + return self.module_types[type].new( trans, tool_id ) + def from_dict( self, trans, d, **kwargs ): + """Return module initialized from the data in dictionary `d`.""" + type = d[ 'type' ] + assert type in self.module_types + return self.module_types[ type ].from_dict( trans, d, **kwargs ) + def from_workflow_step( self, trans, tools_metadata, step ): + """Return module initialized from the WorkflowStep object `step`.""" + type = step.type + return self.module_types[ type ].from_workflow_step( trans, tools_metadata, step ) + +module_factory = RepoWorkflowModuleFactory( dict( data_input=RepoInputDataModule, tool=RepoToolModule ) ) + +class WorkflowController( BaseUIController ): + @web.expose + def view_workflow( self, trans, **kwd ): + repository_metadata_id = kwd.get( 'repository_metadata_id', '' ) + workflow_name = kwd.get( 'workflow_name', '' ) + if workflow_name: + workflow_name = decode( workflow_name ) + webapp = kwd.get( 'webapp', 'community' ) + message = kwd.get( 'message', '' ) + status = kwd.get( 'status', 'done' ) + repository_metadata = get_repository_metadata_by_id( trans, repository_metadata_id ) + repository = get_repository( trans, trans.security.encode_id( repository_metadata.repository_id ) ) + return trans.fill_template( "/webapps/community/repository/view_workflow.mako", + repository=repository, + changeset_revision=repository_metadata.changeset_revision, + repository_metadata_id=repository_metadata_id, + workflow_name=workflow_name, + webapp=webapp, + message=message, + status=status ) + @web.expose + def generate_workflow_image( self, trans, repository_metadata_id, workflow_name, webapp='community' ): + repository_metadata = get_repository_metadata_by_id( trans, repository_metadata_id ) + metadata = repository_metadata.metadata + workflow_name = decode( workflow_name ) + for workflow_dict in metadata[ 'workflows' ]: + if workflow_dict[ 'name' ] == workflow_name: + break + if 'tools' in metadata: + tools_metadata = metadata[ 'tools' ] + else: + tools_metadata = [] + workflow, missing_tool_tups = self.__workflow_from_dict( trans, workflow_dict, tools_metadata ) + data = [] + canvas = svgfig.canvas( style="stroke:black; fill:none; stroke-width:1px; stroke-linejoin:round; text-anchor:left" ) + text = svgfig.SVG( "g" ) + connectors = svgfig.SVG( "g" ) + boxes = svgfig.SVG( "g" ) + svgfig.Text.defaults[ "font-size" ] = "10px" + in_pos = {} + out_pos = {} + margin = 5 + # Spacing between input/outputs. + line_px = 16 + # Store px width for boxes of each step. + widths = {} + max_width, max_x, max_y = 0, 0, 0 + for step in workflow.steps: + step.upgrade_messages = {} + module = module_factory.from_workflow_step( trans, tools_metadata, step ) + tool_errors = module.type == 'tool' and not module.tool + module_data_inputs = self.__get_data_inputs( step, module ) + module_data_outputs = self.__get_data_outputs( step, module, workflow.steps ) + step_dict = { + 'id' : step.order_index, + 'data_inputs' : module_data_inputs, + 'data_outputs' : module_data_outputs, + 'position' : step.position, + 'tool_errors' : tool_errors + } + input_conn_dict = {} + for conn in step.input_connections: + input_conn_dict[ conn.input_name ] = dict( id=conn.output_step.order_index, output_name=conn.output_name ) + step_dict[ 'input_connections' ] = input_conn_dict + data.append( step_dict ) + x, y = step.position[ 'left' ], step.position[ 'top' ] + count = 0 + module_name = self.__get_name( module, missing_tool_tups ) + max_len = len( module_name ) * 1.5 + text.append( svgfig.Text( x, y + 20, module_name, **{ "font-size": "14px" } ).SVG() ) + y += 45 + for di in module_data_inputs: + cur_y = y + count * line_px + if step.order_index not in in_pos: + in_pos[ step.order_index ] = {} + in_pos[ step.order_index ][ di[ 'name' ] ] = ( x, cur_y ) + text.append( svgfig.Text( x, cur_y, di[ 'label' ] ).SVG() ) + count += 1 + max_len = max( max_len, len( di[ 'label' ] ) ) + if len( module.get_data_inputs() ) > 0: + y += 15 + for do in module_data_outputs: + cur_y = y + count * line_px + if step.order_index not in out_pos: + out_pos[ step.order_index ] = {} + out_pos[ step.order_index ][ do[ 'name' ] ] = ( x, cur_y ) + text.append( svgfig.Text( x, cur_y, do[ 'name' ] ).SVG() ) + count += 1 + max_len = max( max_len, len( do['name' ] ) ) + widths[ step.order_index ] = max_len * 5.5 + max_x = max( max_x, step.position[ 'left' ] ) + max_y = max( max_y, step.position[ 'top' ] ) + max_width = max( max_width, widths[ step.order_index ] ) + for step_dict in data: + tool_unavailable = step_dict[ 'tool_errors' ] + width = widths[ step_dict[ 'id' ] ] + x, y = step_dict[ 'position' ][ 'left' ], step_dict[ 'position' ][ 'top' ] + if tool_unavailable: + fill = "#EBBCB2" + else: + fill = "#EBD9B2" + boxes.append( svgfig.Rect( x - margin, y, x + width - margin, y + 30, fill=fill ).SVG() ) + box_height = ( len( step_dict[ 'data_inputs' ] ) + len( step_dict[ 'data_outputs' ] ) ) * line_px + margin + # Draw separator line. + if len( step_dict[ 'data_inputs' ] ) > 0: + box_height += 15 + sep_y = y + len( step_dict[ 'data_inputs' ] ) * line_px + 40 + text.append( svgfig.Line( x - margin, sep_y, x + width - margin, sep_y ).SVG() ) + # Define an input/output box. + boxes.append( svgfig.Rect( x - margin, y + 30, x + width - margin, y + 30 + box_height, fill="#ffffff" ).SVG() ) + for conn, output_dict in step_dict[ 'input_connections' ].iteritems(): + in_coords = in_pos[ step_dict[ 'id' ] ][ conn ] + out_conn_pos = out_pos[ output_dict[ 'id' ] ][ output_dict[ 'output_name' ] ] + adjusted = ( out_conn_pos[ 0 ] + widths[ output_dict[ 'id' ] ], out_conn_pos[ 1 ] ) + text.append( svgfig.SVG( "circle", + cx=out_conn_pos[ 0 ] + widths[ output_dict[ 'id' ] ] - margin, + cy=out_conn_pos[ 1 ] - margin, + r = 5, + fill="#ffffff" ) ) + connectors.append( svgfig.Line( adjusted[ 0 ], + adjusted[ 1 ] - margin, + in_coords[ 0 ] - 10, + in_coords[ 1 ], + arrow_end = "true" ).SVG() ) + canvas.append( connectors ) + canvas.append( boxes ) + canvas.append( text ) + width, height = ( max_x + max_width + 50 ), max_y + 300 + canvas[ 'width' ] = "%s px" % width + canvas[ 'height' ] = "%s px" % height + canvas[ 'viewBox' ] = "0 0 %s %s" % ( width, height ) + trans.response.set_content_type( "image/svg+xml" ) + return canvas.standalone_xml() + def __get_name( self, module, missing_tool_tups ): + module_name = module.get_name() + if module.type == 'tool' and module_name == 'unavailable': + for missing_tool_tup in missing_tool_tups: + missing_tool_id, missing_tool_name, missing_tool_version = missing_tool_tup + if missing_tool_id == module.tool_id: + module_name = '%s' % missing_tool_name + return module_name + def __get_data_inputs( self, step, module ): + if module.type == 'tool': + if module.tool: + return module.get_data_inputs() + else: + data_inputs = [] + for wfsc in step.input_connections: + data_inputs_dict = {} + data_inputs_dict[ 'extensions' ] = [ '' ] + data_inputs_dict[ 'name' ] = wfsc.input_name + data_inputs_dict[ 'label' ] = 'Unknown' + data_inputs.append( data_inputs_dict ) + return data_inputs + return module.get_data_inputs() + def __get_data_outputs( self, step, module, steps ): + if module.type == 'tool': + if module.tool: + return module.get_data_outputs() + else: + data_outputs = [] + data_outputs_dict = {} + data_outputs_dict[ 'extensions' ] = [ 'input' ] + found = False + for workflow_step in steps: + for wfsc in workflow_step.input_connections: + if step.name == wfsc.output_step.name: + data_outputs_dict[ 'name' ] = wfsc.output_name + found = True + break + if found: + break + if not found: + # We're at the last step of the workflow. + data_outputs_dict[ 'name' ] = 'output' + data_outputs.append( data_outputs_dict ) + return data_outputs + return module.get_data_outputs() + def __workflow_from_dict( self, trans, data, tools_metadata ): + """Creates and returns workflow object from a dictionary.""" + trans.workflow_building_mode = True + workflow = model.Workflow() + workflow.name = data[ 'name' ] + workflow.has_errors = False + steps = [] + # Keep ids for each step that we need to use to make connections. + steps_by_external_id = {} + # Keep track of tools required by the workflow that are not available in + # the tool shed repository. Each tuple in the list of missing_tool_tups + # will be ( tool_id, tool_name, tool_version ). + missing_tool_tups = [] + # First pass to build step objects and populate basic values + for key, step_dict in data[ 'steps' ].iteritems(): + # Create the model class for the step + step = model.WorkflowStep() + step.name = step_dict[ 'name' ] + step.position = step_dict[ 'position' ] + module = module_factory.from_dict( trans, step_dict, tools_metadata=tools_metadata, secure=False ) + if module.type == 'tool' and module.tool is None: + # A required tool is not available in the current repository. + step.tool_errors = 'unavailable' + missing_tool_tup = ( step_dict[ 'tool_id' ], step_dict[ 'name' ], step_dict[ 'tool_version' ] ) + if missing_tool_tup not in missing_tool_tups: + missing_tool_tups.append( missing_tool_tup ) + module.save_to_step( step ) + if step.tool_errors: + workflow.has_errors = True + # Stick this in the step temporarily. + step.temp_input_connections = step_dict[ 'input_connections' ] + # Unpack and add post-job actions. + post_job_actions = step_dict.get( 'post_job_actions', {} ) + for name, pja_dict in post_job_actions.items(): + pja = PostJobAction( pja_dict[ 'action_type' ], + step, pja_dict[ 'output_name' ], + pja_dict[ 'action_arguments' ] ) + steps.append( step ) + steps_by_external_id[ step_dict[ 'id' ] ] = step + # Second pass to deal with connections between steps. + for step in steps: + # Input connections. + for input_name, conn_dict in step.temp_input_connections.iteritems(): + if conn_dict: + output_step = steps_by_external_id[ conn_dict[ 'id' ] ] + conn = model.WorkflowStepConnection() + conn.input_step = step + conn.input_name = input_name + conn.output_step = output_step + conn.output_name = conn_dict[ 'output_name' ] + step.input_connections.append( conn ) + del step.temp_input_connections + # Order the steps if possible. + attach_ordered_steps( workflow, steps ) + return workflow, missing_tool_tups + @web.expose + def import_workflow( self, trans, **kwd ): + repository_metadata_id = kwd.get( 'repository_metadata_id', '' ) + workflow_name = kwd.get( 'workflow_name', '' ) + if workflow_name: + workflow_name = decode( workflow_name ) + webapp = kwd.get( 'webapp', 'community' ) + message = kwd.get( 'message', '' ) + status = kwd.get( 'status', 'done' ) + repository_metadata = get_repository_metadata_by_id( trans, repository_metadata_id ) + workflows = repository_metadata.metadata[ 'workflows' ] + workflow_data = None + for workflow_data in workflows: + if workflow_data[ 'name' ] == workflow_name: + break + if workflow_data: + if kwd.get( 'open_for_url', False ): + tmp_fd, tmp_fname = tempfile.mkstemp() + to_file = open( tmp_fname, 'wb' ) + to_file.write( to_json_string( workflow_data ) ) + return open( tmp_fname ) + galaxy_url = trans.get_cookie( name='toolshedgalaxyurl' ) + # TODO: support https in the following url. + url = 'http://%s/workflow/import_workflow?tool_shed_url=%s&repository_metadata_id=%s&workflow_name=%s&webapp=%s' % \ + ( galaxy_url, trans.request.host, repository_metadata_id, encode( workflow_name ), webapp ) + return trans.response.send_redirect( url ) + return trans.response.send_redirect( web.url_for( controller='workflow', + action='view_workflow', + message=message, + status=status ) ) diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/webapps/community/model/__init__.py --- a/lib/galaxy/webapps/community/model/__init__.py +++ b/lib/galaxy/webapps/community/model/__init__.py @@ -204,6 +204,34 @@ self.value = None self.user_value = None +class Workflow( object ): + def __init__( self ): + self.user = None + self.name = None + self.has_cycles = None + self.has_errors = None + self.steps = [] + +class WorkflowStep( object ): + def __init__( self ): + self.id = None + self.type = None + self.name = None + self.tool_id = None + self.tool_inputs = None + self.tool_errors = None + self.position = None + self.input_connections = [] + #self.output_connections = [] + self.config = None + +class WorkflowStepConnection( object ): + def __init__( self ): + self.output_step = None + self.output_name = None + self.input_step = None + self.input_name = None + ## ---- Utility methods ------------------------------------------------------- def sort_by_attr( seq, attr ): """ diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec lib/galaxy/workflow/modules.py --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -205,14 +205,14 @@ def from_workflow_step( Class, trans, step ): tool_id = step.tool_id install_tool_id = None - if tool_id not in trans.app.toolbox.tools_by_id: + if trans.app.toolbox and tool_id not in trans.app.toolbox.tools_by_id: # The id value of tools installed from a Galaxy tool shed is a guid, but # these tool's old_id attribute should contain what we're looking for. for available_tool_id, available_tool in trans.app.toolbox.tools_by_id.items(): if tool_id == available_tool.old_id: install_tool_id = available_tool_id break - if tool_id in trans.app.toolbox.tools_by_id or install_tool_id: + if ( trans.app.toolbox and tool_id in trans.app.toolbox.tools_by_id ) or install_tool_id: module = Class( trans, tool_id ) module.state = DefaultToolState() module.state.inputs = module.tool.params_from_strings( step.tool_inputs, trans.app, ignore_errors=True ) @@ -247,7 +247,9 @@ action_arguments = None n_p = PostJobAction(v['action_type'], step, output_name, action_arguments) def get_name( self ): - return self.tool.name + if self.tool: + return self.tool.name + return 'unavailable' def get_tool_id( self ): return self.tool_id def get_tool_version( self ): diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/webapps/community/repository/common.mako --- a/templates/webapps/community/repository/common.mako +++ b/templates/webapps/community/repository/common.mako @@ -83,7 +83,8 @@ hg clone <a href="${clone_str}">${clone_str}</a></%def> -<%def name="render_repository_tools_and_workflows( metadata, can_set_metadata=False, webapp='community' )"> +<%def name="render_repository_tools_and_workflows( repository_metadata_id, metadata, can_set_metadata=False, webapp='community' )"> + <% from galaxy.webapps.community.controllers.common import encode, decode %> %if metadata or can_set_metadata: <p/><div class="toolForm"> @@ -160,14 +161,31 @@ <table class="grid"><tr><td><b>name</b></td> + <td><b>steps</b></td><td><b>format-version</b></td><td><b>annotation</b></td></tr> %for workflow_dict in workflow_dicts: + <% + workflow_name = workflow_dict[ 'name' ] + steps = workflow_dict[ 'steps' ] + format_version = workflow_dict[ 'format-version' ] + annotation = workflow_dict[ 'annotation' ] + %><tr> - <td>${workflow_dict[ 'name' ]}</td> - <td>${workflow_dict[ 'format-version' ]}</td> - <td>${workflow_dict[ 'annotation' ]}</td> + <td> + <a href="${h.url_for( controller='workflow', action='view_workflow', repository_metadata_id=repository_metadata_id, workflow_name=encode( workflow_name ), webapp=webapp )}">${workflow_name}</a> + </td> + <td> + %if 'steps' in workflow_dict: + ## Initially steps were not stored in the metadata record. + ${len( steps )} + %else: + unknown + %endif + </td> + <td>${format_version}</td> + <td>${annotation}</td></tr> %endfor </table> diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/webapps/community/repository/manage_repository.mako --- a/templates/webapps/community/repository/manage_repository.mako +++ b/templates/webapps/community/repository/manage_repository.mako @@ -184,7 +184,7 @@ </form></div></div> -${render_repository_tools_and_workflows( metadata, can_set_metadata=True )} +${render_repository_tools_and_workflows( repository_metadata_id, metadata, can_set_metadata=True )} <p/><div class="toolForm"><div class="toolFormTitle">Manage categories</div> diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/webapps/community/repository/preview_tools_in_changeset.mako --- a/templates/webapps/community/repository/preview_tools_in_changeset.mako +++ b/templates/webapps/community/repository/preview_tools_in_changeset.mako @@ -103,4 +103,4 @@ </div></div><p/> -${render_repository_tools_and_workflows( metadata, webapp=webapp )} +${render_repository_tools_and_workflows( repository_metadata_id, metadata, webapp=webapp )} diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/webapps/community/repository/view_repository.mako --- a/templates/webapps/community/repository/view_repository.mako +++ b/templates/webapps/community/repository/view_repository.mako @@ -185,7 +185,7 @@ %endif </div></div> -${render_repository_tools_and_workflows( metadata, webapp=webapp )} +${render_repository_tools_and_workflows( repository_metadata_id, metadata, webapp=webapp )} %if repository.categories: <p/><div class="toolForm"> diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/webapps/community/repository/view_workflow.mako --- /dev/null +++ b/templates/webapps/community/repository/view_workflow.mako @@ -0,0 +1,102 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/community/common/common.mako" import="*" /> +<%namespace file="/webapps/community/repository/common.mako" import="*" /> + +<% + from galaxy.web.framework.helpers import time_ago + from galaxy.webapps.community.controllers.common import encode + + in_tool_shed = webapp == 'community' + is_admin = trans.user_is_admin() + is_new = repository.is_new + can_manage = is_admin or trans.user == repository.user + can_contact_owner = in_tool_shed and trans.user and trans.user != repository.user + can_push = in_tool_shed and trans.app.security_agent.can_push( trans.user, repository ) + can_upload = can_push + can_download = in_tool_shed and not is_new and ( not is_malicious or can_push ) + can_browse_contents = in_tool_shed and not is_new + can_set_metadata = in_tool_shed and not is_new + can_rate = in_tool_shed and not is_new and trans.user and repository.user != trans.user + can_view_change_log = in_tool_shed and not is_new + if can_push: + browse_label = 'Browse or delete repository files' + else: + browse_label = 'Browse repository files' +%> + +<%! + def inherit(context): + if context.get('use_panels'): + return '/webapps/community/base_panels.mako' + else: + return '/base.mako' +%> +<%inherit file="${inherit(context)}"/> + +<%def name="render_workflow( repository_metadata_id, workflow_name, webapp )"> + <% center_url = h.url_for( controller='workflow', action='generate_workflow_image', repository_metadata_id=repository_metadata_id, workflow_name=encode( workflow_name ), webapp=webapp ) %> + <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"></iframe> +</%def> + +<br/><br/> +<ul class="manage-table-actions"> + %if in_tool_shed: + %if is_new and can_upload: + <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ), webapp='community' )}">Upload files to repository</a> + %else: + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + %if can_upload: + <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ), webapp='community' )}">Upload files to repository</a> + %endif + %if can_manage: + <a class="action-button" href="${h.url_for( controller='repository', action='manage_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip )}">Manage repository</a> + %else: + <a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip, webapp='community' )}">View repository</a> + %endif + %if can_view_change_log: + <a class="action-button" href="${h.url_for( controller='repository', action='view_changelog', id=trans.app.security.encode_id( repository.id ), webapp='community' )}">View change log</a> + %endif + %if can_rate: + <a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.app.security.encode_id( repository.id ) )}">Rate repository</a> + %endif + %if can_browse_contents: + <a class="action-button" href="${h.url_for( controller='repository', action='browse_repository', id=trans.app.security.encode_id( repository.id ), webapp='community' )}">${browse_label}</a> + %endif + %if can_contact_owner: + <a class="action-button" href="${h.url_for( controller='repository', action='contact_owner', id=trans.security.encode_id( repository.id ), webapp='community' )}">Contact repository owner</a> + %endif + %if can_download: + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='gz' )}">Download as a .tar.gz file</a> + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='bz2' )}">Download as a .tar.bz2 file</a> + <a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision, file_type='zip' )}">Download as a zip file</a> + %endif + </div> + %endif + %else: + <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li> + <div popupmenu="repository-${repository.id}-popup"> + <li><a class="action-button" href="${h.url_for( controller='workflow', action='import_workflow', repository_metadata_id=repository_metadata_id, workflow_name=encode( workflow_name ), webapp=webapp )}">Import workflow to local Galaxy</a></li> + <li><a class="action-button" href="${h.url_for( controller='repository', action='install_repository_revision', repository_id=trans.security.encode_id( repository.id ), webapp=webapp, changeset_revision=changeset_revision )}">Install repository to local Galaxy</a></li> + </div> + <li><a class="action-button" id="toolshed-${repository.id}-popup" class="menubutton">Tool Shed Actions</a></li> + <div popupmenu="toolshed-${repository.id}-popup"> + <a class="action-button" href="${h.url_for( controller='repository', action='browse_valid_repositories', webapp=webapp )}">Browse valid repositories</a> + <a class="action-button" href="${h.url_for( controller='repository', action='find_tools', webapp=webapp )}">Search for valid tools</a> + </div> + %endif +</ul> + +%if message: + ${render_msg( message, status )} +%endif + +<br/> +<b>Boxes are red when tools are not available in this repository</b> +<div class="toolParamHelp" style="clear: both;"> + (this page displays SVG graphics) +</div> +<br clear="left"/> + +${render_workflow( repository_metadata_id, workflow_name, webapp )} diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/workflow/display.mako --- a/templates/workflow/display.mako +++ b/templates/workflow/display.mako @@ -1,7 +1,7 @@ <%inherit file="/display_base.mako"/><%namespace file="/display_common.mako" import="render_message" /> -<%! +<% from galaxy.tools.parameters import DataToolParameter, RuntimeValue from galaxy.web import form_builder %> @@ -82,7 +82,17 @@ %for i, step in enumerate( steps ): <tr><td> %if step.type == 'tool' or step.type is None: - <% tool = app.toolbox.tools_by_id[step.tool_id] %> + <% + try: + tool = trans.app.toolbox.tools_by_id[ step.tool_id ] + except KeyError, e: + # The id value of tools installed from a Galaxy tool shed is a guid, but + # these tool's old_id attribute should contain what we're looking for. + for available_tool_id, available_tool in trans.app.toolbox.tools_by_id.items(): + if step.tool_id == available_tool.old_id: + tool = available_tool + break + %><div class="toolForm"><div class="toolFormTitle">Step ${int(step.order_index)+1}: ${tool.name}</div><div class="toolFormBody"> diff -r 37fb9b1fb62de6a304c6c500d9610b7eea2cf2aa -r 0ba260ea43c4e9613965e9f32705a118bab7c3ec templates/workflow/run.mako --- a/templates/workflow/run.mako +++ b/templates/workflow/run.mako @@ -363,7 +363,17 @@ %endif %for i, step in enumerate( steps ): %if step.type == 'tool' or step.type is None: - <% tool = app.toolbox.tools_by_id[step.tool_id] %> + <% + try: + tool = trans.app.toolbox.tools_by_id[ step.tool_id ] + except KeyError, e: + # The id value of tools installed from a Galaxy tool shed is a guid, but + # these tool's old_id attribute should contain what we're looking for. + for available_tool_id, available_tool in trans.app.toolbox.tools_by_id.items(): + if step.tool_id == available_tool.old_id: + tool = available_tool + break + %><input type="hidden" name="${step.id}|tool_state" value="${step.state.encode( tool, app )}"><div class="toolForm"><div class="toolFormTitle"> 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.
participants (1)
-
Bitbucket