commit/galaxy-central: greg: Display automated tool test results in a container in the tool shed rather than a separate page.
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/1d56c002d32e/ Changeset: 1d56c002d32e User: greg Date: 2013-04-25 22:17:23 Summary: Display automated tool test results in a container in the tool shed rather than a separate page. Affected #: 5 files diff -r 3d569f107f1d449b028fc4d5390dd8d07fdf26ce -r 1d56c002d32e9116f9c6dc57587d9ffa00e24ca0 lib/galaxy/webapps/tool_shed/controllers/repository.py --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -734,50 +734,6 @@ status='error' ) ) @web.expose - def display_tool_functional_test_results( self, trans, repository_id, repository_metadata_id, **kwd ): - """ - The test framework in ~/test/install_and_test_tool_shed_repositories can be executed on a regularly defined schedule (e.g., via cron) to install appropriate - repositories from a tool shed into a Galaxy instance and run defined functional tests for the tools included in the repository. This process affects the values - if these columns in the repository_metadata table: do_not_test, missing_test_components, time_last_tested, tools_functionally_correct and tool_test_results. - """ - params = util.Params( kwd ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - repository = suc.get_repository_by_id( trans, repository_id ) - if repository: - repository_metadata = metadata_util.get_repository_metadata_by_id( trans, repository_metadata_id ) - changeset_revision = repository_metadata.changeset_revision - if repository_metadata: - metadata = repository_metadata.metadata - if metadata: - revision_label = suc.get_revision_label( trans, repository, repository_metadata.changeset_revision ) - return trans.fill_template( '/webapps/tool_shed/repository/display_tool_functional_test_results.mako', - repository=repository, - repository_metadata=repository_metadata, - revision_label=revision_label, - message=message, - status=status ) - else: - message = 'Missing metadata for revision <b>%s</b> of repository <b>%s</b> owned by <b>%s</b>.' % \ - ( str( changeset_revision ), str( repository.name ), str( repository.user.username ) ) - else: - message = 'Invalid repository_metadata_id <b>%s</b> received for displaying functional test errors for repository <b>%s</b>.' % \ - ( str( repository_metadata_id ), str( repository.name ) ) - else: - message = 'Invalid repository_id received for displaying functional test errors.<b>%s</b>.' % str( repository_id ) - return trans.response.send_redirect( web.url_for( controller='repository', - action='browse_repositories', - message=message, - status='error' ) ) - return trans.response.send_redirect( web.url_for( controller='repository', - action='browse_repository', - operation='view_or_manage_repository', - id=repository_id, - changeset_revision=changeset_revision, - message=message, - status='error' ) ) - - @web.expose def display_tool_help_image_in_repository( self, trans, **kwd ): repository_id = kwd.get( 'repository_id', None ) image_file = kwd.get( 'image_file', None ) @@ -2668,11 +2624,6 @@ repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision ) if repository_metadata: repository_metadata_id = trans.security.encode_id( repository_metadata.id ) - # TODO: Fix this when the install and test framework is completed. - # if repository_metadata.tool_test_results: - # tool_test_results = json.from_json_string( repository_metadata.tool_test_results ) - # else: - # tool_test_results = None metadata = repository_metadata.metadata if metadata: if 'tools' in metadata: @@ -2708,7 +2659,6 @@ else: repository_metadata_id = None metadata = None - #tool_test_results = None is_malicious = suc.changeset_is_malicious( trans, repository_id, repository.tip( trans.app ) ) changeset_revision_select_field = grids_util.build_changeset_revision_select_field( trans, repository, @@ -2732,7 +2682,6 @@ tool=tool, tool_metadata_dict=tool_metadata_dict, tool_lineage=tool_lineage, - #tool_test_results=tool_test_results, changeset_revision=changeset_revision, revision_label=revision_label, changeset_revision_select_field=changeset_revision_select_field, diff -r 3d569f107f1d449b028fc4d5390dd8d07fdf26ce -r 1d56c002d32e9116f9c6dc57587d9ffa00e24ca0 lib/galaxy/webapps/tool_shed/util/container_util.py --- a/lib/galaxy/webapps/tool_shed/util/container_util.py +++ b/lib/galaxy/webapps/tool_shed/util/container_util.py @@ -2,6 +2,7 @@ import os import threading from galaxy.util import asbool +from galaxy.web.framework.helpers import time_ago from tool_shed.util import readme_util import tool_shed.util.shed_util_common as suc @@ -29,6 +30,10 @@ self.valid_data_managers = [] self.invalid_data_managers = [] self.tool_dependencies = [] + self.failed_tests = [] + self.missing_test_components = [] + self.passed_tests = [] + self.test_environments = [] self.repository_dependencies = [] self.readme_files = [] self.workflows = [] @@ -85,6 +90,18 @@ self.display_app_containers = display_app_containers +class FailedTest( object ): + """Failed tool tests object""" + + def __init__( self, id=None, stderr=None, test_id=None, tool_id=None, tool_version=None, traceback=None ): + self.id = id + self.stderr = stderr + self.test_id = test_id + self.tool_id = tool_id + self.tool_version = tool_version + self.traceback = traceback + + class InvalidDataManager( object ): """Invalid data Manager object""" @@ -129,6 +146,28 @@ self.error = error +class MissingTestComponent( object ): + """Missing tool test components object""" + + def __init__( self, id=None, missing_components=None, tool_guid=None, tool_id=None, tool_version=None ): + self.id = id + self.missing_components = missing_components + self.tool_guid = tool_guid + self.tool_id = tool_id + self.tool_version = tool_version + + +class PassedTest( object ): + """Passed tool tests object""" + + def __init__( self, id=None, stderr=None, test_id=None, tool_id=None, tool_version=None ): + self.id = id + self.stderr = stderr + self.test_id = test_id + self.tool_id = tool_id + self.tool_version = tool_version + + class ReadMe( object ): """Readme text object""" @@ -157,6 +196,23 @@ return [ self.toolshed, self.repository_name, self.repository_owner, self.changeset_revision, asbool( str( self.prior_installation_required ) ) ] +class TestEnvironment( object ): + """Tool test environment object""" + + def __init__( self, id=None, architecture=None, galaxy_database_version=None, galaxy_revision=None, python_version=None, system=None, time_last_tested=None, + tool_shed_database_version=None, tool_shed_mercurial_version=None, tool_shed_revision=None ): + self.id = id + self.architecture = architecture + self.galaxy_database_version = galaxy_database_version + self.galaxy_revision = galaxy_revision + self.python_version = python_version + self.system = system + self.time_last_tested = time_last_tested + self.tool_shed_database_version = tool_shed_database_version + self.tool_shed_mercurial_version = tool_shed_mercurial_version + self.tool_shed_revision = tool_shed_revision + + class Tool( object ): """Tool object""" @@ -632,6 +688,11 @@ ) if repository_metadata: metadata = repository_metadata.metadata + tool_test_results = repository_metadata.tool_test_results + try: + time_last_tested = time_ago( repository_metadata.time_last_tested ) + except: + time_last_tested = None lock = threading.Lock() lock.acquire( True ) try: @@ -710,6 +771,10 @@ changeset_revision, label='Valid tools' ) containers_dict[ 'valid_tools' ] = valid_tools_root_folder + # Tool test results container. + if tool_test_results: + folder_id, tool_test_results_root_folder = build_tool_test_results_folder( trans, folder_id, tool_test_results, time_last_tested=time_last_tested ) + containers_dict[ 'tool_test_results' ] = tool_test_results_root_folder # Workflows container. if metadata: if 'workflows' in metadata: @@ -928,6 +993,78 @@ tool_dependencies_root_folder = None return folder_id, tool_dependencies_root_folder +def build_tool_test_results_folder( trans, folder_id, tool_test_results_dict, label='Tool test results', time_last_tested=None ): + """Return a folder hierarchy containing tool dependencies.""" + # This container is displayed only in the tool shed. + if tool_test_results_dict: + folder_id += 1 + tool_test_results_root_folder = Folder( id=folder_id, key='root', label='root', parent=None ) + test_environment_dict = tool_test_results_dict[ 'test_environment' ] + if test_environment_dict: + folder_id += 1 + test_results_folder = Folder( id=folder_id, key='test_results', label=label, parent=tool_test_results_root_folder ) + tool_test_results_root_folder.folders.append( test_results_folder ) + folder_id += 1 + folder = Folder( id=folder_id, key='test_environment', label='Automated test environment', parent=test_results_folder ) + test_results_folder.folders.append( folder ) + test_environment = TestEnvironment( id=1, + architecture=test_environment_dict[ 'architecture' ], + galaxy_database_version=test_environment_dict[ 'galaxy_database_version' ], + galaxy_revision=test_environment_dict[ 'galaxy_revision' ], + python_version=test_environment_dict[ 'python_version' ], + system=test_environment_dict[ 'system' ], + time_last_tested=time_last_tested, + tool_shed_database_version=test_environment_dict[ 'tool_shed_database_version' ], + tool_shed_mercurial_version=test_environment_dict[ 'tool_shed_mercurial_version' ], + tool_shed_revision=test_environment_dict[ 'tool_shed_revision' ] ) + folder.test_environments.append( test_environment ) + passed_tests_dicts = tool_test_results_dict[ 'passed_tests' ] + if passed_tests_dicts: + folder_id += 1 + folder = Folder( id=folder_id, key='passed_tests', label='Tests that passed successfully', parent=test_results_folder ) + test_results_folder.folders.append( folder ) + passed_test_id = 0 + for passed_tests_dict in passed_tests_dicts: + passed_test_id += 1 + passed_test = PassedTest( id=passed_test_id, + stderr=passed_tests_dict[ 'stderr' ], + test_id=passed_tests_dict[ 'test_id' ], + tool_id=passed_tests_dict[ 'tool_id' ], + tool_version=passed_tests_dict[ 'tool_version' ] ) + folder.passed_tests.append( passed_test ) + failed_tests_dicts = tool_test_results_dict[ 'failed_tests' ] + if failed_tests_dicts: + folder_id += 1 + folder = Folder( id=folder_id, key='failed_tests', label='Tests that failed', parent=test_results_folder ) + test_results_folder.folders.append( folder ) + failed_test_id = 0 + for failed_tests_dict in failed_tests_dicts: + failed_test_id += 1 + failed_test = FailedTest( id=failed_test_id, + stderr=failed_tests_dict[ 'stderr' ], + test_id=failed_tests_dict[ 'test_id' ], + tool_id=failed_tests_dict[ 'tool_id' ], + tool_version=failed_tests_dict[ 'tool_version' ], + traceback=failed_tests_dict[ 'traceback' ] ) + folder.failed_tests.append( failed_test ) + missing_test_components_dicts = tool_test_results_dict[ 'missing_test_components' ] + if missing_test_components_dicts: + folder_id += 1 + folder = Folder( id=folder_id, key='missing_test_components', label='Tools missing tests or test data', parent=test_results_folder ) + test_results_folder.folders.append( folder ) + missing_test_component_id = 0 + for missing_test_components_dict in missing_test_components_dicts: + missing_test_component_id += 1 + missing_test_component = MissingTestComponent( id=missing_test_component_id, + missing_components=missing_test_components_dict[ 'missing_components' ], + tool_guid=missing_test_components_dict[ 'tool_guid' ], + tool_id=missing_test_components_dict[ 'tool_id' ], + tool_version=missing_test_components_dict[ 'tool_version' ] ) + folder.missing_test_components.append( missing_test_component ) + else: + tool_test_results_root_folder = None + return folder_id, tool_test_results_root_folder + def build_workflows_folder( trans, folder_id, workflows, repository_metadata_id=None, repository_id=None, label='Workflows' ): """ Return a folder hierarchy containing workflow objects for each workflow dictionary in the received workflows list. When diff -r 3d569f107f1d449b028fc4d5390dd8d07fdf26ce -r 1d56c002d32e9116f9c6dc57587d9ffa00e24ca0 templates/webapps/tool_shed/repository/common.mako --- a/templates/webapps/tool_shed/repository/common.mako +++ b/templates/webapps/tool_shed/repository/common.mako @@ -325,6 +325,26 @@ ${render_invalid_data_manager( data_manager, pad, my_row, row_counter, row_is_header )} %endfor %endif + %if folder.test_environments: + %for test_environment in folder.test_environments: + ${render_test_environment( test_environment, pad, my_row, row_counter )} + %endfor + %endif + %if folder.failed_tests: + %for failed_test in folder.failed_tests: + ${render_failed_test( failed_test, pad, my_row, row_counter )} + %endfor + %endif + %if folder.passed_tests: + %for passed_test in folder.passed_tests: + ${render_passed_test( passed_test, pad, my_row, row_counter )} + %endfor + %endif + %if folder.missing_test_components: + %for missing_test_component in folder.missing_test_components: + ${render_missing_test_component( missing_test_component, pad, my_row, row_counter )} + %endfor + %endif </%def><%def name="render_datatype( datatype, pad, parent, row_counter, row_is_header=False )"> @@ -351,22 +371,22 @@ %></%def> -<%def name="render_valid_data_manager( data_manager, pad, parent, row_counter, row_is_header=False )"> - <% - encoded_id = trans.security.encode_id( data_manager.id ) - if row_is_header: - cell_type = 'th' - else: - cell_type = 'td' - %> +<%def name="render_failed_test( failed_test, pad, parent, row_counter, row_is_header=False )"> + <% encoded_id = trans.security.encode_id( failed_test.id ) %><tr class="datasetRow" %if parent is not None: parent="${parent}" %endif id="libraryItem-${encoded_id}"> - <${cell_type} style="padding-left: ${pad+20}px;">${data_manager.name | h}</${cell_type}> - <${cell_type}>${data_manager.version | h}</${cell_type}> - <${cell_type}>${data_manager.data_tables | h}</${cell_type}> + <td style="padding-left: ${pad+20}px;"> + <table class="grid" id="readme_table"> + <tr><td bgcolor="#FFFFCC"><b>Tool id:</b> ${failed_test.tool_id | h}</td></tr> + <tr><td><b>Tool version:</b> ${failed_test.tool_id | h}</td></tr> + <tr><td><b>Test:</b> ${failed_test.test_id | h}</td></tr> + <tr><td><b>Stderr:</b><br/>${failed_test.stderr | h}</td></tr> + <tr><td><b>Traceback:</b><br/>${failed_test.traceback | h}</td></tr> + </table> + </td></tr><% my_row = row_counter.count @@ -467,6 +487,28 @@ %></%def> +<%def name="render_missing_test_component( missing_test_component, pad, parent, row_counter, row_is_header=False )"> + <% encoded_id = trans.security.encode_id( missing_test_component.id ) %> + <tr class="datasetRow" + %if parent is not None: + parent="${parent}" + %endif + id="libraryItem-${encoded_id}"> + <td style="padding-left: ${pad+20}px;"> + <table class="grid" id="readme_table"> + <tr><td bgcolor="#FFFFCC"><b>Tool id:</b> ${missing_test_component.tool_id | h}</td></tr> + <tr><td><b>Tool version:</b> ${missing_test_component.tool_version | h}</td></tr> + <tr><td><b>Tool guid:</b> ${missing_test_component.tool_guid | h}</td></tr> + <tr><td><b>Missing components:</b><br/>${missing_test_component.missing_components | h}</td></tr> + </table> + </td> + </tr> + <% + my_row = row_counter.count + row_counter.increment() + %> +</%def> + <%def name="render_readme( readme, pad, parent, row_counter )"><% from tool_shed.util.shed_util_common import to_safe_string @@ -564,6 +606,28 @@ %></%def> +<%def name="render_passed_test( passed_test, pad, parent, row_counter, row_is_header=False )"> + <% encoded_id = trans.security.encode_id( passed_test.id ) %> + <tr class="datasetRow" + %if parent is not None: + parent="${parent}" + %endif + id="libraryItem-${encoded_id}"> + <td style="padding-left: ${pad+20}px;"> + <table class="grid" id="readme_table"> + <tr><td bgcolor="#FFFFCC"><b>Tool id:</b> ${passed_test.tool_id | h}</td></tr> + <tr><td><b>Tool version:</b> ${passed_test.tool_id | h}</td></tr> + <tr><td><b>Test:</b> ${passed_test.test_id | h}</td></tr> + <tr><td><b>Stderr:</b><br/>${passed_test.stderr | h}</td></tr> + </table> + </td> + </tr> + <% + my_row = row_counter.count + row_counter.increment() + %> +</%def> + <%def name="render_tool( tool, pad, parent, row_counter, row_is_header )"><% encoded_id = trans.security.encode_id( tool.id ) @@ -672,6 +736,56 @@ %></%def> +<%def name="render_test_environment( test_environment, pad, parent, row_counter, row_is_header=False )"> + <% encoded_id = trans.security.encode_id( test_environment.id ) %> + <tr class="datasetRow" + %if parent is not None: + parent="${parent}" + %endif + id="libraryItem-${encoded_id}"> + <td style="padding-left: ${pad+20}px;"> + <table class="grid" id="readme_table"> + <tr><td><b>Time tested:</b> ${test_environment.time_last_tested | h}</td></tr> + <tr><td><b>System:</b> ${test_environment.system | h}</td></tr> + <tr><td><b>Architecture:</b> ${test_environment.architecture | h}</td></tr> + <tr><td><b>Python version:</b> ${test_environment.python_version | h}</td></tr> + <tr><td><b>Galaxy revision:</b> ${test_environment.galaxy_revision | h}</td></tr> + <tr><td><b>Galaxy database version:</b> ${test_environment.galaxy_database_version | h}</td></tr> + <tr><td><b>Tool shed revision:</b> ${test_environment.tool_shed_revision | h}</td></tr> + <tr><td><b>Tool shed database version:</b> ${test_environment.tool_shed_database_version | h}</td></tr> + <tr><td><b>Tool shed mercurial version:</b> ${test_environment.tool_shed_mercurial_version | h}</td></tr> + </table> + </td> + </tr> + <% + my_row = row_counter.count + row_counter.increment() + %> +</%def> + +<%def name="render_valid_data_manager( data_manager, pad, parent, row_counter, row_is_header=False )"> + <% + encoded_id = trans.security.encode_id( data_manager.id ) + if row_is_header: + cell_type = 'th' + else: + cell_type = 'td' + %> + <tr class="datasetRow" + %if parent is not None: + parent="${parent}" + %endif + id="libraryItem-${encoded_id}"> + <${cell_type} style="padding-left: ${pad+20}px;">${data_manager.name | h}</${cell_type}> + <${cell_type}>${data_manager.version | h}</${cell_type}> + <${cell_type}>${data_manager.data_tables | h}</${cell_type}> + </tr> + <% + my_row = row_counter.count + row_counter.increment() + %> +</%def> + <%def name="render_workflow( workflow, pad, parent, row_counter, row_is_header=False )"><% from tool_shed.util.encoding_util import tool_shed_encode @@ -723,18 +837,20 @@ has_workflows = metadata and 'workflows' in metadata datatypes_root_folder = containers_dict.get( 'datatypes', None ) + invalid_data_managers_root_folder = containers_dict.get( 'invalid_data_managers', None ) + invalid_repository_dependencies_root_folder = containers_dict.get( 'invalid_repository_dependencies', None ) + invalid_tool_dependencies_root_folder = containers_dict.get( 'invalid_tool_dependencies', None ) invalid_tools_root_folder = containers_dict.get( 'invalid_tools', None ) - invalid_repository_dependencies_root_folder = containers_dict.get( 'invalid_repository_dependencies', None ) + missing_repository_dependencies_root_folder = containers_dict.get( 'missing_repository_dependencies', None ) + missing_tool_dependencies_root_folder = containers_dict.get( 'missing_tool_dependencies', None ) readme_files_root_folder = containers_dict.get( 'readme_files', None ) repository_dependencies_root_folder = containers_dict.get( 'repository_dependencies', None ) - missing_repository_dependencies_root_folder = containers_dict.get( 'missing_repository_dependencies', None ) - invalid_tool_dependencies_root_folder = containers_dict.get( 'invalid_tool_dependencies', None ) + test_environment_root_folder = containers_dict.get( 'test_environment', None ) tool_dependencies_root_folder = containers_dict.get( 'tool_dependencies', None ) - missing_tool_dependencies_root_folder = containers_dict.get( 'missing_tool_dependencies', None ) + tool_test_results_root_folder = containers_dict.get( 'tool_test_results', None ) + valid_data_managers_root_folder = containers_dict.get( 'valid_data_managers', None ) valid_tools_root_folder = containers_dict.get( 'valid_tools', None ) workflows_root_folder = containers_dict.get( 'workflows', None ) - valid_data_managers_root_folder = containers_dict.get( 'valid_data_managers', None ) - invalid_data_managers_root_folder = containers_dict.get( 'invalid_data_managers', None ) has_contents = datatypes_root_folder or invalid_tools_root_folder or valid_tools_root_folder or workflows_root_folder has_dependencies = \ @@ -864,4 +980,16 @@ </div></div> %endif + %if tool_test_results_root_folder: + <div class="toolForm"> + <div class="toolFormTitle">Automated tool test results</div> + <div class="toolFormBody"> + <p/> + <% row_counter = RowCounter() %> + <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="test_environment"> + ${render_folder( tool_test_results_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )} + </table> + </div> + </div> + %endif </%def> diff -r 3d569f107f1d449b028fc4d5390dd8d07fdf26ce -r 1d56c002d32e9116f9c6dc57587d9ffa00e24ca0 templates/webapps/tool_shed/repository/manage_repository.mako --- a/templates/webapps/tool_shed/repository/manage_repository.mako +++ b/templates/webapps/tool_shed/repository/manage_repository.mako @@ -26,13 +26,6 @@ can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated can_upload = can_push can_view_change_log = not is_new - if repository_metadata: - if repository_metadata.includes_tools and repository_metadata.tool_test_results is not None: - can_display_tool_functional_test_results = True - else: - can_display_tool_functional_test_results = False - else: - can_display_tool_functional_test_results = False if can_push: browse_label = 'Browse or delete repository tip files' @@ -88,9 +81,6 @@ %if can_browse_repository_reviews: <a class="action-button" href="${h.url_for( controller='repository_review', action='manage_repository_reviews', id=trans.app.security.encode_id( repository.id ) )}">Browse reviews of this repository</a> %endif - %if can_display_tool_functional_test_results: - <a class="action-button" href="${h.url_for( controller='repository', action='display_tool_functional_test_results', repository_id=trans.security.encode_id( repository.id ), repository_metadata_id=trans.security.encode_id( repository_metadata.id ) )}">View tool functional test results</a> - %endif %if can_upload: <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> %endif diff -r 3d569f107f1d449b028fc4d5390dd8d07fdf26ce -r 1d56c002d32e9116f9c6dc57587d9ffa00e24ca0 templates/webapps/tool_shed/repository/view_repository.mako --- a/templates/webapps/tool_shed/repository/view_repository.mako +++ b/templates/webapps/tool_shed/repository/view_repository.mako @@ -25,14 +25,6 @@ can_view_change_log = trans.webapp.name == 'tool_shed' and not is_new changeset_revision_is_repository_tip = changeset_revision == repository.tip( trans.app ) - if repository_metadata: - if repository_metadata.includes_tools and repository_metadata.tool_test_results is not None: - can_display_tool_functional_test_results = True - else: - can_display_tool_functional_test_results = False - else: - can_display_tool_functional_test_results = False - if can_push: browse_label = 'Browse or delete repository tip files' else: @@ -88,9 +80,6 @@ %if can_browse_repository_reviews: <a class="action-button" href="${h.url_for( controller='repository_review', action='manage_repository_reviews', id=trans.app.security.encode_id( repository.id ) )}">Browse reviews of this repository</a> %endif - %if can_display_tool_functional_test_results: - <a class="action-button" href="${h.url_for( controller='repository', action='display_tool_functional_test_results', repository_id=trans.security.encode_id( repository.id ), repository_metadata_id=trans.security.encode_id( repository_metadata.id ) )}">View tool functional test results</a> - %endif %if can_upload: <a class="action-button" href="${h.url_for( controller='upload', action='upload', repository_id=trans.security.encode_id( repository.id ) )}">Upload files to repository</a> %endif 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)
-
commits-noreply@bitbucket.org