galaxy-commits
Threads by month
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions
commit/galaxy-central: greg: Enhancements related to displaying tool shed repository contents and dependencies in both Galaxy and the tool shed
by Bitbucket 30 Nov '12
by Bitbucket 30 Nov '12
30 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/14589278b02d/
changeset: 14589278b02d
user: greg
date: 2012-11-30 21:14:26
summary: Enhancements related to displaying tool shed repository contents and dependencies in both Galaxy and the tool shed
affected #: 35 files
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -3111,8 +3111,6 @@
if os.path.exists( relative_path ):
self.shed_config_filename = shed_tool_conf_dict[ 'config_filename' ]
return shed_tool_conf_dict
- #if self.dist_to_shed:
- # #return ./migrated_tools.xml
return default
def get_shed_config_dict( self, app, default=None ):
"""
@@ -3154,8 +3152,8 @@
def in_error_state( self ):
return self.status == self.installation_status.ERROR
@property
- def has_readme( self ):
- return self.metadata and 'readme' in self.metadata
+ def has_readme_files( self ):
+ return self.metadata and 'readme_files' in self.metadata
@property
def installed_tool_dependencies( self ):
"""Return the repository's tool dependencies that are currently installed."""
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/tool_shed/install_manager.py
--- a/lib/galaxy/tool_shed/install_manager.py
+++ b/lib/galaxy/tool_shed/install_manager.py
@@ -6,7 +6,6 @@
from galaxy.tools import ToolSection
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.shed_util import *
-from galaxy.util.shed_util_common import *
from galaxy.util.odict import odict
from galaxy.tool_shed.common_util import *
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/tool_shed/update_manager.py
--- a/lib/galaxy/tool_shed/update_manager.py
+++ b/lib/galaxy/tool_shed/update_manager.py
@@ -4,7 +4,6 @@
import threading, urllib2, logging
from galaxy.util import string_as_bool
from galaxy.util.shed_util import *
-from galaxy.util.shed_util_common import *
log = logging.getLogger( __name__ )
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -33,7 +33,6 @@
from galaxy.util.hash_util import *
from galaxy.util import listify
from galaxy.util.shed_util import *
-from galaxy.util.shed_util_common import *
from galaxy.web import url_for
from galaxy.visualization.genome.visual_analytics import TracksterConfig
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/util/shed_util_common.py
--- a/lib/galaxy/util/shed_util_common.py
+++ b/lib/galaxy/util/shed_util_common.py
@@ -1,10 +1,11 @@
-import os, shutil, tempfile, logging, string
+import os, shutil, tempfile, logging, string, threading
from galaxy import util
from galaxy.tools import parameters
from galaxy.util import inflector
from galaxy.util.json import *
from galaxy.web import url_for
from galaxy.web.form_builder import SelectField
+from galaxy.webapps.community.util.container_util import *
from galaxy.datatypes.checkers import *
from galaxy.model.orm import *
@@ -33,6 +34,133 @@
TOOL_SHED_ADMIN_CONTROLLER = 'TOOL_SHED_ADMIN_CONTROLLER'
VALID_CHARS = set( string.letters + string.digits + "'\"-=_.()/+*^,:?!#[]%\\$@;{}" )
+def add_installation_directories_to_tool_dependencies( trans, repository_name, repository_owner, changeset_revision, tool_dependencies ):
+ for dependency_key, requirements_dict in tool_dependencies.items():
+ if dependency_key not in [ 'set_environment' ]:
+ dependency_name = requirements_dict[ 'name' ]
+ version = requirements_dict[ 'version' ]
+ type = requirements_dict[ 'type' ]
+ install_dir = os.path.join( trans.app.config.tool_dependency_dir,
+ dependency_name,
+ version,
+ repository_owner,
+ repository_name,
+ changeset_revision )
+ requirements_dict[ 'install_dir' ] = install_dir
+ tool_dependencies[ dependency_key ] = requirements_dict
+ return tool_dependencies
+def build_readme_files_dict( repository_metadata ):
+ """Return a dictionary of valid readme file name <-> readme file content pairs for all readme files contained in the received repository_metadata."""
+ readme_files_dict = {}
+ if repository_metadata:
+ metadata = repository_metadata.metadata
+ if metadata and 'readme_files' in metadata:
+ for relative_path_to_readme_file in metadata[ 'readme_files' ]:
+ readme_file_name = os.path.split( relative_path_to_readme_file )[ 1 ]
+ full_path_to_readme_file = os.path.abspath( relative_path_to_readme_file )
+ try:
+ f = open( full_path_to_readme_file, 'r' )
+ text = f.read()
+ f.close()
+ readme_files_dict[ readme_file_name ] = str( text )
+ except Exception, e:
+ log.debug( "Error reading README file '%s' defined in metadata for repository '%s', revision '%s': %s" % \
+ ( str( relative_path_to_readme_file ), str( repository_name ), str( changeset_revision ), str( e ) ) )
+ return readme_files_dict
+def build_repository_containers_for_galaxy( trans, toolshed_base_url, repository_name, repository_owner, changeset_revision,
+ readme_files_dict, repository_dependencies, tool_dependencies ):
+ """Return a dictionary of containers for the received repository's dependencies and readme files for display during installation to Galaxy."""
+ containers_dict = dict( readme_files=None, repository_dependencies=None, tool_dependencies=None )
+ if readme_files_dict or repository_dependencies or tool_dependencies:
+ lock = threading.Lock()
+ lock.acquire( True )
+ if tool_dependencies:
+ # Add the install_dir attribute to the tool_dependencies.
+ tool_dependencies = add_installation_directories_to_tool_dependencies( trans, repository_name, repository_owner, changeset_revision, tool_dependencies )
+ try:
+ folder_id = 0
+ if readme_files_dict:
+ folder_id, readme_files_root_folder = build_readme_files_folder( folder_id, readme_files_dict )
+ containers_dict[ 'readme_files' ] = readme_files_root_folder
+ if repository_dependencies:
+ folder_id, repository_dependencies_root_folder = build_repository_dependencies_folder( toolshed_base_url=toolshed_base_url,
+ repository_name=repository_name,
+ repository_owner=repository_owner,
+ changeset_revision=changeset_revision,
+ folder_id=folder_id,
+ repository_dependencies=repository_dependencies )
+ containers_dict[ 'repository_dependencies' ] = repository_dependencies_root_folder
+ if tool_dependencies:
+ folder_id, tool_dependencies_root_folder = build_tool_dependencies_folder( folder_id, tool_dependencies, for_galaxy=True )
+ containers_dict[ 'tool_dependencies' ] = tool_dependencies_root_folder
+ except Exception, e:
+ log.debug( "Exception in build_repository_containers_for_galaxy: %s" % str( e ) )
+ finally:
+ lock.release()
+ return containers_dict
+def build_repository_containers_for_tool_shed( repository, changeset_revision, repository_dependencies, repository_metadata ):
+ """Return a dictionary of containers for the received repository's dependencies and contents for display in the tool shed."""
+ containers_dict = dict( datatypes=None,
+ invalid_tools=None,
+ readme_files=None,
+ repository_dependencies=None,
+ tool_dependencies=None,
+ valid_tools=None,
+ workflows=None )
+ if repository_metadata:
+ metadata = repository_metadata.metadata
+ lock = threading.Lock()
+ lock.acquire( True )
+ try:
+ folder_id = 0
+ # Datatypes container.
+ if metadata and 'datatypes' in metadata:
+ datatypes = metadata[ 'datatypes' ]
+ folder_id, datatypes_root_folder = build_datatypes_folder( folder_id, datatypes )
+ containers_dict[ 'datatypes' ] = datatypes_root_folder
+ # Invalid tools container.
+ if metadata and 'invalid_tools' in metadata:
+ invalid_tool_configs = metadata[ 'invalid_tools' ]
+ folder_id, invalid_tools_root_folder = build_invalid_tools_folder( folder_id,
+ invalid_tool_configs,
+ changeset_revision,
+ repository=repository,
+ label='Invalid tools' )
+ containers_dict[ 'invalid_tools' ] = invalid_tools_root_folder
+ # Readme files container.
+ readme_files_dict = build_readme_files_dict( repository_metadata )
+ folder_id, readme_files_root_folder = build_readme_files_folder( folder_id, readme_files_dict )
+ containers_dict[ 'readme_files' ] = readme_files_root_folder
+ # Repository dependencies container.
+ toolshed_base_url = str( url_for( '/', qualified=True ) ).rstrip( '/' )
+ folder_id, repository_dependencies_root_folder = build_repository_dependencies_folder( toolshed_base_url=toolshed_base_url,
+ repository_name=repository.name,
+ repository_owner=repository.user.username,
+ changeset_revision=changeset_revision,
+ folder_id=folder_id,
+ repository_dependencies=repository_dependencies )
+ if repository_dependencies_root_folder:
+ containers_dict[ 'repository_dependencies' ] = repository_dependencies_root_folder
+ # Tool dependencies container.
+ if metadata and 'tool_dependencies' in metadata:
+ tool_dependencies = metadata[ 'tool_dependencies' ]
+ folder_id, tool_dependencies_root_folder = build_tool_dependencies_folder( folder_id, tool_dependencies, for_galaxy=False )
+ containers_dict[ 'tool_dependencies' ] = tool_dependencies_root_folder
+ # Valid tools container.
+ if metadata and 'tools' in metadata:
+ valid_tools = metadata[ 'tools' ]
+ folder_id, valid_tools_root_folder = build_tools_folder( folder_id, valid_tools, repository, changeset_revision, label='Valid tools' )
+ containers_dict[ 'valid_tools' ] = valid_tools_root_folder
+ # Workflows container.
+ if metadata and 'workflows' in metadata:
+ workflows = metadata[ 'workflows' ]
+ folder_id, workflows_root_folder = build_workflows_folder( folder_id, workflows, repository_metadata, label='Workflows' )
+ containers_dict[ 'workflows' ] = workflows_root_folder
+ except Exception, e:
+ log.debug( "Exception in build_repository_containers_for_tool_shed: %s" % str( e ) )
+ finally:
+ lock.release()
+ return containers_dict
def build_repository_ids_select_field( trans, cntrller, name='repository_ids', multiple=True, display='checkboxes' ):
"""Method called from both Galaxy and the Tool Shed to generate the current list of repositories for resetting metadata."""
repositories_select_field = SelectField( name=name, multiple=multiple, display=display )
@@ -336,14 +464,40 @@
trans.sa_session.add( repository_metadata )
trans.sa_session.flush()
return repository_metadata
-def create_repo_info_dict( repository, owner, repository_clone_url, changeset_revision, ctx_rev, metadata ):
+def create_repo_info_dict( trans, repo, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_name=None,
+ repository=None, repository_metadata=None ):
+ """
+ Return a dictionary that includes all of the information needed to install a repository into a local Galaxy instance. The dictionary will also contain
+ the recursive list of repository dependencies defined for the repository, as well as the defined tool dependencies. This method is called during the
+ tool shed repository installation process from Galaxy. In this case both the received repository and repository_metadata will be objects, but repository_name
+ sill be None. This method is also called when a tool shed repository that was uninstalled from a Galaxy instance is being re-installed. In this case, both
+ repository and repository_metadata will be None, but repository_name will have a value.
+ """
repo_info_dict = {}
- repo_info_dict[ repository.name ] = ( repository.description,
- repository_clone_url,
- changeset_revision,
- ctx_rev,
- owner,
- metadata.get( 'tool_dependencies', None ) )
+ if repository is None and repository_metadata is None:
+ # The repository associated with the received repository_clone_url is being re-installed into a Galaxy instance, so we need to retrieve the
+ # appropriate repository from the tool shed using the received information.
+ repository = get_repository_by_name_and_owner( trans, repository_name, repository_owner )
+ repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), changeset_revision )
+ if repository_metadata:
+ metadata = repository_metadata.metadata
+ if metadata:
+ # Get a dictionary of all repositories upon which the contents of the received repository depends.
+ repository_dependencies = get_repository_dependencies_for_changeset_revision( trans,
+ repo,
+ repository,
+ repository_metadata,
+ str( url_for( '/', qualified=True ) ).rstrip( '/' ),
+ repository_dependencies=None,
+ all_repository_dependencies=None )
+ # Cast unicode to string.
+ repo_info_dict[ str( repository.name ) ] = ( str( repository.description ),
+ str( repository_clone_url ),
+ str( changeset_revision ),
+ str( ctx_rev ),
+ str( repository_owner ),
+ repository_dependencies,
+ metadata.get( 'tool_dependencies', None ) )
return repo_info_dict
def generate_clone_url_for_repository_in_tool_shed( trans, repository ):
"""Generate the URL for cloning a repository that is in the tool shed."""
@@ -464,7 +618,8 @@
else:
original_repository_metadata = None
readme_file_names = get_readme_file_names( repository.name )
- metadata_dict = { 'shed_config_filename': shed_config_dict.get( 'config_filename' ) }
+ metadata_dict = { 'shed_config_filename' : shed_config_dict.get( 'config_filename' ) }
+ readme_files = []
invalid_file_tups = []
invalid_tool_configs = []
tool_dependencies_config = None
@@ -524,7 +679,7 @@
shed_config_dict,
resetting_all_metadata_on_repository )
metadata_dict = generate_repository_dependency_metadata( relative_path_to_repository_dependencies, metadata_dict )
- # See if we have a READ_ME file.
+ # See if we have one or more READ_ME files.
elif name.lower() in readme_file_names:
relative_path_to_readme = get_relative_path_to_repository_file( root,
name,
@@ -532,7 +687,7 @@
work_dir,
shed_config_dict,
resetting_all_metadata_on_repository )
- metadata_dict[ 'readme' ] = relative_path_to_readme
+ readme_files.append( relative_path_to_readme )
# See if we have a tool config.
elif name not in NOT_TOOL_CONFIGS and name.endswith( '.xml' ):
full_path = str( os.path.abspath( os.path.join( root, name ) ) )
@@ -583,6 +738,8 @@
exported_workflow_dict = from_json_string( workflow_text )
if 'a_galaxy_workflow' in exported_workflow_dict and exported_workflow_dict[ 'a_galaxy_workflow' ] == 'true':
metadata_dict = generate_workflow_metadata( relative_path, exported_workflow_dict, metadata_dict )
+ if readme_files:
+ metadata_dict[ 'readme_files' ] = readme_files
if 'tools' in metadata_dict:
# This step must be done after metadata for tools has been defined.
tool_dependencies_config = get_config_from_disk( 'tool_dependencies.xml', files_dir )
@@ -864,6 +1021,80 @@
valid_filenames.append( '%s.txt' % r )
valid_filenames.append( '%s.txt' % repository_name )
return valid_filenames
+def get_repository_by_name_and_owner( trans, name, owner ):
+ """Get a repository from the database via name and owner"""
+ user = get_user_by_username( trans, owner )
+ return trans.sa_session.query( trans.model.Repository ) \
+ .filter( and_( trans.model.Repository.table.c.name == name,
+ trans.model.Repository.table.c.user_id == user.id ) ) \
+ .first()
+def get_repository_dependencies_for_changeset_revision( trans, repo, repository, repository_metadata, toolshed_base_url, repository_dependencies=None,
+ all_repository_dependencies=None ):
+ """
+ Return a dictionary of all repositories upon which the contents of the received repository_metadata record depend. The dictionary keys
+ are name-spaced values consisting of toolshed_base_url/repository_name/repository_owner/changeset_revision and the values are lists of
+ repository_dependency tuples consisting of ( toolshed_base_url, repository_name, repository_owner, changeset_revision ). This method
+ ensures that all required repositories to the nth degree are returned.
+ """
+ if all_repository_dependencies is None:
+ all_repository_dependencies = {}
+ if repository_dependencies is None:
+ repository_dependencies = []
+ metadata = repository_metadata.metadata
+ if metadata and 'repository_dependencies' in metadata:
+ repository_dependencies_root_key = generate_repository_dependencies_key_for_repository( toolshed_base_url=toolshed_base_url,
+ repository_name=repository.name,
+ repository_owner=repository.user.username,
+ changeset_revision=repository_metadata.changeset_revision )
+ for repository_dependency in metadata[ 'repository_dependencies' ]:
+ if repository_dependency not in repository_dependencies:
+ repository_dependencies.append( repository_dependency )
+ else:
+ repository_dependencies_root_key = None
+ if repository_dependencies:
+ repository_dependency = repository_dependencies.pop( 0 )
+ # Cast unicode to string.
+ repository_dependency = [ str( item ) for item in repository_dependency ]
+ tool_shed, name, owner, changeset_revision = repository_dependency
+ if repository_dependencies_root_key:
+ if repository_dependencies_root_key in all_repository_dependencies:
+ # See if this repository_dependency is contained in the list associated with the repository_dependencies_root_key.
+ all_repository_dependencies_val = all_repository_dependencies[ repository_dependencies_root_key ]
+ if repository_dependency not in all_repository_dependencies_val:
+ all_repository_dependencies_val.append( repository_dependency )
+ all_repository_dependencies[ repository_dependencies_root_key ] = all_repository_dependencies_val
+ else:
+ # Insert this repository_dependency.
+ all_repository_dependencies[ repository_dependencies_root_key ] = [ repository_dependency ]
+ if tool_shed_is_this_tool_shed( tool_shed ):
+ # The repository is in the current tool shed.
+ required_repository = get_repository_by_name_and_owner( trans, name, owner )
+ required_repository_metadata = get_repository_metadata_by_repository_id_changset_revision( trans,
+ trans.security.encode_id( required_repository.id ),
+ changeset_revision )
+ if required_repository_metadata:
+ required_repo_dir = required_repository.repo_path( trans.app )
+ required_repo = hg.repository( get_configured_ui(), required_repo_dir )
+ else:
+ # The repository changeset_revision is no longer installable, so see if there's been an update.
+ required_repo_dir = required_repository.repo_path( trans.app )
+ required_repo = hg.repository( get_configured_ui(), required_repo_dir )
+ required_repository_metadata = get_next_downloadable_changeset_revision( required_repository, required_repo, changeset_revision )
+ if required_repository_metadata:
+ # The required_repository_metadata changeset_revision is installable.
+ required_metadata = required_repository_metadata.metadata
+ if required_metadata:
+ return get_repository_dependencies_for_changeset_revision( trans=trans,
+ repo=required_repo,
+ repository=required_repository,
+ repository_metadata=required_repository_metadata,
+ toolshed_base_url=tool_shed,
+ repository_dependencies=repository_dependencies,
+ all_repository_dependencies=all_repository_dependencies )
+ else:
+ # The repository is in a different tool shed, so build an url and send a request.
+ raise Exception( "Repository dependencies that refer to repositories in other tool sheds is not yet supported." )
+ return all_repository_dependencies
def get_repository_file_contents( file_path ):
if is_gzip( file_path ):
to_html = to_html_str( '\ngzip compressed file\n' )
@@ -917,6 +1148,12 @@
elif all_metadata_records:
return all_metadata_records[ 0 ]
return None
+def get_repository_metadata_by_repository_id_changset_revision( trans, id, changeset_revision ):
+ """Get a specified metadata record for a specified repository."""
+ return trans.sa_session.query( trans.model.RepositoryMetadata ) \
+ .filter( and_( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ),
+ trans.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
+ .first()
def get_relative_path_to_repository_file( root, name, relative_install_dir, work_dir, shed_config_dict, resetting_all_metadata_on_repository ):
if resetting_all_metadata_on_repository:
full_path_to_file = os.path.join( root, name )
@@ -959,6 +1196,11 @@
relative_path_to_sample_file = relative_path_to_sample_file[ len( tool_path ) + 1 :]
sample_file_metadata_paths.append( relative_path_to_sample_file )
return sample_file_metadata_paths, sample_file_copy_paths
+def get_user_by_username( trans, username ):
+ """Get a user from the database by username"""
+ return trans.sa_session.query( trans.model.User ) \
+ .filter( trans.model.User.table.c.username == username ) \
+ .one()
def handle_existing_tool_dependencies_that_changed_in_update( app, repository, original_dependency_dict, new_dependency_dict ):
"""
This method is called when a Galaxy admin is getting updates for an installed tool shed repository in order to cover the case where an
@@ -1348,6 +1590,8 @@
elif c not in [ '\r' ]:
translated.append( '' )
return ''.join( translated )
+def tool_shed_is_this_tool_shed( toolshed_base_url ):
+ return toolshed_base_url.rstrip( '/' ) == str( url_for( '/', qualified=True ) ).rstrip( '/' )
def update_existing_tool_dependency( app, repository, original_dependency_dict, new_dependencies_dict ):
"""
Update an exsiting tool dependency whose definition was updated in a change set pulled by a Galaxy administrator when getting updates
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/webapps/community/controllers/common.py
--- a/lib/galaxy/webapps/community/controllers/common.py
+++ b/lib/galaxy/webapps/community/controllers/common.py
@@ -1,4 +1,4 @@
-import os, string, socket, logging, simplejson, binascii, tempfile, filecmp, threading
+import os, string, socket, logging, simplejson, binascii, tempfile, filecmp
from time import strftime
from datetime import *
from galaxy.datatypes.checkers import *
@@ -7,7 +7,6 @@
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.hash_util import *
from galaxy.util.shed_util_common import *
-from galaxy.webapps.community.util.container_util import *
from galaxy.web.base.controller import *
from galaxy.web.base.controllers.admin import *
from galaxy.webapps.community import model
@@ -21,11 +20,11 @@
log = logging.getLogger( __name__ )
new_repo_email_alert_template = """
+Repository name: ${repository_name}
Revision: ${revision}
Change description:
${description}
-Repository name: ${repository_name}
Uploaded by: ${username}
Date content uploaded: ${display_date}
@@ -41,24 +40,23 @@
"""
email_alert_template = """
-GALAXY TOOL SHED REPOSITORY UPDATE ALERT
------------------------------------------------------------------------------
-You received this alert because you registered to receive email whenever
-changes were made to the repository named "${repository_name}".
------------------------------------------------------------------------------
-
-Date of change: ${display_date}
-Changed by: ${username}
-
+Repository name: ${repository_name}
Revision: ${revision}
Change description:
${description}
+Changed by: ${username}
+Date of change: ${display_date}
+
${content_alert_str}
-----------------------------------------------------------------------------
This change alert was sent from the Galaxy tool shed hosted on the server
"${host}"
+-----------------------------------------------------------------------------
+You received this alert because you registered to receive email whenever
+changes were made to the repository named "${repository_name}".
+-----------------------------------------------------------------------------
"""
contact_owner_template = """
@@ -104,62 +102,6 @@
trans.sa_session.flush()
return item_rating
-def build_repository_containers( repository, changeset_revision, repository_dependencies, repository_metadata ):
- containers_dict = dict( datatypes=None,
- invalid_tools=None,
- repository_dependencies=None,
- tool_dependencies=None,
- valid_tools=None,
- workflows=None )
- if repository_metadata:
- metadata = repository_metadata.metadata
- lock = threading.Lock()
- lock.acquire( True )
- try:
- folder_id = 0
- # Datatypes container.
- if metadata and 'datatypes' in metadata:
- datatypes = metadata[ 'datatypes' ]
- folder_id, datatypes_root_folder = build_datatypes_folder( folder_id, datatypes )
- containers_dict[ 'datatypes' ] = datatypes_root_folder
- # Invalid tools container.
- if metadata and 'invalid_tools' in metadata:
- invalid_tool_configs = metadata[ 'invalid_tools' ]
- folder_id, invalid_tools_root_folder = build_invalid_tools_folder( folder_id,
- invalid_tool_configs,
- repository,
- changeset_revision,
- label='Invalid tools' )
- containers_dict[ 'invalid_tools' ] = invalid_tools_root_folder
- # Repository dependencies container.
- folder_id, repository_dependencies_root_folder = build_repository_dependencies_folder( repository,
- changeset_revision,
- folder_id,
- repository_dependencies )
- if repository_dependencies_root_folder:
- containers_dict[ 'repository_dependencies' ] = repository_dependencies_root_folder
- # Tool dependencies container.
- if metadata and 'tool_dependencies' in metadata:
- tool_dependencies = metadata[ 'tool_dependencies' ]
- folder_id, tool_dependencies_root_folder = build_tool_dependencies_folder( folder_id, tool_dependencies )
- containers_dict[ 'tool_dependencies' ] = tool_dependencies_root_folder
- # Valid tools container.
- if metadata and 'tools' in metadata:
- valid_tools = metadata[ 'tools' ]
- folder_id, valid_tools_root_folder = build_tools_folder( folder_id, valid_tools, repository, changeset_revision, label='Valid tools' )
- containers_dict[ 'valid_tools' ] = valid_tools_root_folder
- # Workflows container.
- if metadata and 'workflows' in metadata:
- workflows = metadata[ 'workflows' ]
- folder_id, workflows_root_folder = build_workflows_folder( folder_id, workflows, repository_metadata, label='Workflows' )
- containers_dict[ 'workflows' ] = workflows_root_folder
- except Exception, e:
- repository_dependencies_root_folder = None
- tool_dependencies_root_folder = None
- log.debug( "Exception in build_repository_containers: %s" % str( e ) )
- finally:
- lock.release()
- return containers_dict
def add_tool_versions( trans, id, repository_metadata, changeset_revisions ):
# Build a dictionary of { 'tool id' : 'parent tool id' } pairs for each tool in repository_metadata.
metadata = repository_metadata.metadata
@@ -401,75 +343,6 @@
def get_repository_by_name( trans, name ):
"""Get a repository from the database via name"""
return trans.sa_session.query( trans.model.Repository ).filter_by( name=name ).one()
-def get_repository_by_name_and_owner( trans, name, owner ):
- """Get a repository from the database via name and owner"""
- user = get_user_by_username( trans, owner )
- return trans.sa_session.query( trans.model.Repository ) \
- .filter( and_( trans.model.Repository.table.c.name == name,
- trans.model.Repository.table.c.user_id == user.id ) ) \
- .first()
-def get_repository_dependencies_for_changeset_revision( trans, repo, repository, repository_metadata, toolshed_base_url, repository_dependencies=None,
- all_repository_dependencies=None ):
- """
- Return a dictionary of all repositories upon which the contents of the received repository_metadata record depend. The dictionary keys
- are name-spaced values consisting of tool_shed_base_url/repository_name/repository_owner/changeset_revision and the values are lists of
- repository_dependency tuples consisting of ( tool_shed_base_url, repository_name, repository_owner, changeset_revision ). This is a
- recursive method, so it ensures that all required repositories to the nth degree are returned.
- """
- if all_repository_dependencies is None:
- all_repository_dependencies = odict()
- if repository_dependencies is None:
- repository_dependencies = []
- metadata = repository_metadata.metadata
- if metadata and 'repository_dependencies' in metadata:
- repository_dependencies_root_key = generate_repository_dependencies_key_for_repository( repository, repository_metadata.changeset_revision )
- for repository_dependency in metadata[ 'repository_dependencies' ]:
- if repository_dependency not in repository_dependencies:
- repository_dependencies.append( repository_dependency )
- else:
- repository_dependencies_root_key = None
- if repository_dependencies:
- repository_dependency = repository_dependencies.pop( 0 )
- tool_shed, name, owner, changeset_revision = repository_dependency
- if repository_dependencies_root_key:
- if repository_dependencies_root_key in all_repository_dependencies:
- # See if this repository_dependency is contained in the list associated with the repository_dependencies_root_key.
- all_repository_dependencies_val = all_repository_dependencies[ repository_dependencies_root_key ]
- if repository_dependency not in all_repository_dependencies_val:
- all_repository_dependencies_val.append( repository_dependency )
- all_repository_dependencies[ repository_dependencies_root_key ] = all_repository_dependencies_val
- else:
- # Insert this repository_dependency.
- all_repository_dependencies[ repository_dependencies_root_key ] = [ repository_dependency ]
- if tool_shed_is_this_tool_shed( tool_shed ):
- # The repository is in the current tool shed.
- required_repository = get_repository_by_name_and_owner( trans, name, owner )
- required_repository_metadata = get_repository_metadata_by_repository_id_changset_revision( trans,
- trans.security.encode_id( required_repository.id ),
- changeset_revision )
- if required_repository_metadata:
- required_repo_dir = required_repository.repo_path( trans.app )
- required_repo = hg.repository( get_configured_ui(), required_repo_dir )
- else:
- # The repository changeset_revision is no longer installable, so see if there's been an update.
- required_repo_dir = required_repository.repo_path( trans.app )
- required_repo = hg.repository( get_configured_ui(), required_repo_dir )
- required_repository_metadata = get_next_downloadable_changeset_revision( required_repository, required_repo, changeset_revision )
- if required_repository_metadata:
- # The required_repository_metadata changeset_revision is installable.
- required_metadata = required_repository_metadata.metadata
- if required_metadata:
- return get_repository_dependencies_for_changeset_revision( trans=trans,
- repo=required_repo,
- repository=required_repository,
- repository_metadata=required_repository_metadata,
- toolshed_base_url=tool_shed,
- repository_dependencies=repository_dependencies,
- all_repository_dependencies=all_repository_dependencies )
- else:
- # The repository is in a different tool shed, so build an url and send a request.
- raise Exception( "Repository dependencies that refer to repositories in other tool sheds is not yet supported." )
- return all_repository_dependencies
def get_repository_metadata_by_id( trans, id ):
"""Get repository metadata from the database"""
return trans.sa_session.query( trans.model.RepositoryMetadata ).get( trans.security.decode_id( id ) )
@@ -477,12 +350,6 @@
"""Get all metadata records for a specified repository."""
return trans.sa_session.query( trans.model.RepositoryMetadata ) \
.filter( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ) )
-def get_repository_metadata_by_repository_id_changset_revision( trans, id, changeset_revision ):
- """Get a specified metadata record for a specified repository."""
- return trans.sa_session.query( trans.model.RepositoryMetadata ) \
- .filter( and_( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ),
- trans.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
- .first()
def get_repository_metadata_revisions_for_review( repository, reviewed=True ):
repository_metadata_revisions = []
metadata_changeset_revision_hashes = []
@@ -635,7 +502,7 @@
else:
email_alerts.append( user.email )
else:
- subject = "Galaxy tool shed repository update alert"
+ subject = "Galaxy tool shed update alert for repository named %s" % str( repository.name )
email_alerts = from_json_string( repository.email_alerts )
for email in email_alerts:
to = email.strip()
@@ -827,8 +694,6 @@
id=trans.security.encode_id( repository.id ),
message=error_message,
status='error' ) )
-def tool_shed_is_this_tool_shed( toolshed_base_url ):
- return toolshed_base_url.rstrip( '/' ) == str( url_for( '/', qualified=True ) ).rstrip( '/' )
def update_for_browsing( trans, repository, current_working_dir, commit_message='' ):
# This method id deprecated, but we'll keep it around for a while in case we need it. The problem is that hg purge
# is not supported by the mercurial API.
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/webapps/community/controllers/hg.py
--- a/lib/galaxy/webapps/community/controllers/hg.py
+++ b/lib/galaxy/webapps/community/controllers/hg.py
@@ -1,6 +1,7 @@
import os, logging
from galaxy.web.base.controller import *
-from galaxy.webapps.community.controllers.common import *
+from galaxy.util.shed_util_common import get_repository_by_name_and_owner
+from galaxy.webapps.community.controllers.common import set_repository_metadata
from galaxy import eggs
eggs.require('mercurial')
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/webapps/community/controllers/repository.py
--- a/lib/galaxy/webapps/community/controllers/repository.py
+++ b/lib/galaxy/webapps/community/controllers/repository.py
@@ -1278,6 +1278,7 @@
it into a local Galaxy instance.
"""
includes_tools = False
+ includes_repository_dependencies = False
includes_tool_dependencies = False
repo_info_dicts = []
for tup in zip( util.listify( repository_ids ), util.listify( changeset_revisions ) ):
@@ -1288,33 +1289,39 @@
metadata = repository_metadata.metadata
if not includes_tools and 'tools' in metadata:
includes_tools = True
+ if not includes_repository_dependencies and 'repository_dependencies' in metadata:
+ includes_repository_dependencies = True
if not includes_tool_dependencies and 'tool_dependencies' in metadata:
includes_tool_dependencies = True
repo_dir = repository.repo_path( trans.app )
repo = hg.repository( get_configured_ui(), repo_dir )
ctx = get_changectx_for_changeset( repo, changeset_revision )
- repo_info_dict = create_repo_info_dict( repository, repository.user.username, repository_clone_url, changeset_revision, str( ctx.rev() ), metadata )
+ repo_info_dict = create_repo_info_dict( trans=trans,
+ repo=repo,
+ repository_clone_url=repository_clone_url,
+ changeset_revision=changeset_revision,
+ ctx_rev=str( ctx.rev() ),
+ repository_owner=repository.user.username,
+ repository_name=None,
+ repository=repository,
+ repository_metadata=repository_metadata )
repo_info_dicts.append( tool_shed_encode( repo_info_dict ) )
- return dict( includes_tools=includes_tools, includes_tool_dependencies=includes_tool_dependencies, repo_info_dicts=repo_info_dicts )
- @web.expose
- def get_readme( self, trans, **kwd ):
- """If the received changeset_revision includes a readme file, return it's contents."""
+ return dict( includes_tools=includes_tools,
+ includes_repository_dependencies=includes_repository_dependencies,
+ includes_tool_dependencies=includes_tool_dependencies,
+ repo_info_dicts=repo_info_dicts )
+ @web.json
+ def get_readme_files( self, trans, **kwd ):
+ """
+ This method is called when installing or re-installing a single repository into a Galaxy instance. If the received changeset_revision
+ includes one or more readme files, return them in a dictionary.
+ """
repository_name = kwd[ 'name' ]
repository_owner = kwd[ 'owner' ]
changeset_revision = kwd[ 'changeset_revision' ]
repository = get_repository_by_name_and_owner( trans, repository_name, repository_owner )
- repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), changeset_revision )
- metadata = repository_metadata.metadata
- if metadata and 'readme' in metadata:
- try:
- f = open( metadata[ 'readme' ], 'r' )
- text = f.read()
- f.close()
- return str( text )
- except Exception, e:
- log.debug( "Error attempting to read README file '%s' defined in metadata for repository '%s', revision '%s': %s" % \
- ( str( metadata[ 'readme' ] ), str( repository_name ), str( changeset_revision ), str( e ) ) )
- return ''
+ repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), changeset_revision )
+ return build_readme_files_dict( repository_metadata )
@web.expose
def get_tool_dependencies( self, trans, **kwd ):
"""Handle a request from a local Galaxy instance."""
@@ -1799,7 +1806,7 @@
review_id = trans.security.encode_id( review.id )
else:
review_id = None
- containers_dict = build_repository_containers( repository, changeset_revision, repository_dependencies, repository_metadata )
+ containers_dict = build_repository_containers_for_tool_shed( repository, changeset_revision, repository_dependencies, repository_metadata )
return trans.fill_template( '/webapps/community/repository/manage_repository.mako',
cntrller=cntrller,
repo_name=repo_name,
@@ -1867,8 +1874,6 @@
return state
@web.json
def open_folder( self, trans, folder_path ):
- # The tool shed includes a repository source file browser, which currently depends upon
- # copies of the hg repository file store in the repo_path for browsing.
# Avoid caching
trans.response.headers['Pragma'] = 'no-cache'
trans.response.headers['Expires'] = '0'
@@ -1904,7 +1909,7 @@
selected_value=changeset_revision,
add_id_to_name=False,
downloadable=False )
- containers_dict = build_repository_containers( repository, changeset_revision, repository_dependencies, repository_metadata )
+ containers_dict = build_repository_containers_for_tool_shed( repository, changeset_revision, repository_dependencies, repository_metadata )
return trans.fill_template( '/webapps/community/repository/preview_tools_in_changeset.mako',
repository=repository,
containers_dict=containers_dict,
@@ -2373,52 +2378,6 @@
action='view_repository',
**kwd ) )
@web.expose
- def view_readme( self, trans, id, changeset_revision, **kwd ):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- cntrller = params.get( 'cntrller', 'repository' )
- repository = get_repository_in_tool_shed( trans, id )
- repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), changeset_revision )
- if repository_metadata:
- metadata = repository_metadata.metadata
- else:
- metadata = None
- if metadata and 'readme' in metadata:
- readme_file = str( metadata[ 'readme' ] )
- repo_files_dir = repository.repo_path( trans.app )
- try:
- f = open( readme_file, 'r' )
- raw_text = f.read()
- f.close()
- except IOError:
- work_dir = tempfile.mkdtemp()
- try:
- manifest_readme_file = self.get_file_from_changeset_revision( repo_files_dir, changeset_revision, readme_file, work_dir )
- f = open( manifest_readme_file, 'r' )
- raw_text = f.read()
- f.close()
- remove_dir( work_dir )
- except Exception, e:
- raw_text = "Error locating and reading this repository's README file '%s': %s" % ( readme_file, str( e ) )
- log.debug( raw_text )
- remove_dir( work_dir )
- except Exception, e:
- raw_text = "Error locating and reading this repository's README file '%s': %s" % ( readme_file, str( e ) )
- log.debug( raw_text )
- readme_text = raw_text
- else:
- readme_text = ''
- is_malicious = changeset_is_malicious( trans, id, changeset_revision )
- return trans.fill_template( '/webapps/community/common/view_readme.mako',
- cntrller=cntrller,
- repository=repository,
- changeset_revision=changeset_revision,
- readme_text=readme_text,
- is_malicious=is_malicious,
- message=message,
- status=status )
- @web.expose
def view_repository( self, trans, id, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
@@ -2492,7 +2451,7 @@
review_id = trans.security.encode_id( review.id )
else:
review_id = None
- containers_dict = build_repository_containers( repository, changeset_revision, repository_dependencies, repository_metadata )
+ containers_dict = build_repository_containers_for_tool_shed( repository, changeset_revision, repository_dependencies, repository_metadata )
return trans.fill_template( '/webapps/community/repository/view_repository.mako',
cntrller=cntrller,
repo=repo,
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/webapps/community/util/container_util.py
--- a/lib/galaxy/webapps/community/util/container_util.py
+++ b/lib/galaxy/webapps/community/util/container_util.py
@@ -1,4 +1,4 @@
-import logging
+import os, logging
from galaxy.web import url_for
log = logging.getLogger( __name__ )
@@ -18,6 +18,7 @@
self.valid_tools = []
self.tool_dependencies = []
self.repository_dependencies = []
+ self.readme_files = []
self.workflows = []
def contains_folder( self, folder ):
for index, contained_folder in enumerate( self.folders ):
@@ -42,6 +43,13 @@
self.repository_id = repository_id
self.changeset_revision = changeset_revision
+class ReadMe( object ):
+ """Readme text object"""
+ def __init__( self, id=None, name=None, text=None ):
+ self.id = id
+ self.name = name
+ self.text = text
+
class RepositoryDependency( object ):
"""Repository dependency object"""
def __init__( self, id=None, toolshed=None, repository_name=None, repository_owner=None, changeset_revision=None ):
@@ -67,11 +75,13 @@
class ToolDependency( object ):
"""Tool dependency object"""
- def __init__( self, id=None, name=None, version=None, type=None ):
+ def __init__( self, id=None, name=None, version=None, type=None, install_dir=None, readme=None ):
self.id = id
self.name = name
self.version = version
self.type = type
+ self.install_dir = install_dir
+ self.readme = readme
class Workflow( object ):
"""Workflow object"""
@@ -111,8 +121,9 @@
else:
datatypes_root_folder = None
return folder_id, datatypes_root_folder
-def build_invalid_tools_folder( folder_id, invalid_tool_configs, repository, changeset_revision, label='Invalid tools' ):
+def build_invalid_tools_folder( folder_id, invalid_tool_configs, changeset_revision, repository=None, label='Invalid tools' ):
"""Return a folder hierarchy containing invalid tools."""
+ # TODO: Should we display invalid tools on the tool panel selection page when installing the repository into Galaxy?
if invalid_tool_configs:
invalid_tool_id = 0
folder_id += 1
@@ -122,15 +133,41 @@
invalid_tools_root_folder.folders.append( folder )
for invalid_tool_config in invalid_tool_configs:
invalid_tool_id += 1
+ if repository:
+ repository_id = repository.id
+ else:
+ repository_id = None
invalid_tool = InvalidTool( id=invalid_tool_id,
tool_config=invalid_tool_config,
- repository_id=repository.id,
+ repository_id=repository_id,
changeset_revision=changeset_revision )
folder.invalid_tools.append( invalid_tool )
else:
invalid_tools_root_folder = None
return folder_id, invalid_tools_root_folder
-def build_repository_dependencies_folder( repository, changeset_revision, folder_id, repository_dependencies, label='Repository dependencies' ):
+def build_readme_files_folder( folder_id, readme_files_dict, label='Readme files' ):
+ """Return a folder hierarchy containing readme text files."""
+ if readme_files_dict:
+ readme_id = 0
+ folder_id += 1
+ readme_files_root_folder = Folder( id=folder_id, key='root', label='root' )
+ folder_id += 1
+ readme_files_folder = Folder( id=folder_id, key='readme_files', label=label )
+ readme_files_root_folder.folders.append( readme_files_folder )
+ for readme_file_name, readme_file_text in readme_files_dict.items():
+ readme_id += 1
+ readme = ReadMe( id=readme_id,
+ name=readme_file_name,
+ text=readme_file_text )
+ folder_id += 1
+ folder = Folder( id=folder_id, key=readme.name, label=readme.name )
+ folder.readme_files.append( readme )
+ readme_files_folder.folders.append( folder )
+ else:
+ readme_files_root_folder = None
+ return folder_id, readme_files_root_folder
+def build_repository_dependencies_folder( toolshed_base_url, repository_name, repository_owner, changeset_revision, folder_id, repository_dependencies,
+ label='Repository dependencies' ):
"""Return a folder hierarchy containing repository dependencies."""
if repository_dependencies:
repository_dependency_id = 0
@@ -139,7 +176,7 @@
repository_dependencies_root_folder = Folder( id=folder_id, key='root', label='root' )
folder_id += 1
# Create the Repository dependencies folder and add it to the root folder.
- key = generate_repository_dependencies_key_for_repository( repository, changeset_revision )
+ key = generate_repository_dependencies_key_for_repository( toolshed_base_url, repository_name, repository_owner, changeset_revision )
repository_dependencies_folder = Folder( id=folder_id, key=key, label=label )
repository_dependencies_root_folder.folders.append( repository_dependencies_folder )
# Process the repository dependencies.
@@ -149,7 +186,7 @@
if not folder:
# Create a new folder.
folder_id += 1
- label = generate_repository_dependencies_folder_label_from_key( repository, changeset_revision, key )
+ label = generate_repository_dependencies_folder_label_from_key( repository_name, repository_owner, changeset_revision, key )
folder = Folder( id=folder_id, key=key, label=label )
for repository_dependency_tup in val:
toolshed, name, owner, changeset_revision = repository_dependency_tup
@@ -213,7 +250,7 @@
else:
tools_root_folder = None
return folder_id, tools_root_folder
-def build_tool_dependencies_folder( folder_id, tool_dependencies, label='Tool dependencies' ):
+def build_tool_dependencies_folder( folder_id, tool_dependencies, label='Tool dependencies', for_galaxy=False ):
"""Return a folder hierarchy containing tool dependencies."""
if tool_dependencies:
tool_dependency_id = 0
@@ -224,22 +261,40 @@
tool_dependencies_root_folder.folders.append( folder )
# Insert a header row.
tool_dependency_id += 1
- tool_dependency = ToolDependency( id=tool_dependency_id,
- name='Name',
- version='Version',
- type='Type' )
+ if for_galaxy:
+ # Include the installation directory.
+ tool_dependency = ToolDependency( id=tool_dependency_id,
+ name='Name',
+ version='Version',
+ type='Type',
+ install_dir='Install directory' )
+ else:
+ tool_dependency = ToolDependency( id=tool_dependency_id,
+ name='Name',
+ version='Version',
+ type='Type' )
folder.tool_dependencies.append( tool_dependency )
for dependency_key, requirements_dict in tool_dependencies.items():
tool_dependency_id += 1
if dependency_key == 'set_environment':
- version = None
+ for set_environment_dict in requirements_dict:
+ name = set_environment_dict[ 'name' ]
+ type = set_environment_dict[ 'type' ]
+ tool_dependency = ToolDependency( id=tool_dependency_id,
+ name=name,
+ type=type )
+ folder.tool_dependencies.append( tool_dependency )
else:
+ name = requirements_dict[ 'name' ]
version = requirements_dict[ 'version' ]
- tool_dependency = ToolDependency( id=tool_dependency_id,
- name=requirements_dict[ 'name' ],
- version=version,
- type=requirements_dict[ 'type' ] )
- folder.tool_dependencies.append( tool_dependency )
+ type = requirements_dict[ 'type' ]
+ install_dir = requirements_dict.get( 'install_dir', None )
+ tool_dependency = ToolDependency( id=tool_dependency_id,
+ name=name,
+ version=version,
+ type=type,
+ install_dir=install_dir )
+ folder.tool_dependencies.append( tool_dependency )
else:
tool_dependencies_root_folder = None
return folder_id, tool_dependencies_root_folder
@@ -279,18 +334,23 @@
else:
workflows_root_folder = None
return folder_id, workflows_root_folder
-def generate_repository_dependencies_folder_label_from_key( repository, changeset_revision, key ):
+def generate_repository_dependencies_folder_label_from_key( repository_name, repository_owner, changeset_revision, key ):
"""Return a repository dependency label based on the repository dependency key."""
- if key_is_current_repositorys_key( repository, changeset_revision, key ):
+ if key_is_current_repositorys_key( repository_name, repository_owner, changeset_revision, key ):
label = 'Repository dependencies'
else:
toolshed_base_url, name, owner, revision = get_components_from_key( key )
label = "Repository <b>%s</b> revision <b>%s</b> owned by <b>%s</b>" % ( name, revision, owner )
return label
-def generate_repository_dependencies_key_for_repository( repository, changeset_revision ):
+def generate_repository_dependencies_key_for_repository( toolshed_base_url, repository_name, repository_owner, changeset_revision ):
# FIXME: assumes tool shed is current tool shed since repository dependencies across tool sheds is not yet supported.
- toolshed_base_url = str( url_for( '/', qualified=True ) ).rstrip( '/' )
- return '%s%s%s%s%s%s%s' % ( toolshed_base_url, STRSEP, repository.name, STRSEP, repository.user.username, STRSEP, changeset_revision )
+ return '%s%s%s%s%s%s%s' % ( str( toolshed_base_url ).rstrip( '/' ),
+ STRSEP,
+ str( repository_name ),
+ STRSEP,
+ str( repository_owner ),
+ STRSEP,
+ str( changeset_revision ) )
def get_folder( folder, key ):
if folder and folder.key == key:
return folder
@@ -308,7 +368,7 @@
def is_folder( folder_keys, toolshed_base_url, repository_name, repository_owner, changeset_revision ):
key = '%s%s%s%s%s%s%s' % ( toolshed_base_url, STRSEP, repository_name, STRSEP, repository_owner, STRSEP, changeset_revision )
return key in folder_keys
-def key_is_current_repositorys_key( repository, changeset_revision, key ):
- toolshed_base_url, repository_name, repository_owner, changeset_revision = get_components_from_key( key )
- return repository_name == repository.name and repository_owner == repository.user.username and repository_changeset_revision == changeset_revision
+def key_is_current_repositorys_key( repository_name, repository_owner, changeset_revision, key ):
+ toolshed_base_url, key_name, key_owner, key_changeset_revision = get_components_from_key( key )
+ return repository_name == key_name and repository_owner == key_owner and changeset_revision == key_changeset_revision
\ No newline at end of file
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
--- a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
+++ b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
@@ -2,7 +2,6 @@
from admin import *
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.shed_util import *
-from galaxy.util.shed_util_common import *
from galaxy.tool_shed.encoding_util import *
from galaxy import eggs, tools
@@ -961,12 +960,6 @@
url_args=dict( controller='admin_toolshed',
action='deactivate_or_uninstall_repository',
id=trans.security.encode_id( tool_shed_repository.id ) ) ) ]
- if tool_shed_repository.metadata and 'readme' in tool_shed_repository.metadata:
- view_readme_action = grids.GridAction( label='View README',
- url_args=dict( controller='admin_toolshed',
- action='view_readme',
- id=trans.security.encode_id( tool_shed_repository.id ) ) )
- self.tool_dependency_grid.global_actions.insert( 1, view_readme_action )
if 'operation' in kwd:
operation = kwd[ 'operation' ].lower()
if not tool_dependency_ids:
@@ -1050,6 +1043,9 @@
status = kwd.get( 'status', 'done' )
includes_tools = util.string_as_bool( kwd.get( 'includes_tools', False ) )
tool_shed_url = kwd[ 'tool_shed_url' ]
+ # Handle repository dependencies.
+ includes_repository_dependencies = util.string_as_bool( kwd.get( 'includes_repository_dependencies', False ) )
+ install_repository_dependencies = kwd.get( 'install_repository_dependencies', '' )
# Every repository will be installed into the same tool panel section or all will be installed outside of any sections.
new_tool_panel_section = kwd.get( 'new_tool_panel_section', '' )
tool_panel_section = kwd.get( 'tool_panel_section', '' )
@@ -1071,10 +1067,13 @@
response.close()
repo_information_dict = from_json_string( raw_text )
includes_tools = util.string_as_bool( repo_information_dict[ 'includes_tools' ] )
+ includes_repository_dependencies = util.string_as_bool( repo_information_dict[ 'includes_repository_dependencies' ] )
includes_tool_dependencies = util.string_as_bool( repo_information_dict[ 'includes_tool_dependencies' ] )
encoded_repo_info_dicts = util.listify( repo_information_dict[ 'repo_info_dicts' ] )
repo_info_dicts = [ tool_shed_decode( encoded_repo_info_dict ) for encoded_repo_info_dict in encoded_repo_info_dicts ]
- if not includes_tools or ( includes_tools and kwd.get( 'select_tool_panel_section_button', False ) ):
+ if ( not includes_tools and not includes_repository_dependencies ) or \
+ ( ( includes_tools or includes_repository_dependencies ) and kwd.get( 'select_tool_panel_section_button', False ) ):
+ install_repository_dependencies = CheckboxField.is_checked( install_repository_dependencies )
if includes_tools:
install_tool_dependencies = CheckboxField.is_checked( install_tool_dependencies )
shed_tool_conf = kwd[ 'shed_tool_conf' ]
@@ -1100,10 +1099,9 @@
filtered_repo_info_dicts = []
for repo_info_dict in repo_info_dicts:
for name, repo_info_tuple in repo_info_dict.items():
- description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, tool_dependencies = repo_info_tuple
+ description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies = repo_info_tuple
clone_dir = os.path.join( tool_path, self.generate_tool_path( repository_clone_url, changeset_revision ) )
relative_install_dir = os.path.join( clone_dir, name )
- owner = get_repository_owner( clean_repository_clone_url( repository_clone_url ) )
# Make sure the repository was not already installed.
installed_tool_shed_repository, installed_changeset_revision = self.repository_was_previously_installed( trans,
tool_shed_url,
@@ -1111,7 +1109,7 @@
repo_info_tuple,
clone_dir )
if installed_tool_shed_repository:
- message += "The tool shed repository <b>%s</b> with owner <b>%s</b> and changeset revision <b>%s</b> " % ( name, owner, changeset_revision )
+ message += "Revision <b>%s</b> of tool shed repository <b>%s</b> owned by <b>%s</b> " % ( changeset_revision, name, repository_owner )
if installed_changeset_revision != changeset_revision:
message += "was previously installed using changeset revision <b>%s</b>. " % installed_changeset_revision
else:
@@ -1123,7 +1121,8 @@
if installed_changeset_revision != changeset_revision:
message += "You can get the latest updates for the repository using the <b>Get updates</b> option from the repository's "
message += "<b>Repository Actions</b> pop-up menu. "
- message+= 'Click <a href="%s">here</a> to manage the repository. ' % ( web.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( installed_tool_shed_repository.id ) ) )
+ message+= 'Click <a href="%s">here</a> to manage the repository. ' % \
+ ( web.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( installed_tool_shed_repository.id ) ) )
status = 'error'
if len( repo_info_dicts ) == 1:
new_kwd = dict( message=message, status=status )
@@ -1131,7 +1130,7 @@
action='browse_repositories',
**new_kwd ) )
else:
- print "Adding new row (or updating an existing row) for repository '%s' in the tool_shed_repository table." % name
+ log.debug( "Adding new row (or updating an existing row) for repository '%s' in the tool_shed_repository table." % name )
tool_shed_repository = create_or_update_tool_shed_repository( app=trans.app,
name=name,
description=description,
@@ -1141,7 +1140,7 @@
metadata_dict={},
status=trans.model.ToolShedRepository.installation_status.NEW,
current_changeset_revision=changeset_revision,
- owner=owner,
+ owner=repository_owner,
dist_to_shed=False )
created_or_updated_tool_shed_repositories.append( tool_shed_repository )
filtered_repo_info_dicts.append( tool_shed_encode( repo_info_dict ) )
@@ -1171,6 +1170,7 @@
tool_section = None
tsrids_list = [ trans.security.encode_id( tsr.id ) for tsr in created_or_updated_tool_shed_repositories ]
new_kwd = dict( includes_tools=includes_tools,
+ includes_repository_dependencies=includes_repository_dependencies,
includes_tool_dependencies=includes_tool_dependencies,
install_tool_dependencies=install_tool_dependencies,
message=message,
@@ -1203,42 +1203,53 @@
shed_tool_conf = shed_tool_conf.replace( './', '', 1 )
shed_tool_conf_select_field = None
tool_panel_section_select_field = build_tool_panel_section_select_field( trans )
- if includes_tools and len( repo_info_dicts ) == 1:
- # If we're installing a single repository, see if it contains a readme file tha twe can display.
+ if len( repo_info_dicts ) == 1:
+ # If we're installing a single repository, see if it contains a readme or dependencies that we can display.
repo_info_dict = repo_info_dicts[ 0 ]
name = repo_info_dict.keys()[ 0 ]
repo_info_tuple = repo_info_dict[ name ]
- description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, tool_dependencies = repo_info_tuple
+ description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies = repo_info_tuple
url = url_join( tool_shed_url,
- 'repository/get_readme?name=%s&owner=%s&changeset_revision=%s' % \
+ 'repository/get_readme_files?name=%s&owner=%s&changeset_revision=%s' % \
( name, repository_owner, changeset_revision ) )
response = urllib2.urlopen( url )
raw_text = response.read()
response.close()
- readme_text = raw_text
+ readme_files_dict = from_json_string( raw_text )
+ containers_dict = build_repository_containers_for_galaxy( trans=trans,
+ toolshed_base_url=tool_shed_url,
+ repository_name=name,
+ repository_owner=repository_owner,
+ changeset_revision=changeset_revision,
+ readme_files_dict=readme_files_dict,
+ repository_dependencies=repository_dependencies,
+ tool_dependencies=tool_dependencies )
else:
- readme_text = ''
+ containers_dict = dict( readme_files_dict=None, repository_dependencies=None, tool_dependencies=None )
+ # Handle tool dependencies chack box.
if trans.app.config.tool_dependency_dir is None:
if includes_tool_dependencies:
- message = "Tool dependencies defined in this repository can be automatically installed if you set the value of your <b>tool_dependency_dir</b>"
- message += " setting in your Galaxy config file (universe_wsgi.ini) and restart your Galaxy server before installing the repository."
+ message = "Tool dependencies defined in this repository can be automatically installed if you set the value of your <b>tool_dependency_dir</b> "
+ message += "setting in your Galaxy config file (universe_wsgi.ini) and restart your Galaxy server before installing the repository."
status = "warning"
- checked = False
+ install_tool_dependencies_check_box_checked = False
else:
- checked = True
- install_tool_dependencies_check_box = CheckboxField( 'install_tool_dependencies', checked=checked )
+ install_tool_dependencies_check_box_checked = True
+ install_tool_dependencies_check_box = CheckboxField( 'install_tool_dependencies', checked=install_tool_dependencies_check_box_checked )
+ # Handle repository dependencies check box.
+ install_repository_dependencies_check_box = CheckboxField( 'install_repository_dependencies', checked=True )
return trans.fill_template( '/admin/tool_shed_repository/select_tool_panel_section.mako',
encoded_repo_info_dicts=encoded_repo_info_dicts,
includes_tools=includes_tools,
includes_tool_dependencies=includes_tool_dependencies,
+ install_repository_dependencies_check_box=install_repository_dependencies_check_box,
install_tool_dependencies_check_box=install_tool_dependencies_check_box,
new_tool_panel_section=new_tool_panel_section,
- repo_info_dicts=repo_info_dicts,
+ containers_dict=containers_dict,
shed_tool_conf=shed_tool_conf,
shed_tool_conf_select_field=shed_tool_conf_select_field,
tool_panel_section_select_field=tool_panel_section_select_field,
tool_shed_url=kwd[ 'tool_shed_url' ],
- readme_text=readme_text,
message=message,
status=status )
@web.expose
@@ -1269,8 +1280,8 @@
tool_panel_dict = generate_tool_panel_dict_for_new_install( metadata[ 'tools' ] )
else:
tool_panel_dict = generate_tool_panel_dict_for_new_install( metadata[ 'tools' ] )
- # TODO: Fix this to handle the case where the tools are distributed across in more than 1 ToolSection. The
- # following assumes everything was loaded into 1 section (or no section) in the tool panel.
+ # Fix this to handle the case where the tools are distributed across in more than 1 ToolSection - this assumes everything was loaded into 1
+ # section (or no section) in the tool panel.
tool_section_dicts = tool_panel_dict[ tool_panel_dict.keys()[ 0 ] ]
tool_section_dict = tool_section_dicts[ 0 ]
original_section_id = tool_section_dict[ 'id' ]
@@ -1328,12 +1339,16 @@
# The repo_info_dict should be encoded.
if not repo_info_dict:
# This should only happen if the tool_shed_repository does not include any valid tools.
- repo_info_dict = create_repo_info_dict( tool_shed_repository,
- tool_shed_repository.owner,
- repository_clone_url,
- tool_shed_repository.installed_changeset_revision,
- ctx_rev,
- metadata )
+ repo = hg.repository( get_configured_ui(), path=os.path.abspath( tool_shed_repository.repo_path( trans.app ) ) )
+ repo_info_dict = create_repo_info_dict( trans=trans,
+ repo=repo,
+ repository_clone_url=repository_clone_url,
+ changeset_revision=tool_shed_repository.installed_changeset_revision,
+ ctx_rev=ctx_rev,
+ repository_owner=tool_shed_repository.owner,
+ repository_name=tool_shed_repository.name,
+ repository=None,
+ repository_metadata=None )
repo_info_dict = tool_shed_encode( repo_info_dict )
new_kwd = dict( includes_tool_dependencies=tool_shed_repository.includes_tool_dependencies,
includes_tools=tool_shed_repository.includes_tools,
@@ -1410,12 +1425,16 @@
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
ctx_rev = get_ctx_rev( tool_shed_url, repository.name, repository.owner, repository.installed_changeset_revision )
repository_clone_url = generate_clone_url_for_installed_repository( trans, repository )
- repo_info_dict = create_repo_info_dict( repository,
- repository.owner,
- repository_clone_url,
- repository.installed_changeset_revision,
- ctx_rev,
- metadata )
+ repo = hg.repository( get_configured_ui(), path=os.path.abspath( tool_shed_repository.repo_path( trans.app ) ) )
+ repo_info_dict = create_repo_info_dict( trans=trans,
+ repo=repo,
+ repository_clone_url=repository_clone_url,
+ changeset_revision=repository.installed_changeset_revision,
+ ctx_rev=ctx_rev,
+ repository_owner=repository.owner,
+ repository_name=repository.name,
+ repository=None,
+ repository_metadata=None )
# Get the location in the tool panel in which the tool was originally loaded.
if 'tool_panel_section' in metadata:
tool_panel_dict = metadata[ 'tool_panel_section' ]
@@ -1444,28 +1463,46 @@
message = "The tools contained in your <b>%s</b> repository were last loaded into the tool panel outside of any sections. " % repository.name
message += "Uncheck the <b>No changes</b> check box and select a tool panel section to load the tools into that section."
status = 'warning'
- includes_tool_dependencies = 'tool_dependencies' in metadata
- install_tool_dependencies_check_box = CheckboxField( 'install_tool_dependencies', checked=True )
- if metadata and 'readme' in metadata:
+ if metadata and 'readme_files' in metadata:
url = url_join( tool_shed_url,
- 'repository/get_readme?name=%s&owner=%s&changeset_revision=%s' % \
+ 'repository/get_readme_files?name=%s&owner=%s&changeset_revision=%s' % \
( repository.name, repository.owner, repository.installed_changeset_revision ) )
response = urllib2.urlopen( url )
raw_text = response.read()
response.close()
- readme_text = raw_text
+ readme_files_dict = from_json_string( raw_text )
+ containers_dict = build_repository_containers_for_galaxy( trans=trans,
+ toolshed_base_url=tool_shed_url,
+ repository_name=name,
+ repository_owner=repository_owner,
+ changeset_revision=changeset_revision,
+ readme_files_dict=readme_files_dict,
+ repository_dependencies=repository_dependencies,
+ tool_dependencies=tool_dependencies )
else:
- readme_text = ''
+ containers_dict = dict( readme_files_dict=None, repository_dependencies=None, tool_dependencies=None )
+ # Handle repository dependencies check box.
+ install_repository_dependencies_check_box = CheckboxField( 'install_repository_dependencies', checked=True )
+ # Handle tool dependencies check box.
+ if trans.app.config.tool_dependency_dir is None:
+ if includes_tool_dependencies:
+ message = "Tool dependencies defined in this repository can be automatically installed if you set the value of your <b>tool_dependency_dir</b> "
+ message += "setting in your Galaxy config file (universe_wsgi.ini) and restart your Galaxy server before installing the repository."
+ status = "warning"
+ install_tool_dependencies_check_box_checked = False
+ else:
+ install_tool_dependencies_check_box_checked = True
+ install_tool_dependencies_check_box = CheckboxField( 'install_tool_dependencies', checked=install_tool_dependencies_check_box_checked )
return trans.fill_template( '/admin/tool_shed_repository/reselect_tool_panel_section.mako',
repository=repository,
- readme_text=readme_text,
no_changes_check_box=no_changes_check_box,
original_section_name=original_section_name,
+ install_repository_dependencies_check_box=install_repository_dependencies_check_box,
install_tool_dependencies_check_box=install_tool_dependencies_check_box,
+ containers_dict=containers_dict,
tool_panel_section_select_field=tool_panel_section_select_field,
encoded_repo_info_dict=tool_shed_encode( repo_info_dict ),
repo_info_dict=repo_info_dict,
- includes_tool_dependencies=includes_tool_dependencies,
message=message,
status=status )
@web.expose
@@ -1714,41 +1751,6 @@
status=status ) )
@web.expose
@web.require_admin
- def view_readme( self, trans, id, **kwd ):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- cntrller = params.get( 'cntrller', 'admin_toolshed' )
- status = params.get( 'status', 'done' )
- repository = get_installed_tool_shed_repository( trans, id )
- metadata = repository.metadata
- shed_config_dict = repository.get_shed_config_dict( trans.app )
- tool_path = shed_config_dict.get( 'tool_path', None )
- if metadata and 'readme' in metadata:
- readme_filename = metadata[ 'readme' ]
- if tool_path:
- readme_filename = os.path.join( tool_path, readme_filename )
- try:
- f = open( readme_filename, 'r' )
- raw_text = f.read()
- f.close()
- readme_text = raw_text
- except Exception, e:
- log.debug( "Error attempting to read README file '%s' defined in metadata for repository '%s', revision '%s': %s" % \
- ( str( readme_filename ), str( repository.name ), str( repository.changeset_revision ), str( e ) ) )
- readme_text = ''
- else:
- readme_text = ''
- is_malicious = False
- return trans.fill_template( '/webapps/community/common/view_readme.mako',
- cntrller=cntrller,
- repository=repository,
- changeset_revision=repository.changeset_revision,
- readme_text=readme_text,
- is_malicious=is_malicious,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
def view_tool_metadata( self, trans, repository_id, tool_id, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/browse_repository.mako
--- a/templates/admin/tool_shed_repository/browse_repository.mako
+++ b/templates/admin/tool_shed_repository/browse_repository.mako
@@ -18,9 +18,6 @@
<li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li><div popupmenu="repository-${repository.id}-popup"><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ) )}">Manage repository</a>
- %if repository.has_readme:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='view_readme', id=trans.security.encode_id( repository.id ) )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
%if repository.can_reset_metadata:
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='reset_repository_metadata', id=trans.security.encode_id( repository.id ) )}">Reset repository metadata</a>
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/browse_tool_dependency.mako
--- a/templates/admin/tool_shed_repository/browse_tool_dependency.mako
+++ b/templates/admin/tool_shed_repository/browse_tool_dependency.mako
@@ -20,9 +20,6 @@
<li><a class="action-button" id="tool_dependency-${tool_dependency.id}-popup" class="menubutton">Repository Actions</a></li><div popupmenu="tool_dependency-${tool_dependency.id}-popup"><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ) )}">Manage repository</a>
- %if repository.has_readme:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='view_readme', id=trans.security.encode_id( repository.id ) )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">Browse repository files</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
%if repository.can_reset_metadata:
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/common.mako
--- a/templates/admin/tool_shed_repository/common.mako
+++ b/templates/admin/tool_shed_repository/common.mako
@@ -1,3 +1,5 @@
+<%namespace file="/webapps/community/repository/common.mako" import="*" />
+
<%def name="browse_files(title_text, directory_path)"><script type="text/javascript">
$(function(){
@@ -67,146 +69,107 @@
</script></%def>
-<%def name="render_tool_dependency_section( install_tool_dependencies_check_box, repo_info_dicts )">
- <% import os %>
+<%def name="render_dependencies_section( install_repository_dependencies_check_box, install_tool_dependencies_check_box, containers_dict )">
+ <style type="text/css">
+ #dependency_table{ table-layout:fixed;
+ width:100%;
+ overflow-wrap:normal;
+ overflow:hidden;
+ border:0px;
+ word-break:keep-all;
+ word-wrap:break-word;
+ line-break:strict; }
+ </style>
+ <%
+ class RowCounter( object ):
+ def __init__( self ):
+ self.count = 0
+ def increment( self ):
+ self.count += 1
+ def __str__( self ):
+ return str( self.count )
+
+ repository_dependencies_root_folder = containers_dict[ 'repository_dependencies' ]
+ tool_dependencies_root_folder = containers_dict[ 'tool_dependencies' ]
+ env_settings_heaader_row_displayed = False
+ package_header_row_displayed = False
+ %><div class="form-row"><div class="toolParamHelp" style="clear: both;"><p>
- These tool dependencies can be automatically handled with the installed repository, providing significant benefits, and
+ These dependencies can be automatically handled with the installed repository, providing significant benefits, and
Galaxy includes various features to manage them.
</p></div></div>
- <div class="form-row">
- <label>Handle tool dependencies?</label>
- <% disabled = trans.app.config.tool_dependency_dir is None %>
- ${install_tool_dependencies_check_box.get_html( disabled=disabled )}
- <div class="toolParamHelp" style="clear: both;">
- %if disabled:
- Set the tool_dependency_dir configuration value in your universe_wsgi.ini to automatically handle tool dependencies.
- %else:
- Un-check to skip automatic handling of these tool dependencies.
+ %if repository_dependencies_root_folder:
+ <div class="form-row">
+ <label>Handle repository dependencies?</label>
+ ${install_repository_dependencies_check_box.get_html()}
+ <div class="toolParamHelp" style="clear: both;">
+ Un-check to skip automatic installation of these additional repositories required by this repository.
+ </div>
+ </div>
+ <div style="clear: both"></div>
+ <div class="form-row">
+ %if repository_dependencies_root_folder:
+ <p/>
+ <% row_counter = RowCounter() %>
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table">
+ ${render_folder( repository_dependencies_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ </table>
%endif
+ <div style="clear: both"></div></div>
- </div>
- <div style="clear: both"></div>
- <div class="form-row">
- <style type="text/css">
- #dependency_table{ table-layout:fixed;
- width:100%;
- overflow-wrap:normal;
- overflow:hidden;
- border:0px;
- word-break:keep-all;
- word-wrap:break-word;
- line-break:strict; }
- </style>
- <table class="grid" id="dependency_table">
- <tr><td colspan="4" bgcolor="#D8D8D8"><b>Tool dependencies</b></td></tr>
- <%
- env_settings_heaader_row_displayed = False
- package_header_row_displayed = False
- %>
- %for repo_info_dict in repo_info_dicts:
- %for repository_name, repo_info_tuple in repo_info_dict.items():
- <% description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, tool_dependencies = repo_info_tuple %>
- %if tool_dependencies:
- <%
- # See if tool dependencies are packages, environment settings or both.
- contains_packages = False
- for k in tool_dependencies.keys():
- if k != 'set_environment':
- contains_packages = True
- break
- contains_env_settings = 'set_environment' in tool_dependencies.keys()
- %>
- %if contains_packages:
- %if not package_header_row_displayed:
- <%
- info_str = "Each of these packages may require their own build requirements (e.g., CMake, g++, etc). "
- info_str += "Galaxy will not attempt to install these build requirements, so tool dependency installation "
- info_str += "may partially fail if any are missing from your environment, but the repository and all of it's "
- info_str += "contents will be installed. You can install the missing build requirements and have Galaxy attempt "
- info_str += "to install the packages again if tool dependency installation fails in any way."
- %>
- </tr><td bgcolor="#FFFFCC" colspan="4">${info_str}</td></tr>
- <tr>
- <th>Name</th>
- <th>Version</th>
- <th>Type</th>
- <th>Install directory</th>
- </tr>
- <% package_header_row_displayed = True %>
- %endif
- %for dependency_key, requirements_dict in tool_dependencies.items():
- %if dependency_key not in [ 'set_environment' ]:
- <%
- name = requirements_dict[ 'name' ]
- version = requirements_dict[ 'version' ]
- type = requirements_dict[ 'type' ]
- install_dir = os.path.join( trans.app.config.tool_dependency_dir,
- name,
- version,
- repository_owner,
- repository_name,
- changeset_revision )
- tool_dependency_readme_text = requirements_dict.get( 'readme', None )
- %>
- %if not os.path.exists( install_dir ):
- <tr>
- <td>${name}</td>
- <td>${version}</td>
- <td>${type}</td>
- <td>${install_dir}</td>
- </tr>
- %if tool_dependency_readme_text:
- <tr><td colspan="4" bgcolor="#FFFFCC">${name} ${version} requirements and installation information</td></tr>
- <tr><td colspan="4"><pre>${tool_dependency_readme_text}</pre></td></tr>
- %endif
- %endif
- %endif
- %endfor
- %endif
- %if contains_env_settings:
- <% environment_settings = tool_dependencies[ 'set_environment' ] %>
- %if not env_settings_heaader_row_displayed:
- <%
- info_str = "Each of these environment settings will be defined in a file named env.sh in the installation directory."
- %>
- </tr><td bgcolor="#FFFFCC" colspan="4">${info_str}</td></tr>
- <tr>
- <th>Name</th>
- <th>Type</th>
- <th colspan="2">Install directory for environment shell file</th>
- </tr>
- <% env_settings_heaader_row_displayed = True %>
- %endif
- %for environment_setting_dict in environment_settings:
- <%
- name = environment_setting_dict[ 'name' ]
- type = environment_setting_dict[ 'type' ]
- install_dir = os.path.join( trans.app.config.tool_dependency_dir,
- 'environment_settings',
- name,
- repository_owner,
- repository_name,
- changeset_revision )
- %>
- %if not os.path.exists( install_dir ):
- <tr>
- <td>${name}</td>
- <td>${type}</td>
- <td colspan="2">${install_dir}</td>
- </tr>
- %endif
- %endfor
- %endif
- %endif
- %endfor
- %endfor
- </table>
+ %endif
+ %if tool_dependencies_root_folder:
+ <div class="form-row">
+ <label>Handle tool dependencies?</label>
+ <% disabled = trans.app.config.tool_dependency_dir is None %>
+ ${install_tool_dependencies_check_box.get_html( disabled=disabled )}
+ <div class="toolParamHelp" style="clear: both;">
+ %if disabled:
+ Set the tool_dependency_dir configuration value in your Galaxy config to automatically handle tool dependencies.
+ %else:
+ Un-check to skip automatic handling of these tool dependencies.
+ %endif
+ </div>
+ </div><div style="clear: both"></div>
- </div>
+ <div class="form-row">
+ %if tool_dependencies_root_folder:
+ <p/>
+ <% row_counter = RowCounter() %>
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="dependency_table">
+ ${render_folder( tool_dependencies_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ </table>
+ %endif
+ <div style="clear: both"></div>
+ </div>
+ %endif
+</%def>
+
+<%def name="render_readme_section( containers_dict )">
+ <%
+ class RowCounter( object ):
+ def __init__( self ):
+ self.count = 0
+ def increment( self ):
+ self.count += 1
+ def __str__( self ):
+ return str( self.count )
+
+ readme_files_root_folder = containers_dict[ 'readme_files' ]
+ %>
+ %if readme_files_root_folder:
+ <p/>
+ <div class="form-row">
+ <% row_counter = RowCounter() %>
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table">
+ ${render_folder( readme_files_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ </table>
+ </div>
+ %endif
</%def><%def name="dependency_status_updater()">
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/deactivate_or_uninstall_repository.mako
--- a/templates/admin/tool_shed_repository/deactivate_or_uninstall_repository.mako
+++ b/templates/admin/tool_shed_repository/deactivate_or_uninstall_repository.mako
@@ -7,9 +7,6 @@
<li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li><div popupmenu="repository-${repository.id}-popup"><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ) )}">Manage repository</a>
- %if repository.has_readme:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='view_readme', id=trans.security.encode_id( repository.id ) )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">Browse repository files</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
%if repository.can_reset_metadata:
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/manage_repository.mako
--- a/templates/admin/tool_shed_repository/manage_repository.mako
+++ b/templates/admin/tool_shed_repository/manage_repository.mako
@@ -14,9 +14,6 @@
%elif repository.can_install:
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ), operation='install' )}">Install</a>
%elif repository.can_uninstall:
- %if repository.has_readme:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='view_readme', id=trans.security.encode_id( repository.id ) )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">Browse repository files</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
%if repository.can_reset_metadata:
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/reselect_tool_panel_section.mako
--- a/templates/admin/tool_shed_repository/reselect_tool_panel_section.mako
+++ b/templates/admin/tool_shed_repository/reselect_tool_panel_section.mako
@@ -1,19 +1,33 @@
<%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" />
-<%namespace file="/admin/tool_shed_repository/common.mako" import="render_tool_dependency_section" />
-<%namespace file="/webapps/community/common/common.mako" import="render_readme" />
+<%namespace file="/admin/tool_shed_repository/common.mako" import="render_dependencies_section" />
+<%namespace file="/admin/tool_shed_repository/common.mako" import="render_readme_section" />
%if message:
${render_msg( message, status )}
%endif
<div class="toolForm">
- <div class="toolFormTitle">Choose the tool panel section to contain the installed tools (optional)</div><div class="toolFormBody"><form name="reselect_tool_panel_section" id="reselect_tool_panel_section" action="${h.url_for( controller='admin_toolshed', action='reinstall_repository', id=trans.security.encode_id( repository.id ), repo_info_dict=encoded_repo_info_dict )}" method="post" ><div style="clear: both"></div>
- %if includes_tool_dependencies:
- ${render_tool_dependency_section( install_tool_dependencies_check_box, [ repo_info_dict ] )}
+ <% readme_files_dict = containers_dict[ 'readme_files' ] %>
+ %if readme_files_dict:
+ <div class="form-row">
+ <table class="colored" width="100%">
+ <th bgcolor="#EBD9B2">Repository README file (may contain important installation or license information)</th>
+ </table>
+ </div>
+ ${render_readme_section( containers_dict )}
+ <div style="clear: both"></div>
+ %endif
+ %if includes_repository_dependencies or includes_tool_dependencies:
+ <div class="form-row">
+ <table class="colored" width="100%">
+ <th bgcolor="#EBD9B2">Confirm dependency installation</th>
+ </table>
+ </div>
+ ${render_dependencies_section( install_repository_dependencies_check_box, install_tool_dependencies_check_box, containers_dict )}
%endif
<div style="clear: both"></div><div class="form-row">
@@ -43,6 +57,3 @@
</form></div></div>
-%if readme_text:
- ${render_readme( readme_text )}
-%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/select_tool_panel_section.mako
--- a/templates/admin/tool_shed_repository/select_tool_panel_section.mako
+++ b/templates/admin/tool_shed_repository/select_tool_panel_section.mako
@@ -1,7 +1,19 @@
<%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" />
-<%namespace file="/admin/tool_shed_repository/common.mako" import="render_tool_dependency_section" />
-<%namespace file="/webapps/community/common/common.mako" import="render_readme" />
+<%namespace file="/admin/tool_shed_repository/common.mako" import="render_dependencies_section" />
+<%namespace file="/admin/tool_shed_repository/common.mako" import="render_readme_section" />
+<%namespace file="/webapps/community/repository/common.mako" import="*" />
+
+<%def name="stylesheets()">
+ ${parent.stylesheets()}
+ ${h.css( "library" )}
+</%def>
+
+<%def name="javascripts()">
+ ${parent.javascripts()}
+ ${h.js("libs/jquery/jquery.rating", "libs/jquery/jstorage" )}
+ ${container_javascripts()}
+</%def>
%if message:
${render_msg( message, status )}
@@ -21,26 +33,34 @@
general questions or concerns.
</p></div>
-<br/>
-
<div class="toolForm">
- %if includes_tool_dependencies and trans.app.config.use_tool_dependencies:
- <div class="toolFormTitle">Confirm tool dependency installation</div>
- %else:
- <div class="toolFormTitle">Choose the tool panel section to contain the installed tools (optional)</div>
- %endif
<div class="toolFormBody"><form name="select_tool_panel_section" id="select_tool_panel_section" action="${h.url_for( controller='admin_toolshed', action='prepare_for_install', tool_shed_url=tool_shed_url, encoded_repo_info_dicts=encoded_repo_info_dicts, includes_tools=includes_tools, includes_tool_dependencies=includes_tool_dependencies )}" method="post" ><div style="clear: both"></div>
- %if includes_tool_dependencies and trans.app.config.use_tool_dependencies:
- ${render_tool_dependency_section( install_tool_dependencies_check_box, repo_info_dicts )}
- <div style="clear: both"></div>
+ <% readme_files_dict = containers_dict[ 'readme_files' ] %>
+ %if readme_files_dict:
<div class="form-row"><table class="colored" width="100%">
- <th bgcolor="#EBD9B2">Choose the tool panel section to contain the installed tools (optional)</th>
+ <th bgcolor="#EBD9B2">Repository README file (may contain important installation or license information)</th></table></div>
+ ${render_readme_section( containers_dict )}
+ <div style="clear: both"></div>
%endif
+ %if includes_repository_dependencies or ( includes_tool_dependencies and trans.app.config.use_tool_dependencies ):
+ <div class="form-row">
+ <table class="colored" width="100%">
+ <th bgcolor="#EBD9B2">Confirm dependency installation</th>
+ </table>
+ </div>
+ ${render_dependencies_section( install_repository_dependencies_check_box, install_tool_dependencies_check_box, containers_dict )}
+ <div style="clear: both"></div>
+ %endif
+ <div class="form-row">
+ <table class="colored" width="100%">
+ <th bgcolor="#EBD9B2">Choose the tool panel section to contain the installed tools (optional)</th>
+ </table>
+ </div>
%if shed_tool_conf_select_field:
<div class="form-row"><label>Shed tool configuration file:</label>
@@ -77,6 +97,3 @@
</form></div></div>
-%if readme_text:
- ${render_readme( readme_text )}
-%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/uninstall_tool_dependencies.mako
--- a/templates/admin/tool_shed_repository/uninstall_tool_dependencies.mako
+++ b/templates/admin/tool_shed_repository/uninstall_tool_dependencies.mako
@@ -7,9 +7,6 @@
<ul class="manage-table-actions"><li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li><div popupmenu="repository-${repository.id}-popup">
- %if repository.has_readme:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='view_readme', id=trans.security.encode_id( repository.id ) )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">Browse repository files</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ) )}">Manage repository</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/admin/tool_shed_repository/view_tool_metadata.mako
--- a/templates/admin/tool_shed_repository/view_tool_metadata.mako
+++ b/templates/admin/tool_shed_repository/view_tool_metadata.mako
@@ -5,9 +5,6 @@
<ul class="manage-table-actions"><li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li><div popupmenu="repository-${repository.id}-popup">
- %if repository_metadata and 'readme' in repository_metadata:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='view_readme', id=trans.security.encode_id( repository.id ) )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='admin_toolshed', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">Browse repository files</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ) )}">Manage repository</a><a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/common/common.mako
--- a/templates/webapps/community/common/common.mako
+++ b/templates/webapps/community/common/common.mako
@@ -57,29 +57,6 @@
${html}
</%def>
-<%def name="render_readme( readme_text )">
- <style type="text/css">
- #readme_table{ table-layout:fixed;
- width:100%;
- overflow-wrap:normal;
- overflow:hidden;
- border:0px;
- word-break:keep-all;
- word-wrap:break-word;
- line-break:strict; }
- </style>
- <div class="toolForm">
- <div class="toolFormTitle">Repository README file (may contain important installation or license information)</div>
- <div class="toolFormBody">
- <div class="form-row">
- <table id="readme_table">
- <tr><td>${ escape_html_add_breaks( readme_text ) }</td></tr>
- </table>
- </div>
- </div>
- </div>
-</%def>
-
<%def name="render_long_description( description_text )"><style type="text/css">
#description_table{ table-layout:fixed;
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/common/view_readme.mako
--- a/templates/webapps/community/common/view_readme.mako
+++ /dev/null
@@ -1,88 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-<%namespace file="/webapps/community/common/common.mako" import="render_readme" />
-
-<%
- if trans.webapp.name == 'community':
- is_admin = trans.user_is_admin()
- is_new = repository.is_new( trans.app )
- can_contact_owner = trans.user and trans.user != repository.user
- can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository )
- can_rate = not is_new and trans.user and repository.user != trans.user
- can_upload = can_push
- can_download = not is_new and ( not is_malicious or can_push )
- can_browse_contents = not is_new
- can_view_change_log = not is_new
- can_manage = is_admin or repository.user == trans.user
- if can_push:
- browse_label = 'Browse or delete repository tip files'
- else:
- browse_label = 'Browse repository tip files'
-%>
-
-<br/><br/>
-<ul class="manage-table-actions">
- %if trans.webapp.name == 'community':
- <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li>
- <div popupmenu="repository-${repository.id}-popup">
- %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( trans.app ) )}">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( trans.app ) )}">View repository</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
- %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 ) )}">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 ) )}">${browse_label | h}</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 ) )}">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>
- %else:
- %if cntrller=='repository':
- <li><a class="action-button" href="${h.url_for( controller='repository', action='browse_valid_repositories', operation='preview_tools_in_changeset', id=trans.security.encode_id( repository.id ) )}">Preview tools for install</a></li>
- <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Tool Shed Actions</a></li>
- <div popupmenu="repository-${repository.id}-popup">
- <a class="action-button" href="${h.url_for( controller='repository', action='browse_valid_categories' )}">Browse valid repositories</a>
- <a class="action-button" href="${h.url_for( controller='repository', action='find_tools' )}">Search for valid tools</a>
- <a class="action-button" href="${h.url_for( controller='repository', action='find_workflows' )}">Search for workflows</a>
- </div>
- %else:
- <li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Repository Actions</a></li>
- <div popupmenu="repository-${repository.id}-popup">
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_repository', id=trans.security.encode_id( repository.id ) )}">Manage repository</a>
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='browse_repository', id=trans.security.encode_id( repository.id ) )}">Browse repository files</a>
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='check_for_updates', id=trans.security.encode_id( repository.id ) )}">Get repository updates</a>
- %if repository.includes_tools:
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='set_tool_versions', id=trans.security.encode_id( repository.id ) )}">Set tool versions</a>
- %endif
- %if repository.tool_dependencies:
- <% tool_dependency_ids = [ trans.security.encode_id( td.id ) for td in repository.tool_dependencies ] %>
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='manage_tool_dependencies', tool_dependency_ids=tool_dependency_ids )}">Manage tool dependencies</a>
- %endif
- <a class="action-button" href="${h.url_for( controller='admin_toolshed', action='deactivate_or_uninstall_repository', id=trans.security.encode_id( repository.id ) )}">Deactivate or uninstall repository</a>
- </div>
- %endif
- %endif
-</ul>
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-%if readme_text:
- ${render_readme( readme_text )}
-%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/browse_repository.mako
--- a/templates/webapps/community/repository/browse_repository.mako
+++ b/templates/webapps/community/repository/browse_repository.mako
@@ -77,9 +77,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ) )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/common.mako
--- a/templates/webapps/community/repository/common.mako
+++ b/templates/webapps/community/repository/common.mako
@@ -1,3 +1,5 @@
+<%namespace file="/webapps/community/common/common.mako" import="escape_html_add_breaks" />
+
<%def name="common_javascripts(repository)"><script type="text/javascript">
$(function(){
@@ -75,7 +77,7 @@
</script></%def>
-<%def name="dependency_javascripts()">
+<%def name="container_javascripts()"><script type="text/javascript">
var init_dependencies = function() {
var storage_id = "library-expand-state-${trans.security.encode_id(10000)}";
@@ -199,30 +201,29 @@
><%
col_span_str = ''
+ folder_label = str( folder.label )
if folder.datatypes:
- label = folder.label
col_span_str = 'colspan="4"'
elif folder.label == 'Repository dependencies':
- label = "%s<i> - this repository requires installation of these additional repositories</i>" % folder.label
- elif folder.repository_dependencies:
- label = folder.label
+ folder_label = "%s<i> - this repository requires installation of these additional repositories</i>" % folder_label
+ elif folder.key == 'readme_files':
+ folder_label = "%s<i> - may contain important installation or license information</i>" % folder_label
elif folder.invalid_tools:
- label = "%s<i> - click the tool config file name to see why the tool is invalid</i>" % folder.label
+ folder_label = "%s<i> - click the tool config file name to see why the tool is invalid</i>" % folder_label
elif folder.tool_dependencies:
- label = "%s<i> - this repository's tools require installation of these dependencies</i>" % folder.label
+ folder_label = "%s<i> - this repository's tools require handling of these dependencies</i>" % folder_label
col_span_str = 'colspan="3"'
elif folder.valid_tools:
- label = "%s<i> - click the name to preview the tool and use the pop-up menu to inspect all metadata</i>" % folder.label
+ folder_label = "%s<i> - click the name to preview the tool and use the pop-up menu to inspect all metadata</i>" % folder_label
col_span_str = 'colspan="3"'
elif folder.workflows:
- label = folder.label
col_span_str = 'colspan="4"'
%><td ${col_span_str} style="padding-left: ${folder_pad}px;"><span class="expandLink folder-${encoded_id}-click"><div style="float: left; margin-left: 2px;" class="expandLink folder-${encoded_id}-click"><a class="folder-${encoded_id}-click" href="javascript:void(0);">
- ${label}
+ ${folder_label}
</a></div></span>
@@ -236,6 +237,9 @@
%for sub_folder in folder.folders:
${render_folder( sub_folder, pad, parent=my_row, row_counter=row_counter, is_root_folder=False )}
%endfor
+ %for readme in folder.readme_files:
+ ${render_readme( readme, pad, my_row, row_counter )}
+ %endfor
%for repository_dependency in folder.repository_dependencies:
${render_repository_dependency( repository_dependency, pad, my_row, row_counter )}
%endfor
@@ -298,9 +302,30 @@
%endif
id="libraryItem-${encoded_id}"><td style="padding-left: ${pad+20}px;">
- <a class="view-info" href="${h.url_for( controller='repository', action='load_invalid_tool', repository_id=trans.security.encode_id( invalid_tool.repository_id ), tool_config=invalid_tool.tool_config, changeset_revision=invalid_tool.changeset_revision )}">
+ %if invalid_tool.repository_id and invalid_tool.tool_config and invalid_tool.changeset_revision:
+ <a class="view-info" href="${h.url_for( controller='repository', action='load_invalid_tool', repository_id=trans.security.encode_id( invalid_tool.repository_id ), tool_config=invalid_tool.tool_config, changeset_revision=invalid_tool.changeset_revision )}">
+ ${invalid_tool.tool_config | h}
+ </a>
+ %else:
${invalid_tool.tool_config | h}
- </a>
+ %endif
+ </td>
+ </tr>
+ <%
+ my_row = row_counter.count
+ row_counter.increment()
+ %>
+</%def>
+
+<%def name="render_readme( readme, pad, parent, row_counter )">
+ <% encoded_id = trans.security.encode_id( readme.id ) %>
+ <tr class="datasetRow"
+ %if parent is not None:
+ parent="${parent}"
+ %endif
+ id="libraryItem-${encoded_id}">
+ <td style="padding-left: ${pad+20}px;">
+ ${escape_html_add_breaks( readme.text )}
</td></tr><%
@@ -311,14 +336,19 @@
<%def name="render_repository_dependency( repository_dependency, pad, parent, row_counter )">
- <% encoded_id = trans.security.encode_id( repository_dependency.id ) %>
+ <%
+ encoded_id = trans.security.encode_id( repository_dependency.id )
+ repository_name = str( repository_dependency.repository_name )
+ changeset_revision = str( repository_dependency.changeset_revision )
+ repository_owner = str( repository_dependency.repository_owner )
+ %><tr class="datasetRow"
%if parent is not None:
parent="${parent}"
%endif
id="libraryItem-${encoded_id}">
##<td style="padding-left: ${pad+20}px;">${repository_dependency.toolshed | h}</td>
- <td style="padding-left: ${pad+20}px;">Repository <b>${repository_dependency.repository_name | h}</b> revision <b>${repository_dependency.changeset_revision | h}</b> owned by <b>${repository_dependency.repository_owner | h}</b></td>
+ <td style="padding-left: ${pad+20}px;">Repository <b>${repository_name | h}</b> revision <b>${changeset_revision | h}</b> owned by <b>${repository_owner | h}</b></td></tr><%
my_row = row_counter.count
@@ -385,6 +415,9 @@
${version_str | h}
</${cell_type}><${cell_type}>${tool_dependency.type | h}</${cell_type}>
+ %if tool_dependency.install_dir:
+ <${cell_type}>${tool_dependency.install_dir | h}</${cell_type}>
+ %endif
</tr><%
my_row = row_counter.count
@@ -428,17 +461,18 @@
from galaxy.tool_shed.encoding_util import tool_shed_encode
has_datatypes = metadata and 'datatypes' in metadata
- has_readme = metadata and 'readme' in metadata
+ has_readme_files = metadata and 'readme_files' in metadata
has_workflows = metadata and 'workflows' in metadata
datatypes_root_folder = containers_dict[ 'datatypes' ]
invalid_tools_root_folder = containers_dict[ 'invalid_tools' ]
+ readme_files_root_folder = containers_dict[ 'readme_files' ]
repository_dependencies_root_folder = containers_dict[ 'repository_dependencies' ]
tool_dependencies_root_folder = containers_dict[ 'tool_dependencies' ]
valid_tools_root_folder = containers_dict[ 'valid_tools' ]
workflows_root_folder = containers_dict[ 'workflows' ]
- has_contents = datatypes_root_folder, invalid_tools_root_folder or valid_tools_root_folder or workflows_root_folder
+ has_contents = datatypes_root_folder or invalid_tools_root_folder or valid_tools_root_folder or workflows_root_folder
class RowCounter( object ):
def __init__( self ):
@@ -448,6 +482,18 @@
def __str__( self ):
return str( self.count )
%>
+ %if readme_files_root_folder:
+ <div class="toolForm">
+ <div class="toolFormTitle">Repository README files (may contain important installation or license information)</div>
+ <div class="toolFormBody">
+ <p/>
+ <% row_counter = RowCounter() %>
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="readme_files">
+ ${render_folder( readme_files_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ </table>
+ </div>
+ </div>
+ %endif
%if repository_dependencies_root_folder or tool_dependencies_root_folder:
<div class="toolForm"><div class="toolFormTitle">Dependencies of this repository</div>
@@ -455,15 +501,15 @@
%if repository_dependencies_root_folder:
<p/><% row_counter = RowCounter() %>
- <table cellspacing="0" cellpadding="0" border="0" width="100%" class="tables container-table" id="repository_dependencies">
- ${self.render_folder( repository_dependencies_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="repository_dependencies">
+ ${render_folder( repository_dependencies_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
</table>
%endif
%if tool_dependencies_root_folder:
<p/><% row_counter = RowCounter() %>
- <table cellspacing="0" cellpadding="0" border="0" width="100%" class="tables container-table" id="tool_dependencies">
- ${self.render_folder( tool_dependencies_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="tool_dependencies">
+ ${render_folder( tool_dependencies_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
</table>
%endif
</div>
@@ -477,29 +523,29 @@
%if valid_tools_root_folder:
<p/><% row_counter = RowCounter() %>
- <table cellspacing="0" cellpadding="0" border="0" width="100%" class="tables container-table" id="valid_tools">
- ${self.render_folder( valid_tools_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="valid_tools">
+ ${render_folder( valid_tools_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
</table>
%endif
%if invalid_tools_root_folder:
<p/><% row_counter = RowCounter() %>
- <table cellspacing="0" cellpadding="0" border="0" width="100%" class="tables container-table" id="invalid_tools">
- ${self.render_folder( invalid_tools_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="invalid_tools">
+ ${render_folder( invalid_tools_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
</table>
%endif
%if workflows_root_folder:
<p/><% row_counter = RowCounter() %>
- <table cellspacing="0" cellpadding="0" border="0" width="100%" class="tables container-table" id="workflows">
- ${self.render_folder( workflows_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="workflows">
+ ${render_folder( workflows_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
</table>
%endif
%if datatypes_root_folder:
<p/><% row_counter = RowCounter() %>
- <table cellspacing="0" cellpadding="0" border="0" width="100%" class="tables container-table" id="datatypes">
- ${self.render_folder( datatypes_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
+ <table cellspacing="2" cellpadding="2" border="0" width="100%" class="tables container-table" id="datatypes">
+ ${render_folder( datatypes_root_folder, 0, parent=None, row_counter=row_counter, is_root_folder=True )}
</table>
%endif
</div>
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/contact_owner.mako
--- a/templates/webapps/community/repository/contact_owner.mako
+++ b/templates/webapps/community/repository/contact_owner.mako
@@ -43,9 +43,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ) )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/manage_repository.mako
--- a/templates/webapps/community/repository/manage_repository.mako
+++ b/templates/webapps/community/repository/manage_repository.mako
@@ -5,23 +5,26 @@
<%
from galaxy.web.framework.helpers import time_ago
+
+ has_metadata = repository.metadata_revisions
+ has_readme = metadata and 'readme' in metadata
is_admin = trans.user_is_admin()
is_new = repository.is_new( trans.app )
is_deprecated = repository.deprecated
+
+ can_browse_contents = not is_new
can_contact_owner = trans.user and trans.user != repository.user
+ can_deprecate = not is_new and trans.user and ( is_admin or repository.user == trans.user ) and not is_deprecated
can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository )
+ can_download = not is_deprecated and not is_new and ( not is_malicious or can_push )
+ can_rate = not is_new and not is_deprecated and trans.user and repository.user != trans.user
+ can_reset_all_metadata = not is_deprecated and is_admin and len( repo ) > 0
+ can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
+ can_set_metadata = not is_new and not is_deprecated
+ can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision == repository.tip( trans.app )
+ can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated
can_upload = can_push
- can_download = not is_deprecated and not is_new and ( not is_malicious or can_push )
- can_browse_contents = not is_new
- can_set_metadata = not is_new and not is_deprecated
- can_rate = not is_new and not is_deprecated and trans.user and repository.user != trans.user
can_view_change_log = not is_new
- can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision == repository.tip( trans.app )
- can_deprecate = not is_new and trans.user and ( is_admin or repository.user == trans.user ) and not is_deprecated
- can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated
- can_reset_all_metadata = not is_deprecated and is_admin and len( repo ) > 0
- has_readme = metadata and 'readme' in metadata
- can_review_repository = not is_new and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
reviewing_repository = cntrller and cntrller == 'repository_review'
if can_push:
@@ -51,7 +54,7 @@
<%def name="javascripts()">
${parent.javascripts()}
${h.js("libs/jquery/jquery.rating", "libs/jquery/jstorage" )}
- ${dependency_javascripts()}
+ ${container_javascripts()}
</%def><br/><br/>
@@ -78,9 +81,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 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
@@ -36,7 +36,7 @@
<%def name="javascripts()">
${parent.javascripts()}
${h.js("libs/jquery/jquery.rating", "libs/jquery/jstorage" )}
- ${dependency_javascripts()}
+ ${container_javascripts()}
</%def><br/><br/>
@@ -44,9 +44,6 @@
<li><a class="action-button" href="${h.url_for( controller='repository', action='install_repositories_by_revision', repository_ids=trans.security.encode_id( repository.id ), changeset_revisions=changeset_revision )}">Install to local Galaxy</a></li><li><a class="action-button" id="repository-${repository.id}-popup" class="menubutton">Tool Shed Actions</a></li><div popupmenu="repository-${repository.id}-popup">
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</a>
- %endif
<a class="action-button" href="${h.url_for( controller='repository', action='browse_valid_categories' )}">Browse valid repositories</a><a class="action-button" href="${h.url_for( controller='repository', action='find_tools' )}">Search for valid tools</a><a class="action-button" href="${h.url_for( controller='repository', action='find_workflows' )}">Search for workflows</a>
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/rate_repository.mako
--- a/templates/webapps/community/repository/rate_repository.mako
+++ b/templates/webapps/community/repository/rate_repository.mako
@@ -84,9 +84,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ) )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/tool_form.mako
--- a/templates/webapps/community/repository/tool_form.mako
+++ b/templates/webapps/community/repository/tool_form.mako
@@ -147,9 +147,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/view_changelog.mako
--- a/templates/webapps/community/repository/view_changelog.mako
+++ b/templates/webapps/community/repository/view_changelog.mako
@@ -52,9 +52,6 @@
%else:
<a class="action-button" href="${h.url_for( controller='repository', action='view_repository', id=trans.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ) )}">View repository</a>
%endif
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ) )}">View README</a>
- %endif
%if can_rate:
<a class="action-button" href="${h.url_for( controller='repository', action='rate_repository', id=trans.security.encode_id( repository.id ) )}">Rate repository</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/view_changeset.mako
--- a/templates/webapps/community/repository/view_changeset.mako
+++ b/templates/webapps/community/repository/view_changeset.mako
@@ -53,9 +53,6 @@
%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( trans.app ) )}">View repository</a>
%endif
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ) )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/view_repository.mako
--- a/templates/webapps/community/repository/view_repository.mako
+++ b/templates/webapps/community/repository/view_repository.mako
@@ -5,22 +5,26 @@
<%
from galaxy.web.framework.helpers import time_ago
+
+ has_readme = metadata and 'readme' in metadata
+ has_metadata = repository.metadata_revisions
is_new = repository.is_new( trans.app )
is_deprecated = repository.deprecated
+
+ can_browse_contents = trans.webapp.name == 'community' and not is_new
can_contact_owner = trans.user and trans.user != repository.user
+ can_download = not is_deprecated and not is_new and ( not is_malicious or can_push )
can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository )
can_rate = not is_deprecated and not is_new and trans.user and repository.user != trans.user
+ can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
can_upload = can_push
- can_download = not is_deprecated and not is_new and ( not is_malicious or can_push )
- can_browse_contents = trans.webapp.name == 'community' and not is_new
can_view_change_log = trans.webapp.name == 'community' and not is_new
+ reviewing_repository = cntrller and cntrller == 'repository_review'
+
if can_push:
browse_label = 'Browse or delete repository tip files'
else:
browse_label = 'Browse repository tip files'
- has_readme = metadata and 'readme' in metadata
- reviewing_repository = cntrller and cntrller == 'repository_review'
- can_review_repository = not is_new and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
%><%!
@@ -40,7 +44,7 @@
<%def name="javascripts()">
${parent.javascripts()}
${h.js("libs/jquery/jquery.rating", "libs/jquery/jstorage" )}
- ${dependency_javascripts()}
+ ${container_javascripts()}
</%def><br/><br/>
@@ -68,9 +72,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/view_tool_metadata.mako
--- a/templates/webapps/community/repository/view_tool_metadata.mako
+++ b/templates/webapps/community/repository/view_tool_metadata.mako
@@ -6,23 +6,27 @@
<%
from galaxy.web.framework.helpers import time_ago
from urllib import quote_plus
+
+ has_metadata = repository.metadata_revisions
+ has_readme = metadata and 'readme' in metadata
is_admin = trans.user_is_admin()
is_new = repository.is_new( trans.app )
is_deprecated = repository.deprecated
+
+ can_browse_contents = trans.webapp.name == 'community' and not is_new
can_contact_owner = trans.user and trans.user != repository.user
+ can_download = not is_new and ( not is_malicious or can_push )
+ can_manage = is_admin or repository.user == trans.user
can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository )
+ can_rate = repository.user != trans.user
+ can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
can_upload = can_push
- can_download = not is_new and ( not is_malicious or can_push )
- can_browse_contents = trans.webapp.name == 'community' and not is_new
- can_rate = repository.user != trans.user
- can_manage = is_admin or repository.user == trans.user
can_view_change_log = trans.webapp.name == 'community' and not is_new
+
if can_push:
browse_label = 'Browse or delete repository tip files'
else:
browse_label = 'Browse repository tip files'
- has_readme = metadata and 'readme' in metadata
- can_review_repository = not is_new and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
%><%!
@@ -69,9 +73,6 @@
%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
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 templates/webapps/community/repository/view_workflow.mako
--- a/templates/webapps/community/repository/view_workflow.mako
+++ b/templates/webapps/community/repository/view_workflow.mako
@@ -55,9 +55,6 @@
%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( trans.app ) )}">View repository</a>
%endif
- %if has_readme:
- <a class="action-button" href="${h.url_for( controller='repository', action='view_readme', id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">View README</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 ) )}">View change log</a>
%endif
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -134,12 +134,6 @@
tc.fv( "1", "selected_files_to_delete", ','.join( files_to_delete ) )
tc.submit( 'select_files_to_delete_button' )
self.check_for_strings( strings_displayed, strings_not_displayed )
- def display_readme_file( self, repository, changeset_revision=None, strings_displayed=[], strings_not_displayed=[] ):
- if changeset_revision is None:
- changeset_revision = self.get_repository_tip( repository )
- repository_id = self.security.encode_id( repository.id )
- self.visit_url( '/repository/view_readme?changeset_revision=%s&id=%s' % ( changeset_revision, repository_id ) )
- self.check_for_strings( strings_displayed, strings_not_displayed )
def display_repository_clone_page( self, owner_name, repository_name, strings_displayed=[], strings_not_displayed=[] ):
url = '/repos/%s/%s' % ( owner_name, repository_name )
self.visit_url( url )
diff -r 88aba66bb81351cbd625ca7fb9ed39874016b36a -r 14589278b02d0dea916319811ddebf17ecca0747 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -105,7 +105,7 @@
commit_message="Uploaded filtering.txt",
uncompress_file='No',
remove_repo_files_not_in_tar='No' )
- self.display_readme_file( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
+ self.manage_repository( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
def test_0055_upload_filtering_test_data( self ):
'''Upload filtering test data.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
@@ -137,22 +137,16 @@
'''Upload readme.txt file associated with tool version 2.2.0.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
self.upload_file( repository, 'readme.txt', commit_message="Uploaded readme.txt" )
- self.display_readme_file( repository, strings_displayed=[ 'This is a readme file.' ] )
+ self.manage_repository( repository, strings_displayed=[ 'This is a readme file.' ] )
# Verify that there is a different readme file for each metadata revision.
metadata_revisions = self.get_repository_metadata_revisions( repository )
- strings_to_check = [ 'Readme file for filtering 1.1.0', 'This is a readme file.' ]
- for key, metadata_revision in enumerate( metadata_revisions ):
- # Metadata revisions are in order newest to oldest at this point.
- self.display_readme_file( repository, metadata_revision, strings_not_displayed=[ strings_to_check[ key ] ] )
+ self.manage_repository( repository, strings_displayed=[ 'Readme file for filtering 1.1.0', 'This is a readme file.' ] )
def test_0075_delete_readme_txt_file( self ):
'''Delete the readme.txt file.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
self.delete_files_from_repository( repository, filenames=[ 'readme.txt' ] )
self.check_count_of_metadata_revisions_associated_with_repository( repository, metadata_count=2 )
- # Deleting a readme file for a specific revision should make the repository fall back
- # to a previous revision's readme file, if one exists.
- # TODO: All readme files should be displayed.
- self.display_readme_file( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
+ self.manage_repository( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
def test_0080_search_for_valid_filter_tool( self ):
'''Verify that the "search for valid tool" feature finds the filtering tool.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
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.
1
0
4 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/84022ca3c573/
changeset: 84022ca3c573
user: carlfeberhard
date: 2012-11-30 02:23:14
summary: history panel: remove IE-incompatible console statements
affected #: 3 files
diff -r 2aae11d62620b6c839b513d68a06d2cc57c5fa21 -r 84022ca3c5733277d8666a999edcb11381f40394 static/scripts/mvc/history/history-model.js
--- a/static/scripts/mvc/history/history-model.js
+++ b/static/scripts/mvc/history/history-model.js
@@ -187,9 +187,9 @@
// if not interruption by iframe reload
//TODO: remove when iframes are removed
if( !( ( xhr.readyState === 0 ) && ( xhr.status === 0 ) ) ){
- if( console && console.warn ){
- console.warn( 'Error getting history updates from the server:', xhr, status, error );
- }
+ //if( console && console.warn ){
+ // console.warn( 'Error getting history updates from the server:', xhr, status, error );
+ //}
alert( _l( 'Error getting history updates from the server.' ) + '\n' + error );
}
});
diff -r 2aae11d62620b6c839b513d68a06d2cc57c5fa21 -r 84022ca3c5733277d8666a999edcb11381f40394 static/scripts/packed/mvc/history/history-model.js
--- a/static/scripts/packed/mvc/history/history-model.js
+++ b/static/scripts/packed/mvc/history/history-model.js
@@ -1,1 +1,1 @@
-var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",diskSize:0,deleted:false,annotation:null,message:null},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b&&b.length){this.hdas.reset(b);this.checkForUpdates()}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.trigger("loaded:user",e[0]);b.trigger("loaded",d[0])}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.hdaIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();b.trigger("loaded:hdas",d);if(c){callback(b)}})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}else{this.trigger("ready")}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.hdas.update(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},4000)}else{c.trigger("ready")}}).error(function(f,d,e){if(!((f.readyState===0)&&(f.status===0))){if(console&&console.warn){console.warn("Error getting history updates from the server:",f,d,e)}alert(_l("Error getting history updates from the server.")+"\n"+e)}})},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HistoryCollection=Backbone.Collection.extend(LoggableMixin).extend({model:History,urlRoot:"api/histories"});
\ No newline at end of file
+var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",diskSize:0,deleted:false,annotation:null,message:null},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b&&b.length){this.hdas.reset(b);this.checkForUpdates()}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.trigger("loaded:user",e[0]);b.trigger("loaded",d[0])}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.hdaIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();b.trigger("loaded:hdas",d);if(c){callback(b)}})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}else{this.trigger("ready")}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.hdas.update(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},4000)}else{c.trigger("ready")}}).error(function(f,d,e){if(!((f.readyState===0)&&(f.status===0))){alert(_l("Error getting history updates from the server.")+"\n"+e)}})},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HistoryCollection=Backbone.Collection.extend(LoggableMixin).extend({model:History,urlRoot:"api/histories"});
\ No newline at end of file
diff -r 2aae11d62620b6c839b513d68a06d2cc57c5fa21 -r 84022ca3c5733277d8666a999edcb11381f40394 templates/root/alternate_history.mako
--- a/templates/root/alternate_history.mako
+++ b/templates/root/alternate_history.mako
@@ -304,11 +304,11 @@
Galaxy.historyFrame = window;
// ostensibly, this is the App
- if( console && console.debug ){
- //if( console.clear ){ console.clear(); }
- console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
- top.storage = jQuery.jStorage
- }
+ //if( console && console.debug ){
+ // //if( console.clear ){ console.clear(); }
+ // console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
+ // top.storage = jQuery.jStorage
+ //}
// LOAD INITIAL DATA IN THIS PAGE - since we're already sending it...
@@ -330,7 +330,7 @@
var historyPanel = new HistoryPanel({
model : new History( history, hdas ),
urlTemplates : galaxy_paths.attributes,
- logger : console,
+ //logger : console,
// is page sending in show settings? if so override history's
show_deleted : ${ 'true' if show_deleted == True else ( 'null' if show_deleted == None else 'false' ) },
show_hidden : ${ 'true' if show_hidden == True else ( 'null' if show_hidden == None else 'false' ) }
https://bitbucket.org/galaxy/galaxy-central/changeset/c08a5d0d2a4f/
changeset: c08a5d0d2a4f
user: natefoo
date: 2012-11-30 03:33:01
summary: Metadata detection would never complete if setting externally.
affected #: 1 file
diff -r 84022ca3c5733277d8666a999edcb11381f40394 -r c08a5d0d2a4fd1f97943174ed677e8ae17566647 lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -133,7 +133,7 @@
.join(model.HistoryDatasetAssociation) \
.join(model.Dataset) \
.filter(and_((model.Job.state == model.Job.states.NEW),
- or_((model.HistoryDatasetAssociation._state != None),
+ or_((model.HistoryDatasetAssociation._state == model.HistoryDatasetAssociation.states.FAILED_METADATA),
(model.HistoryDatasetAssociation.deleted == True ),
(model.Dataset.state != model.Dataset.states.OK ),
(model.Dataset.deleted == True)))).subquery()
https://bitbucket.org/galaxy/galaxy-central/changeset/f364d992270c/
changeset: f364d992270c
user: natefoo
date: 2012-11-30 16:42:50
summary: Prevent waiting jobs from circumventing limits upon server startup.
affected #: 1 file
diff -r c08a5d0d2a4fd1f97943174ed677e8ae17566647 -r f364d992270c6201a748249c819b5cc41ed95cfb lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -87,8 +87,11 @@
if job.tool_id not in self.app.toolbox.tools_by_id:
log.warning( "(%s) Tool '%s' removed from tool config, unable to recover job" % ( job.id, job.tool_id ) )
JobWrapper( job, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator.' )
- elif job.job_runner_name is None:
- log.debug( "(%s) No job runner assigned and job still in '%s' state, adding to the job handler queue" % ( job.id, job.state ) )
+ elif job.job_runner_name is None or (job.job_runner_name is not None and job.job_runner_external_id is None):
+ if job.job_runner_name is None:
+ log.debug( "(%s) No job runner assigned and job still in '%s' state, adding to the job handler queue" % ( job.id, job.state ) )
+ else:
+ log.debug( "(%s) Job runner assigned but no external ID recorded, adding to the job handler queue" % job.id )
if self.track_jobs_in_database:
job.state = model.Job.states.NEW
else:
https://bitbucket.org/galaxy/galaxy-central/changeset/88aba66bb813/
changeset: 88aba66bb813
user: natefoo
date: 2012-11-30 16:54:58
summary: Merge back dist changes
affected #: 2 files
diff -r 7bfce80911c3a628710ebaaad0119ee46ea9e532 -r 88aba66bb81351cbd625ca7fb9ed39874016b36a lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -87,8 +87,11 @@
if job.tool_id not in self.app.toolbox.tools_by_id:
log.warning( "(%s) Tool '%s' removed from tool config, unable to recover job" % ( job.id, job.tool_id ) )
JobWrapper( job, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator.' )
- elif job.job_runner_name is None:
- log.debug( "(%s) No job runner assigned and job still in '%s' state, adding to the job handler queue" % ( job.id, job.state ) )
+ elif job.job_runner_name is None or (job.job_runner_name is not None and job.job_runner_external_id is None):
+ if job.job_runner_name is None:
+ log.debug( "(%s) No job runner assigned and job still in '%s' state, adding to the job handler queue" % ( job.id, job.state ) )
+ else:
+ log.debug( "(%s) Job runner assigned but no external ID recorded, adding to the job handler queue" % job.id )
if self.track_jobs_in_database:
job.state = model.Job.states.NEW
else:
@@ -133,7 +136,7 @@
.join(model.HistoryDatasetAssociation) \
.join(model.Dataset) \
.filter(and_((model.Job.state == model.Job.states.NEW),
- or_((model.HistoryDatasetAssociation._state != None),
+ or_((model.HistoryDatasetAssociation._state == model.HistoryDatasetAssociation.states.FAILED_METADATA),
(model.HistoryDatasetAssociation.deleted == True ),
(model.Dataset.state != model.Dataset.states.OK ),
(model.Dataset.deleted == True)))).subquery()
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.
1
0
30 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/7bfce80911c3/
changeset: 7bfce80911c3
user: inithello
date: 2012-11-30 15:21:47
summary: More tool shed functional test fixes.
affected #: 3 files
diff -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 -r 7bfce80911c3a628710ebaaad0119ee46ea9e532 test/tool_shed/base/test_db_util.py
--- a/test/tool_shed/base/test_db_util.py
+++ b/test/tool_shed/base/test_db_util.py
@@ -42,13 +42,13 @@
def get_repository_by_name_and_owner( name, owner_username ):
owner = get_user_by_name( owner_username )
repository = sa_session.query( model.Repository ) \
- .filter( model.Repository.table.c.name==name ) \
- .filter( model.Repository.table.c.user_id==owner.id ) \
+ .filter( and_( model.Repository.table.c.name == name,
+ model.Repository.table.c.user_id == owner.id ) ) \
.first()
return repository
def get_repository_metadata_by_repository_id_changeset_revision( repository_id, changeset_revision ):
repository_metadata = sa_session.query( model.RepositoryMetadata ) \
- .filter( and_( model.RepositoryMetadata.table.c.id == repository_id,
+ .filter( and_( model.RepositoryMetadata.table.c.repository_id == repository_id,
model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
.first()
return repository_metadata
diff -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 -r 7bfce80911c3a628710ebaaad0119ee46ea9e532 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -56,39 +56,6 @@
assert len( self.get_repository_metadata_revisions( repository ) ) > 0, \
'Repository tip is not a metadata revision: Repository tip - %s, metadata revisions - %s.' % \
( self.get_repository_tip( repository ), ', '.join( self.get_repository_metadata_revisions( repository ) ) )
- def check_repository_tools( self, repository, include_invalid=False ):
- '''
- Loop through each repository_metadata dict in the repository object.
- For each of these, check for a tools attribute, and load the tool metadata
- page if it exists, then display that tool's page.
- '''
- tool_list, invalid_tool_list = self.get_tools_from_repository_metadata( repository, include_invalid=include_invalid )
- for valid_tool_dict in tool_list:
- changeset_revision = valid_tool_dict[ 'changeset_revision' ]
- for tool in valid_tool_dict[ 'tools' ]:
- metadata_strings_displayed = [ tool[ 'guid' ],
- tool[ 'version' ],
- tool[ 'id' ],
- tool[ 'name' ],
- tool[ 'description' ],
- changeset_revision ]
- url = '/repository/view_tool_metadata?repository_id=%s&changeset_revision=%s&tool_id=%s' % \
- ( self.security.encode_id( repository.id ), changeset_revision, tool[ 'id' ] )
- self.visit_url( url )
- self.check_for_strings( metadata_strings_displayed )
- self.load_display_tool_page( repository, tool_xml_path=tool[ 'tool_config' ],
- changeset_revision=changeset_revision,
- strings_displayed=[ '%s (version %s)' % ( tool[ 'name' ], tool[ 'version' ] ) ],
- strings_not_displayed=[] )
- if include_invalid and invalid_tool_list:
- for invalid_tool_dict in invalid_tool_list:
- for tool in invalid_tool_dict[ 'tools' ]:
- tool_path = '%s/%s' % ( self.get_repo_path( repository ), tool )
- self.load_display_tool_page( repository,
- tool_xml_path=tool_path,
- changeset_revision=changeset_revision,
- strings_displayed=[ 'properly loaded' ],
- strings_not_displayed=[] )
def check_repository_tools_for_changeset_revision( self, repository, changeset_revision ):
'''
Loop through each tool dictionary in the repository metadata associated with the received changeset_revision.
@@ -97,22 +64,32 @@
repository_metadata = get_repository_metadata_by_repository_id_changeset_revision( repository.id, changeset_revision )
metadata = repository_metadata.metadata
for tool_dict in metadata[ 'tools' ]:
- metadata_strings_displayed = [ tool[ 'guid' ],
- tool[ 'version' ],
- tool[ 'id' ],
- tool[ 'name' ],
- tool[ 'description' ],
+ metadata_strings_displayed = [ tool_dict[ 'guid' ],
+ tool_dict[ 'version' ],
+ tool_dict[ 'id' ],
+ tool_dict[ 'name' ],
+ tool_dict[ 'description' ],
changeset_revision ]
url = '/repository/view_tool_metadata?repository_id=%s&changeset_revision=%s&tool_id=%s' % \
- ( self.security.encode_id( repository.id ), changeset_revision, tool[ 'id' ] )
+ ( self.security.encode_id( repository.id ), changeset_revision, tool_dict[ 'id' ] )
self.visit_url( url )
self.check_for_strings( metadata_strings_displayed )
- self.load_display_tool_page( repository, tool_xml_path=tool[ 'tool_config' ],
+ self.load_display_tool_page( repository, tool_xml_path=tool_dict[ 'tool_config' ],
changeset_revision=changeset_revision,
- strings_displayed=[ '%s (version %s)' % ( tool[ 'name' ], tool[ 'version' ] ) ],
+ strings_displayed=[ '%s (version %s)' % ( tool_dict[ 'name' ], tool_dict[ 'version' ] ) ],
strings_not_displayed=[] )
def check_repository_invalid_tools_for_changeset_revision( self, repository, changeset_revision ):
- pass
+ repository_metadata = get_repository_metadata_by_repository_id_changeset_revision( repository.id, changeset_revision )
+ metadata = repository_metadata.metadata
+ if 'invalid_tools' not in metadata:
+ return
+ for tool_xml in metadata[ 'tools' ]:
+ tool_path = '%s/%s' % ( self.get_repo_path( repository ), tool_xml )
+ self.load_display_tool_page( repository,
+ tool_xml_path=tool_path,
+ changeset_revision=changeset_revision,
+ strings_displayed=[ 'properly loaded' ],
+ strings_not_displayed=[] )
def check_string_count_in_page( self, pattern, min_count, max_count=None ):
"""Checks the number of 'pattern' occurrences in the current browser page"""
page = self.last_page()
@@ -309,6 +286,15 @@
url = '/repository/manage_repository?user_access_button=Remove&id=%s&remove_auth=%s' % \
( self.security.encode_id( repository.id ), username )
self.visit_url( url )
+ def search_for_valid_tools( self, search_options={}, strings_displayed=[], strings_not_displayed=[] ):
+ for exact_matches in [ True, False ]:
+ for key, value in search_options.items():
+ url = '/repository/find_tools'
+ self.visit_url( url )
+ tc.fv( "1", "exact_matches", exact_matches )
+ tc.fv( "1", key, value )
+ tc.submit()
+ self.check_for_strings( strings_displayed, strings_not_displayed )
def set_repository_deprecated( self, repository, set_deprecated=True, strings_displayed=[], strings_not_displayed=[] ):
url = '/repository/deprecate?id=%s&mark_deprecated=%s' % ( self.security.encode_id( repository.id ), set_deprecated )
self.visit_url( url )
diff -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 -r 7bfce80911c3a628710ebaaad0119ee46ea9e532 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -67,7 +67,8 @@
latest_changeset_revision = self.get_repository_tip( repository )
self.check_for_valid_tools( repository, strings_displayed=[ 'Filter1' ] )
self.check_count_of_metadata_revisions_associated_with_repository( repository, metadata_count=1 )
- self.check_repository_tools( repository )
+ tip = self.get_repository_tip( repository )
+ self.check_repository_tools_for_changeset_revision( repository, tip )
self.check_repository_metadata( repository, tip_only=False )
self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] )
self.display_repository_clone_page( admin_username,
@@ -125,11 +126,12 @@
def test_0065_verify_filtering_repository( self ):
'''Verify the new tool versions and repository metadata.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ tip = self.get_repository_tip( repository )
self.check_for_valid_tools( repository )
strings_displayed = self.get_repository_metadata_revisions( repository ).append( 'Select a revision' )
self.manage_repository( repository, strings_displayed=strings_displayed )
self.check_count_of_metadata_revisions_associated_with_repository( repository, metadata_count=2 )
- self.check_repository_tools( repository, include_invalid=False )
+ self.check_repository_tools_for_changeset_revision( repository, tip )
self.check_repository_metadata( repository, tip_only=False )
def test_0070_upload_readme_txt_file( self ):
'''Upload readme.txt file associated with tool version 2.2.0.'''
@@ -151,3 +153,9 @@
# to a previous revision's readme file, if one exists.
# TODO: All readme files should be displayed.
self.display_readme_file( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
+ def test_0080_search_for_valid_filter_tool( self ):
+ '''Verify that the "search for valid tool" feature finds the filtering tool.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ tip_changeset = self.get_repository_tip( repository )
+ search_options = dict( tool_id='Filter1', tool_name='filter', tool_version='2.2.0' )
+ self.search_for_valid_tools( search_options=search_options, strings_displayed=[ tip_changeset ], strings_not_displayed=[] )
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.
1
0
commit/galaxy-central: carlfeberhard: history panel: comment out console statements; controllers/visualization: remove non-2.5 compliant 'with' statement
by Bitbucket 29 Nov '12
by Bitbucket 29 Nov '12
29 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/e0e5462cb049/
changeset: e0e5462cb049
user: carlfeberhard
date: 2012-11-29 23:43:15
summary: history panel: comment out console statements; controllers/visualization: remove non-2.5 compliant 'with' statement
affected #: 4 files
diff -r dc83012e4b83cbf33c45b28982232bfcfcd29f3b -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -801,14 +801,22 @@
# try to get the first line (assuming it's a header)
#TODO: doesn't belong here
try:
- with open( hda.file_name ) as infile:
- for index, line in enumerate( infile ):
- if 'comment_lines' not in hda_dict:
- hda_dict[ 'comment_lines' ] = []
- hda_dict[ 'comment_lines' ].append( line )
- if index >= 3:
- break
+ num_comment_lines = 1
+ if hda_dict[ 'metadata_comment_lines' ]:
+ num_comment_lines = hda_dict[ 'metadata_comment_lines' ]
+
+ infile = open( hda.file_name )
+ for index, line in enumerate( infile ):
+ if 'comment_lines' not in hda_dict:
+ hda_dict[ 'comment_lines' ] = []
+ hda_dict[ 'comment_lines' ].append( line )
+ if index >= num_comment_lines:
+ break
+ infile.close()
+
except Exception, exc:
+ if infile:
+ infile.close()
log.error( str( exc ) )
history_id = trans.security.encode_id( hda.history.id )
diff -r dc83012e4b83cbf33c45b28982232bfcfcd29f3b -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 static/scripts/mvc/history/history-model.js
--- a/static/scripts/mvc/history/history-model.js
+++ b/static/scripts/mvc/history/history-model.js
@@ -187,9 +187,9 @@
// if not interruption by iframe reload
//TODO: remove when iframes are removed
if( !( ( xhr.readyState === 0 ) && ( xhr.status === 0 ) ) ){
- if( console && console.warn ){
- console.warn( 'Error getting history updates from the server:', xhr, status, error );
- }
+ //if( console && console.warn ){
+ // console.warn( 'Error getting history updates from the server:', xhr, status, error );
+ //}
alert( _l( 'Error getting history updates from the server.' ) + '\n' + error );
}
});
diff -r dc83012e4b83cbf33c45b28982232bfcfcd29f3b -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 static/scripts/packed/mvc/history/history-model.js
--- a/static/scripts/packed/mvc/history/history-model.js
+++ b/static/scripts/packed/mvc/history/history-model.js
@@ -1,1 +1,1 @@
-var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",diskSize:0,deleted:false,annotation:null,message:null},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b&&b.length){this.hdas.reset(b);this.checkForUpdates()}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.trigger("loaded:user",e[0]);b.trigger("loaded",d[0])}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.hdaIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();b.trigger("loaded:hdas",d);if(c){callback(b)}})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}else{this.trigger("ready")}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.hdas.update(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},4000)}else{c.trigger("ready")}}).error(function(f,d,e){if(!((f.readyState===0)&&(f.status===0))){if(console&&console.warn){console.warn("Error getting history updates from the server:",f,d,e)}alert(_l("Error getting history updates from the server.")+"\n"+e)}})},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HistoryCollection=Backbone.Collection.extend(LoggableMixin).extend({model:History,urlRoot:"api/histories"});
\ No newline at end of file
+var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",diskSize:0,deleted:false,annotation:null,message:null},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b&&b.length){this.hdas.reset(b);this.checkForUpdates()}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.trigger("loaded:user",e[0]);b.trigger("loaded",d[0])}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.hdaIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();b.trigger("loaded:hdas",d);if(c){callback(b)}})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}else{this.trigger("ready")}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.hdas.update(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},4000)}else{c.trigger("ready")}}).error(function(f,d,e){if(!((f.readyState===0)&&(f.status===0))){alert(_l("Error getting history updates from the server.")+"\n"+e)}})},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HistoryCollection=Backbone.Collection.extend(LoggableMixin).extend({model:History,urlRoot:"api/histories"});
\ No newline at end of file
diff -r dc83012e4b83cbf33c45b28982232bfcfcd29f3b -r e0e5462cb0495fc856ed5bd4a47bf386ac990a61 templates/root/alternate_history.mako
--- a/templates/root/alternate_history.mako
+++ b/templates/root/alternate_history.mako
@@ -304,12 +304,11 @@
Galaxy.historyFrame = window;
// ostensibly, this is the App
- if( console && console.debug ){
- //if( console.clear ){ console.clear(); }
- console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
- top.storage = jQuery.jStorage
- }
-
+ //if( console && console.debug ){
+ // //if( console.clear ){ console.clear(); }
+ // console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
+ // top.storage = jQuery.jStorage
+ //}
// LOAD INITIAL DATA IN THIS PAGE - since we're already sending it...
// ...use mako to 'bootstrap' the models
@@ -330,7 +329,7 @@
var historyPanel = new HistoryPanel({
model : new History( history, hdas ),
urlTemplates : galaxy_paths.attributes,
- logger : console,
+ //logger : console,
// is page sending in show settings? if so override history's
show_deleted : ${ 'true' if show_deleted == True else ( 'null' if show_deleted == None else 'false' ) },
show_hidden : ${ 'true' if show_hidden == True else ( 'null' if show_hidden == None else 'false' ) }
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.
1
0
commit/galaxy-central: inithello: Fixed server error when tool search returns no results.
by Bitbucket 29 Nov '12
by Bitbucket 29 Nov '12
29 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/dc83012e4b83/
changeset: dc83012e4b83
user: inithello
date: 2012-11-29 21:02:56
summary: Fixed server error when tool search returns no results.
affected #: 1 file
diff -r 44ae555b804bae7aa8170326ff37d5c0dc47be28 -r dc83012e4b83cbf33c45b28982232bfcfcd29f3b lib/galaxy/webapps/community/controllers/repository.py
--- a/lib/galaxy/webapps/community/controllers/repository.py
+++ b/lib/galaxy/webapps/community/controllers/repository.py
@@ -501,7 +501,8 @@
.filter( or_( *clause_list ) ) \
.order_by( model.Repository.name )
# Return an empty query
- return []
+ return trans.sa_session.query( model.RepositoryMetadata ) \
+ .filter( model.RepositoryMetadata.id < 0 )
class InstallMatchedRepositoryGrid( MatchedRepositoryGrid ):
columns = [ col for col in MatchedRepositoryGrid.columns ]
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.
1
0
commit/galaxy-central: greg: More enhancements for hte tool shed functional test framework.
by Bitbucket 29 Nov '12
by Bitbucket 29 Nov '12
29 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/44ae555b804b/
changeset: 44ae555b804b
user: greg
date: 2012-11-29 20:39:31
summary: More enhancements for hte tool shed functional test framework.
affected #: 4 files
diff -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a -r 44ae555b804bae7aa8170326ff37d5c0dc47be28 test/tool_shed/base/test_db_util.py
--- a/test/tool_shed/base/test_db_util.py
+++ b/test/tool_shed/base/test_db_util.py
@@ -46,3 +46,9 @@
.filter( model.Repository.table.c.user_id==owner.id ) \
.first()
return repository
+def get_repository_metadata_by_repository_id_changeset_revision( repository_id, changeset_revision ):
+ repository_metadata = sa_session.query( model.RepositoryMetadata ) \
+ .filter( and_( model.RepositoryMetadata.table.c.id == repository_id,
+ model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
+ .first()
+ return repository_metadata
diff -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a -r 44ae555b804bae7aa8170326ff37d5c0dc47be28 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -1,13 +1,19 @@
from base.twilltestcase import *
-import ConfigParser
+from galaxy.webapps.community.util.hgweb_config import *
+from test_db_util import *
+
+from galaxy import eggs
+eggs.require('mercurial')
+from mercurial import hg, ui
class ShedTwillTestCase( TwillTestCase ):
def setUp( self ):
# Security helper
self.security = security.SecurityHelper( id_secret='changethisinproductiontoo' )
self.history_id = None
- self.hgweb_config_manager = None
self.hgweb_config_dir = os.environ.get( 'TEST_HG_WEB_CONFIG_DIR' )
+ self.hgweb_config_manager = HgWebConfigManager()
+ self.hgweb_config_manager.hgweb_config_dir = self.hgweb_config_dir
self.host = os.environ.get( 'TOOL_SHED_TEST_HOST' )
self.port = os.environ.get( 'TOOL_SHED_TEST_PORT' )
self.url = "http://%s:%s" % ( self.host, self.port )
@@ -35,7 +41,7 @@
def check_for_valid_tools( self, repository, strings_displayed=[], strings_not_displayed=[] ):
strings_displayed.append( 'Valid tools' )
self.manage_repository( repository, strings_displayed, strings_not_displayed )
- def check_metadata_in_repository_changelog( self, repository, metadata_count ):
+ def check_count_of_metadata_revisions_associated_with_repository( self, repository, metadata_count ):
self.check_repository_changelog( repository )
self.check_string_count_in_page( 'Repository metadata is associated with this change set.', metadata_count )
def check_repository_changelog( self, repository, strings_displayed=[], strings_not_displayed=[] ):
@@ -43,9 +49,13 @@
self.visit_url( url )
self.check_for_strings( strings_displayed, strings_not_displayed )
def check_repository_metadata( self, repository, tip_only=True ):
- assert self.tip_has_metadata( repository, tip_only ), \
- 'Repository tip is not a metadata revision: Repository tip - %s, metadata revisions - %s.' % \
- ( self.get_repository_tip( repository ), ', '.join( self.get_repository_metadata_revisions( repository ) ) )
+ if tip_only:
+ assert self.tip_has_metadata( repository ) and len( self.get_repository_metadata_revisions( repository ) ) == 1, \
+ 'Repository tip is not a metadata revision: Repository tip - %s, metadata revisions - %s.'
+ else:
+ assert len( self.get_repository_metadata_revisions( repository ) ) > 0, \
+ 'Repository tip is not a metadata revision: Repository tip - %s, metadata revisions - %s.' % \
+ ( self.get_repository_tip( repository ), ', '.join( self.get_repository_metadata_revisions( repository ) ) )
def check_repository_tools( self, repository, include_invalid=False ):
'''
Loop through each repository_metadata dict in the repository object.
@@ -73,12 +83,36 @@
if include_invalid and invalid_tool_list:
for invalid_tool_dict in invalid_tool_list:
for tool in invalid_tool_dict[ 'tools' ]:
- tool_path = '%s/%s' % ( self.get_repository_filesystem_path( repository ), tool )
+ tool_path = '%s/%s' % ( self.get_repo_path( repository ), tool )
self.load_display_tool_page( repository,
tool_xml_path=tool_path,
changeset_revision=changeset_revision,
strings_displayed=[ 'properly loaded' ],
strings_not_displayed=[] )
+ def check_repository_tools_for_changeset_revision( self, repository, changeset_revision ):
+ '''
+ Loop through each tool dictionary in the repository metadata associated with the received changeset_revision.
+ For each of these, check for a tools attribute, and load the tool metadata page if it exists, then display that tool's page.
+ '''
+ repository_metadata = get_repository_metadata_by_repository_id_changeset_revision( repository.id, changeset_revision )
+ metadata = repository_metadata.metadata
+ for tool_dict in metadata[ 'tools' ]:
+ metadata_strings_displayed = [ tool[ 'guid' ],
+ tool[ 'version' ],
+ tool[ 'id' ],
+ tool[ 'name' ],
+ tool[ 'description' ],
+ changeset_revision ]
+ url = '/repository/view_tool_metadata?repository_id=%s&changeset_revision=%s&tool_id=%s' % \
+ ( self.security.encode_id( repository.id ), changeset_revision, tool[ 'id' ] )
+ self.visit_url( url )
+ self.check_for_strings( metadata_strings_displayed )
+ self.load_display_tool_page( repository, tool_xml_path=tool[ 'tool_config' ],
+ changeset_revision=changeset_revision,
+ strings_displayed=[ '%s (version %s)' % ( tool[ 'name' ], tool[ 'version' ] ) ],
+ strings_not_displayed=[] )
+ def check_repository_invalid_tools_for_changeset_revision( self, repository, changeset_revision ):
+ pass
def check_string_count_in_page( self, pattern, min_count, max_count=None ):
"""Checks the number of 'pattern' occurrences in the current browser page"""
page = self.last_page()
@@ -202,15 +236,10 @@
def get_repo_path( self, repository ):
# An entry in the hgweb.config file looks something like: repos/test/mira_assembler = database/community_files/000/repo_123
lhs = "repos/%s/%s" % ( repository.user.username, repository.name )
- hgweb_config = "%s/hgweb.config" % self.hgweb_config_dir
- if not os.path.exists( hgweb_config ):
- raise Exception( "Required file hgweb.config does not exist in directory %s" % self.hgweb_config_dir )
- config = ConfigParser.ConfigParser()
- config.read( hgweb_config )
- for option in config.options( "paths" ):
- if option == lhs:
- return config.get( "paths", option )
- raise Exception( "Entry for repository %s missing in %s/hgweb.config file." % ( lhs, self.hgweb_config_dir ) )
+ try:
+ return self.hgweb_config_manager.get_entry( lhs )
+ except:
+ raise Exception( "Entry for repository %s missing in hgweb config file %s." % ( lhs, self.hgweb_config_manager.hgweb_config ) )
def get_repository_file_list( self, base_path, current_path=None ):
'''Recursively load repository folder contents and append them to a list. Similar to os.walk but via /repository/open_folder.'''
if current_path is None:
@@ -247,15 +276,8 @@
def get_repository_metadata_revisions( self, repository ):
return [ str( repository_metadata.changeset_revision ) for repository_metadata in repository.metadata_revisions ]
def get_repository_tip( self, repository ):
- self.browse_repository( repository )
- html = self.last_page()
- revision_regex = re.search( 'revision ([0-9a-f]+) \(repository tip\)', html )
- if revision_regex is not None:
- return revision_regex.group( 1 )
- return None
- def get_repository_filesystem_path( self, repository ):
- repo_subdirectory = '%03d' % int( repository.id / 1000 )
- return os.path.join( 'database', 'community_files', repo_subdirectory, 'repo_%d' % repository.id )
+ repo = hg.repository( ui.ui(), self.get_repo_path( repository ) )
+ return str( repo.changectx( repo.changelog.tip() ) )
def get_tools_from_repository_metadata( self, repository, include_invalid=False ):
'''Get a list of valid and (optionally) invalid tool dicts from the repository metadata.'''
valid_tools = []
@@ -296,12 +318,9 @@
tc.fv( "malicious", "malicious", set_malicious )
tc.submit( "malicious_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
- def tip_has_metadata( self, repository, tip_only=True ):
+ def tip_has_metadata( self, repository ):
tip = self.get_repository_tip( repository )
- changeset_revisions = [ str( repository_metadata.changeset_revision ) for repository_metadata in repository.metadata_revisions ]
- if tip_only:
- return len( changeset_revisions ) == 1 and tip in changeset_revisions
- return tip in changeset_revisions
+ return get_repository_metadata_by_repository_id_changeset_revision( repository.id, tip )
def upload_file( self,
repository,
filename,
diff -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a -r 44ae555b804bae7aa8170326ff37d5c0dc47be28 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -66,7 +66,7 @@
repository = get_repository_by_name_and_owner( repository_name, admin_username )
latest_changeset_revision = self.get_repository_tip( repository )
self.check_for_valid_tools( repository, strings_displayed=[ 'Filter1' ] )
- self.check_metadata_in_repository_changelog( repository, metadata_count=1 )
+ self.check_count_of_metadata_revisions_associated_with_repository( repository, metadata_count=1 )
self.check_repository_tools( repository )
self.check_repository_metadata( repository, tip_only=False )
self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] )
@@ -128,7 +128,7 @@
self.check_for_valid_tools( repository )
strings_displayed = self.get_repository_metadata_revisions( repository ).append( 'Select a revision' )
self.manage_repository( repository, strings_displayed=strings_displayed )
- self.check_metadata_in_repository_changelog( repository, metadata_count=2 )
+ self.check_count_of_metadata_revisions_associated_with_repository( repository, metadata_count=2 )
self.check_repository_tools( repository, include_invalid=False )
self.check_repository_metadata( repository, tip_only=False )
def test_0070_upload_readme_txt_file( self ):
@@ -146,7 +146,7 @@
'''Delete the readme.txt file.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
self.delete_files_from_repository( repository, filenames=[ 'readme.txt' ] )
- self.check_metadata_in_repository_changelog( repository, metadata_count=2 )
+ self.check_count_of_metadata_revisions_associated_with_repository( repository, metadata_count=2 )
# Deleting a readme file for a specific revision should make the repository fall back
# to a previous revision's readme file, if one exists.
# TODO: All readme files should be displayed.
diff -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a -r 44ae555b804bae7aa8170326ff37d5c0dc47be28 test/tool_shed/functional_tests.py
--- a/test/tool_shed/functional_tests.py
+++ b/test/tool_shed/functional_tests.py
@@ -270,7 +270,7 @@
for dir in [ tool_shed_test_tmp_dir ]:
if os.path.exists( dir ):
log.info( "Cleaning up temporary files in %s" % dir )
- shutil.rmtree( dir )
+ #shutil.rmtree( dir )
except:
pass
if success:
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.
1
0
commit/galaxy-central: carlfeberhard: LazyDataLoader.js: now handles NaN/Infinity/-Infinity as null, allows overriding jQuery's datatype converters; controllers/root: added echo_json for debugging/testing
by Bitbucket 29 Nov '12
by Bitbucket 29 Nov '12
29 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/17d6bfafebec/
changeset: 17d6bfafebec
user: carlfeberhard
date: 2012-11-29 19:32:44
summary: LazyDataLoader.js: now handles NaN/Infinity/-Infinity as null, allows overriding jQuery's datatype converters; controllers/root: added echo_json for debugging/testing
affected #: 3 files
diff -r 7fbdc96660062a9194fc16039e6be3b63831ee4e -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a lib/galaxy/webapps/galaxy/controllers/root.py
--- a/lib/galaxy/webapps/galaxy/controllers/root.py
+++ b/lib/galaxy/webapps/galaxy/controllers/root.py
@@ -531,6 +531,25 @@
rval += "-> %s" % kwd[k].file.read()
return rval
+ @web.json
+ def echo_json( self, trans, **kwd ):
+ """Echos parameters as JSON (debugging)
+
+ Attempts to parse values passed as boolean, float, then int. Defaults
+ to string. Non-recursive (will not parse lists).
+ """
+ rval = {}
+ for k in kwd:
+ rval[ k ] = kwd[k]
+ try:
+ if rval[ k ] in [ 'true', 'True', 'false', 'False' ]:
+ rval[ k ] = util.string_as_bool( rval[ k ] )
+ rval[ k ] = float( rval[ k ] )
+ rval[ k ] = int( rval[ k ] )
+ except:
+ pass
+ return rval
+
@web.expose
def generate_error( self, trans ):
raise Exception( "Fake error!" )
diff -r 7fbdc96660062a9194fc16039e6be3b63831ee4e -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a static/scripts/packed/utils/LazyDataLoader.js
--- a/static/scripts/packed/utils/LazyDataLoader.js
+++ b/static/scripts/packed/utils/LazyDataLoader.js
@@ -1,1 +1,1 @@
-function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:4000,start:0,size:4000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a};
\ No newline at end of file
+function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:4000,start:0,size:4000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){console.error("ERROR fetching data:",f)},converters:{"* text":window.String,"text html":true,"text xml":jQuery.parseXML,"text json":function(e){e=e.replace(/NaN/g,"null");e=e.replace(/-Infinity/g,"null");e=e.replace(/Infinity/g,"null");return jQuery.parseJSON(e)}},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),converters:a.converters,dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a};
\ No newline at end of file
diff -r 7fbdc96660062a9194fc16039e6be3b63831ee4e -r 17d6bfafebecd39dd42fd2377e1a5ce1e16e566a static/scripts/utils/LazyDataLoader.js
--- a/static/scripts/utils/LazyDataLoader.js
+++ b/static/scripts/utils/LazyDataLoader.js
@@ -97,7 +97,6 @@
if( config.hasOwnProperty( 'initialize' ) ){
config.initialize.call( loader, config );
}
-
this.log( this + ' initialized:', loader );
},
@@ -115,6 +114,23 @@
//OVERRIDE: to handle ajax errors differently
ajaxErrorFn : function( xhr, status, error ){
+ console.error( 'ERROR fetching data:', error );
+ },
+
+ // converters passed to the jQuery ajax call for data type parsing
+ //OVERRIDE: to provide custom parsing
+ converters : {
+ '* text' : window.String,
+ 'text html' : true,
+ 'text xml' : jQuery.parseXML,
+
+ // add NaN, inf, -inf handling to jquery json parser (by default)
+ 'text json' : function( json ){
+ json = json.replace( /NaN/g, 'null' );
+ json = json.replace( /-Infinity/g, 'null' );
+ json = json.replace( /Infinity/g, 'null' );
+ return jQuery.parseJSON( json );
+ }
},
// interface to begin load (and first recursive call)
@@ -149,7 +165,9 @@
jQuery.ajax({
url : loader.buildUrl( start, size ),
+ converters : loader.converters,
dataType : 'json',
+
error : function( xhr, status, error ){
loader.log( '\t ajax error, status:', status, 'error:', error );
if( loader.currentIntervalId ){
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.
1
0
commit/galaxy-central: inithello: Fixed functional test script creating stray directories in galaxy root.
by Bitbucket 29 Nov '12
by Bitbucket 29 Nov '12
29 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/7fbdc9666006/
changeset: 7fbdc9666006
user: inithello
date: 2012-11-29 19:06:02
summary: Fixed functional test script creating stray directories in galaxy root.
affected #: 1 file
diff -r 607e9db258f20ad47b8bb813508bd6a9056596b8 -r 7fbdc96660062a9194fc16039e6be3b63831ee4e test/tool_shed/functional_tests.py
--- a/test/tool_shed/functional_tests.py
+++ b/test/tool_shed/functional_tests.py
@@ -129,7 +129,7 @@
else:
database_connection = 'sqlite:///' + os.path.join( db_path, 'universe.sqlite' )
kwargs = {}
- for dir in tool_shed_test_tmp_dir:
+ for dir in [ tool_shed_test_tmp_dir ]:
try:
os.makedirs( dir )
except OSError:
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.
1
0
29 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/607e9db258f2/
changeset: 607e9db258f2
user: greg
date: 2012-11-29 18:42:04
summary: Fixes for tool shed functional tests.
affected #: 4 files
diff -r 60786207e48d952a69ccef95fd8fdc1740970923 -r 607e9db258f20ad47b8bb813508bd6a9056596b8 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -1,4 +1,5 @@
from base.twilltestcase import *
+import ConfigParser
class ShedTwillTestCase( TwillTestCase ):
def setUp( self ):
@@ -6,6 +7,7 @@
self.security = security.SecurityHelper( id_secret='changethisinproductiontoo' )
self.history_id = None
self.hgweb_config_manager = None
+ self.hgweb_config_dir = os.environ.get( 'TEST_HG_WEB_CONFIG_DIR' )
self.host = os.environ.get( 'TOOL_SHED_TEST_HOST' )
self.port = os.environ.get( 'TOOL_SHED_TEST_PORT' )
self.url = "http://%s:%s" % ( self.host, self.port )
@@ -108,8 +110,8 @@
self.check_for_strings( strings_displayed, strings_not_displayed )
def delete_files_from_repository( self, repository, filenames=[], strings_displayed=[ 'were deleted from the repository' ], strings_not_displayed=[] ):
files_to_delete = []
- basepath = self.get_repository_base_path_from_browse_page( repository )
- repository_files = self.get_repository_file_list( repository, basepath )
+ basepath = self.get_repo_path( repository )
+ repository_files = self.get_repository_file_list( base_path=basepath, current_path=None )
# Verify that the files to delete actually exist in the repository.
for filename in repository_files:
if filename in filenames:
@@ -133,14 +135,15 @@
self.check_for_strings( strings_displayed, strings_not_displayed )
def display_repository_file_contents( self, repository, filename, filepath=None, strings_displayed=[], strings_not_displayed=[] ):
'''Find a file in the repository and display the contents.'''
- basepath = self.get_repository_base_path_from_browse_page( repository )
+ basepath = self.get_repo_path( repository )
repository_file_list = []
- relative_path = filename
- if filepath is not None:
- relative_path = os.path.join( filepath, filename )
- repository_file_list = self.get_repository_file_list( repository, basepath )
- assert relative_path in repository_file_list, 'File %s not found in the repository under %s.' % ( filename, filepath )
- url = '/repository/get_file_contents?file_path=%s' % os.path.join( basepath, relative_path ).replace( '/', '%2f' )
+ if filepath:
+ relative_path = os.path.join( basepath, filepath )
+ else:
+ relative_path = basepath
+ repository_file_list = self.get_repository_file_list( base_path=relative_path, current_path=None )
+ assert filename in repository_file_list, 'File %s not found in the repository under %s.' % ( filename, relative_path )
+ url = '/repository/get_file_contents?file_path=%s' % os.path.join( relative_path, filename )
self.visit_url( url )
self.check_for_strings( strings_displayed, strings_not_displayed )
def edit_repository_categories( self, repository, categories_to_add=[], categories_to_remove=[], restore_original=True ):
@@ -196,27 +199,27 @@
def get_latest_repository_metadata_for_repository( self, repository ):
# TODO: This will not work as expected. Fix it.
return repository.metadata_revisions[ 0 ]
- def get_repository_base_path_from_browse_page( self, repository ):
- '''Get the base path from the javascript dynatree parameters. This must be updated if the browse feature is altered.'''
- self.visit_url( '/repository/browse_repository?id=%s' % self.security.encode_id( repository.id ) )
- html = self.last_page()
- found_file = None
- basepath = None
- search = re.search( 'data: { folder_path: "([^"]+)" },', html )
- if search is not None:
- basepath = search.group( 1 )
- return basepath
- def get_repository_file_list( self, repository, base_path, current_path=None ):
- '''
- Recursively load repository folder contents and append them to a list. Similar to os.walk,
- but via /repository/open_folder.
- '''
+ def get_repo_path( self, repository ):
+ # An entry in the hgweb.config file looks something like: repos/test/mira_assembler = database/community_files/000/repo_123
+ lhs = "repos/%s/%s" % ( repository.user.username, repository.name )
+ hgweb_config = "%s/hgweb.config" % self.hgweb_config_dir
+ if not os.path.exists( hgweb_config ):
+ raise Exception( "Required file hgweb.config does not exist in directory %s" % self.hgweb_config_dir )
+ config = ConfigParser.ConfigParser()
+ config.read( hgweb_config )
+ for option in config.options( "paths" ):
+ if option == lhs:
+ return config.get( "paths", option )
+ raise Exception( "Entry for repository %s missing in %s/hgweb.config file." % ( lhs, self.hgweb_config_dir ) )
+ def get_repository_file_list( self, base_path, current_path=None ):
+ '''Recursively load repository folder contents and append them to a list. Similar to os.walk but via /repository/open_folder.'''
if current_path is None:
- full_path = base_path
+ request_param_path = base_path
else:
- full_path = os.path.join( base_path, current_path )
+ request_param_path = os.path.join( base_path, current_path )
+ #request_param_path = request_param_path.replace( '/', '%2f' )
# Get the current folder's contents.
- url = '/repository/open_folder?folder_path=%s' % full_path.replace( '/', '%2f' )
+ url = '/repository/open_folder?folder_path=%s' % request_param_path
self.visit_url( url )
file_list = from_json_string( self.last_page() )
returned_file_list = []
@@ -228,10 +231,10 @@
# This is a folder. Get the contents of the folder and append it to the list,
# prefixed with the path relative to the repository root, if any.
if current_path is None:
- returned_file_list.extend( self.get_repository_file_list( repository, base_path, file_dict[ 'title' ] ) )
+ returned_file_list.extend( self.get_repository_file_list( base_path=base_path, current_path=file_dict[ 'title' ] ) )
else:
sub_path = os.path.join( current_path, file_dict[ 'title' ] )
- returned_file_list.extend( self.get_repository_file_list( repository, base_path, sub_path ) )
+ returned_file_list.extend( self.get_repository_file_list( base_path=base_path, current_path=sub_path ) )
else:
# This is a regular file, prefix the filename with the current path and append it to the list.
if current_path is not None:
diff -r 60786207e48d952a69ccef95fd8fdc1740970923 -r 607e9db258f20ad47b8bb813508bd6a9056596b8 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -15,7 +15,7 @@
repository_description = "Galaxy's filtering tool"
repository_long_description = "Long description of Galaxy's filtering tool"
-class TestCreateRepository( ShedTwillTestCase ):
+class TestBasicRepositoryFeatures( ShedTwillTestCase ):
def test_0000_initiate_users( self ):
"""Create necessary user accounts and login as an admin user."""
@@ -89,9 +89,13 @@
strings_displayed=[ 'has been marked as not deprecated', 'Mark as deprecated' ],
set_deprecated=False )
def test_0045_display_repository_tip_file( self ):
- '''Display the contents of a file in the repository tip revision'''
+ '''Display the contents of filtering.xml in the repository tip revision'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
- self.display_repository_file_contents( repository, filename='filtering.xml', strings_displayed=[ '1.1.0' ] )
+ self.display_repository_file_contents( repository=repository,
+ filename='filtering.xml',
+ filepath=None,
+ strings_displayed=[ '1.1.0' ],
+ strings_not_displayed=[] )
def test_0050_upload_filtering_txt_file( self ):
'''Upload filtering.txt file associated with tool version 1.1.0.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
@@ -105,7 +109,11 @@
'''Upload filtering test data.'''
repository = get_repository_by_name_and_owner( repository_name, admin_username )
self.upload_file( repository, 'filtering_test_data.tar', commit_message="Uploaded filtering test data", remove_repo_files_not_in_tar='No' )
- self.display_repository_file_contents( repository, filename='1.bed', filepath='test-data' )
+ self.display_repository_file_contents( repository=repository,
+ filename='1.bed',
+ filepath='test-data',
+ strings_displayed=[],
+ strings_not_displayed=[] )
self.check_repository_metadata( repository, tip_only=True )
def test_0060_upload_filtering_2_2_0( self ):
'''Upload filtering version 2.2.0'''
diff -r 60786207e48d952a69ccef95fd8fdc1740970923 -r 607e9db258f20ad47b8bb813508bd6a9056596b8 test/tool_shed/functional/test_0010_repository_with_tool_dependencies.py
--- /dev/null
+++ b/test/tool_shed/functional/test_0010_repository_with_tool_dependencies.py
@@ -0,0 +1,19 @@
+from tool_shed.base.twilltestcase import *
+from tool_shed.base.test_db_util import *
+
+admin_user = None
+admin_user_private_role = None
+admin_email = 'test(a)bx.psu.edu'
+admin_username = 'admin-user'
+
+regular_user = None
+regular_user_private_role = None
+regular_email = 'test-1(a)bx.psu.edu'
+regular_username = 'user1'
+
+repository_name = 'freebayes'
+repository_description = "Galaxy's freebayes tool"
+repository_long_description = "Long description of Galaxy's freebayes tool"
+
+class TestRepositoryWithToolDependencies( ShedTwillTestCase ):
+ pass
\ No newline at end of file
diff -r 60786207e48d952a69ccef95fd8fdc1740970923 -r 607e9db258f20ad47b8bb813508bd6a9056596b8 test/tool_shed/functional_tests.py
--- a/test/tool_shed/functional_tests.py
+++ b/test/tool_shed/functional_tests.py
@@ -6,6 +6,10 @@
cwd = os.getcwd()
tool_shed_home_directory = os.path.join( cwd, 'test', 'tool_shed' )
default_tool_shed_test_file_dir = os.path.join( tool_shed_home_directory, 'test_data' )
+# Here's the directory where everything happens. Temporary directories are created within this directory to contain
+# the hgweb.config file, the database, new repositories, etc. Since the tool shed browses repository contents via HTTP,
+# the full path to the temporary directroy wher eht repositories are located cannot contain invalid url characters.
+tool_shed_test_tmp_dir = os.path.join( tool_shed_home_directory, 'tmp' )
new_path = [ os.path.join( cwd, "lib" ) ]
new_path.extend( sys.path[1:] )
sys.path = new_path
@@ -31,7 +35,6 @@
import galaxy.webapps.community.app
from galaxy.webapps.community.app import UniverseApplication
from galaxy.webapps.community import buildapp
-#from galaxy.webapps.community.util.hgweb_config import *
import nose.core
import nose.config
@@ -81,6 +84,8 @@
use_distributed_object_store = os.environ.get( 'TOOL_SHED_USE_DISTRIBUTED_OBJECT_STORE', False )
if start_server:
+ if not os.path.isdir( tool_shed_test_tmp_dir ):
+ os.mkdir( tool_shed_test_tmp_dir )
psu_production = False
tool_shed_test_proxy_port = None
if 'TOOL_SHED_TEST_PSU_PRODUCTION' in os.environ:
@@ -102,7 +107,8 @@
if not nginx_upload_store:
raise Exception( 'Set TOOL_SHED_TEST_NGINX_UPLOAD_STORE to the path where the nginx upload module places uploaded files' )
file_path = tempfile.mkdtemp( dir=base_file_path )
- new_file_path = tempfile.mkdtemp( dir=base_new_file_path )
+ new_repos_path = tempfile.mkdtemp( dir=base_new_file_path )
+ hgweb_config_file_path = tempfile.mkdtemp( dir=tool_shed_test_tmp_dir )
kwargs = dict( database_engine_option_pool_size = '10',
database_engine_option_max_overflow = '20',
database_engine_option_strategy = 'threadlocal',
@@ -113,16 +119,17 @@
if 'TOOL_SHED_TEST_DBPATH' in os.environ:
db_path = os.environ[ 'TOOL_SHED_TEST_DBPATH' ]
else:
- tempdir = tempfile.mkdtemp()
+ tempdir = tempfile.mkdtemp( dir=tool_shed_test_tmp_dir )
db_path = os.path.join( tempdir, 'database' )
file_path = os.path.join( db_path, 'files' )
- new_file_path = os.path.join( db_path, 'tmp' )
+ hgweb_config_file_path = tempfile.mkdtemp( dir=tool_shed_test_tmp_dir )
+ new_repos_path = tempfile.mkdtemp( dir=tool_shed_test_tmp_dir )
if 'TOOL_SHED_TEST_DBURI' in os.environ:
database_connection = os.environ[ 'TOOL_SHED_TEST_DBURI' ]
else:
database_connection = 'sqlite:///' + os.path.join( db_path, 'universe.sqlite' )
kwargs = {}
- for dir in file_path, new_file_path:
+ for dir in tool_shed_test_tmp_dir:
try:
os.makedirs( dir )
except OSError:
@@ -130,6 +137,11 @@
print "Database connection:", database_connection
+ hgweb_config_dir = hgweb_config_file_path
+ os.environ[ 'TEST_HG_WEB_CONFIG_DIR' ] = hgweb_config_dir
+
+ print "Directory location for hgweb.config:", hgweb_config_dir
+
# ---- Build Application --------------------------------------------------
app = None
if start_server:
@@ -150,7 +162,7 @@
database_connection = database_connection,
database_engine_option_pool_size = '10',
file_path = file_path,
- new_file_path = new_file_path,
+ new_file_path = new_repos_path,
tool_path=tool_path,
datatype_converters_config_file = 'datatype_converters_conf.xml.sample',
tool_parse_help = False,
@@ -163,9 +175,8 @@
admin_users = 'test(a)bx.psu.edu',
global_conf = global_conf,
running_functional_tests = True,
- hgweb_config_dir = new_file_path,
+ hgweb_config_dir = hgweb_config_dir,
**kwargs )
-# app.hgweb_config_manager = HgWebConfigManager()
log.info( "Embedded Universe application started" )
@@ -254,20 +265,14 @@
app.shutdown()
app = None
log.info( "Embedded Universe application stopped" )
- try:
- if os.path.exists( tempdir ) and 'TOOL_SHED_TEST_NO_CLEANUP' not in os.environ:
- log.info( "Cleaning up temporary files in %s" % tempdir )
- shutil.rmtree( tempdir )
- except:
- pass
- if psu_production and 'TOOL_SHED_TEST_NO_CLEANUP' not in os.environ:
- for dir in ( file_path, new_file_path ):
- try:
+ if 'TOOL_SHED_TEST_NO_CLEANUP' not in os.environ:
+ try:
+ for dir in [ tool_shed_test_tmp_dir ]:
if os.path.exists( dir ):
- log.info( 'Cleaning up temporary files in %s' % dir )
+ log.info( "Cleaning up temporary files in %s" % dir )
shutil.rmtree( dir )
- except:
- pass
+ except:
+ pass
if success:
return 0
else:
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.
1
0
commit/galaxy-central: inithello: Added more functional tests for basic tool shed repository features.
by Bitbucket 28 Nov '12
by Bitbucket 28 Nov '12
28 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/e291341d0127/
changeset: e291341d0127
user: inithello
date: 2012-11-28 21:21:23
summary: Added more functional tests for basic tool shed repository features.
affected #: 9 files
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 lib/galaxy/webapps/community/controllers/upload.py
--- a/lib/galaxy/webapps/community/controllers/upload.py
+++ b/lib/galaxy/webapps/community/controllers/upload.py
@@ -77,7 +77,7 @@
elif file_data not in ( '', None ):
uploaded_file = file_data.file
uploaded_file_name = uploaded_file.name
- uploaded_file_filename = file_data.filename
+ uploaded_file_filename = os.path.split( file_data.filename )[ -1 ]
isempty = os.path.getsize( os.path.abspath( uploaded_file_name ) ) == 0
if uploaded_file or uploaded_directory:
ok = True
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/base/test_db_util.py
--- a/test/tool_shed/base/test_db_util.py
+++ b/test/tool_shed/base/test_db_util.py
@@ -39,7 +39,7 @@
sa_session.flush()
def refresh( obj ):
sa_session.refresh( obj )
-def get_repository_by_name( name, owner_username ):
+def get_repository_by_name_and_owner( name, owner_username ):
owner = get_user_by_name( owner_username )
repository = sa_session.query( model.Repository ) \
.filter( model.Repository.table.c.name==name ) \
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -5,6 +5,7 @@
# Security helper
self.security = security.SecurityHelper( id_secret='changethisinproductiontoo' )
self.history_id = None
+ self.hgweb_config_manager = None
self.host = os.environ.get( 'TOOL_SHED_TEST_HOST' )
self.port = os.environ.get( 'TOOL_SHED_TEST_PORT' )
self.url = "http://%s:%s" % ( self.host, self.port )
@@ -29,18 +30,67 @@
if strings_not_displayed:
for string in strings_not_displayed:
self.check_string_not_in_page( string )
- def check_for_tool_metadata(self, repository, changeset_revision, tool_id, strings_displayed=[], strings_not_displayed=[] ):
- url = '/repository/view_tool_metadata?repository_id=%s&changeset_revision=%s&tool_id=%s' % \
- ( self.security.encode_id( repository.id ), changeset_revision, tool_id )
- self.visit_url( url )
- self.check_for_strings( strings_displayed, strings_not_displayed )
- def check_for_valid_tools( self, repository ):
- self.manage_repository( repository )
- self.check_page_for_string( 'Valid tools' )
+ def check_for_valid_tools( self, repository, strings_displayed=[], strings_not_displayed=[] ):
+ strings_displayed.append( 'Valid tools' )
+ self.manage_repository( repository, strings_displayed, strings_not_displayed )
+ def check_metadata_in_repository_changelog( self, repository, metadata_count ):
+ self.check_repository_changelog( repository )
+ self.check_string_count_in_page( 'Repository metadata is associated with this change set.', metadata_count )
def check_repository_changelog( self, repository, strings_displayed=[], strings_not_displayed=[] ):
url = '/repository/view_changelog?id=%s' % self.security.encode_id( repository.id )
self.visit_url( url )
self.check_for_strings( strings_displayed, strings_not_displayed )
+ def check_repository_metadata( self, repository, tip_only=True ):
+ assert self.tip_has_metadata( repository, tip_only ), \
+ 'Repository tip is not a metadata revision: Repository tip - %s, metadata revisions - %s.' % \
+ ( self.get_repository_tip( repository ), ', '.join( self.get_repository_metadata_revisions( repository ) ) )
+ def check_repository_tools( self, repository, include_invalid=False ):
+ '''
+ Loop through each repository_metadata dict in the repository object.
+ For each of these, check for a tools attribute, and load the tool metadata
+ page if it exists, then display that tool's page.
+ '''
+ tool_list, invalid_tool_list = self.get_tools_from_repository_metadata( repository, include_invalid=include_invalid )
+ for valid_tool_dict in tool_list:
+ changeset_revision = valid_tool_dict[ 'changeset_revision' ]
+ for tool in valid_tool_dict[ 'tools' ]:
+ metadata_strings_displayed = [ tool[ 'guid' ],
+ tool[ 'version' ],
+ tool[ 'id' ],
+ tool[ 'name' ],
+ tool[ 'description' ],
+ changeset_revision ]
+ url = '/repository/view_tool_metadata?repository_id=%s&changeset_revision=%s&tool_id=%s' % \
+ ( self.security.encode_id( repository.id ), changeset_revision, tool[ 'id' ] )
+ self.visit_url( url )
+ self.check_for_strings( metadata_strings_displayed )
+ self.load_display_tool_page( repository, tool_xml_path=tool[ 'tool_config' ],
+ changeset_revision=changeset_revision,
+ strings_displayed=[ '%s (version %s)' % ( tool[ 'name' ], tool[ 'version' ] ) ],
+ strings_not_displayed=[] )
+ if include_invalid and invalid_tool_list:
+ for invalid_tool_dict in invalid_tool_list:
+ for tool in invalid_tool_dict[ 'tools' ]:
+ tool_path = '%s/%s' % ( self.get_repository_filesystem_path( repository ), tool )
+ self.load_display_tool_page( repository,
+ tool_xml_path=tool_path,
+ changeset_revision=changeset_revision,
+ strings_displayed=[ 'properly loaded' ],
+ strings_not_displayed=[] )
+ def check_string_count_in_page( self, pattern, min_count, max_count=None ):
+ """Checks the number of 'pattern' occurrences in the current browser page"""
+ page = self.last_page()
+ pattern_count = page.count( pattern )
+ if max_count is None:
+ max_count = min_count
+ # The number of occurrences of pattern in the page should be between min_count
+ # and max_count, so show error if pattern_count is less than min_count or greater
+ # than max_count.
+ if pattern_count < min_count or pattern_count > max_count:
+ fname = self.write_temp_file( page )
+ errmsg = "%i occurrences of '%s' found (min. %i, max. %i).\npage content written to '%s' " % \
+ ( pattern_count, pattern, min_count, max_count, fname )
+ raise AssertionError( errmsg )
def create_category( self, category_name, category_description ):
self.visit_url( '/admin/manage_categories?operation=create' )
tc.fv( "1", "name", category_name )
@@ -56,10 +106,43 @@
tc.fv( "1", "category_id", "+%s" % category )
tc.submit( "create_repository_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
+ def delete_files_from_repository( self, repository, filenames=[], strings_displayed=[ 'were deleted from the repository' ], strings_not_displayed=[] ):
+ files_to_delete = []
+ basepath = self.get_repository_base_path_from_browse_page( repository )
+ repository_files = self.get_repository_file_list( repository, basepath )
+ # Verify that the files to delete actually exist in the repository.
+ for filename in repository_files:
+ if filename in filenames:
+ files_to_delete.append( os.path.join( basepath, filename ) )
+ self.browse_repository( repository )
+ # Twill sets hidden form fields to read-only by default. We need to write to this field.
+ form = tc.browser.get_form( 'select_files_to_delete' )
+ form.find_control( "selected_files_to_delete" ).readonly = False
+ tc.fv( "1", "selected_files_to_delete", ','.join( files_to_delete ) )
+ tc.submit( 'select_files_to_delete_button' )
+ self.check_for_strings( strings_displayed, strings_not_displayed )
+ def display_readme_file( self, repository, changeset_revision=None, strings_displayed=[], strings_not_displayed=[] ):
+ if changeset_revision is None:
+ changeset_revision = self.get_repository_tip( repository )
+ repository_id = self.security.encode_id( repository.id )
+ self.visit_url( '/repository/view_readme?changeset_revision=%s&id=%s' % ( changeset_revision, repository_id ) )
+ self.check_for_strings( strings_displayed, strings_not_displayed )
def display_repository_clone_page( self, owner_name, repository_name, strings_displayed=[], strings_not_displayed=[] ):
url = '/repos/%s/%s' % ( owner_name, repository_name )
self.visit_url( url )
self.check_for_strings( strings_displayed, strings_not_displayed )
+ def display_repository_file_contents( self, repository, filename, filepath=None, strings_displayed=[], strings_not_displayed=[] ):
+ '''Find a file in the repository and display the contents.'''
+ basepath = self.get_repository_base_path_from_browse_page( repository )
+ repository_file_list = []
+ relative_path = filename
+ if filepath is not None:
+ relative_path = os.path.join( filepath, filename )
+ repository_file_list = self.get_repository_file_list( repository, basepath )
+ assert relative_path in repository_file_list, 'File %s not found in the repository under %s.' % ( filename, filepath )
+ url = '/repository/get_file_contents?file_path=%s' % os.path.join( basepath, relative_path ).replace( '/', '%2f' )
+ self.visit_url( url )
+ self.check_for_strings( strings_displayed, strings_not_displayed )
def edit_repository_categories( self, repository, categories_to_add=[], categories_to_remove=[], restore_original=True ):
url = '/repository/manage_repository?id=%s' % self.security.encode_id( repository.id )
self.visit_url( url )
@@ -67,10 +150,10 @@
strings_not_displayed = []
for category in categories_to_add:
tc.fv( "2", "category_id", '+%s' % category)
- strings_displayed.append( "selected>%s</option>" % category )
+ strings_displayed.append( "selected>%s" % category )
for category in categories_to_remove:
tc.fv( "2", "category_id", '-%s' % category)
- strings_not_displayed.append( "selected>%s</option>" % category )
+ strings_not_displayed.append( "selected>%s" % category )
tc.submit( "manage_categories_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
if restore_original:
@@ -78,22 +161,22 @@
strings_not_displayed = []
for category in categories_to_remove:
tc.fv( "2", "category_id", '+%s' % category)
- strings_displayed.append( "selected>%s</option>" % category )
+ strings_displayed.append( "selected>%s" % category )
for category in categories_to_add:
tc.fv( "2", "category_id", '-%s' % category)
- strings_not_displayed.append( "selected>%s</option>" % category )
+ strings_not_displayed.append( "selected>%s" % category )
tc.submit( "manage_categories_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
- def edit_repository_information( self, repository, **kwargs ):
+ def edit_repository_information( self, repository, **kwd ):
url = '/repository/manage_repository?id=%s' % self.security.encode_id( repository.id )
self.visit_url( url )
original_information = dict( repo_name=repository.name, description=repository.description, long_description=repository.long_description )
strings_displayed = []
strings_not_displayed = []
for input_elem_name in [ 'repo_name', 'description', 'long_description' ]:
- if input_elem_name in kwargs:
- tc.fv( "1", input_elem_name, kwargs[ input_elem_name ] )
- strings_displayed.append( self.escape_html( kwargs[ input_elem_name ] ) )
+ if input_elem_name in kwd:
+ tc.fv( "1", input_elem_name, kwd[ input_elem_name ] )
+ strings_displayed.append( self.escape_html( kwd[ input_elem_name ] ) )
tc.submit( "edit_repository_button" )
self.check_for_strings( strings_displayed )
strings_displayed = []
@@ -102,19 +185,84 @@
strings_displayed.append( self.escape_html( original_information[ input_elem_name ] ) )
tc.submit( "edit_repository_button" )
self.check_for_strings( strings_displayed )
- def escape_html( self, string ):
+ def escape_html( self, string, unescape=False ):
html_entities = [ ('&', 'X' ), ( "'", ''' ), ( '"', '"' ) ]
for character, replacement in html_entities:
- string = string.replace( character, replacement )
+ if unescape:
+ string = string.replace( replacement, character )
+ else:
+ string = string.replace( character, replacement )
return string
def get_latest_repository_metadata_for_repository( self, repository ):
+ # TODO: This will not work as expected. Fix it.
return repository.metadata_revisions[ 0 ]
- def get_readme( self, repository, strings_displayed=[], strings_not_displayed=[] ):
- repository_metadata = self.get_latest_repository_metadata_for_repository( repository )
- changeset_revision = repository_metadata.changeset_revision
- repository_id = self.security.encode_id( repository.id )
- self.visit_url( '/repository/view_readme?changeset_revision=%s&id=%s' % ( changeset_revision, repository_id ) )
- self.check_for_strings( strings_displayed, strings_not_displayed )
+ def get_repository_base_path_from_browse_page( self, repository ):
+ '''Get the base path from the javascript dynatree parameters. This must be updated if the browse feature is altered.'''
+ self.visit_url( '/repository/browse_repository?id=%s' % self.security.encode_id( repository.id ) )
+ html = self.last_page()
+ found_file = None
+ basepath = None
+ search = re.search( 'data: { folder_path: "([^"]+)" },', html )
+ if search is not None:
+ basepath = search.group( 1 )
+ return basepath
+ def get_repository_file_list( self, repository, base_path, current_path=None ):
+ '''
+ Recursively load repository folder contents and append them to a list. Similar to os.walk,
+ but via /repository/open_folder.
+ '''
+ if current_path is None:
+ full_path = base_path
+ else:
+ full_path = os.path.join( base_path, current_path )
+ # Get the current folder's contents.
+ url = '/repository/open_folder?folder_path=%s' % full_path.replace( '/', '%2f' )
+ self.visit_url( url )
+ file_list = from_json_string( self.last_page() )
+ returned_file_list = []
+ if current_path is not None:
+ returned_file_list.append( current_path )
+ # Loop through the json dict returned by /repository/open_folder.
+ for file_dict in file_list:
+ if file_dict[ 'isFolder' ]:
+ # This is a folder. Get the contents of the folder and append it to the list,
+ # prefixed with the path relative to the repository root, if any.
+ if current_path is None:
+ returned_file_list.extend( self.get_repository_file_list( repository, base_path, file_dict[ 'title' ] ) )
+ else:
+ sub_path = os.path.join( current_path, file_dict[ 'title' ] )
+ returned_file_list.extend( self.get_repository_file_list( repository, base_path, sub_path ) )
+ else:
+ # This is a regular file, prefix the filename with the current path and append it to the list.
+ if current_path is not None:
+ returned_file_list.append( os.path.join( current_path, file_dict[ 'title' ] ) )
+ else:
+ returned_file_list.append( file_dict[ 'title' ] )
+ return returned_file_list
+ def get_repository_metadata( self, repository ):
+ return [ metadata_revision for metadata_revision in repository.metadata_revisions ]
+ def get_repository_metadata_revisions( self, repository ):
+ return [ str( repository_metadata.changeset_revision ) for repository_metadata in repository.metadata_revisions ]
+ def get_repository_tip( self, repository ):
+ self.browse_repository( repository )
+ html = self.last_page()
+ revision_regex = re.search( 'revision ([0-9a-f]+) \(repository tip\)', html )
+ if revision_regex is not None:
+ return revision_regex.group( 1 )
+ return None
+ def get_repository_filesystem_path( self, repository ):
+ repo_subdirectory = '%03d' % int( repository.id / 1000 )
+ return os.path.join( 'database', 'community_files', repo_subdirectory, 'repo_%d' % repository.id )
+ def get_tools_from_repository_metadata( self, repository, include_invalid=False ):
+ '''Get a list of valid and (optionally) invalid tool dicts from the repository metadata.'''
+ valid_tools = []
+ invalid_tools = []
+ for repository_metadata in repository.metadata_revisions:
+ if 'tools' in repository_metadata.metadata:
+ valid_tools.append( dict( tools=repository_metadata.metadata[ 'tools' ], changeset_revision=repository_metadata.changeset_revision ) )
+ if include_invalid and 'invalid_tools' in repository_metadata.metadata:
+ invalid_tools.append( dict( tools=repository_metadata.metadata[ 'invalid_tools' ], changeset_revision=repository_metadata.changeset_revision ) )
+ return valid_tools, invalid_tools
def grant_write_access( self, repository, usernames=[], strings_displayed=[], strings_not_displayed=[] ):
self.manage_repository( repository )
tc.fv( "3", "allow_push", '-Select one' )
@@ -122,10 +270,8 @@
tc.fv( "3", "allow_push", '+%s' % username )
tc.submit( 'user_access_button' )
self.check_for_strings( strings_displayed, strings_not_displayed )
- def load_display_tool_page( self, repository, tool_xml_filename, changeset_revision, strings_displayed=[], strings_not_displayed=[] ):
+ def load_display_tool_page( self, repository, tool_xml_path, changeset_revision, strings_displayed=[], strings_not_displayed=[] ):
repository_id = self.security.encode_id( repository.id )
- repo_subdirectory = '%03d' % int( repository.id / 1000 )
- tool_xml_path = '%2f'.join( [ 'database', 'community_files', repo_subdirectory, 'repo_%d' % repository.id, tool_xml_filename ] )
url = '/repository/display_tool?repository_id=%s&tool_config=%s&changeset_revision=%s' % \
( repository_id, tool_xml_path, changeset_revision )
self.visit_url( url )
@@ -134,20 +280,37 @@
url = '/repository/manage_repository?id=%s' % self.security.encode_id( repository.id )
self.visit_url( url )
self.check_for_strings( strings_displayed, strings_not_displayed )
- def set_repository_malicious( self, repository, strings_displayed=[], strings_not_displayed=[] ):
+ def revoke_write_access( self, repository, username ):
+ url = '/repository/manage_repository?user_access_button=Remove&id=%s&remove_auth=%s' % \
+ ( self.security.encode_id( repository.id ), username )
+ self.visit_url( url )
+ def set_repository_deprecated( self, repository, set_deprecated=True, strings_displayed=[], strings_not_displayed=[] ):
+ url = '/repository/deprecate?id=%s&mark_deprecated=%s' % ( self.security.encode_id( repository.id ), set_deprecated )
+ self.visit_url( url )
+ self.check_for_strings( strings_displayed, strings_not_displayed )
+ def set_repository_malicious( self, repository, set_malicious=True, strings_displayed=[], strings_not_displayed=[] ):
self.manage_repository( repository )
- tc.fv( "malicious", "malicious", True )
+ tc.fv( "malicious", "malicious", set_malicious )
tc.submit( "malicious_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
- def unset_repository_malicious( self, repository, strings_displayed=[], strings_not_displayed=[] ):
- self.manage_repository( repository )
- tc.fv( "malicious", "malicious", False )
- tc.submit( "malicious_button" )
- self.check_for_strings( strings_displayed, strings_not_displayed )
- def upload( self, repository, filename, strings_displayed=[], strings_not_displayed=[], **kwargs ):
+ def tip_has_metadata( self, repository, tip_only=True ):
+ tip = self.get_repository_tip( repository )
+ changeset_revisions = [ str( repository_metadata.changeset_revision ) for repository_metadata in repository.metadata_revisions ]
+ if tip_only:
+ return len( changeset_revisions ) == 1 and tip in changeset_revisions
+ return tip in changeset_revisions
+ def upload_file( self,
+ repository,
+ filename,
+ valid_tools_only=True,
+ strings_displayed=[],
+ strings_not_displayed=[],
+ **kwd ):
self.visit_url( '/upload/upload?repository_id=%s' % self.security.encode_id( repository.id ) )
- for key in kwargs:
- tc.fv( "1", key, kwargs[ key ] )
+ if valid_tools_only:
+ strings_displayed.append( "has been successfully uploaded to the repository." )
+ for key in kwd:
+ tc.fv( "1", key, kwd[ key ] )
tc.formfile( "1", "file_data", self.get_filename( filename ) )
tc.submit( "upload_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -11,14 +11,14 @@
regular_email = 'test-1(a)bx.psu.edu'
regular_username = 'user1'
-repository_name = 'filter'
-repository_description = "Galaxy's filter tool"
-repository_long_description = "Long description of Galaxy's filter tool"
+repository_name = 'filtering'
+repository_description = "Galaxy's filtering tool"
+repository_long_description = "Long description of Galaxy's filtering tool"
class TestCreateRepository( ShedTwillTestCase ):
def test_0000_initiate_users( self ):
- """Create necessary users and login as an admin user."""
+ """Create necessary user accounts and login as an admin user."""
self.login( email=regular_email, username=regular_username )
regular_user = get_user( regular_email )
assert regular_user is not None, 'Problem retrieving user with email %s from the database' % regular_email
@@ -29,60 +29,117 @@
assert admin_user is not None, 'Problem retrieving user with email %s from the database' % admin_email
admin_user_private_role = get_private_role( admin_user )
def test_0005_create_categories( self ):
- """Create a category"""
+ """Create categories"""
self.create_category( 'Text Manipulation', 'Tools for manipulating text' )
self.create_category( 'Text Analysis', 'Tools for analyzing text' )
def test_0010_create_repository( self ):
- """Create a repository"""
- strings_displayed = [ '<div class="toolFormTitle">Repository %s</div>' % "'%s'" % repository_name, \
+ """Create the filtering repository"""
+ strings_displayed = [ 'Repository %s' % "'%s'" % repository_name,
'Repository %s has been created' % "'%s'" % repository_name ]
- self.create_repository( repository_name, repository_description, \
- repository_long_description=repository_long_description, \
- categories=[ 'Text Manipulation' ], \
+ self.create_repository( repository_name,
+ repository_description,
+ repository_long_description=repository_long_description,
+ categories=[ 'Text Manipulation' ],
strings_displayed=strings_displayed )
def test_0015_edit_repository( self ):
"""Edit the repository name, description, and long description"""
- repository = get_repository_by_name( repository_name, admin_username )
- new_name = "renamed_filter"
- new_description = "Edited filter tool"
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ new_name = "renamed_filtering"
+ new_description = "Edited filtering tool"
new_long_description = "Edited long description"
self.edit_repository_information( repository, repo_name=new_name, description=new_description, long_description=new_long_description )
def test_0020_change_repository_category( self ):
- """Change the category of a repository"""
- repository = get_repository_by_name( repository_name, admin_username )
+ """Change the categories associated with the filtering repository"""
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
self.edit_repository_categories( repository, categories_to_add=[ "Text Analysis" ], categories_to_remove=[ "Text Manipulation" ] )
-# def test_0025_grant_write_access( self ):
-# '''Grant write access to another user'''
-# repository = get_repository_by_name( repository_name, admin_username )
-# self.grant_write_access( repository, usernames=[ regular_username ] )
- def test_0030_upload_tarball( self ):
+ def test_0025_grant_write_access( self ):
+ '''Grant write access to another user'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.grant_write_access( repository, usernames=[ regular_username ] )
+ self.revoke_write_access( repository, regular_username )
+ def test_0030_upload_filtering_1_1_0( self ):
"""Upload filtering_1.1.0.tar to the repository"""
- repository = get_repository_by_name( repository_name, admin_username )
- self.upload( repository,
- 'filtering_1.1.0.tar',
- strings_displayed=[ "has been successfully uploaded to the repository." ],
- commit_message="Uploaded filtering 1.1.0" )
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.upload_file( repository, 'filtering_1.1.0.tar', commit_message="Uploaded filtering 1.1.0" )
+ def test_0035_verify_repository( self ):
+ '''Display basic repository pages'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ latest_changeset_revision = self.get_repository_tip( repository )
+ self.check_for_valid_tools( repository, strings_displayed=[ 'Filter1' ] )
+ self.check_metadata_in_repository_changelog( repository, metadata_count=1 )
+ self.check_repository_tools( repository )
+ self.check_repository_metadata( repository, tip_only=False )
+ self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] )
+ self.display_repository_clone_page( admin_username,
+ repository_name,
+ strings_displayed=[ 'Uploaded filtering 1.1.0', latest_changeset_revision ] )
+ def test_0040_alter_repository_states( self ):
+ '''Test toggling the malicious and deprecated repository flags.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.set_repository_malicious( repository, set_malicious=True, strings_displayed=[ 'The repository tip has been defined as malicious.' ] )
+ self.set_repository_malicious( repository,
+ set_malicious=False,
+ strings_displayed=[ 'The repository tip has been defined as <b>not</b> malicious.' ] )
+ self.set_repository_deprecated( repository,
+ strings_displayed=[ 'has been marked as deprecated', 'Mark as not deprecated' ] )
+ self.manage_repository( repository,
+ strings_displayed=[ 'This repository has been marked as deprecated' ],
+ strings_not_displayed=[ 'Upload files', 'Reset all repository metadata' ] )
+ self.set_repository_deprecated( repository,
+ strings_displayed=[ 'has been marked as not deprecated', 'Mark as deprecated' ],
+ set_deprecated=False )
+ def test_0045_display_repository_tip_file( self ):
+ '''Display the contents of a file in the repository tip revision'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.display_repository_file_contents( repository, filename='filtering.xml', strings_displayed=[ '1.1.0' ] )
+ def test_0050_upload_filtering_txt_file( self ):
+ '''Upload filtering.txt file associated with tool version 1.1.0.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.upload_file( repository,
+ 'filtering.txt',
+ commit_message="Uploaded filtering.txt",
+ uncompress_file='No',
+ remove_repo_files_not_in_tar='No' )
+ self.display_readme_file( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
+ def test_0055_upload_filtering_test_data( self ):
+ '''Upload filtering test data.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.upload_file( repository, 'filtering_test_data.tar', commit_message="Uploaded filtering test data", remove_repo_files_not_in_tar='No' )
+ self.display_repository_file_contents( repository, filename='1.bed', filepath='test-data' )
+ self.check_repository_metadata( repository, tip_only=True )
+ def test_0060_upload_filtering_2_2_0( self ):
+ '''Upload filtering version 2.2.0'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.upload_file( repository,
+ 'filtering_2.2.0.tar',
+ commit_message="Uploaded filtering 2.2.0",
+ remove_repo_files_not_in_tar='No' )
+ def test_0065_verify_filtering_repository( self ):
+ '''Verify the new tool versions and repository metadata.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
self.check_for_valid_tools( repository )
- latest_repository_metadata = self.get_latest_repository_metadata_for_repository( repository )
- changeset_revision = latest_repository_metadata.changeset_revision
- self.check_repository_changelog( repository, strings_displayed=[ 'Repository metadata is associated with this change set.' ] )
- self.set_repository_malicious( repository, strings_displayed=[ 'The repository tip has been defined as malicious.' ] )
- self.unset_repository_malicious( repository, strings_displayed=[ 'The repository tip has been defined as <b>not</b> malicious.' ] )
- self.load_display_tool_page( repository, tool_xml_filename='filtering.xml', \
- changeset_revision=changeset_revision, \
- strings_displayed=[ 'Filter (version 1.1.0)', "c1=='chr1'" ], \
- strings_not_displayed=[] )
- tool = latest_repository_metadata.metadata[ 'tools' ][0]
- metadata_strings_displayed = [ tool[ 'guid' ], tool[ 'version' ], tool[ 'id' ], tool[ 'name' ], tool[ 'description' ], changeset_revision ]
- self.check_for_tool_metadata( repository, changeset_revision, 'Filter1', strings_displayed=metadata_strings_displayed )
- def test_0035_repository_browse_page( self ):
- '''Visit the repository browse page'''
- repository = get_repository_by_name( repository_name, admin_username )
- self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] )
- def test_0040_visit_clone_url_via_hgweb( self ):
- '''Visit the repository clone URL via hgweb'''
- repository = get_repository_by_name( repository_name, admin_username )
- latest_changeset_revision = self.get_latest_repository_metadata_for_repository( repository )
- self.display_repository_clone_page( admin_username, \
- repository_name, \
- strings_displayed=[ 'Uploaded filtering 1.1.0', latest_changeset_revision.changeset_revision ] )
+ strings_displayed = self.get_repository_metadata_revisions( repository ).append( 'Select a revision' )
+ self.manage_repository( repository, strings_displayed=strings_displayed )
+ self.check_metadata_in_repository_changelog( repository, metadata_count=2 )
+ self.check_repository_tools( repository, include_invalid=False )
+ self.check_repository_metadata( repository, tip_only=False )
+ def test_0070_upload_readme_txt_file( self ):
+ '''Upload readme.txt file associated with tool version 2.2.0.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.upload_file( repository, 'readme.txt', commit_message="Uploaded readme.txt" )
+ self.display_readme_file( repository, strings_displayed=[ 'This is a readme file.' ] )
+ # Verify that there is a different readme file for each metadata revision.
+ metadata_revisions = self.get_repository_metadata_revisions( repository )
+ strings_to_check = [ 'Readme file for filtering 1.1.0', 'This is a readme file.' ]
+ for key, metadata_revision in enumerate( metadata_revisions ):
+ # Metadata revisions are in order newest to oldest at this point.
+ self.display_readme_file( repository, metadata_revision, strings_not_displayed=[ strings_to_check[ key ] ] )
+ def test_0075_delete_readme_txt_file( self ):
+ '''Delete the readme.txt file.'''
+ repository = get_repository_by_name_and_owner( repository_name, admin_username )
+ self.delete_files_from_repository( repository, filenames=[ 'readme.txt' ] )
+ self.check_metadata_in_repository_changelog( repository, metadata_count=2 )
+ # Deleting a readme file for a specific revision should make the repository fall back
+ # to a previous revision's readme file, if one exists.
+ # TODO: All readme files should be displayed.
+ self.display_readme_file( repository, strings_displayed=[ 'Readme file for filtering 1.1.0' ] )
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/functional_tests.py
--- a/test/tool_shed/functional_tests.py
+++ b/test/tool_shed/functional_tests.py
@@ -31,6 +31,7 @@
import galaxy.webapps.community.app
from galaxy.webapps.community.app import UniverseApplication
from galaxy.webapps.community import buildapp
+#from galaxy.webapps.community.util.hgweb_config import *
import nose.core
import nose.config
@@ -164,6 +165,8 @@
running_functional_tests = True,
hgweb_config_dir = new_file_path,
**kwargs )
+# app.hgweb_config_manager = HgWebConfigManager()
+
log.info( "Embedded Universe application started" )
# ---- Run webserver ------------------------------------------------------
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/test_data/filtering.txt
--- /dev/null
+++ b/test/tool_shed/test_data/filtering.txt
@@ -0,0 +1,1 @@
+Readme file for filtering 1.1.0
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/test_data/filtering_2.2.0.tar
Binary file test/tool_shed/test_data/filtering_2.2.0.tar has changed
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/test_data/filtering_test_data.tar
Binary file test/tool_shed/test_data/filtering_test_data.tar has changed
diff -r 4eaa644dd47877c40ee7090d56379998d7aecafa -r e291341d0127df0fd9bf8f66ac1d9df3f58b9bd7 test/tool_shed/test_data/readme.txt
--- /dev/null
+++ b/test/tool_shed/test_data/readme.txt
@@ -0,0 +1,1 @@
+This is a readme file.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: scatterplot: add header discovery from first dataset line; fix animation; better hover info; fix column selection; pack scripts
by Bitbucket 28 Nov '12
by Bitbucket 28 Nov '12
28 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/4eaa644dd478/
changeset: 4eaa644dd478
user: carlfeberhard
date: 2012-11-28 16:45:01
summary: scatterplot: add header discovery from first dataset line; fix animation; better hover info; fix column selection; pack scripts
affected #: 15 files
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa lib/galaxy/webapps/galaxy/api/datasets.py
--- a/lib/galaxy/webapps/galaxy/api/datasets.py
+++ b/lib/galaxy/webapps/galaxy/api/datasets.py
@@ -187,7 +187,6 @@
when the dataset is not yet indexed and hence using data would
be slow because indexes need to be created.
"""
-
# Dataset check.
msg = self.check_dataset_state( trans, dataset )
if msg:
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa lib/galaxy/webapps/galaxy/controllers/history.py
--- a/lib/galaxy/webapps/galaxy/controllers/history.py
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py
@@ -1322,4 +1322,3 @@
def get_item( self, trans, id ):
return self.get_history( trans, id )
-
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -793,9 +793,23 @@
hda = self.get_dataset( trans, dataset_id, check_ownership=False, check_accessible=True )
hda_dict = hda.get_api_value()
hda_dict[ 'id' ] = dataset_id
- if( hda_dict[ 'metadata_column_names' ] == None
- and hasattr( hda.datatype, 'column_names' ) ):
- hda_dict[ 'metadata_column_names' ] = hda.datatype.column_names
+
+ if( ( hda_dict[ 'metadata_column_names' ] == None )
+ and ( hasattr( hda.datatype, 'column_names' ) ) ):
+ hda_dict[ 'metadata_column_names' ] = hda.datatype.column_names
+
+ # try to get the first line (assuming it's a header)
+ #TODO: doesn't belong here
+ try:
+ with open( hda.file_name ) as infile:
+ for index, line in enumerate( infile ):
+ if 'comment_lines' not in hda_dict:
+ hda_dict[ 'comment_lines' ] = []
+ hda_dict[ 'comment_lines' ].append( line )
+ if index >= 3:
+ break
+ except Exception, exc:
+ log.error( str( exc ) )
history_id = trans.security.encode_id( hda.history.id )
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/mvc/visualizations/scatterplotControlForm.js
--- a/static/scripts/mvc/visualizations/scatterplotControlForm.js
+++ b/static/scripts/mvc/visualizations/scatterplotControlForm.js
@@ -67,8 +67,9 @@
//logger : console,
className : 'scatterplot-control-form',
- dataLoadDelay : 500,
- dataLoadSize : 3001,
+ //NOTE: should include time needed to render
+ dataLoadDelay : 4000,
+ dataLoadSize : 5000,
loadingIndicatorImage : 'loading_small_white_bg.gif',
fetchMsg : 'Fetching data...',
@@ -107,6 +108,16 @@
}
this.log( '\t dataset:', this.dataset );
+ // attempt to get possible headers from the data's first line
+ if( this.dataset.comment_lines && this.dataset.comment_lines.length ){
+ //TODO:??
+ var firstLine = this.dataset.comment_lines[0],
+ possibleHeaders = firstLine.split( '\t' );
+ if( possibleHeaders.length === this.dataset.metadata_column_types.length ){
+ this.possibleHeaders = possibleHeaders;
+ }
+ }
+
// passed from mako helper
//TODO: integrate to galaxyPaths
//TODO: ?? seems like data loader section would be better
@@ -194,16 +205,24 @@
// controls for which columns are used to plot datapoints (and ids/additional info to attach if desired)
var view = this,
allColumns = [],
- numericColumns = [];
-
+ numericColumns = [],
+ usePossibleHeaders = ( this.possibleHeaders && this.$dataControl )?
+ ( this.$dataControl.find( '#first-line-header-checkbox' ).is( ':checked' ) ):( false );
+
// gather column indeces (from metadata_column_types) and names (from metadata_columnnames)
_.each( this.dataset.metadata_column_types, function( type, index ){
// use a 1 based index in names/values within the form (will be dec. when parsed out)
var oneBasedIndex = index + 1,
- // label with the name if available (fall back on 'column <index>')
+ // default name is 'column <index>'...
name = 'column ' + oneBasedIndex;
+
+ // ...but label with the name if available...
if( view.dataset.metadata_column_names ){
name = view.dataset.metadata_column_names[ index ];
+
+ // ...or, use the first line as headers if the user wants
+ } else if( usePossibleHeaders ){
+ name = view.possibleHeaders[ index ];
}
// cache all columns here
@@ -218,11 +237,17 @@
// render the html
var $dataControl = this.$el.find( '.tab-pane#data-control' );
- $dataControl.append( ScatterplotControlForm.templates.dataControl({
+ $dataControl.html( ScatterplotControlForm.templates.dataControl({
allColumns : allColumns,
- numericColumns : numericColumns
+ numericColumns : numericColumns,
+ possibleHeaders : ( this.possibleHeaders )?( this.possibleHeaders.join( ', ' ) ):( '' ),
+ usePossibleHeaders : usePossibleHeaders
}));
+ if( !this.dataset.metadata_column_names && this.possibleHeaders ){
+ $dataControl.find( '#first-line-header' ).show();
+ }
+
// preset to column selectors if they were passed in the config in the query string
$dataControl.find( '#X-select' ).val( this.chartConfig.xColumn );
$dataControl.find( '#Y-select' ).val( this.chartConfig.yColumn );
@@ -288,6 +313,7 @@
// ------------------------------------------------------------------------- EVENTS
events : {
'change #include-id-checkbox' : 'toggleThirdColumnSelector',
+ 'change #first-line-header-checkbox' : 'rerenderDataControl',
'click #data-control #render-button' : 'renderChart',
'click #chart-control #render-button' : 'changeChartSettings'
},
@@ -296,6 +322,10 @@
// show/hide the id selector on the data settings panel
this.$el.find( 'select[name="ID"]' ).parent().toggle();
},
+
+ rerenderDataControl : function(){
+ this.$dataControl = this._render_dataControl();
+ },
showLoadingIndicator : function( message, callback ){
// display the loading indicator over the tab panels if hidden, update message (if passed)
@@ -577,10 +607,8 @@
settings.yLabel = ( chartSettingsYLabel === 'Y' )?
( colSelections.Y.colName ):( chartSettingsYLabel );
- settings.animDuration = 10;
- if( this.$chartControl.find( '#animDuration.checkbox-input' ).is( ':checked' ) ){
- settings.animDuration = 500;
- }
+ settings.animDuration = ( this.$chartControl.find( '#animate-chart' ).is( ':checked' ) )?
+ ( this.chart.defaults.animDuration ):( 0 );
this.log( '\t chartSettings:', settings );
return settings;
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/packed/mvc/visualizations/scatterplotControlForm.js
--- a/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js
+++ b/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js
@@ -1,1 +1,1 @@
-var ScatterplotControlForm=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",dataLoadDelay:500,dataLoadSize:3001,loadingIndicatorImage:"loading_small_white_bg.gif",fetchMsg:"Fetching data...",renderMsg:"Rendering...",initialize:function(a){this.log(this+".initialize, attributes:",a);this.dataset=null;this.chartConfig=null;this.chart=null;this.loader=null;this.$dataControl=null;this.$chartControl=null;this.$statsDisplay=null;this.$chartDisplay=null;this.dataFetch=null;this.initializeFromAttributes(a);this.initializeChart(a);this.initializeDataLoader(a)},initializeFromAttributes:function(a){if(!a||!a.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=a.dataset}if(jQuery.type(this.dataset.metadata_column_types)==="string"){this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ")}this.log("\t dataset:",this.dataset);if(!a.apiDatasetsURL){throw ("ScatterplotView requires a apiDatasetsURL")}else{this.dataURL=a.apiDatasetsURL+"/"+this.dataset.id+"?"}this.log("\t dataURL:",this.dataURL)},initializeChart:function(a){this.chartConfig=a.chartConfig||{};this.log("\t initial chartConfig:",this.chartConfig);this.chart=new TwoVarScatterplot(this.chartConfig);this.chartConfig=this.chart.config},initializeDataLoader:function(b){var a=this;this.loader=new LazyDataLoader({url:null,start:b.start||0,total:b.total||this.dataset.metadata_data_lines,delay:this.dataLoadDelay,size:this.dataLoadSize,buildUrl:function(d,c){return this.url+"&"+jQuery.param({start_val:d,max_vals:c})}});$(this.loader).bind("error",function(e,c,d){a.log("ERROR:",c,d);alert("ERROR fetching data:\n"+c+"\n"+d);a.hideLoadingIndicator()})},render:function(){this.log(this+".render");this.$el.append(ScatterplotControlForm.templates.mainLayout({loadingIndicatorImagePath:galaxy_paths.get("image_path")+"/"+this.loadingIndicatorImage,message:""}));this.$dataControl=this._render_dataControl();this.$chartControl=this._render_chartControl();this.$statsDisplay=this.$el.find(".tab-pane#stats-display");this.$chartDisplay=this._render_chartDisplay();if(this.chartConfig.xColumn&&this.chartConfig.yColumn){this.renderChart()}this.$el.find(".tooltip").tooltip();return this},_render_dataControl:function(){var b=this,a=[],d=[];_.each(this.dataset.metadata_column_types,function(h,f){var g=f+1,e="column "+g;if(b.dataset.metadata_column_names){e=b.dataset.metadata_column_names[f]}a.push({index:g,name:e});if(h==="int"||h==="float"){d.push({index:g,name:e})}});var c=this.$el.find(".tab-pane#data-control");c.append(ScatterplotControlForm.templates.dataControl({allColumns:a,numericColumns:d}));c.find("#X-select").val(this.chartConfig.xColumn);c.find("#Y-select").val(this.chartConfig.yColumn);if(this.chartConfig.idColumn!==undefined){c.find("#include-id-checkbox").attr("checked",true).trigger("change");c.find("#ID-select").val(this.chartConfig.idColumn)}return c},_render_chartControl:function(){var a=this,b=this.$el.find(".tab-pane#chart-control"),c={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};b.append(ScatterplotControlForm.templates.chartControl(this.chartConfig));b.find(".numeric-slider-input").each(function(){var f=$(this),e=f.find(".slider-output"),g=f.find(".slider"),h=f.attr("id");function d(){var j=$(this),i=j.slider("value");e.text(i)}g.slider(_.extend(c[h],{value:a.chartConfig[h],change:d,slide:d}))});return b},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");a.append(ScatterplotControlForm.templates.chartDisplay(this.chartConfig));return a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control #render-button":"renderChart","click #chart-control #render-button":"changeChartSettings"},toggleThirdColumnSelector:function(){this.$el.find('select[name="ID"]').parent().toggle()},showLoadingIndicator:function(b,c){b=b||"";var a=this.$el.find("div#loading-indicator");messageBox=a.find(".loading-message");if(a.is(":visible")){if(b){messageBox.fadeOut("fast",function(){messageBox.text(b);messageBox.fadeIn("fast",c)})}else{c()}}else{if(b){messageBox.text(b)}a.fadeIn("fast",c)}},hideLoadingIndicator:function(a){this.$el.find("div#loading-indicator").fadeOut("fast",a)},renderChart:function(){this.log(this+".renderChart");this.data=null;this.meta=null;_.extend(this.chartConfig,this.getChartSettings());this.log("\t chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);this.loader.url=this.dataURL+"&"+jQuery.param(this.getDataSettings());this.log("\t loader: total lines:",this.loader.total," url:",this.loader.url);var a=this;$(this.loader).bind("loaded.new",function(c,b){a.log(a+" loaded.new",b);a.postProcessDataFetchResponse(b);a.log("\t postprocessed data:",a.data);a.log("\t postprocessed meta:",a.meta);a.showLoadingIndicator(a.renderMsg,function(){a.chart.render(a.data,a.meta);a.renderStats(a.data,a.meta);a.hideLoadingIndicator()})});$(this.loader).bind("complete",function(b,c){a.log(a+" complete",c);$(a.loader).unbind()});a.showLoadingIndicator(a.fetchMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.loader.load()})},renderStats:function(){this.log(this+".renderStats");this.$statsDisplay.html(ScatterplotControlForm.templates.statsDisplay({stats:[{name:"Count",xval:this.meta[0].count,yval:this.meta[1].count},{name:"Min",xval:this.meta[0].min,yval:this.meta[1].min},{name:"Max",xval:this.meta[0].max,yval:this.meta[1].max},{name:"Sum",xval:this.meta[0].sum,yval:this.meta[1].sum},{name:"Mean",xval:this.meta[0].mean,yval:this.meta[1].mean},{name:"Median",xval:this.meta[0].median,yval:this.meta[1].median}]}))},changeChartSettings:function(){var a=this;newChartSettings=this.getChartSettings();_.extend(this.chartConfig,newChartSettings);this.log("this.chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);if(a.data&&a.meta){a.showLoadingIndicator(a.renderMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.chart.render(a.data,a.meta);a.hideLoadingIndicator()})}else{this.renderChart()}},postProcessDataFetchResponse:function(a){this.postProcessData(a.data);this.postProcessMeta(a.meta)},postProcessData:function(b){var a=this;if(a.data){_.each(b,function(d,c){a.data[c]=a.data[c].concat(d)})}else{a.data=b}},postProcessMeta:function(c){var a=this,b=this.dataset.metadata_column_types;if(a.meta){_.each(c,function(e,d){var i=a.meta[d],g=b[d];i.count+=(e.count)?(e.count):(0);if((g==="int")||(g==="float")){i.min=Math.min(e.min,i.min);i.max=Math.max(e.max,i.max);i.sum=e.sum+i.sum;i.mean=(i.count)?(i.sum/i.count):(null);var f=a.data[d].slice().sort(),h=Math.floor(f.length/2);if(f.length%2===0){i.median=((f[h]+f[(h+1)])/2)}else{i.median=f[h]}}})}else{a.meta=c}},getDataSettings:function(){var b=this.getColumnSelections(),a=[];this.log("\t columnSelections:",b);a=[b.X.colIndex-1,b.Y.colIndex-1];if(this.$dataControl.find("#include-id-checkbox").attr("checked")){a.push(b.ID.colIndex-1)}var c={data_type:"raw_data",columns:"["+a+"]"};this.log("\t data settings (url params):",c);return c},getColumnSelections:function(){var a={};this.$dataControl.find("div.column-select select").each(function(){var b=$(this),c=b.val();a[b.attr("name")]={colIndex:c,colName:b.children('[value="'+c+'"]').text()}});return a},getChartSettings:function(){var c={},d=this.getColumnSelections();c.datapointSize=this.$chartControl.find("#datapointSize.numeric-slider-input").find(".slider").slider("value");c.width=this.$chartControl.find("#width.numeric-slider-input").find(".slider").slider("value");c.height=this.$chartControl.find("#height.numeric-slider-input").find(".slider").slider("value");var b=this.$chartControl.find("input#X-axis-label").val(),a=this.$chartControl.find("input#Y-axis-label").val();c.xLabel=(b==="X")?(d.X.colName):(b);c.yLabel=(a==="Y")?(d.Y.colName):(a);c.animDuration=10;if(this.$chartControl.find("#animDuration.checkbox-input").is(":checked")){c.animDuration=500}this.log("\t chartSettings:",c);return c},toString:function(){return"ScatterplotControlForm("+((this.dataset)?(this.dataset.id):(""))+")"}});ScatterplotControlForm.templates={mainLayout:Handlebars.templates["template-visualization-scatterplotControlForm"],dataControl:Handlebars.templates["template-visualization-dataControl"],chartControl:Handlebars.templates["template-visualization-chartControl"],statsDisplay:Handlebars.templates["template-visualization-statsDisplay"],chartDisplay:Handlebars.templates["template-visualization-chartDisplay"]};
\ No newline at end of file
+var ScatterplotControlForm=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",dataLoadDelay:4000,dataLoadSize:5000,loadingIndicatorImage:"loading_small_white_bg.gif",fetchMsg:"Fetching data...",renderMsg:"Rendering...",initialize:function(a){this.log(this+".initialize, attributes:",a);this.dataset=null;this.chartConfig=null;this.chart=null;this.loader=null;this.$dataControl=null;this.$chartControl=null;this.$statsDisplay=null;this.$chartDisplay=null;this.dataFetch=null;this.initializeFromAttributes(a);this.initializeChart(a);this.initializeDataLoader(a)},initializeFromAttributes:function(a){if(!a||!a.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=a.dataset}if(jQuery.type(this.dataset.metadata_column_types)==="string"){this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ")}this.log("\t dataset:",this.dataset);if(this.dataset.comment_lines&&this.dataset.comment_lines.length){var b=this.dataset.comment_lines[0],c=b.split("\t");if(c.length===this.dataset.metadata_column_types.length){this.possibleHeaders=c}}if(!a.apiDatasetsURL){throw ("ScatterplotView requires a apiDatasetsURL")}else{this.dataURL=a.apiDatasetsURL+"/"+this.dataset.id+"?"}this.log("\t dataURL:",this.dataURL)},initializeChart:function(a){this.chartConfig=a.chartConfig||{};this.log("\t initial chartConfig:",this.chartConfig);this.chart=new TwoVarScatterplot(this.chartConfig);this.chartConfig=this.chart.config},initializeDataLoader:function(b){var a=this;this.loader=new LazyDataLoader({url:null,start:b.start||0,total:b.total||this.dataset.metadata_data_lines,delay:this.dataLoadDelay,size:this.dataLoadSize,buildUrl:function(d,c){return this.url+"&"+jQuery.param({start_val:d,max_vals:c})}});$(this.loader).bind("error",function(e,c,d){a.log("ERROR:",c,d);alert("ERROR fetching data:\n"+c+"\n"+d);a.hideLoadingIndicator()})},render:function(){this.log(this+".render");this.$el.append(ScatterplotControlForm.templates.mainLayout({loadingIndicatorImagePath:galaxy_paths.get("image_path")+"/"+this.loadingIndicatorImage,message:""}));this.$dataControl=this._render_dataControl();this.$chartControl=this._render_chartControl();this.$statsDisplay=this.$el.find(".tab-pane#stats-display");this.$chartDisplay=this._render_chartDisplay();if(this.chartConfig.xColumn&&this.chartConfig.yColumn){this.renderChart()}this.$el.find(".tooltip").tooltip();return this},_render_dataControl:function(){var b=this,a=[],e=[],c=(this.possibleHeaders&&this.$dataControl)?(this.$dataControl.find("#first-line-header-checkbox").is(":checked")):(false);_.each(this.dataset.metadata_column_types,function(i,g){var h=g+1,f="column "+h;if(b.dataset.metadata_column_names){f=b.dataset.metadata_column_names[g]}else{if(c){f=b.possibleHeaders[g]}}a.push({index:h,name:f});if(i==="int"||i==="float"){e.push({index:h,name:f})}});var d=this.$el.find(".tab-pane#data-control");d.html(ScatterplotControlForm.templates.dataControl({allColumns:a,numericColumns:e,possibleHeaders:(this.possibleHeaders)?(this.possibleHeaders.join(", ")):(""),usePossibleHeaders:c}));if(!this.dataset.metadata_column_names&&this.possibleHeaders){d.find("#first-line-header").show()}d.find("#X-select").val(this.chartConfig.xColumn);d.find("#Y-select").val(this.chartConfig.yColumn);if(this.chartConfig.idColumn!==undefined){d.find("#include-id-checkbox").attr("checked",true).trigger("change");d.find("#ID-select").val(this.chartConfig.idColumn)}return d},_render_chartControl:function(){var a=this,b=this.$el.find(".tab-pane#chart-control"),c={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};b.append(ScatterplotControlForm.templates.chartControl(this.chartConfig));b.find(".numeric-slider-input").each(function(){var f=$(this),e=f.find(".slider-output"),g=f.find(".slider"),h=f.attr("id");function d(){var j=$(this),i=j.slider("value");e.text(i)}g.slider(_.extend(c[h],{value:a.chartConfig[h],change:d,slide:d}))});return b},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");a.append(ScatterplotControlForm.templates.chartDisplay(this.chartConfig));return a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","change #first-line-header-checkbox":"rerenderDataControl","click #data-control #render-button":"renderChart","click #chart-control #render-button":"changeChartSettings"},toggleThirdColumnSelector:function(){this.$el.find('select[name="ID"]').parent().toggle()},rerenderDataControl:function(){this.$dataControl=this._render_dataControl()},showLoadingIndicator:function(b,c){b=b||"";var a=this.$el.find("div#loading-indicator");messageBox=a.find(".loading-message");if(a.is(":visible")){if(b){messageBox.fadeOut("fast",function(){messageBox.text(b);messageBox.fadeIn("fast",c)})}else{c()}}else{if(b){messageBox.text(b)}a.fadeIn("fast",c)}},hideLoadingIndicator:function(a){this.$el.find("div#loading-indicator").fadeOut("fast",a)},renderChart:function(){this.log(this+".renderChart");this.data=null;this.meta=null;_.extend(this.chartConfig,this.getChartSettings());this.log("\t chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);this.loader.url=this.dataURL+"&"+jQuery.param(this.getDataSettings());this.log("\t loader: total lines:",this.loader.total," url:",this.loader.url);var a=this;$(this.loader).bind("loaded.new",function(c,b){a.log(a+" loaded.new",b);a.postProcessDataFetchResponse(b);a.log("\t postprocessed data:",a.data);a.log("\t postprocessed meta:",a.meta);a.showLoadingIndicator(a.renderMsg,function(){a.chart.render(a.data,a.meta);a.renderStats(a.data,a.meta);a.hideLoadingIndicator()})});$(this.loader).bind("complete",function(b,c){a.log(a+" complete",c);$(a.loader).unbind()});a.showLoadingIndicator(a.fetchMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.loader.load()})},renderStats:function(){this.log(this+".renderStats");this.$statsDisplay.html(ScatterplotControlForm.templates.statsDisplay({stats:[{name:"Count",xval:this.meta[0].count,yval:this.meta[1].count},{name:"Min",xval:this.meta[0].min,yval:this.meta[1].min},{name:"Max",xval:this.meta[0].max,yval:this.meta[1].max},{name:"Sum",xval:this.meta[0].sum,yval:this.meta[1].sum},{name:"Mean",xval:this.meta[0].mean,yval:this.meta[1].mean},{name:"Median",xval:this.meta[0].median,yval:this.meta[1].median}]}))},changeChartSettings:function(){var a=this;newChartSettings=this.getChartSettings();_.extend(this.chartConfig,newChartSettings);this.log("this.chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);if(a.data&&a.meta){a.showLoadingIndicator(a.renderMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.chart.render(a.data,a.meta);a.hideLoadingIndicator()})}else{this.renderChart()}},postProcessDataFetchResponse:function(a){this.postProcessData(a.data);this.postProcessMeta(a.meta)},postProcessData:function(b){var a=this;if(a.data){_.each(b,function(d,c){a.data[c]=a.data[c].concat(d)})}else{a.data=b}},postProcessMeta:function(c){var a=this,b=this.dataset.metadata_column_types;if(a.meta){_.each(c,function(e,d){var i=a.meta[d],g=b[d];i.count+=(e.count)?(e.count):(0);if((g==="int")||(g==="float")){i.min=Math.min(e.min,i.min);i.max=Math.max(e.max,i.max);i.sum=e.sum+i.sum;i.mean=(i.count)?(i.sum/i.count):(null);var f=a.data[d].slice().sort(),h=Math.floor(f.length/2);if(f.length%2===0){i.median=((f[h]+f[(h+1)])/2)}else{i.median=f[h]}}})}else{a.meta=c}},getDataSettings:function(){var b=this.getColumnSelections(),a=[];this.log("\t columnSelections:",b);a=[b.X.colIndex-1,b.Y.colIndex-1];if(this.$dataControl.find("#include-id-checkbox").attr("checked")){a.push(b.ID.colIndex-1)}var c={data_type:"raw_data",columns:"["+a+"]"};this.log("\t data settings (url params):",c);return c},getColumnSelections:function(){var a={};this.$dataControl.find("div.column-select select").each(function(){var b=$(this),c=b.val();a[b.attr("name")]={colIndex:c,colName:b.children('[value="'+c+'"]').text()}});return a},getChartSettings:function(){var c={},d=this.getColumnSelections();c.datapointSize=this.$chartControl.find("#datapointSize.numeric-slider-input").find(".slider").slider("value");c.width=this.$chartControl.find("#width.numeric-slider-input").find(".slider").slider("value");c.height=this.$chartControl.find("#height.numeric-slider-input").find(".slider").slider("value");var b=this.$chartControl.find("input#X-axis-label").val(),a=this.$chartControl.find("input#Y-axis-label").val();c.xLabel=(b==="X")?(d.X.colName):(b);c.yLabel=(a==="Y")?(d.Y.colName):(a);c.animDuration=(this.$chartControl.find("#animate-chart").is(":checked"))?(this.chart.defaults.animDuration):(0);this.log("\t chartSettings:",c);return c},toString:function(){return"ScatterplotControlForm("+((this.dataset)?(this.dataset.id):(""))+")"}});ScatterplotControlForm.templates={mainLayout:Handlebars.templates["template-visualization-scatterplotControlForm"],dataControl:Handlebars.templates["template-visualization-dataControl"],chartControl:Handlebars.templates["template-visualization-chartControl"],statsDisplay:Handlebars.templates["template-visualization-statsDisplay"],chartDisplay:Handlebars.templates["template-visualization-chartDisplay"]};
\ No newline at end of file
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/packed/templates/compiled/template-visualization-chartControl.js
--- a/static/scripts/packed/templates/compiled/template-visualization-chartControl.js
+++ b/static/scripts/packed/templates/compiled/template-visualization-chartControl.js
@@ -1,1 +1,1 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartControl"]=b(function(f,m,e,l,k){e=e||f.helpers;var i="",c,h,g="function",j=this.escapeExpression,n=this;function d(p,o){return' checked="true"'}i+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n </p>\n\n <div id="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">';h=e.datapointSize;if(h){c=h.call(m,{hash:{}})}else{c=m.datapointSize;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id="animDuration" class="form-input checkbox-input">\n <label for="animated">Animate graph transitions?: </label>\n <input type="checkbox" id="animated"\n class="checkbox control"';c=m.animDuration;c=e["if"].call(m,c,{hash:{},inverse:n.noop,fn:n.program(1,d,k)});if(c||c===0){i+=c}i+=' />\n <p class="form-help help-text-small">\n Uncheck this to disable the animations used on the graph\n </p>\n </div>\n\n <div id="width" class="form-input numeric-slider-input">\n <label for="width">Graph width: </label>\n <div class="slider-output">';h=e.width;if(h){c=h.call(m,{hash:{}})}else{c=m.width;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id="height" class="form-input numeric-slider-input">\n <label for="height">Graph height: </label>\n <div class="slider-output">';h=e.height;if(h){c=h.call(m,{hash:{}})}else{c=m.height;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="';h=e.xLabel;if(h){c=h.call(m,{hash:{}})}else{c=m.xLabel;c=typeof c===g?c():c}i+=j(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <div id="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="';h=e.yLabel;if(h){c=h.call(m,{hash:{}})}else{c=m.yLabel;c=typeof c===g?c():c}i+=j(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <input id="render-button" type="button" value="Draw" />';return i})})();
\ No newline at end of file
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartControl"]=b(function(f,m,e,l,k){e=e||f.helpers;var i="",c,h,g="function",j=this.escapeExpression,n=this;function d(p,o){return' checked="true"'}i+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n </p>\n\n <div id="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">';h=e.datapointSize;if(h){c=h.call(m,{hash:{}})}else{c=m.datapointSize;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id="animDuration" class="form-input checkbox-input">\n <label for="animate-chart">Animate chart transitions?: </label>\n <input type="checkbox" id="animate-chart"\n class="checkbox control"';c=m.animDuration;c=e["if"].call(m,c,{hash:{},inverse:n.noop,fn:n.program(1,d,k)});if(c||c===0){i+=c}i+=' />\n <p class="form-help help-text-small">\n Uncheck this to disable the animations used on the chart\n </p>\n </div>\n\n <div id="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">';h=e.width;if(h){c=h.call(m,{hash:{}})}else{c=m.width;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">';h=e.height;if(h){c=h.call(m,{hash:{}})}else{c=m.height;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="';h=e.xLabel;if(h){c=h.call(m,{hash:{}})}else{c=m.xLabel;c=typeof c===g?c():c}i+=j(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <div id="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="';h=e.yLabel;if(h){c=h.call(m,{hash:{}})}else{c=m.yLabel;c=typeof c===g?c():c}i+=j(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <input id="render-button" type="button" value="Draw" />';return i})})();
\ No newline at end of file
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/packed/templates/compiled/template-visualization-dataControl.js
--- a/static/scripts/packed/templates/compiled/template-visualization-dataControl.js
+++ b/static/scripts/packed/templates/compiled/template-visualization-dataControl.js
@@ -1,1 +1,1 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-dataControl"]=b(function(g,m,f,l,k){f=f||g.helpers;var i="",d,h="function",j=this.escapeExpression,o=this;function e(t,s){var q="",r,p;q+='\n <option value="';p=f.index;if(p){r=p.call(t,{hash:{}})}else{r=t.index;r=typeof r===h?r():r}q+=j(r)+'">';p=f.name;if(p){r=p.call(t,{hash:{}})}else{r=t.name;r=typeof r===h?r():r}q+=j(r)+"</option>\n ";return q}function c(t,s){var q="",r,p;q+='\n <option value="';p=f.index;if(p){r=p.call(t,{hash:{}})}else{r=t.index;r=typeof r===h?r():r}q+=j(r)+'">';p=f.name;if(p){r=p.call(t,{hash:{}})}else{r=t.name;r=typeof r===h?r():r}q+=j(r)+"</option>\n ";return q}function n(t,s){var q="",r,p;q+='\n <option value="';p=f.index;if(p){r=p.call(t,{hash:{}})}else{r=t.index;r=typeof r===h?r():r}q+=j(r)+'">';p=f.name;if(p){r=p.call(t,{hash:{}})}else{r=t.name;r=typeof r===h?r():r}q+=j(r)+"</option>\n ";return q}i+="<p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n ";i+='\n <div class="column-select">\n <label for="X-select">Data column for X: </label>\n <select name="X" id="X-select">\n ';d=m.numericColumns;d=f.each.call(m,d,{hash:{},inverse:o.noop,fn:o.program(1,e,k)});if(d||d===0){i+=d}i+='\n </select>\n </div>\n <div class="column-select">\n <label for="Y-select">Data column for Y: </label>\n <select name="Y" id="Y-select">\n ';d=m.numericColumns;d=f.each.call(m,d,{hash:{},inverse:o.noop,fn:o.program(3,c,k)});if(d||d===0){i+=d}i+="\n </select>\n </div>\n\n ";i+='\n <div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="ID" id="ID-select">\n ';d=m.allColumns;d=f.each.call(m,d,{hash:{},inverse:o.noop,fn:o.program(5,n,k)});if(d||d===0){i+=d}i+='\n </select>\n </div>\n\n <input id="render-button" type="button" value="Draw" />\n <div class="clear"></div>';return i})})();
\ No newline at end of file
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-dataControl"]=b(function(g,n,f,m,l){f=f||g.helpers;var j="",d,i,h="function",k=this.escapeExpression,q=this;function e(v,u){var s="",t,r;s+='\n <option value="';r=f.index;if(r){t=r.call(v,{hash:{}})}else{t=v.index;t=typeof t===h?t():t}s+=k(t)+'">';r=f.name;if(r){t=r.call(v,{hash:{}})}else{t=v.name;t=typeof t===h?t():t}s+=k(t)+"</option>\n ";return s}function c(v,u){var s="",t,r;s+='\n <option value="';r=f.index;if(r){t=r.call(v,{hash:{}})}else{t=v.index;t=typeof t===h?t():t}s+=k(t)+'">';r=f.name;if(r){t=r.call(v,{hash:{}})}else{t=v.name;t=typeof t===h?t():t}s+=k(t)+"</option>\n ";return s}function p(v,u){var s="",t,r;s+='\n <option value="';r=f.index;if(r){t=r.call(v,{hash:{}})}else{t=v.index;t=typeof t===h?t():t}s+=k(t)+'">';r=f.name;if(r){t=r.call(v,{hash:{}})}else{t=v.name;t=typeof t===h?t():t}s+=k(t)+"</option>\n ";return s}function o(s,r){return'checked="true"'}j+="<p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n ";j+='\n <div class="column-select">\n <label for="X-select">Data column for X: </label>\n <select name="X" id="X-select">\n ';d=n.numericColumns;d=f.each.call(n,d,{hash:{},inverse:q.noop,fn:q.program(1,e,l)});if(d||d===0){j+=d}j+='\n </select>\n </div>\n <div class="column-select">\n <label for="Y-select">Data column for Y: </label>\n <select name="Y" id="Y-select">\n ';d=n.numericColumns;d=f.each.call(n,d,{hash:{},inverse:q.noop,fn:q.program(3,c,l)});if(d||d===0){j+=d}j+="\n </select>\n </div>\n\n ";j+='\n <div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="ID" id="ID-select">\n ';d=n.allColumns;d=f.each.call(n,d,{hash:{},inverse:q.noop,fn:q.program(5,p,l)});if(d||d===0){j+=d}j+="\n </select>\n </div>\n\n ";j+='\n <div id="first-line-header" style="display: none;">\n <p>Possible headers: ';i=f.possibleHeaders;if(i){d=i.call(n,{hash:{}})}else{d=n.possibleHeaders;d=typeof d===h?d():d}j+=k(d)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ';d=n.usePossibleHeaders;d=f["if"].call(n,d,{hash:{},inverse:q.noop,fn:q.program(7,o,l)});if(d||d===0){j+=d}j+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n </div>\n\n <input id="render-button" type="button" value="Draw" />\n <div class="clear"></div>';return j})})();
\ No newline at end of file
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/packed/utils/LazyDataLoader.js
--- a/static/scripts/packed/utils/LazyDataLoader.js
+++ b/static/scripts/packed/utils/LazyDataLoader.js
@@ -1,1 +1,1 @@
-function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:500,start:0,size:1000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a};
\ No newline at end of file
+function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:4000,start:0,size:4000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a};
\ No newline at end of file
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/packed/viz/scatterplot.js
--- a/static/scripts/packed/viz/scatterplot.js
+++ b/static/scripts/packed/viz/scatterplot.js
@@ -1,1 +1,1 @@
-function TwoVarScatterplot(d){var b=10,f=7,e=10,c=8,a=5;this.log=function(){if(this.debugging&&console&&console.debug){var g=Array.prototype.slice.call(arguments);g.unshift(this.toString());console.debug.apply(console,g)}};this.log("new TwoVarScatterplot:",d);this.defaults={id:"TwoVarScatterplot",containerSelector:"body",maxDataPoints:30000,datapointSize:4,animDuration:500,xNumTicks:10,yNumTicks:10,xAxisLabelBumpY:40,yAxisLabelBumpX:-35,width:400,height:400,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,xMin:null,xMax:null,yMin:null,yMax:null,xLabel:"X",yLabel:"Y"};this.config=_.extend({},this.defaults,d);this.log("intial config:",this.config);this.updateConfig=function(g,h){_.extend(this.config,g);this.log(this+".updateConfig:",this.config)};this.toString=function(){return this.config.id};this.translateStr=function(g,h){return"translate("+g+","+h+")"};this.rotateStr=function(h,g,i){return"rotate("+h+","+g+","+i+")"};this.adjustChartDimensions=function(j,h,g,i){j=j||0;h=h||0;g=g||0;i=i||0;this.svg.attr("width",this.config.width+(this.config.marginRight+h)+(this.config.marginLeft+i)).attr("height",this.config.height+(this.config.marginTop+j)+(this.config.marginBottom+g)).style("display","block");this.content=this.svg.select("g.content").attr("transform",this.translateStr(this.config.marginLeft+i,this.config.marginTop+j))};this.preprocessData=function(i,h,g){return(i.length>this.config.maxDataPoints)?(i.slice(0,this.config.maxDataPoints)):(i)};this.findMinMaxes=function(g,i,h){this.xMin=this.config.xMin||(h)?(h[0].min):(d3.min(g));this.xMax=this.config.xMax||(h)?(h[0].max):(d3.max(g));this.yMin=this.config.yMin||(h)?(h[1].min):(d3.min(i));this.yMax=this.config.yMax||(h)?(h[1].max):(d3.max(i))};this.setUpScales=function(){this.xScale=d3.scale.linear().domain([this.xMin,this.xMax]).range([0,this.config.width]),this.yScale=d3.scale.linear().domain([this.yMin,this.yMax]).range([this.config.height,0])};this.setUpXAxis=function(){this.xAxisFn=d3.svg.axis().scale(this.xScale).ticks(this.config.xNumTicks).orient("bottom");this.xAxis.attr("transform",this.translateStr(0,this.config.height)).call(this.xAxisFn);var g=d3.max(_.map([this.xMin,this.xMax],function(h){return(String(h)).length}));if(g>=a){this.xAxis.selectAll("g").filter(":nth-child(odd)").style("display","none")}this.log("this.config.xLabel:",this.config.xLabel);this.xAxisLabel.attr("x",this.config.width/2).attr("y",this.config.xAxisLabelBumpY).attr("text-anchor","middle").text(this.config.xLabel);this.log("xAxisLabel:",this.xAxisLabel)};this.setUpYAxis=function(){this.yAxisFn=d3.svg.axis().scale(this.yScale).ticks(this.config.yNumTicks).orient("left");this.yAxis.call(this.yAxisFn);var g=this.yAxis.selectAll("text").filter(function(k,j){return j!==0});this.log("yTickLabels:",g);this.yLongestLabel=d3.max(g[0].map(function(k,j){return(d3.select(k).text()).length}))||0;var h=b+(this.yLongestLabel*f)+c+e;this.config.yAxisLabelBumpX=-(h-e);if(this.config.marginLeft<h){var i=(h)-this.config.marginLeft;i=(i<0)?(0):(i);this.adjustChartDimensions(0,0,0,i)}this.yAxisLabel.attr("x",this.config.yAxisLabelBumpX).attr("y",this.config.height/2).attr("text-anchor","middle").attr("transform",this.rotateStr(-90,this.config.yAxisLabelBumpX,this.config.height/2)).text(this.config.yLabel)};this.renderGrid=function(){this.vGridLines=this.content.selectAll("line.v-grid-line").data(this.xScale.ticks(this.xAxisFn.ticks()[0]));this.vGridLines.enter().append("svg:line").classed("grid-line v-grid-line",true);this.vGridLines.attr("x1",this.xScale).attr("y1",0).attr("x2",this.xScale).attr("y2",this.config.height);this.vGridLines.exit().remove();this.hGridLines=this.content.selectAll("line.h-grid-line").data(this.yScale.ticks(this.yAxisFn.ticks()[0]));this.hGridLines.enter().append("svg:line").classed("grid-line h-grid-line",true);this.hGridLines.attr("x1",0).attr("y1",this.yScale).attr("x2",this.config.width).attr("y2",this.yScale);this.hGridLines.exit().remove()};this.glyphEnterState=function(g){};this.glyphFinalState=function(g){};this.glyphExitState=function(g){};this.renderDatapoints=function(g,j,h){this.log(this+".renderDatapoints",arguments);var i=0;this.datapoints=this.addDatapoints(g,j,h,".glyph");this.datapoints.exit().each(function(){i+=1}).transition().duration(this.config.animDuration).attr("cy",this.config.height).attr("r",0).remove();this.log(i," glyphs removed")};this.addDatapoints=function(k,i,g,j){this.log(this+".addDatapoints",arguments);var n=this,m=0,o=function(q,p){return n.xScale(k[p])},h=function(q,p){return n.yScale(i[p])};var l=this.content.selectAll(j);this.log("existing datapoints:",l);l=l.data(k);m=0;l.enter().append("svg:circle").each(function(){m+=1}).classed("glyph",true).attr("cx",o).attr("cy",h).attr("r",0);this.log(m," new glyphs created");m=0;l.transition().duration(this.config.animDuration).each(function(){m+=1}).attr("cx",o).attr("cy",h).attr("r",n.config.datapointSize);this.log(m," existing glyphs transitioned");if(g){l.attr("data",function(q,p){return(g[p])})}l.attr("svg:title",function(q,p){return((g)?(g[p]+": "):(""))+k[p]+", "+i[p]});l.on("mouseover",function(r,p){var q=d3.select(this);q.style("fill","red").style("fill-opacity",1);n.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",q.attr("cx")).attr("y1",q.attr("cy")).attr("x2",0).attr("y2",q.attr("cy")).classed("hoverline",true);n.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",q.attr("cx")).attr("y1",q.attr("cy")).attr("x2",q.attr("cx")).attr("y2",n.config.height).classed("hoverline",true)}).on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",0.2);d3.selectAll(".hoverline").remove()});return l};this.render=function(i,j){this.log(this+".render",arguments);this.log("\t config:",this.config);var g=i[0],k=i[1],h=(i.length>2)?(i[2]):(undefined);g=this.preprocessData(g);k=this.preprocessData(k);this.log("xCol len",g.length,"yCol len",k.length);this.findMinMaxes(g,k,j);this.setUpScales();if(!this.svg){this.svg=d3.select("svg").attr("class","chart")}if(!this.content){this.content=this.svg.append("svg:g").attr("class","content").attr("id",this.config.id)}this.adjustChartDimensions();if(!this.xAxis){this.xAxis=this.content.append("g").attr("class","axis").attr("id","x-axis")}if(!this.xAxisLabel){this.xAxisLabel=this.xAxis.append("text").attr("class","axis-label").attr("id","x-axis-label")}if(!this.yAxis){this.yAxis=this.content.append("g").attr("class","axis").attr("id","y-axis")}if(!this.yAxisLabel){this.yAxisLabel=this.yAxis.append("text").attr("class","axis-label").attr("id","y-axis-label")}this.setUpXAxis();this.setUpYAxis();this.renderGrid();this.renderDatapoints(g,k,h)}};
\ No newline at end of file
+function TwoVarScatterplot(d){var b=10,f=7,e=10,c=8,a=5;this.log=function(){if(this.debugging&&console&&console.debug){var g=Array.prototype.slice.call(arguments);g.unshift(this.toString());console.debug.apply(console,g)}};this.log("new TwoVarScatterplot:",d);this.defaults={id:"TwoVarScatterplot",containerSelector:"body",maxDataPoints:30000,datapointSize:4,animDuration:500,xNumTicks:10,yNumTicks:10,xAxisLabelBumpY:40,yAxisLabelBumpX:-40,width:400,height:400,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,xMin:null,xMax:null,yMin:null,yMax:null,xLabel:"X",yLabel:"Y"};this.config=_.extend({},this.defaults,d);this.log("intial config:",this.config);this.updateConfig=function(g,h){_.extend(this.config,g);this.log(this+".updateConfig:",this.config)};this.toString=function(){return this.config.id};this.translateStr=function(g,h){return"translate("+g+","+h+")"};this.rotateStr=function(h,g,i){return"rotate("+h+","+g+","+i+")"};this.adjustChartDimensions=function(j,h,g,i){j=j||0;h=h||0;g=g||0;i=i||0;this.svg.attr("width",this.config.width+(this.config.marginRight+h)+(this.config.marginLeft+i)).attr("height",this.config.height+(this.config.marginTop+j)+(this.config.marginBottom+g)).style("display","block");this.content=this.svg.select("g.content").attr("transform",this.translateStr(this.config.marginLeft+i,this.config.marginTop+j))};this.preprocessData=function(i,h,g){return(i.length>this.config.maxDataPoints)?(i.slice(0,this.config.maxDataPoints)):(i)};this.findMinMaxes=function(g,i,h){this.xMin=this.config.xMin||(h)?(h[0].min):(d3.min(g));this.xMax=this.config.xMax||(h)?(h[0].max):(d3.max(g));this.yMin=this.config.yMin||(h)?(h[1].min):(d3.min(i));this.yMax=this.config.yMax||(h)?(h[1].max):(d3.max(i))};this.setUpScales=function(){this.xScale=d3.scale.linear().domain([this.xMin,this.xMax]).range([0,this.config.width]),this.yScale=d3.scale.linear().domain([this.yMin,this.yMax]).range([this.config.height,0])};this.setUpXAxis=function(){this.xAxisFn=d3.svg.axis().scale(this.xScale).ticks(this.config.xNumTicks).orient("bottom");this.xAxis.attr("transform",this.translateStr(0,this.config.height)).call(this.xAxisFn);var g=d3.max(_.map([this.xMin,this.xMax],function(h){return(String(h)).length}));if(g>=a){this.xAxis.selectAll("g").filter(":nth-child(odd)").style("display","none")}this.log("this.config.xLabel:",this.config.xLabel);this.xAxisLabel.attr("x",this.config.width/2).attr("y",this.config.xAxisLabelBumpY).attr("text-anchor","middle").text(this.config.xLabel);this.log("xAxisLabel:",this.xAxisLabel)};this.setUpYAxis=function(){this.yAxisFn=d3.svg.axis().scale(this.yScale).ticks(this.config.yNumTicks).orient("left");this.yAxis.call(this.yAxisFn);var g=this.yAxis.selectAll("text").filter(function(k,j){return j!==0});this.log("yTickLabels:",g);this.yLongestLabel=d3.max(g[0].map(function(k,j){return(d3.select(k).text()).length}))||0;var h=b+(this.yLongestLabel*f)+c+e;this.config.yAxisLabelBumpX=-(h-e);if(this.config.marginLeft<h){var i=(h)-this.config.marginLeft;i=(i<0)?(0):(i);this.adjustChartDimensions(0,0,0,i)}this.yAxisLabel.attr("x",this.config.yAxisLabelBumpX).attr("y",this.config.height/2).attr("text-anchor","middle").attr("transform",this.rotateStr(-90,this.config.yAxisLabelBumpX,this.config.height/2)).text(this.config.yLabel)};this.renderGrid=function(){this.vGridLines=this.content.selectAll("line.v-grid-line").data(this.xScale.ticks(this.xAxisFn.ticks()[0]));this.vGridLines.enter().append("svg:line").classed("grid-line v-grid-line",true);this.vGridLines.attr("x1",this.xScale).attr("y1",0).attr("x2",this.xScale).attr("y2",this.config.height);this.vGridLines.exit().remove();this.hGridLines=this.content.selectAll("line.h-grid-line").data(this.yScale.ticks(this.yAxisFn.ticks()[0]));this.hGridLines.enter().append("svg:line").classed("grid-line h-grid-line",true);this.hGridLines.attr("x1",0).attr("y1",this.yScale).attr("x2",this.config.width).attr("y2",this.yScale);this.hGridLines.exit().remove()};this.renderDatapoints=function(g,n,j){this.log(this+".renderDatapoints",arguments);var k=0,m=this,i=function(p,o){return m.xScale(g[o])},h=function(p,o){return m.yScale(n[o])};var l=this.content.selectAll(".glyph").data(g);k=0;l.enter().append("svg:circle").each(function(){k+=1}).classed("glyph",true).attr("cx",0).attr("cy",this.config.height).attr("r",0);this.log(k," new glyphs created");k=0;l.transition().duration(this.config.animDuration).each(function(){k+=1}).attr("cx",i).attr("cy",h).attr("r",m.config.datapointSize);this.log(k," existing glyphs transitioned");l.exit().each(function(){k+=1}).transition().duration(this.config.animDuration).attr("cy",this.config.height).attr("r",0).remove();this.log(k," glyphs removed");this._addDatapointEventhandlers(l,g,n,j)};this._addDatapointEventhandlers=function(j,g,k,h){var i=this;j.on("mouseover",function(o,l){var n=d3.select(this);n.style("fill","red").style("fill-opacity",1);i.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",n.attr("cx")-i.config.datapointSize).attr("y1",n.attr("cy")).attr("x2",0).attr("y2",n.attr("cy")).classed("hoverline",true);if(n.attr("cy")<i.config.height){i.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",n.attr("cx")).attr("y1",n.attr("cy")+i.config.datapointSize).attr("x2",n.attr("cx")).attr("y2",i.config.height).classed("hoverline",true)}var m=$(this).offset();i.datapointInfoBox=i.infoBox(m.top,m.left,i.infoHtml(g[l],k[l],(h)?(h[l]):(undefined)));$("body").append(i.datapointInfoBox)}).on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",0.2);i.content.selectAll(".hoverline").remove();if(i.datapointInfoBox){i.datapointInfoBox.remove()}})},this.render=function(i,j){this.log(this+".render",arguments);this.log("\t config:",this.config);var g=i[0],k=i[1],h=(i.length>2)?(i[2]):(undefined);g=this.preprocessData(g);k=this.preprocessData(k);this.log("xCol len",g.length,"yCol len",k.length);this.findMinMaxes(g,k,j);this.setUpScales();if(!this.svg){this.svg=d3.select("svg").attr("class","chart")}if(!this.content){this.content=this.svg.append("svg:g").attr("class","content").attr("id",this.config.id)}this.adjustChartDimensions();if(!this.xAxis){this.xAxis=this.content.append("g").attr("class","axis").attr("id","x-axis")}if(!this.xAxisLabel){this.xAxisLabel=this.xAxis.append("text").attr("class","axis-label").attr("id","x-axis-label")}if(!this.yAxis){this.yAxis=this.content.append("g").attr("class","axis").attr("id","y-axis")}if(!this.yAxisLabel){this.yAxisLabel=this.yAxis.append("text").attr("class","axis-label").attr("id","y-axis-label")}this.setUpXAxis();this.setUpYAxis();this.renderGrid();this.renderDatapoints(g,k,h)};this.infoHtml=function(g,j,i){var h=$("<div/>");if(i){$("<div/>").text(i).css("font-weight","bold").appendTo(h)}$("<div/>").text(g).appendTo(h);$("<div/>").text(j).appendTo(h);return h.html()};this.infoBox=function(l,k,i,h,g){h=h||0;g=g||20;var j=$("<div />").addClass("chart-info-box").css({position:"absolute",top:l+h,left:k+g});j.html(i);return j}};
\ No newline at end of file
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/templates/compiled/template-visualization-chartControl.js
--- a/static/scripts/templates/compiled/template-visualization-chartControl.js
+++ b/static/scripts/templates/compiled/template-visualization-chartControl.js
@@ -13,19 +13,19 @@
foundHelper = helpers.datapointSize;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.datapointSize; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id=\"animDuration\" class=\"form-input checkbox-input\">\n <label for=\"animated\">Animate graph transitions?: </label>\n <input type=\"checkbox\" id=\"animated\"\n class=\"checkbox control\"";
+ buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id=\"animDuration\" class=\"form-input checkbox-input\">\n <label for=\"animate-chart\">Animate chart transitions?: </label>\n <input type=\"checkbox\" id=\"animate-chart\"\n class=\"checkbox control\"";
stack1 = depth0.animDuration;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += " />\n <p class=\"form-help help-text-small\">\n Uncheck this to disable the animations used on the graph\n </p>\n </div>\n\n <div id=\"width\" class=\"form-input numeric-slider-input\">\n <label for=\"width\">Graph width: </label>\n <div class=\"slider-output\">";
+ buffer += " />\n <p class=\"form-help help-text-small\">\n Uncheck this to disable the animations used on the chart\n </p>\n </div>\n\n <div id=\"width\" class=\"form-input numeric-slider-input\">\n <label for=\"width\">Chart width: </label>\n <div class=\"slider-output\">";
foundHelper = helpers.width;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.width; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id=\"height\" class=\"form-input numeric-slider-input\">\n <label for=\"height\">Graph height: </label>\n <div class=\"slider-output\">";
+ buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id=\"height\" class=\"form-input numeric-slider-input\">\n <label for=\"height\">Chart height: </label>\n <div class=\"slider-output\">";
foundHelper = helpers.height;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.height; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id=\"X-axis-label\"class=\"text-input form-input\">\n <label for=\"X-axis-label\">Re-label the X axis: </label>\n <input type=\"text\" name=\"X-axis-label\" id=\"X-axis-label\" value=\"";
+ buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id=\"X-axis-label\"class=\"text-input form-input\">\n <label for=\"X-axis-label\">Re-label the X axis: </label>\n <input type=\"text\" name=\"X-axis-label\" id=\"X-axis-label\" value=\"";
foundHelper = helpers.xLabel;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.xLabel; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/templates/compiled/template-visualization-dataControl.js
--- a/static/scripts/templates/compiled/template-visualization-dataControl.js
+++ b/static/scripts/templates/compiled/template-visualization-dataControl.js
@@ -2,7 +2,7 @@
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['template-visualization-dataControl'] = template(function (Handlebars,depth0,helpers,partials,data) {
helpers = helpers || Handlebars.helpers;
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+ var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
@@ -46,6 +46,11 @@
buffer += escapeExpression(stack1) + "</option>\n ";
return buffer;}
+function program7(depth0,data) {
+
+
+ return "checked=\"true\"";}
+
buffer += "<p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n ";
buffer += "\n <div class=\"column-select\">\n <label for=\"X-select\">Data column for X: </label>\n <select name=\"X\" id=\"X-select\">\n ";
stack1 = depth0.numericColumns;
@@ -60,6 +65,15 @@
stack1 = depth0.allColumns;
stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />\n <div class=\"clear\"></div>";
+ buffer += "\n </select>\n </div>\n\n ";
+ buffer += "\n <div id=\"first-line-header\" style=\"display: none;\">\n <p>Possible headers: ";
+ foundHelper = helpers.possibleHeaders;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.possibleHeaders; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\n </p>\n <label for=\"first-line-header-checkbox\">Use the above as column headers?</label>\n <input type=\"checkbox\" name=\"include-id\" id=\"first-line-header-checkbox\"\n ";
+ stack1 = depth0.usePossibleHeaders;
+ stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(7, program7, data)});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "/>\n <p class=\"help-text-small\">\n It looks like Galaxy couldn't get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />\n <div class=\"clear\"></div>";
return buffer;});
})();
\ No newline at end of file
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/templates/visualization-templates.html
--- a/static/scripts/templates/visualization-templates.html
+++ b/static/scripts/templates/visualization-templates.html
@@ -90,6 +90,19 @@
</select></div>
+ {{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
+ <div id="first-line-header" style="display: none;">
+ <p>Possible headers: {{ possibleHeaders }}
+ </p>
+ <label for="first-line-header-checkbox">Use the above as column headers?</label>
+ <input type="checkbox" name="include-id" id="first-line-header-checkbox"
+ {{#if usePossibleHeaders }}checked="true"{{/if}}/>
+ <p class="help-text-small">
+ It looks like Galaxy couldn't get proper column headers for this data.
+ Would you like to use the column headers above as column names to select columns?
+ </p>
+ </div>
+
<input id="render-button" type="button" value="Draw" /><div class="clear"></div></script>
@@ -112,29 +125,29 @@
</div><div id="animDuration" class="form-input checkbox-input">
- <label for="animated">Animate graph transitions?: </label>
- <input type="checkbox" id="animated"
+ <label for="animate-chart">Animate chart transitions?: </label>
+ <input type="checkbox" id="animate-chart"
class="checkbox control"{{#if animDuration}} checked="true"{{/if}} /><p class="form-help help-text-small">
- Uncheck this to disable the animations used on the graph
+ Uncheck this to disable the animations used on the chart
</p></div><div id="width" class="form-input numeric-slider-input">
- <label for="width">Graph width: </label>
+ <label for="width">Chart width: </label><div class="slider-output">{{width}}</div><div class="slider"></div><p class="form-help help-text-small">
- (not including graph margins and axes)
+ (not including chart margins and axes)
</p></div><div id="height" class="form-input numeric-slider-input">
- <label for="height">Graph height: </label>
+ <label for="height">Chart height: </label><div class="slider-output">{{height}}</div><div class="slider"></div><p class="form-help help-text-small">
- (not including graph margins and axes)
+ (not including chart margins and axes)
</p></div>
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/utils/LazyDataLoader.js
--- a/static/scripts/utils/LazyDataLoader.js
+++ b/static/scripts/utils/LazyDataLoader.js
@@ -81,11 +81,11 @@
// it's the responsibility of the code using this to combine them properly
data : [],
// ms btwn recursive loads
- delay : 500,
+ delay : 4000,
// starting line, element, whatever
start : 0,
// size to fetch per load
- size : 1000,
+ size : 4000,
// loader init func: extends loader with config and calls config.init if there
//@param {object} config : object containing variables to override (or additional)
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa static/scripts/viz/scatterplot.js
--- a/static/scripts/viz/scatterplot.js
+++ b/static/scripts/viz/scatterplot.js
@@ -90,7 +90,7 @@
xNumTicks : 10,
yNumTicks : 10,
xAxisLabelBumpY : 40,
- yAxisLabelBumpX : -35,
+ yAxisLabelBumpX : -40,
width : 400,
height : 400,
//TODO: anyway to make this a sub-obj?
@@ -304,75 +304,37 @@
};
// ........................................................ data points
- //TODO: these to config ...somehow
- //TODO: use these in renderDatapoints ...somehow
- this.glyphEnterState = function( d3Elem ){
-
- };
- this.glyphFinalState = function( d3Elem ){
-
- };
- this.glyphExitState = function( d3Elem ){
-
- };
-
- // initial render or complete re-render (REPLACE datapoints)
this.renderDatapoints = function( xCol, yCol, ids ){
this.log( this + '.renderDatapoints', arguments );
- var count = 0;
-
- this.datapoints = this.addDatapoints( xCol, yCol, ids, ".glyph" );
-
- // glyphs that need to be removed: transition to from normal state to 'exit' state, remove from DOM
- this.datapoints.exit()
- .each( function(){ count += 1; } )
- .transition().duration( this.config.animDuration )
- .attr( "cy", this.config.height )
- .attr( "r", 0 )
- .remove();
- this.log( count, ' glyphs removed' );
-
- //this.log( this.datapoints.length, ' glyphs in the graph' );
- };
-
- // adding points to existing
- this.addDatapoints = function( newXCol, newYCol, ids, selectorForExisting ){
- this.log( this + '.addDatapoints', arguments );
- // ADD datapoints to plot that's already rendered
- // if selectorForExisting === undefined (as in not passed), addDatapoints won't update existing
- // pass in the class ( '.glyph' ) to update exising datapoints
- var plot = this,
- count = 0,
+ var count = 0,
+ plot = this,
xPosFn = function( d, i ){
//if( d ){ this.log( 'x.data:', newXCol[ i ], 'plotted:', plot.xScale( newXCol[ i ] ) ); }
- return plot.xScale( newXCol[ i ] );
+ return plot.xScale( xCol[ i ] );
},
yPosFn = function( d, i ){
//if( d ){ this.log( 'y.data:', newYCol[ i ], 'plotted:', plot.yScale( newYCol[ i ] ) ); }
- return plot.yScale( newYCol[ i ] );
+ return plot.yScale( yCol[ i ] );
};
-
- // select all existing glyphs and compare to incoming data
- // enter() will yield those glyphs that need to be added
- var newDatapoints = this.content.selectAll( selectorForExisting );
- this.log( 'existing datapoints:', newDatapoints );
- newDatapoints = newDatapoints.data( newXCol );
-
- // enter - new data to be added as glyphs: give them a 'entry' position and style
+
+ //this.datapoints = this.addDatapoints( xCol, yCol, ids, ".glyph" );
+ var datapoints = this.content.selectAll( '.glyph' ).data( xCol );
+
+ // enter - NEW data to be added as glyphs: give them a 'entry' position and style
count = 0;
- newDatapoints.enter()
+ datapoints.enter()
.append( 'svg:circle' )
.each( function(){ count += 1; } )
.classed( "glyph", true )
- .attr( "cx", xPosFn )
- .attr( "cy", yPosFn )
+ .attr( "cx", 0 )
+ .attr( "cy", this.config.height )
// start all bubbles small...
.attr( "r", 0 );
this.log( count, ' new glyphs created' );
- // for all existing glyphs and those that need to be added: transition anim to final state
+ // for all EXISTING glyphs and those that need to be added: transition anim to final state
count = 0;
- newDatapoints
+ datapoints
// ...animate to final position
.transition().duration( this.config.animDuration )
.each( function(){ count += 1; } )
@@ -381,18 +343,22 @@
.attr( "r", plot.config.datapointSize );
this.log( count, ' existing glyphs transitioned' );
- // attach ids
- if( ids ){
- newDatapoints.attr( 'data', function( d, i ){ return ( ids[ i ] ); } );
- }
+ // events
+ // glyphs that need to be removed: transition to from normal state to 'exit' state, remove from DOM
+ datapoints.exit()
+ .each( function(){ count += 1; } )
+ .transition().duration( this.config.animDuration )
+ .attr( "cy", this.config.height )
+ .attr( "r", 0 )
+ .remove();
+ this.log( count, ' glyphs removed' );
- // titles
- newDatapoints.attr( 'svg:title', function( d, i ){
- return (( ids )?( ids[ i ] + ': ' ):( '' )) + newXCol[ i ] + ', ' + newYCol[ i ];
- });
-
- // events
- newDatapoints
+ this._addDatapointEventhandlers( datapoints, xCol, yCol, ids );
+ };
+
+ this._addDatapointEventhandlers = function( datapoints, xCol, yCol, ids ){
+ var plot = this;
+ datapoints
//TODO: remove magic numbers
.on( 'mouseover', function( d, i ){
var datapoint = d3.select( this );
@@ -404,36 +370,42 @@
plot.content.append( 'line' )
.attr( 'stroke', 'red' )
.attr( 'stroke-width', 1 )
- .attr( 'x1', datapoint.attr( 'cx' ) ).attr( 'y1', datapoint.attr( 'cy' ) )
- .attr( 'x2', 0 ).attr( 'y2', datapoint.attr( 'cy' ) )
- .classed( 'hoverline', true );
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- .attr( 'x1', datapoint.attr( 'cx' ) ).attr( 'y1', datapoint.attr( 'cy' ) )
- .attr( 'x2', datapoint.attr( 'cx' ) ).attr( 'y2', plot.config.height )
+ // start not at center, but at the edge of the circle - to prevent mouseover thrashing
+ .attr( 'x1', datapoint.attr( 'cx' ) - plot.config.datapointSize )
+ .attr( 'y1', datapoint.attr( 'cy' ) )
+ .attr( 'x2', 0 )
+ .attr( 'y2', datapoint.attr( 'cy' ) )
.classed( 'hoverline', true );
- //var datapointWindowPos = $( this ).position();
- //var datapointWindowPos = $( this ).offset();
- //window.dot = this;
- ////this.popup = make_abs_box( datapointWindowPos.top, datapointWindowPos.left + datapoint.attr( 'r' ),
- //this.popup = make_abs_box( datapointWindowPos.top, datapointWindowPos.left,
- // newXCol[ i ], newYCol[ i ], ( ids )?( ids[ i ] ):( undefined ) );
- //$( 'body' ).append( this.popup );
+ // if the vertical hoverline
+ if( datapoint.attr( 'cy' ) < plot.config.height ){
+ plot.content.append( 'line' )
+ .attr( 'stroke', 'red' )
+ .attr( 'stroke-width', 1 )
+ .attr( 'x1', datapoint.attr( 'cx' ) )
+ .attr( 'y1', datapoint.attr( 'cy' ) + plot.config.datapointSize )
+ .attr( 'x2', datapoint.attr( 'cx' ) )
+ .attr( 'y2', plot.config.height )
+ .classed( 'hoverline', true );
+ }
+
+ var datapointWindowPos = $( this ).offset();
+ plot.datapointInfoBox = plot.infoBox(
+ datapointWindowPos.top, datapointWindowPos.left,
+ plot.infoHtml( xCol[ i ], yCol[ i ], ( ids )?( ids[ i ] ):( undefined ) )
+ );
+ $( 'body' ).append( plot.datapointInfoBox );
})
.on( 'mouseout', function(){
d3.select( this )
.style( 'fill', 'black' )
.style( 'fill-opacity', 0.2 );
- d3.selectAll( '.hoverline' ).remove();
- //if( this.popup ){
- // this.popup.remove();
- //}
+ plot.content.selectAll( '.hoverline' ).remove();
+ if( plot.datapointInfoBox ){
+ plot.datapointInfoBox.remove();
+ }
});
-
- return newDatapoints;
- };
+ },
this.render = function( columnData, meta ){
this.log( this + '.render', arguments );
@@ -457,7 +429,7 @@
//this.log( 'xMin, xMax, yMin, yMax:', this.xMin, this.xMax, this.yMin, this.yMax );
this.setUpScales();
- // build the svg dom infrastructure
+ // find (or build if it doesn't exist) the svg dom infrastructure
if( !this.svg ){ this.svg = d3.select( 'svg' ).attr( "class", "chart" ); }
if( !this.content ){
this.content = this.svg.append( "svg:g" ).attr( "class", "content" ).attr( 'id', this.config.id );
@@ -485,6 +457,32 @@
this.renderGrid();
this.renderDatapoints( xCol, yCol, ids );
};
+
+ this.infoHtml = function( x, y, id ){
+ var retDiv = $( '<div/>' );
+ if( id ){
+ $( '<div/>' ).text( id ).css( 'font-weight', 'bold' ).appendTo( retDiv );
+ }
+ $( '<div/>' ).text( x ).appendTo( retDiv );
+ $( '<div/>' ).text( y ).appendTo( retDiv );
+ return retDiv.html();
+ };
+
+ //TODO: html for now
+ this.infoBox = function( top, left, html, adjTop, adjLeft ){
+ adjTop = adjTop || 0;
+ adjLeft = adjLeft || 20;
+ var infoBox = $( '<div />' )
+ .addClass( 'chart-info-box' )
+ .css({
+ 'position' : 'absolute',
+ 'top' : top + adjTop,
+ 'left' : left + adjLeft
+ });
+ infoBox.html( html );
+ return infoBox;
+ };
+
}
//==============================================================================
diff -r 3c4696f6af0d9c6c7885568c1fee02df53849322 -r 4eaa644dd47877c40ee7090d56379998d7aecafa templates/visualization/scatterplot.mako
--- a/templates/visualization/scatterplot.mako
+++ b/templates/visualization/scatterplot.mako
@@ -26,6 +26,7 @@
padding : 8px;
background-color: #ebd9b2;
margin-bottom: 16px;
+ overflow: auto;
}
#chart-header .subtitle {
@@ -182,12 +183,11 @@
}
/* -------------------------------------------- info box */
-.zero-dimensions {
- width: 0;
- height: 0;
- border-top: 8px solid transparent;
- border-bottom: 8px solid transparent;
- border-right: 8px solid grey;
+.chart-info-box {
+ border-radius: 4px;
+ padding: 4px;
+ background-color: white;
+ border: 1px solid black;
}
</style>
@@ -255,63 +255,3 @@
</div><div id="scatterplot" class="scatterplot-control-form"></div></%def>
-
-
-<script type="text/javascript">
-function make_abs_box( top, left, x, y, id ){
- var ARROW_SIZE = 8,
- ARROW_COLOR = 'grey',
- DIST_TO_POINT = 4,
- halfArrowSize = ARROW_SIZE / 2;
-
- var boxContainer = $( '<div />' )
- .attr( 'id', 'abs-box-container' )
- // top left arrow
- .css({
- 'position' : 'absolute',
- 'top' : top - halfArrowSize,
- 'left' : left + ARROW_SIZE + DIST_TO_POINT,
- 'background-color': 'transparent',
- });
- window.boxContainer = boxContainer;
-
- var arrowLeft = $( '<div />' )
- .attr( 'id', 'abs-box-arrow' )
- .addClass( 'zero-dimensions' )
- .css({
- 'border-top' : ARROW_SIZE + 'px solid transparent',
- 'border-bottom' : ARROW_SIZE + 'px solid transparent',
- 'border-right' : ARROW_SIZE + 'px solid ' + ARROW_COLOR,
- });
- boxContainer.append( arrowLeft );
- window.arrow = arrowLeft;
-
- console.debug( 'arrow height:', arrowLeft.height() );
- var boxInfo = $( '<div />' )
- .attr( 'id', 'abs-box' )
- .css({
- 'position' : 'relative',
- //TODO: 4 here is the border-radius
- 'top' : -( 2 * ARROW_SIZE + 6 ),
- 'left' : ARROW_SIZE,
- 'border' : '2px solid grey',
- 'border-radius' : '4px',
- 'padding' : '4px',
- 'background-color': 'white',
- 'box-shadow' : '4px 4px 4px black'
- });
-
- // remove
- if( id ){
- $( '<div />' ).addClass( 'abs-box-id' ).css( 'font-weight', 'bold' ).text( id ).appendTo( boxInfo );
- }
- $( '<div />' ).addClass( 'abs-box-x' ).text( x ).appendTo( boxInfo );
- $( '<div />' ).addClass( 'abs-box-y' ).text( y ).appendTo( boxInfo );
- boxContainer.append( boxInfo );
- boxContainer.append( '<div style="clear:both"></div>' );
- window.boxInfo = boxContainer;
-
- //console.debug( boxContainer );
- return boxContainer;
-}
-</script>
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.
1
0
commit/galaxy-central: natefoo: Fix check_galaxy for recent framework changes.
by Bitbucket 27 Nov '12
by Bitbucket 27 Nov '12
27 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/3c4696f6af0d/
changeset: 3c4696f6af0d
user: natefoo
date: 2012-11-27 21:57:05
summary: Fix check_galaxy for recent framework changes.
affected #: 1 file
diff -r 51ffca8538ff769f54f379d6e070494fc2c14833 -r 3c4696f6af0d9c6c7885568c1fee02df53849322 contrib/nagios/check_galaxy.py
--- a/contrib/nagios/check_galaxy.py
+++ b/contrib/nagios/check_galaxy.py
@@ -4,7 +4,7 @@
via the check_galaxy.sh script in Galaxy's cron/ directory.
"""
-import socket, sys, os, time, tempfile, filecmp, htmllib, formatter, getopt
+import socket, sys, os, time, tempfile, filecmp, htmllib, formatter, getopt, json
from user import home
import warnings
@@ -63,13 +63,6 @@
username = args[1]
password = args[2]
-if server.endswith(".g2.bx.psu.edu"):
- if debug:
- print "Checking a PSU Galaxy server, using maint file"
- maint = "/errordocument/502/%s/maint" % args[0].split('.', 1)[0]
-else:
- maint = None
-
new_history = False
for o, a in opts:
if o == "-n":
@@ -95,13 +88,12 @@
def __init__(self):
self.server = server
- self.maint = maint
self.tool = None
self.tool_opts = None
- self.id = None
- self.status = None
+ self._hda_id = None
+ self._hda_state = None
+ self._history_id = None
self.check_file = None
- self.hid = None
self.cookie_jar = os.path.join( var_dir, "cookie_jar" )
dprint("cookie jar path: %s" % self.cookie_jar)
if not os.access(self.cookie_jar, os.R_OK):
@@ -116,19 +108,14 @@
def reset(self):
self.tool = None
self.tool_opts = None
- self.id = None
- self.status = None
+ self._hda_id = None
+ self._hda_state = None
+ self._history_id = None
self.check_file = None
- self.delete_datasets()
- self.get("/root/history")
- p = didParser()
- p.feed(tc.browser.get_html())
- if len(p.dids) > 0:
- print "Remaining datasets ids:", " ".join( p.dids )
- raise Exception, "History still contains datasets after attempting to delete them"
if new_history:
self.get("/history/delete_current")
tc.save_cookies(self.cookie_jar)
+ self.delete_datasets()
def check_redir(self, url):
try:
@@ -143,25 +130,9 @@
dprint( "%s is not returning redirect (302): %s" % (url, e) )
code = tc.browser.get_code()
if code == 502:
- is_maint = self.check_maint()
- if is_maint:
- dprint( "Galaxy is down, but a maint file was found, so not sending alert" )
- sys.exit(0)
- else:
- print "Galaxy is down (code 502)"
- sys.exit(1)
- return(False)
-
- # checks for a maint file
- def check_maint(self):
- if self.maint is None:
- #dprint( "Warning: unable to check maint file for %s" % self.server )
- return(False)
- try:
- self.get(self.maint)
- return(True)
- except twill.errors.TwillAssertionError, e:
- return(False)
+ print "Galaxy is down (code 502)"
+ sys.exit(1)
+ return False
def login(self, user, pw):
self.get("/user/login")
@@ -210,15 +181,64 @@
tc.submit("runtool_btn")
tc.code(200)
+ @property
+ def history_id(self):
+ if self._history_id is None:
+ self.get('/api/histories')
+ self._history_id = json.loads(tc.browser.get_html())[0]['id']
+ return self._history_id
+
+ @property
+ def history_contents(self):
+ self.get('/api/histories/%s/contents' % self.history_id)
+ return json.loads(tc.browser.get_html())
+
+ @property
+ def hda_id(self):
+ if self._hda_id is None:
+ self.set_top_hda()
+ return self._hda_id
+
+ @property
+ def hda_state(self):
+ if self._hda_state is None:
+ self.set_top_hda()
+ return self._hda_state
+
+ def set_top_hda(self):
+ self.get(self.history_contents[-1]['url'])
+ hda = json.loads(tc.browser.get_html())
+ self._hda_id = hda['id']
+ self._hda_state = hda['state']
+
+ @property
+ def undeleted_hdas(self):
+ rval = []
+ for item in self.history_contents:
+ self.get(item['url'])
+ hda = json.loads(tc.browser.get_html())
+ if hda['deleted'] == False:
+ rval.append(hda)
+ return rval
+
+ @property
+ def history_state(self):
+ self.get('/api/histories/%s' % self.history_id)
+ return json.loads(tc.browser.get_html())['state']
+
+ @property
+ def history_state_terminal(self):
+ if self.history_state not in ['queued', 'running', 'paused']:
+ return True
+ return False
+
def wait(self):
sleep_amount = 1
count = 0
maxiter = 16
while count < maxiter:
count += 1
- self.get("/root/history")
- page = tc.browser.get_html()
- if page.find( '<!-- running: do not change this comment, used by TwillTestCase.wait -->' ) > -1:
+ if not self.history_state_terminal:
time.sleep( sleep_amount )
sleep_amount += 1
else:
@@ -226,20 +246,14 @@
if count == maxiter:
raise Exception, "Tool never finished"
- def check_status(self):
- self.get("/root/history")
- p = historyParser()
- p.feed(tc.browser.get_html())
- if p.status != "ok":
- self.get("/datasets/%s/stderr" % p.id)
+ def check_state(self):
+ if self.hda_state != "ok":
+ self.get("/datasets/%s/stderr" % self.hda_id)
print tc.browser.get_html()
- raise Exception, "HDA %s NOT OK: %s" % (p.id, p.status)
- self.id = p.id
- self.status = p.status
- #return((p.id, p.status))
+ raise Exception, "HDA %s NOT OK: %s" % (self.hda_id, self.hda_state)
def diff(self):
- self.get("/datasets/%s/display?to_ext=%s" % (self.id, self.tool_opts.get('out_format', 'fasta')))
+ self.get("/datasets/%s/display?to_ext=%s" % (self.hda_id, self.tool_opts.get('out_format', 'fasta')))
data = tc.browser.get_html()
tmp = tempfile.mkstemp()
dprint("tmp file: %s" % tmp[1])
@@ -256,12 +270,12 @@
os.remove(tmp[1])
def delete_datasets(self):
- self.get("/root/history")
- p = didParser()
- p.feed(tc.browser.get_html())
- dids = p.dids
- for did in dids:
- self.get("/datasets/%s/delete" % did)
+ for hda in self.undeleted_hdas:
+ self.get('/datasets/%s/delete' % hda['id'])
+ hdas = [hda['id'] for hda in self.undeleted_hdas]
+ if hdas:
+ print "Remaining datasets ids:", " ".join(hdas)
+ raise Exception, "History still contains datasets after attempting to delete them"
def check_if_logged_in(self):
self.get("/user?cntrller=user")
@@ -294,33 +308,6 @@
elif data == "User with that email already exists":
self.already_exists = True
-class historyParser(htmllib.HTMLParser):
- def __init__(self):
- htmllib.HTMLParser.__init__(self, formatter.NullFormatter())
- self.status = None
- self.id = None
- def start_div(self, attrs):
- # find the top history item
- for i in attrs:
- if i[0] == "class" and i[1].startswith("historyItemWrapper historyItem historyItem-"):
- self.status = i[1].rsplit("historyItemWrapper historyItem historyItem-", 1)[1]
- dprint("status: %s" % self.status)
- if i[0] == "id" and i[1].startswith("historyItem-"):
- self.id = i[1].rsplit("historyItem-", 1)[1]
- dprint("id: %s" % self.id)
- if self.status is not None:
- self.reset()
-
-class didParser(htmllib.HTMLParser):
- def __init__(self):
- htmllib.HTMLParser.__init__(self, formatter.NullFormatter())
- self.dids = []
- def start_div(self, attrs):
- for i in attrs:
- if i[0] == "id" and i[1].startswith("historyItemContainer-"):
- self.dids.append( i[1].rsplit("historyItemContainer-", 1)[1] )
- dprint("got a dataset id: %s" % self.dids[-1])
-
class loggedinParser(htmllib.HTMLParser):
def __init__(self):
htmllib.HTMLParser.__init__(self, formatter.NullFormatter())
@@ -379,15 +366,9 @@
b.runtool()
b.wait()
- b.check_status()
+ b.check_state()
b.diff()
b.delete_datasets()
- # by this point, everything else has succeeded. there should be no maint.
- is_maint = b.check_maint()
- if is_maint:
- print "Galaxy is up and fully functional, but a maint file is in place."
- sys.exit(1)
-
print "OK"
sys.exit(0)
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.
1
0
commit/galaxy-central: inithello: Fixed error when granting write access to a repository.
by Bitbucket 27 Nov '12
by Bitbucket 27 Nov '12
27 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/51ffca8538ff/
changeset: 51ffca8538ff
user: inithello
date: 2012-11-27 19:53:17
summary: Fixed error when granting write access to a repository.
affected #: 1 file
diff -r 2aae11d62620b6c839b513d68a06d2cc57c5fa21 -r 51ffca8538ff769f54f379d6e070494fc2c14833 lib/galaxy/webapps/community/model/__init__.py
--- a/lib/galaxy/webapps/community/model/__init__.py
+++ b/lib/galaxy/webapps/community/model/__init__.py
@@ -143,7 +143,7 @@
if username not in allow_push:
allow_push.append( username )
allow_push = '%s\n' % ','.join( allow_push )
- repo = hg.repository( ui.ui(), path=self.repo_path )
+ repo = hg.repository( ui.ui(), path=self.repo_path( app ) )
# Why doesn't the following work?
#repo.ui.setconfig( 'web', 'allow_push', allow_push )
lines = repo.opener( 'hgrc', 'rb' ).readlines()
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.
1
0
27 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/2aae11d62620/
changeset: 2aae11d62620
user: natefoo
date: 2012-11-27 19:50:54
summary: Split job limits on ' = ' rather than '='.
affected #: 1 file
diff -r 4a95ae9a26d96f0dc9a0fe3b083a2c7b99b0466b -r 2aae11d62620b6c839b513d68a06d2cc57c5fa21 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -224,8 +224,8 @@
try:
job_limits = global_conf_parser.items( 'galaxy:job_limits' )
for k, v in job_limits:
- # ConfigParser considers the first colon to be the delimiter, undo this behavior
- more_k, v = v.split('=', 1)
+ # Since the URL contains a colon and possibly an equals sign, consider ' = ' the delimiter
+ more_k, v = v.split(' = ', 1)
k = '%s:%s' % (k, more_k.strip())
v = v.strip().rsplit(None, 1)
v[1] = int(v[1])
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.
1
0
27 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/4a95ae9a26d9/
changeset: 4a95ae9a26d9
user: natefoo
date: 2012-11-27 18:18:08
summary: Handle invalid job ids in the drmaa runner.
affected #: 1 file
diff -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff -r 4a95ae9a26d96f0dc9a0fe3b083a2c7b99b0466b lib/galaxy/jobs/runners/drmaa.py
--- a/lib/galaxy/jobs/runners/drmaa.py
+++ b/lib/galaxy/jobs/runners/drmaa.py
@@ -293,6 +293,7 @@
galaxy_job_id = drm_job_state.job_wrapper.job_id
old_state = drm_job_state.old_state
try:
+ assert job_id not in ( None, 'None' ), 'Invalid job id: %s' % job_id
state = self.ds.jobStatus( job_id )
# InternalException was reported to be necessary on some DRMs, but
# this could cause failures to be detected as completion! Please
@@ -308,7 +309,7 @@
continue
except Exception, e:
# so we don't kill the monitor thread
- log.exception("(%s/%s) Unable to check job status" % ( galaxy_job_id, job_id ) )
+ log.exception("(%s/%s) Unable to check job status: %s" % ( galaxy_job_id, job_id, str( e ) ) )
log.warning("(%s/%s) job will now be errored" % ( galaxy_job_id, job_id ) )
drm_job_state.fail_message = "Cluster could not complete job"
self.work_queue.put( ( 'fail', drm_job_state ) )
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.
1
0
commit/galaxy-central: carlfeberhard: scatterplot: renaming/re-organization pass; layout change to vertical tabs and inclusion of chart under tabs; pack scripts
by Bitbucket 27 Nov '12
by Bitbucket 27 Nov '12
27 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/9bf411ee2476/
changeset: 9bf411ee2476
user: carlfeberhard
date: 2012-11-27 17:19:32
summary: scatterplot: renaming/re-organization pass; layout change to vertical tabs and inclusion of chart under tabs; pack scripts
affected #: 19 files
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/mvc/visualizations/scatterplotControlForm.js
--- /dev/null
+++ b/static/scripts/mvc/visualizations/scatterplotControlForm.js
@@ -0,0 +1,602 @@
+/* =============================================================================
+todo:
+ I'd like to move the svg creation out of the splot constr. to:
+ allow adding splots to an existing canvas
+ allow mult. splots sharing a canvas
+
+
+ outside this:
+ BUG: setting width, height in plot controls doesn't re-interpolate data locations!!
+ BUG?: get metadata_column_names (from datatype if necessary)
+ BUG: single vis in popupmenu should have tooltip with that name NOT 'Visualizations'
+
+ wire label setters, anim setter
+
+ TwoVarScatterplot:
+ ??: maybe better to do this with a canvas...
+ save as visualization
+ to seperate file?
+ remove underscore dependencies
+ add interface to change values (seperate)?
+ download svg -> base64 encode
+ incorporate glyphs, glyph state renderers
+
+ ScatterplotSettingsForm:
+ some css bug that lowers the width of settings form when plot-controls tab is open
+ causes chart to shift
+ what can be abstracted/reused for other graphs?
+ avoid direct manipulation of this.plot
+ allow option to put plot into seperate tab of interface (for small multiples)
+
+ provide callback in view to load data incrementally - for large sets
+ paginate
+ handle rerender
+ use endpoint (here and on the server (fileptr))
+ fetch (new?) data
+ handle rerender
+ use d3.TSV?
+ render warning on long data (> maxDataPoints)
+ adjust endpoint
+
+ selectable list of preset column comparisons (rnaseq etc.)
+ how to know what sort of Tabular the data is?
+ smarter about headers
+ validate columns selection (here or server)
+
+ set stats column names by selected columns
+ move chart into tabbed area...
+
+ Scatterplot.mako:
+ multiple plots on one page (small multiples)
+ ?? ensure svg styles thru d3 or css?
+ d3: configable (easily)
+ css: standard - better maintenance
+ ? override at config
+
+============================================================================= */
+/**
+ * Scatterplot control UI as a backbone view
+ * handles:
+ * getting the desired data
+ * configuring the plot display
+ * showing (general) statistics
+ *
+ * initialize attributes REQUIRES a dataset and an apiDatasetsURL
+ */
+var ScatterplotControlForm = BaseView.extend( LoggableMixin ).extend({
+ //logger : console,
+ className : 'scatterplot-control-form',
+
+ dataLoadDelay : 500,
+ dataLoadSize : 3001,
+
+ loadingIndicatorImage : 'loading_small_white_bg.gif',
+ fetchMsg : 'Fetching data...',
+ renderMsg : 'Rendering...',
+
+ initialize : function( attributes ){
+ this.log( this + '.initialize, attributes:', attributes );
+
+ this.dataset = null;
+ this.chartConfig = null;
+ this.chart = null;
+ this.loader = null;
+
+ // set up refs to the four tab areas
+ this.$dataControl = null;
+ this.$chartControl = null;
+ this.$statsDisplay = null;
+ this.$chartDisplay = null;
+
+ this.dataFetch = null;
+
+ this.initializeFromAttributes( attributes );
+ this.initializeChart( attributes );
+ this.initializeDataLoader( attributes );
+ },
+
+ initializeFromAttributes : function( attributes ){
+ // required settings: ensure certain vars we need are passed in attributes
+ if( !attributes || !attributes.dataset ){
+ throw( "ScatterplotView requires a dataset" );
+ } else {
+ this.dataset = attributes.dataset;
+ }
+ if( jQuery.type( this.dataset.metadata_column_types ) === 'string' ){
+ this.dataset.metadata_column_types = this.dataset.metadata_column_types.split( ', ' );
+ }
+ this.log( '\t dataset:', this.dataset );
+
+ // passed from mako helper
+ //TODO: integrate to galaxyPaths
+ //TODO: ?? seems like data loader section would be better
+ if( !attributes.apiDatasetsURL ){
+ throw( "ScatterplotView requires a apiDatasetsURL" );
+ } else {
+ this.dataURL = attributes.apiDatasetsURL + '/' + this.dataset.id + '?';
+ }
+ this.log( '\t dataURL:', this.dataURL );
+ },
+
+ initializeChart : function( attributes ){
+ // set up the basic chart infrastructure and config (if any)
+ this.chartConfig = attributes.chartConfig || {};
+ //if( this.logger ){ this.chartConfig.debugging = true; }
+ this.log( '\t initial chartConfig:', this.chartConfig );
+
+ this.chart = new TwoVarScatterplot( this.chartConfig );
+ //TODO: remove 2nd ref, use this.chart.config
+ this.chartConfig = this.chart.config;
+ },
+
+ initializeDataLoader : function( attributes ){
+ // set up data loader
+ var view = this;
+ this.loader = new LazyDataLoader({
+ //logger : ( this.logger )?( this.logger ):( null ),
+ // we'll generate this when columns are chosen
+ url : null,
+ start : attributes.start || 0,
+ //NOTE: metadata_data_lines can be null (so we won't know the total)
+ total : attributes.total || this.dataset.metadata_data_lines,
+ delay : this.dataLoadDelay,
+ size : this.dataLoadSize,
+
+ buildUrl : function( start, size ){
+ // currently VERY SPECIFIC to using data_providers.py start_val, max_vals params
+ return this.url + '&' + jQuery.param({
+ start_val: start,
+ max_vals: size
+ });
+ }
+ });
+ $( this.loader ).bind( 'error', function( event, status, error ){
+ view.log( 'ERROR:', status, error );
+ alert( 'ERROR fetching data:\n' + status + '\n' + error );
+ view.hideLoadingIndicator();
+ });
+ },
+
+ // ------------------------------------------------------------------------- CONTROLS RENDERING
+ render : function(){
+ this.log( this + '.render' );
+
+ // render the tab controls, areas and loading indicator
+ this.$el.append( ScatterplotControlForm.templates.mainLayout({
+ loadingIndicatorImagePath : galaxy_paths.get( 'image_path' ) + '/' + this.loadingIndicatorImage,
+ message : ''
+ }));
+
+ // render the tab content
+ this.$dataControl = this._render_dataControl();
+ this.$chartControl = this._render_chartControl();
+ this.$statsDisplay = this.$el.find( '.tab-pane#stats-display' );
+ this.$chartDisplay = this._render_chartDisplay();
+
+ // auto render if given both x, y column choices in query for page
+ //TODO:?? add autoRender=1 to query maybe?
+ if( this.chartConfig.xColumn && this.chartConfig.yColumn ){
+ this.renderChart();
+ }
+
+ // set up behaviours
+ this.$el.find( '.tooltip' ).tooltip();
+
+ // uncomment any of the following to have that tab show on initial load (for testing)
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#data-control"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-control"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#stats-display"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ return this;
+ },
+
+ _render_dataControl : function(){
+ // controls for which columns are used to plot datapoints (and ids/additional info to attach if desired)
+ var view = this,
+ allColumns = [],
+ numericColumns = [];
+
+ // gather column indeces (from metadata_column_types) and names (from metadata_columnnames)
+ _.each( this.dataset.metadata_column_types, function( type, index ){
+ // use a 1 based index in names/values within the form (will be dec. when parsed out)
+ var oneBasedIndex = index + 1,
+ // label with the name if available (fall back on 'column <index>')
+ name = 'column ' + oneBasedIndex;
+ if( view.dataset.metadata_column_names ){
+ name = view.dataset.metadata_column_names[ index ];
+ }
+
+ // cache all columns here
+ allColumns.push({ index: oneBasedIndex, name: name });
+
+ // filter numeric columns to their own list
+ if( type === 'int' || type === 'float' ){
+ numericColumns.push({ index: oneBasedIndex, name: name });
+ }
+ });
+ //TODO: other vals: max_vals, start_val, pagination (chart-settings)
+
+ // render the html
+ var $dataControl = this.$el.find( '.tab-pane#data-control' );
+ $dataControl.append( ScatterplotControlForm.templates.dataControl({
+ allColumns : allColumns,
+ numericColumns : numericColumns
+ }));
+
+ // preset to column selectors if they were passed in the config in the query string
+ $dataControl.find( '#X-select' ).val( this.chartConfig.xColumn );
+ $dataControl.find( '#Y-select' ).val( this.chartConfig.yColumn );
+ if( this.chartConfig.idColumn !== undefined ){
+ $dataControl.find( '#include-id-checkbox' )
+ .attr( 'checked', true ).trigger( 'change' );
+ $dataControl.find( '#ID-select' ).val( this.chartConfig.idColumn );
+ }
+
+ return $dataControl;
+ },
+
+ _render_chartControl : function(){
+ // tab content to control how the chart is rendered (data glyph size, chart size, etc.)
+ var view = this,
+ $chartControl = this.$el.find( '.tab-pane#chart-control' ),
+ // limits for controls (by control/chartConfig id)
+ //TODO: move into TwoVarScatterplot
+ controlRanges = {
+ 'datapointSize' : { min: 2, max: 10, step: 1 },
+ 'width' : { min: 200, max: 800, step: 20 },
+ 'height' : { min: 200, max: 800, step: 20 }
+ };
+
+ // render the html
+ $chartControl.append( ScatterplotControlForm.templates.chartControl( this.chartConfig ) );
+
+ // set up behaviours, js on sliders
+ $chartControl.find( '.numeric-slider-input' ).each( function(){
+ var $this = $( this ),
+ $output = $this.find( '.slider-output' ),
+ $slider = $this.find( '.slider' ),
+ id = $this.attr( 'id' );
+ //chartControl.log( 'slider set up', 'this:', $this, 'slider:', $slider, 'id', id );
+
+ // what to do when the slider changes: update display and update chartConfig
+ //TODO: move out of loop
+ function onSliderChange(){
+ var $this = $( this ),
+ newValue = $this.slider( 'value' );
+ //chartControl.log( 'slider change', 'this:', $this, 'output:', $output, 'value', newValue );
+ $output.text( newValue );
+ //chartControl.chartConfig[ id ] = newValue;
+ }
+
+ $slider.slider( _.extend( controlRanges[ id ], {
+ value : view.chartConfig[ id ],
+ change : onSliderChange,
+ slide : onSliderChange
+ }));
+ });
+
+ return $chartControl;
+ },
+
+ _render_chartDisplay : function(){
+ // render the tab content where the chart is displayed (but not the chart itself)
+ var $chartDisplay = this.$el.find( '.tab-pane#chart-display' );
+ $chartDisplay.append( ScatterplotControlForm.templates.chartDisplay( this.chartConfig ) );
+ return $chartDisplay;
+ },
+
+ // ------------------------------------------------------------------------- EVENTS
+ events : {
+ 'change #include-id-checkbox' : 'toggleThirdColumnSelector',
+ 'click #data-control #render-button' : 'renderChart',
+ 'click #chart-control #render-button' : 'changeChartSettings'
+ },
+
+ toggleThirdColumnSelector : function(){
+ // show/hide the id selector on the data settings panel
+ this.$el.find( 'select[name="ID"]' ).parent().toggle();
+ },
+
+ showLoadingIndicator : function( message, callback ){
+ // display the loading indicator over the tab panels if hidden, update message (if passed)
+ message = message || '';
+ var indicator = this.$el.find( 'div#loading-indicator' );
+ messageBox = indicator.find( '.loading-message' );
+
+ if( indicator.is( ':visible' ) ){
+ if( message ){
+ messageBox.fadeOut( 'fast', function(){
+ messageBox.text( message );
+ messageBox.fadeIn( 'fast', callback );
+ });
+ } else {
+ callback();
+ }
+
+ } else {
+ if( message ){ messageBox.text( message ); }
+ indicator.fadeIn( 'fast', callback );
+ }
+ },
+
+ hideLoadingIndicator : function( callback ){
+ this.$el.find( 'div#loading-indicator' ).fadeOut( 'fast', callback );
+ },
+
+ // ------------------------------------------------------------------------- CHART/STATS RENDERING
+ renderChart : function(){
+ // fetch the data, (re-)render the chart
+ this.log( this + '.renderChart' );
+
+ //TODO: separate data fetch
+
+ // this is a complete re-render, so clear the prev. data
+ this.data = null;
+ this.meta = null;
+
+ // update the chartConfig (here and chart) using chart settings
+ //TODO: separate and improve (used in changeChartSettings too)
+ _.extend( this.chartConfig, this.getChartSettings() );
+ this.log( '\t chartConfig:', this.chartConfig );
+ this.chart.updateConfig( this.chartConfig, false );
+
+ // build the url with the current data settings
+ this.loader.url = this.dataURL + '&' + jQuery.param( this.getDataSettings() );
+ this.log( '\t loader: total lines:', this.loader.total, ' url:', this.loader.url );
+
+ // bind the new data event to: aggregate data, update the chart and stats with new data
+ var view = this;
+ $( this.loader ).bind( 'loaded.new', function( event, response ){
+ view.log( view + ' loaded.new', response );
+
+ // aggregate data and meta
+ view.postProcessDataFetchResponse( response );
+ view.log( '\t postprocessed data:', view.data );
+ view.log( '\t postprocessed meta:', view.meta );
+
+ // update the chart and stats
+ view.showLoadingIndicator( view.renderMsg, function(){
+ view.chart.render( view.data, view.meta );
+ view.renderStats( view.data, view.meta );
+ view.hideLoadingIndicator();
+ });
+ });
+ // when all data loaded - unbind (or we'll start doubling event handlers)
+ $( this.loader ).bind( 'complete', function( event, data ){
+ view.log( view + ' complete', data );
+ $( view.loader ).unbind();
+ });
+
+ // begin loading the data, switch to the chart display tab
+ view.showLoadingIndicator( view.fetchMsg, function(){
+ view.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ view.loader.load();
+ });
+ },
+
+ renderStats : function(){
+ this.log( this + '.renderStats' );
+ // render the stats table in the stats panel
+ //TODO: there's a better way
+ this.$statsDisplay.html( ScatterplotControlForm.templates.statsDisplay({
+ stats: [
+ { name: 'Count', xval: this.meta[0].count, yval: this.meta[1].count },
+ { name: 'Min', xval: this.meta[0].min, yval: this.meta[1].min },
+ { name: 'Max', xval: this.meta[0].max, yval: this.meta[1].max },
+ { name: 'Sum', xval: this.meta[0].sum, yval: this.meta[1].sum },
+ { name: 'Mean', xval: this.meta[0].mean, yval: this.meta[1].mean },
+ { name: 'Median', xval: this.meta[0].median, yval: this.meta[1].median }
+ ]
+ }));
+ },
+
+ changeChartSettings : function(){
+ // re-render the chart with new chart settings and OLD data
+ var view = this;
+ newChartSettings = this.getChartSettings();
+
+ // update the chart config from the chartSettings panel controls
+ _.extend( this.chartConfig, newChartSettings );
+ this.log( 'this.chartConfig:', this.chartConfig );
+ this.chart.updateConfig( this.chartConfig, false );
+
+ // if there's current data, call chart.render with it (no data fetch)
+ if( view.data && view.meta ){
+ view.showLoadingIndicator( view.renderMsg, function(){
+ view.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ view.chart.render( view.data, view.meta );
+ view.hideLoadingIndicator();
+ });
+
+ // no current data, call renderChart instead (which will fetch data)
+ } else {
+ this.renderChart();
+ }
+ },
+
+ // ------------------------------------------------------------------------- DATA AGGREGATION
+ postProcessDataFetchResponse : function( response ){
+ // the loader only returns new data - it's up to this to munge the fetches together properly
+ //TODO: we're now storing data in two places: loader and here
+ // can't we reduce incoming data into loader.data[0]? are there concurrency problems?
+ this.postProcessData( response.data );
+ this.postProcessMeta( response.meta );
+ },
+
+ postProcessData : function( newData ){
+ // stack the column data on top of each other into this.data
+ //this.log( this + '.postProcessData:', newData );
+ var view = this;
+
+ // if we already have data: aggregate
+ if( view.data ){
+ _.each( newData, function( newColData, colIndex ){
+ //view.log( colIndex + ' data:', newColData );
+ //TODO??: time, space efficiency of this?
+ view.data[ colIndex ] = view.data[ colIndex ].concat( newColData );
+ });
+
+ // otherwise: assign (first load)
+ } else {
+ view.data = newData;
+ }
+ },
+
+ postProcessMeta : function( newMeta ){
+ // munge the meta data (stats) from the server fetches together
+ //pre: this.data must be preprocessed (needed for medians)
+ //this.log( this + '.postProcessMeta:', newMeta );
+ var view = this,
+ colTypes = this.dataset.metadata_column_types;
+
+ // if we already have meta: aggregate
+ if( view.meta ){
+ _.each( newMeta, function( newColMeta, colIndex ){
+ var colMeta = view.meta[ colIndex ],
+ colType = colTypes[ colIndex ];
+ //view.log( '\t ' + colIndex + ' postprocessing meta:', newColMeta );
+ //view.log( colIndex + ' old meta:',
+ // 'min:', colMeta.min,
+ // 'max:', colMeta.max,
+ // 'sum:', colMeta.sum,
+ // 'mean:', colMeta.mean,
+ // 'median:', colMeta.median
+ //);
+
+ //!TODO: at what point are we getting int/float overflow on these?!
+ //??: need to be null safe?
+ colMeta.count += ( newColMeta.count )?( newColMeta.count ):( 0 );
+ //view.log( colIndex, 'count:', colMeta.count );
+
+ if( ( colType === 'int' ) || ( colType === 'float' ) ){
+ //view.log( colIndex + ' incoming meta:',
+ // 'min:', newColMeta.min,
+ // 'max:', newColMeta.max,
+ // 'sum:', newColMeta.sum,
+ // 'mean:', newColMeta.mean,
+ // 'median:', newColMeta.median
+ //);
+
+ colMeta.min = Math.min( newColMeta.min, colMeta.min );
+ colMeta.max = Math.max( newColMeta.max, colMeta.max );
+ colMeta.sum = newColMeta.sum + colMeta.sum;
+ colMeta.mean = ( colMeta.count )?( colMeta.sum / colMeta.count ):( null );
+
+ // median's a pain bc of sorting (requires the data as well)
+ var sortedCol = view.data[ colIndex ].slice().sort(),
+ middleIndex = Math.floor( sortedCol.length / 2 );
+
+ if( sortedCol.length % 2 === 0 ){
+ colMeta.median = ( ( sortedCol[ middleIndex ] + sortedCol[( middleIndex + 1 )] ) / 2 );
+
+ } else {
+ colMeta.median = sortedCol[ middleIndex ];
+ }
+
+ //view.log( colIndex + ' new meta:',
+ // 'min:', colMeta.min,
+ // 'max:', colMeta.max,
+ // 'sum:', colMeta.sum,
+ // 'mean:', colMeta.mean,
+ // 'median:', colMeta.median
+ //);
+ }
+ });
+
+ // otherwise: assign (first load)
+ } else {
+ view.meta = newMeta;
+ //view.log( '\t meta (first load):', view.meta );
+ }
+ },
+
+ // ------------------------------------------------------------------------- GET DATA/CHART SETTINGS
+ getDataSettings : function(){
+ // parse the column values for both indeces (for the data fetch) and names (for the chart)
+ var columnSelections = this.getColumnSelections(),
+ columns = [];
+ this.log( '\t columnSelections:', columnSelections );
+
+ //TODO: validate columns - minimally: we can assume either set by selectors or via a good query string
+
+ // get column indices for params, include the desired ID column (if any)
+ //NOTE: these are presented in human-readable 1 base index (to match the data.peek) - adjust
+ columns = [
+ columnSelections.X.colIndex - 1,
+ columnSelections.Y.colIndex - 1
+ ];
+ if( this.$dataControl.find( '#include-id-checkbox' ).attr( 'checked' ) ){
+ columns.push( columnSelections.ID.colIndex - 1 );
+ }
+ //TODO: other vals: max, start, page
+
+ var params = {
+ data_type : 'raw_data',
+ columns : '[' + columns + ']'
+ };
+ this.log( '\t data settings (url params):', params );
+ return params;
+ },
+
+ getColumnSelections : function(){
+ // gets the current user-selected values for which columns to fetch from the data settings panel
+ // returns a map: { column-select name (eg. X) : { colIndex : column-selector val,
+ // colName : selected option text }, ... }
+ var selections = {};
+ this.$dataControl.find( 'div.column-select select' ).each( function(){
+ var $this = $( this ),
+ val = $this.val();
+ selections[ $this.attr( 'name' ) ] = {
+ colIndex : val,
+ colName : $this.children( '[value="' + val + '"]' ).text()
+ };
+ });
+ return selections;
+ },
+
+ getChartSettings : function(){
+ // gets the user-selected chartConfig from the chart settings panel
+ var settings = {},
+ colSelections = this.getColumnSelections();
+ //this.log( 'colSelections:', colSelections );
+
+ //TODO: simplify with keys and loop
+ settings.datapointSize = this.$chartControl.find( '#datapointSize.numeric-slider-input' )
+ .find( '.slider' ).slider( 'value' );
+ settings.width = this.$chartControl.find( '#width.numeric-slider-input' )
+ .find( '.slider' ).slider( 'value' );
+ settings.height = this.$chartControl.find( '#height.numeric-slider-input' )
+ .find( '.slider' ).slider( 'value' );
+
+ // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName
+ //TODO: a little confusing
+ var chartSettingsXLabel = this.$chartControl.find( 'input#X-axis-label' ).val(),
+ chartSettingsYLabel = this.$chartControl.find( 'input#Y-axis-label' ).val();
+ settings.xLabel = ( chartSettingsXLabel === 'X' )?
+ ( colSelections.X.colName ):( chartSettingsXLabel );
+ settings.yLabel = ( chartSettingsYLabel === 'Y' )?
+ ( colSelections.Y.colName ):( chartSettingsYLabel );
+
+ settings.animDuration = 10;
+ if( this.$chartControl.find( '#animDuration.checkbox-input' ).is( ':checked' ) ){
+ settings.animDuration = 500;
+ }
+
+ this.log( '\t chartSettings:', settings );
+ return settings;
+ },
+
+ toString : function(){
+ return 'ScatterplotControlForm(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')';
+ }
+});
+
+ScatterplotControlForm.templates = {
+ mainLayout : Handlebars.templates[ 'template-visualization-scatterplotControlForm' ],
+ dataControl : Handlebars.templates[ 'template-visualization-dataControl' ],
+ chartControl : Handlebars.templates[ 'template-visualization-chartControl' ],
+ statsDisplay : Handlebars.templates[ 'template-visualization-statsDisplay' ],
+ chartDisplay : Handlebars.templates[ 'template-visualization-chartDisplay' ]
+};
+
+//==============================================================================
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/mvc/visualizations/scatterplotControlForm.js
--- /dev/null
+++ b/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js
@@ -0,0 +1,1 @@
+var ScatterplotControlForm=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",dataLoadDelay:500,dataLoadSize:3001,loadingIndicatorImage:"loading_small_white_bg.gif",fetchMsg:"Fetching data...",renderMsg:"Rendering...",initialize:function(a){this.log(this+".initialize, attributes:",a);this.dataset=null;this.chartConfig=null;this.chart=null;this.loader=null;this.$dataControl=null;this.$chartControl=null;this.$statsDisplay=null;this.$chartDisplay=null;this.dataFetch=null;this.initializeFromAttributes(a);this.initializeChart(a);this.initializeDataLoader(a)},initializeFromAttributes:function(a){if(!a||!a.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=a.dataset}if(jQuery.type(this.dataset.metadata_column_types)==="string"){this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ")}this.log("\t dataset:",this.dataset);if(!a.apiDatasetsURL){throw ("ScatterplotView requires a apiDatasetsURL")}else{this.dataURL=a.apiDatasetsURL+"/"+this.dataset.id+"?"}this.log("\t dataURL:",this.dataURL)},initializeChart:function(a){this.chartConfig=a.chartConfig||{};this.log("\t initial chartConfig:",this.chartConfig);this.chart=new TwoVarScatterplot(this.chartConfig);this.chartConfig=this.chart.config},initializeDataLoader:function(b){var a=this;this.loader=new LazyDataLoader({url:null,start:b.start||0,total:b.total||this.dataset.metadata_data_lines,delay:this.dataLoadDelay,size:this.dataLoadSize,buildUrl:function(d,c){return this.url+"&"+jQuery.param({start_val:d,max_vals:c})}});$(this.loader).bind("error",function(e,c,d){a.log("ERROR:",c,d);alert("ERROR fetching data:\n"+c+"\n"+d);a.hideLoadingIndicator()})},render:function(){this.log(this+".render");this.$el.append(ScatterplotControlForm.templates.mainLayout({loadingIndicatorImagePath:galaxy_paths.get("image_path")+"/"+this.loadingIndicatorImage,message:""}));this.$dataControl=this._render_dataControl();this.$chartControl=this._render_chartControl();this.$statsDisplay=this.$el.find(".tab-pane#stats-display");this.$chartDisplay=this._render_chartDisplay();if(this.chartConfig.xColumn&&this.chartConfig.yColumn){this.renderChart()}this.$el.find(".tooltip").tooltip();return this},_render_dataControl:function(){var b=this,a=[],d=[];_.each(this.dataset.metadata_column_types,function(h,f){var g=f+1,e="column "+g;if(b.dataset.metadata_column_names){e=b.dataset.metadata_column_names[f]}a.push({index:g,name:e});if(h==="int"||h==="float"){d.push({index:g,name:e})}});var c=this.$el.find(".tab-pane#data-control");c.append(ScatterplotControlForm.templates.dataControl({allColumns:a,numericColumns:d}));c.find("#X-select").val(this.chartConfig.xColumn);c.find("#Y-select").val(this.chartConfig.yColumn);if(this.chartConfig.idColumn!==undefined){c.find("#include-id-checkbox").attr("checked",true).trigger("change");c.find("#ID-select").val(this.chartConfig.idColumn)}return c},_render_chartControl:function(){var a=this,b=this.$el.find(".tab-pane#chart-control"),c={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};b.append(ScatterplotControlForm.templates.chartControl(this.chartConfig));b.find(".numeric-slider-input").each(function(){var f=$(this),e=f.find(".slider-output"),g=f.find(".slider"),h=f.attr("id");function d(){var j=$(this),i=j.slider("value");e.text(i)}g.slider(_.extend(c[h],{value:a.chartConfig[h],change:d,slide:d}))});return b},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");a.append(ScatterplotControlForm.templates.chartDisplay(this.chartConfig));return a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control #render-button":"renderChart","click #chart-control #render-button":"changeChartSettings"},toggleThirdColumnSelector:function(){this.$el.find('select[name="ID"]').parent().toggle()},showLoadingIndicator:function(b,c){b=b||"";var a=this.$el.find("div#loading-indicator");messageBox=a.find(".loading-message");if(a.is(":visible")){if(b){messageBox.fadeOut("fast",function(){messageBox.text(b);messageBox.fadeIn("fast",c)})}else{c()}}else{if(b){messageBox.text(b)}a.fadeIn("fast",c)}},hideLoadingIndicator:function(a){this.$el.find("div#loading-indicator").fadeOut("fast",a)},renderChart:function(){this.log(this+".renderChart");this.data=null;this.meta=null;_.extend(this.chartConfig,this.getChartSettings());this.log("\t chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);this.loader.url=this.dataURL+"&"+jQuery.param(this.getDataSettings());this.log("\t loader: total lines:",this.loader.total," url:",this.loader.url);var a=this;$(this.loader).bind("loaded.new",function(c,b){a.log(a+" loaded.new",b);a.postProcessDataFetchResponse(b);a.log("\t postprocessed data:",a.data);a.log("\t postprocessed meta:",a.meta);a.showLoadingIndicator(a.renderMsg,function(){a.chart.render(a.data,a.meta);a.renderStats(a.data,a.meta);a.hideLoadingIndicator()})});$(this.loader).bind("complete",function(b,c){a.log(a+" complete",c);$(a.loader).unbind()});a.showLoadingIndicator(a.fetchMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.loader.load()})},renderStats:function(){this.log(this+".renderStats");this.$statsDisplay.html(ScatterplotControlForm.templates.statsDisplay({stats:[{name:"Count",xval:this.meta[0].count,yval:this.meta[1].count},{name:"Min",xval:this.meta[0].min,yval:this.meta[1].min},{name:"Max",xval:this.meta[0].max,yval:this.meta[1].max},{name:"Sum",xval:this.meta[0].sum,yval:this.meta[1].sum},{name:"Mean",xval:this.meta[0].mean,yval:this.meta[1].mean},{name:"Median",xval:this.meta[0].median,yval:this.meta[1].median}]}))},changeChartSettings:function(){var a=this;newChartSettings=this.getChartSettings();_.extend(this.chartConfig,newChartSettings);this.log("this.chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);if(a.data&&a.meta){a.showLoadingIndicator(a.renderMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.chart.render(a.data,a.meta);a.hideLoadingIndicator()})}else{this.renderChart()}},postProcessDataFetchResponse:function(a){this.postProcessData(a.data);this.postProcessMeta(a.meta)},postProcessData:function(b){var a=this;if(a.data){_.each(b,function(d,c){a.data[c]=a.data[c].concat(d)})}else{a.data=b}},postProcessMeta:function(c){var a=this,b=this.dataset.metadata_column_types;if(a.meta){_.each(c,function(e,d){var i=a.meta[d],g=b[d];i.count+=(e.count)?(e.count):(0);if((g==="int")||(g==="float")){i.min=Math.min(e.min,i.min);i.max=Math.max(e.max,i.max);i.sum=e.sum+i.sum;i.mean=(i.count)?(i.sum/i.count):(null);var f=a.data[d].slice().sort(),h=Math.floor(f.length/2);if(f.length%2===0){i.median=((f[h]+f[(h+1)])/2)}else{i.median=f[h]}}})}else{a.meta=c}},getDataSettings:function(){var b=this.getColumnSelections(),a=[];this.log("\t columnSelections:",b);a=[b.X.colIndex-1,b.Y.colIndex-1];if(this.$dataControl.find("#include-id-checkbox").attr("checked")){a.push(b.ID.colIndex-1)}var c={data_type:"raw_data",columns:"["+a+"]"};this.log("\t data settings (url params):",c);return c},getColumnSelections:function(){var a={};this.$dataControl.find("div.column-select select").each(function(){var b=$(this),c=b.val();a[b.attr("name")]={colIndex:c,colName:b.children('[value="'+c+'"]').text()}});return a},getChartSettings:function(){var c={},d=this.getColumnSelections();c.datapointSize=this.$chartControl.find("#datapointSize.numeric-slider-input").find(".slider").slider("value");c.width=this.$chartControl.find("#width.numeric-slider-input").find(".slider").slider("value");c.height=this.$chartControl.find("#height.numeric-slider-input").find(".slider").slider("value");var b=this.$chartControl.find("input#X-axis-label").val(),a=this.$chartControl.find("input#Y-axis-label").val();c.xLabel=(b==="X")?(d.X.colName):(b);c.yLabel=(a==="Y")?(d.Y.colName):(a);c.animDuration=10;if(this.$chartControl.find("#animDuration.checkbox-input").is(":checked")){c.animDuration=500}this.log("\t chartSettings:",c);return c},toString:function(){return"ScatterplotControlForm("+((this.dataset)?(this.dataset.id):(""))+")"}});ScatterplotControlForm.templates={mainLayout:Handlebars.templates["template-visualization-scatterplotControlForm"],dataControl:Handlebars.templates["template-visualization-dataControl"],chartControl:Handlebars.templates["template-visualization-chartControl"],statsDisplay:Handlebars.templates["template-visualization-statsDisplay"],chartDisplay:Handlebars.templates["template-visualization-chartDisplay"]};
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/templates/compiled/template-visualization-chartControl.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-visualization-chartControl.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartControl"]=b(function(f,m,e,l,k){e=e||f.helpers;var i="",c,h,g="function",j=this.escapeExpression,n=this;function d(p,o){return' checked="true"'}i+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n </p>\n\n <div id="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">';h=e.datapointSize;if(h){c=h.call(m,{hash:{}})}else{c=m.datapointSize;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id="animDuration" class="form-input checkbox-input">\n <label for="animated">Animate graph transitions?: </label>\n <input type="checkbox" id="animated"\n class="checkbox control"';c=m.animDuration;c=e["if"].call(m,c,{hash:{},inverse:n.noop,fn:n.program(1,d,k)});if(c||c===0){i+=c}i+=' />\n <p class="form-help help-text-small">\n Uncheck this to disable the animations used on the graph\n </p>\n </div>\n\n <div id="width" class="form-input numeric-slider-input">\n <label for="width">Graph width: </label>\n <div class="slider-output">';h=e.width;if(h){c=h.call(m,{hash:{}})}else{c=m.width;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id="height" class="form-input numeric-slider-input">\n <label for="height">Graph height: </label>\n <div class="slider-output">';h=e.height;if(h){c=h.call(m,{hash:{}})}else{c=m.height;c=typeof c===g?c():c}i+=j(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="';h=e.xLabel;if(h){c=h.call(m,{hash:{}})}else{c=m.xLabel;c=typeof c===g?c():c}i+=j(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <div id="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="';h=e.yLabel;if(h){c=h.call(m,{hash:{}})}else{c=m.yLabel;c=typeof c===g?c():c}i+=j(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <input id="render-button" type="button" value="Draw" />';return i})})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/templates/compiled/template-visualization-chartDisplay.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-visualization-chartDisplay.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartDisplay"]=b(function(e,l,d,k,j){d=d||e.helpers;var h="",c,g,f="function",i=this.escapeExpression;h+='<svg width="';g=d.width;if(g){c=g.call(l,{hash:{}})}else{c=l.width;c=typeof c===f?c():c}h+=i(c)+'" height="';g=d.height;if(g){c=g.call(l,{hash:{}})}else{c=l.height;c=typeof c===f?c():c}h+=i(c)+'"></svg>';return h})})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/templates/compiled/template-visualization-dataControl.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-visualization-dataControl.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-dataControl"]=b(function(g,m,f,l,k){f=f||g.helpers;var i="",d,h="function",j=this.escapeExpression,o=this;function e(t,s){var q="",r,p;q+='\n <option value="';p=f.index;if(p){r=p.call(t,{hash:{}})}else{r=t.index;r=typeof r===h?r():r}q+=j(r)+'">';p=f.name;if(p){r=p.call(t,{hash:{}})}else{r=t.name;r=typeof r===h?r():r}q+=j(r)+"</option>\n ";return q}function c(t,s){var q="",r,p;q+='\n <option value="';p=f.index;if(p){r=p.call(t,{hash:{}})}else{r=t.index;r=typeof r===h?r():r}q+=j(r)+'">';p=f.name;if(p){r=p.call(t,{hash:{}})}else{r=t.name;r=typeof r===h?r():r}q+=j(r)+"</option>\n ";return q}function n(t,s){var q="",r,p;q+='\n <option value="';p=f.index;if(p){r=p.call(t,{hash:{}})}else{r=t.index;r=typeof r===h?r():r}q+=j(r)+'">';p=f.name;if(p){r=p.call(t,{hash:{}})}else{r=t.name;r=typeof r===h?r():r}q+=j(r)+"</option>\n ";return q}i+="<p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n ";i+='\n <div class="column-select">\n <label for="X-select">Data column for X: </label>\n <select name="X" id="X-select">\n ';d=m.numericColumns;d=f.each.call(m,d,{hash:{},inverse:o.noop,fn:o.program(1,e,k)});if(d||d===0){i+=d}i+='\n </select>\n </div>\n <div class="column-select">\n <label for="Y-select">Data column for Y: </label>\n <select name="Y" id="Y-select">\n ';d=m.numericColumns;d=f.each.call(m,d,{hash:{},inverse:o.noop,fn:o.program(3,c,k)});if(d||d===0){i+=d}i+="\n </select>\n </div>\n\n ";i+='\n <div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="ID" id="ID-select">\n ';d=m.allColumns;d=f.each.call(m,d,{hash:{},inverse:o.noop,fn:o.program(5,n,k)});if(d||d===0){i+=d}i+='\n </select>\n </div>\n\n <input id="render-button" type="button" value="Draw" />\n <div class="clear"></div>';return i})})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/templates/compiled/template-visualization-scatterplotControlForm.js
--- a/static/scripts/packed/templates/compiled/template-visualization-scatterplotControlForm.js
+++ b/static/scripts/packed/templates/compiled/template-visualization-scatterplotControlForm.js
@@ -1,1 +1,1 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-scatterplotControlForm"]=b(function(g,n,f,m,l){f=f||g.helpers;var j="",d,i,h="function",k=this.escapeExpression,p=this;function e(u,t){var r="",s,q;r+='\n <option value="';q=f.index;if(q){s=q.call(u,{hash:{}})}else{s=u.index;s=typeof s===h?s():s}r+=k(s)+'">';q=f.name;if(q){s=q.call(u,{hash:{}})}else{s=u.name;s=typeof s===h?s():s}r+=k(s)+"</option>\n ";return r}function c(u,t){var r="",s,q;r+='\n <option value="';q=f.index;if(q){s=q.call(u,{hash:{}})}else{s=u.index;s=typeof s===h?s():s}r+=k(s)+'">';q=f.name;if(q){s=q.call(u,{hash:{}})}else{s=u.name;s=typeof s===h?s():s}r+=k(s)+"</option>\n ";return r}function o(u,t){var r="",s,q;r+='\n <option value="';q=f.index;if(q){s=q.call(u,{hash:{}})}else{s=u.index;s=typeof s===h?s():s}r+=k(s)+'">';q=f.name;if(q){s=q.call(u,{hash:{}})}else{s=u.name;s=typeof s===h?s():s}r+=k(s)+"</option>\n ";return r}j+='\n\n<ul class="nav nav-tabs">\n <li class="active"><a data-toggle="tab" href="#data-settings">Data Controls</a></li>\n <li><a data-toggle="tab" href="#chart-settings">Plot Controls</a></li>\n <li><a data-toggle="tab" href="#chart-stats">Statistics</a></li>\n <li><a data-toggle="tab" href="#chart">Chart</a></li>\n</ul>\n\n';j+='\n<div class="tab-content">\n<div id="data-settings" class="tab-pane active">\n\n <p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n </p>\n \n ';j+='\n <div class="column-select">\n <label for="X-select">Data column for X: </label>\n <select name="X" id="X-select">\n ';d=n.numericColumns;d=f.each.call(n,d,{hash:{},inverse:p.noop,fn:p.program(1,e,l)});if(d||d===0){j+=d}j+='\n </select>\n </div>\n <div class="column-select">\n <label for="Y-select">Data column for Y: </label>\n <select name="Y" id="Y-select">\n ';d=n.numericColumns;d=f.each.call(n,d,{hash:{},inverse:p.noop,fn:p.program(3,c,l)});if(d||d===0){j+=d}j+="\n </select>\n </div>\n \n ";j+='\n <div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="ID" id="ID-select">\n ';d=n.allColumns;d=f.each.call(n,d,{hash:{},inverse:p.noop,fn:p.program(5,o,l)});if(d||d===0){j+=d}j+='\n </select>\n </div>\n \n <input id="render-button" type="button" value="Draw" />\n <input id="save-button" type="button" value="Download Plot as SVG" style="display: none;" />\n <div class="clear"></div>\n</div>\n\n<div id="chart-settings" class="tab-pane">\n</div>\n\n<div id="chart-stats" class="tab-pane">\n</div>\n</div>';j+="\n\n";j+='\n<div id="loading-indicator" style="display: none;">\n <img class="loading-img" src="';i=f.loadingIndicatorImagePath;if(i){d=i.call(n,{hash:{}})}else{d=n.loadingIndicatorImagePath;d=typeof d===h?d():d}j+=k(d)+'" />\n <span class="loading-message">';i=f.message;if(i){d=i.call(n,{hash:{}})}else{d=n.message;d=typeof d===h?d():d}j+=k(d)+"</span>\n</div>";return j})})();
\ No newline at end of file
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-scatterplotControlForm"]=b(function(e,l,d,k,j){d=d||e.helpers;var h="",c,g,f="function",i=this.escapeExpression;h+='\n\n<div class="scatterplot-container chart-container tabbable tabs-left">\n ';h+='\n <ul class="nav nav-tabs">\n ';h+='\n <li class="active"><a href="#data-control" data-toggle="tab" class="tooltip"\n title="Use this tab to change which data are used">Data Controls</a></li>\n <li><a href="#chart-control" data-toggle="tab" class="tooltip"\n title="Use this tab to change how the chart is drawn">Chart Controls</a></li>\n <li><a href="#stats-display" data-toggle="tab" class="tooltip"\n title="This tab will display overall statistics for your data">Statistics</a></li>\n <li><a href="#chart-display" data-toggle="tab" class="tooltip"\n title="This tab will display the chart">Chart</a>\n ';h+='\n <div id="loading-indicator" style="display: none;">\n <img class="loading-img" src="';g=d.loadingIndicatorImagePath;if(g){c=g.call(l,{hash:{}})}else{c=l.loadingIndicatorImagePath;c=typeof c===f?c():c}h+=i(c)+'" />\n <span class="loading-message">';g=d.message;if(g){c=g.call(l,{hash:{}})}else{c=l.message;c=typeof c===f?c():c}h+=i(c)+"</span>\n </div>\n </li>\n </ul>\n\n ";h+='\n <div class="tab-content">\n ';h+='\n <div id="data-control" class="tab-pane active">\n ';h+="\n </div>\n \n ";h+='\n <div id="chart-control" class="tab-pane">\n ';h+="\n </div>\n\n ";h+='\n <div id="stats-display" class="tab-pane">\n ';h+="\n </div>\n\n ";h+='\n <div id="chart-display" class="tab-pane">\n ';h+="\n </div>\n\n </div>";h+="\n</div>";return h})})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/templates/compiled/template-visualization-statsDisplay.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-visualization-statsDisplay.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-statsDisplay"]=b(function(f,l,e,k,j){e=e||f.helpers;var h="",c,g="function",i=this.escapeExpression,m=this;function d(r,q){var o="",p,n;o+="\n <tr><td>";n=e.name;if(n){p=n.call(r,{hash:{}})}else{p=r.name;p=typeof p===g?p():p}o+=i(p)+"</td><td>";n=e.xval;if(n){p=n.call(r,{hash:{}})}else{p=r.xval;p=typeof p===g?p():p}o+=i(p)+"</td><td>";n=e.yval;if(n){p=n.call(r,{hash:{}})}else{p=r.yval;p=typeof p===g?p():p}o+=i(p)+"</td></tr>\n </tr>\n ";return o}h+='<p class="help-text">By column:</p>\n <table id="chart-stats-table">\n <thead><th></th><th>X</th><th>Y</th></thead>\n ';c=l.stats;c=e.each.call(l,c,{hash:{},inverse:m.noop,fn:m.program(1,d,j)});if(c||c===0){h+=c}h+="\n </table>";return h})})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/packed/viz/scatterplot.js
--- a/static/scripts/packed/viz/scatterplot.js
+++ b/static/scripts/packed/viz/scatterplot.js
@@ -1,1 +1,1 @@
-define(["../libs/underscore","../mvc/base-mvc","../utils/LazyDataLoader","../templates/compiled/template-visualization-scatterplotControlForm","../templates/compiled/template-visualization-statsTable","../templates/compiled/template-visualization-chartSettings","../libs/d3","../libs/bootstrap","../libs/jquery/jquery-ui"],function(){function a(f){var d=10,h=7,g=10,e=8,c=5;this.log=function(){if(this.debugging&&console&&console.debug){var i=Array.prototype.slice.call(arguments);i.unshift(this.toString());console.debug.apply(console,i)}};this.log("new TwoVarScatterplot:",f);this.defaults={id:"TwoVarScatterplot",containerSelector:"body",maxDataPoints:30000,datapointSize:4,animDuration:500,xNumTicks:10,yNumTicks:10,xAxisLabelBumpY:40,yAxisLabelBumpX:-35,width:500,height:500,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,xMin:null,xMax:null,yMin:null,yMax:null,xLabel:"X",yLabel:"Y"};this.config=_.extend({},this.defaults,f);this.updateConfig=function(i,j){_.extend(this.config,i)};this.toString=function(){return this.config.id};this.translateStr=function(i,j){return"translate("+i+","+j+")"};this.rotateStr=function(j,i,k){return"rotate("+j+","+i+","+k+")"};this.svg=d3.select(this.config.containerSelector).append("svg:svg").attr("class","chart");this.content=this.svg.append("svg:g").attr("class","content").attr("id",this.config.id);this.xAxis=this.content.append("g").attr("class","axis").attr("id","x-axis");this.xAxisLabel=this.xAxis.append("text").attr("class","axis-label").attr("id","x-axis-label");this.yAxis=this.content.append("g").attr("class","axis").attr("id","y-axis");this.yAxisLabel=this.yAxis.append("text").attr("class","axis-label").attr("id","y-axis-label");this.adjustChartDimensions=function(l,j,i,k){l=l||0;j=j||0;i=i||0;k=k||0;this.svg.attr("width",this.config.width+(this.config.marginRight+j)+(this.config.marginLeft+k)).attr("height",this.config.height+(this.config.marginTop+l)+(this.config.marginBottom+i)).style("display","block");this.content=this.svg.select("g.content").attr("transform",this.translateStr(this.config.marginLeft+k,this.config.marginTop+l))};this.preprocessData=function(k,j,i){return(k.length>this.config.maxDataPoints)?(k.slice(0,this.config.maxDataPoints)):(k)};this.setUpDomains=function(i,k,j){this.xMin=this.config.xMin||(j)?(j[0].min):(d3.min(i));this.xMax=this.config.xMax||(j)?(j[0].max):(d3.max(i));this.yMin=this.config.yMin||(j)?(j[1].min):(d3.min(k));this.yMax=this.config.yMax||(j)?(j[1].max):(d3.max(k))};this.setUpScales=function(){this.xScale=d3.scale.linear().domain([this.xMin,this.xMax]).range([0,this.config.width]),this.yScale=d3.scale.linear().domain([this.yMin,this.yMax]).range([this.config.height,0])};this.setUpXAxis=function(){this.xAxisFn=d3.svg.axis().scale(this.xScale).ticks(this.config.xNumTicks).orient("bottom");this.xAxis.attr("transform",this.translateStr(0,this.config.height)).call(this.xAxisFn);this.xLongestLabel=d3.max(_.map([this.xMin,this.xMax],function(i){return(String(i)).length}));if(this.xLongestLabel>=c){this.xAxis.selectAll("g").filter(":nth-child(odd)").style("display","none")}this.xAxisLabel.attr("x",this.config.width/2).attr("y",this.config.xAxisLabelBumpY).attr("text-anchor","middle").text(this.config.xLabel)};this.setUpYAxis=function(){this.yAxisFn=d3.svg.axis().scale(this.yScale).ticks(this.config.yNumTicks).orient("left");this.yAxis.call(this.yAxisFn);var i=this.yAxis.selectAll("text").filter(function(m,l){return l!==0});this.yLongestLabel=d3.max(i[0].map(function(m,l){return(d3.select(m).text()).length}))||0;var j=d+(this.yLongestLabel*h)+e+g;this.config.yAxisLabelBumpX=-(j-g);if(this.config.marginLeft<j){var k=(j)-this.config.marginLeft;k=(k<0)?(0):(k);this.adjustChartDimensions(0,0,0,k)}this.yAxisLabel.attr("x",this.config.yAxisLabelBumpX).attr("y",this.config.height/2).attr("text-anchor","middle").attr("transform",this.rotateStr(-90,this.config.yAxisLabelBumpX,this.config.height/2)).text(this.config.yLabel)};this.renderGrid=function(){this.vGridLines=this.content.selectAll("line.v-grid-line").data(this.xScale.ticks(this.xAxisFn.ticks()[0]));this.vGridLines.enter().append("svg:line").classed("grid-line v-grid-line",true);this.vGridLines.attr("x1",this.xScale).attr("y1",0).attr("x2",this.xScale).attr("y2",this.config.height);this.vGridLines.exit().remove();this.hGridLines=this.content.selectAll("line.h-grid-line").data(this.yScale.ticks(this.yAxisFn.ticks()[0]));this.hGridLines.enter().append("svg:line").classed("grid-line h-grid-line",true);this.hGridLines.attr("x1",0).attr("y1",this.yScale).attr("x2",this.config.width).attr("y2",this.yScale);this.hGridLines.exit().remove()};this.glyphEnterState=function(i){};this.glyphFinalState=function(i){};this.glyphExitState=function(i){};this.renderDatapoints=function(i,l,j){this.log(this+".renderDatapoints",arguments);var k=0;this.datapoints=this.addDatapoints(i,l,j,".glyph");this.datapoints.exit().each(function(){k+=1}).transition().duration(this.config.animDuration).attr("cy",this.config.height).attr("r",0).remove();this.log(k," glyphs removed")};this.addDatapoints=function(m,k,i,l){this.log(this+".addDatapoints",arguments);var p=this,o=0,q=function(s,r){return p.xScale(m[r])},j=function(s,r){return p.yScale(k[r])};var n=this.content.selectAll(l);this.log("existing datapoints:",n);n=n.data(m);o=0;n.enter().append("svg:circle").each(function(){o+=1}).classed("glyph",true).attr("cx",q).attr("cy",j).attr("r",0);this.log(o," new glyphs created");o=0;n.transition().duration(this.config.animDuration).each(function(){o+=1}).attr("cx",q).attr("cy",j).attr("r",p.config.datapointSize);this.log(o," existing glyphs transitioned");if(i){n.attr("data",function(s,r){return(i[r])})}n.attr("title",function(s,r){return((i)?(i[r]+": "):(""))+m[r]+", "+k[r]});n.on("mouseover",function(t,r){var s=d3.select(this);s.style("fill","red").style("fill-opacity",1);p.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",s.attr("cx")).attr("y1",s.attr("cy")).attr("x2",0).attr("y2",s.attr("cy")).classed("hoverline",true);p.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",s.attr("cx")).attr("y1",s.attr("cy")).attr("x2",s.attr("cx")).attr("y2",p.config.height).classed("hoverline",true)}).on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",0.2);d3.selectAll(".hoverline").remove()});return n};this.render=function(k,l){this.log(this+".render",arguments);var i=k[0],m=k[1],j=(k.length>2)?(k[2]):(undefined);i=this.preprocessData(i);m=this.preprocessData(m);this.log("xCol len",i.length,"yCol len",m.length);this.setUpDomains(i,m,l);this.setUpScales();this.adjustChartDimensions();this.setUpXAxis();this.setUpYAxis();this.renderGrid();this.renderDatapoints(i,m,j)}}var b=BaseView.extend(LoggableMixin).extend({className:"scatterplot-settings-form",dataLoadDelay:500,dataLoadSize:3001,loadingIndicatorImage:"loading_large_white_bg.gif",initialize:function(c){this.log(this+".initialize, attributes:",c);this.dataset=null;this.chartConfig=null;this.plot=null;this.loader=null;this.$statsPanel=null;this.$chartSettingsPanel=null;this.$dataSettingsPanel=null;this.dataFetch=null;this.initializeFromAttributes(c);this.initializeChart(c);this.initializeDataLoader(c)},initializeFromAttributes:function(c){if(!c||!c.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=c.dataset}if(jQuery.type(this.dataset.metadata_column_types)==="string"){this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ")}this.log("dataset:",this.dataset);if(!c.apiDatasetsURL){throw ("ScatterplotView requires a apiDatasetsURL")}else{this.dataURL=c.apiDatasetsURL+"/"+this.dataset.id+"?"}this.log("this.dataURL:",this.dataURL)},initializeChart:function(c){this.chartConfig=c.chartConfig||{};if(this.logger){this.chartConfig.debugging=true}this.log("initial chartConfig:",this.chartConfig);this.plot=new a(this.chartConfig);this.chartConfig=this.plot.config},initializeDataLoader:function(d){var c=this;this.loader=new LazyDataLoader({logger:(this.logger)?(this.logger):(null),url:null,start:d.start||0,total:d.total||this.dataset.metadata_data_lines,delay:this.dataLoadDelay,size:this.dataLoadSize,buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})}});$(this.loader).bind("error",function(g,e,f){c.log("ERROR:",e,f);alert("ERROR fetching data:\n"+e+"\n"+f);c.hideLoadingIndicator()})},render:function(){var c=this,d={config:this.chartConfig,allColumns:[],numericColumns:[],loadingIndicatorImagePath:galaxy_paths.get("image_path")+"/"+this.loadingIndicatorImage};_.each(this.dataset.metadata_column_types,function(g,f){var e="column "+(f+1);if(c.dataset.metadata_column_names){e=c.dataset.metadata_column_names[f]}if(g==="int"||g==="float"){d.numericColumns.push({index:(f+1),name:e})}d.allColumns.push({index:(f+1),name:e})});this.$el.append(b.templates.form(d));this.$dataSettingsPanel=this.$el.find(".tab-pane#data-settings");this.$chartSettingsPanel=this._render_chartSettings();this.$statsPanel=this.$el.find(".tab-pane#chart-stats");this.$dataSettingsPanel.find("#X-select").val(this.chartConfig.xColumn);this.$dataSettingsPanel.find("#Y-select").val(this.chartConfig.yColumn);if(this.chartConfig.idColumn!==undefined){this.$dataSettingsPanel.find("#include-id-checkbox").attr("checked",true).trigger("change");this.$dataSettingsPanel.find("#ID-select").val(this.chartConfig.idColumn)}if(this.chartConfig.xColumn&&this.chartConfig.yColumn){this.renderPlot()}return this},isColumnNumeric:function(c){if((c>=0)&&(c<this.dataset.metadata_column_types.length)){var d=this.dataset.metadata_column_types[c];return(d==="int"||d==="float")}return false},_render_chartSettings:function(){var c=this,d=this.$el.find(".tab-pane#chart-settings"),e={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};d.append(b.templates.chartSettings(this.chartConfig));d.find(".numeric-slider-input").each(function(){var h=$(this),g=h.find(".slider-output"),i=h.find(".slider"),j=h.attr("id");function f(){var l=$(this),k=l.slider("value");g.text(k)}i.slider(_.extend(e[j],{value:c.chartConfig[j],change:f,slide:f}))});return d},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-settings #render-button":"renderPlot","click #chart-settings #render-button":"changeChartSettings"},toggleThirdColumnSelector:function(){this.$el.find('select[name="ID"]').parent().toggle()},showLoadingIndicator:function(d,e){d=d||"";var c=this.$el.find("div#loading-indicator");messageBox=c.find(".loading-message");if(c.is(":visible")){if(d){messageBox.fadeOut("fast",function(){messageBox.text(d);messageBox.fadeIn("fast",e)})}else{e()}}else{if(d){messageBox.text(d)}c.fadeIn("fast",e)}},hideLoadingIndicator:function(c){this.$el.find("div#loading-indicator").fadeOut("fast",c)},renderPlot:function(){var c=this;c.data=null;c.meta=null;_.extend(this.chartConfig,this.getGraphSettings());this.log("this.chartConfig:",this.chartConfig);this.plot.updateConfig(this.chartConfig,false);this.loader.url=this.dataURL+"&"+jQuery.param(this.getDataSettings());this.log("this.loader, url:",this.loader.url,"total:",this.loader.total);$(this.loader).bind("loaded.new",function(e,d){c.log(c+" loaded.new",d);c.postProcessDataFetchResponse(d);c.log("postprocessed data:",c.data,"meta:",c.meta);c.showLoadingIndicator("Rendering...",function(){c.$el.find("ul.nav").find('a[href="#chart-stats"]').tab("show");c.plot.render(c.data,c.meta);c.renderStats(c.data,c.meta);c.hideLoadingIndicator()})});$(this.loader).bind("complete",function(d,e){c.log("complete",e);$(c.loader).unbind()});c.showLoadingIndicator("Fetching data...",function(){c.loader.load()})},renderStats:function(){this.$statsPanel.html(b.templates.statsTable({stats:[{name:"Count",xval:this.meta[0].count,yval:this.meta[1].count},{name:"Min",xval:this.meta[0].min,yval:this.meta[1].min},{name:"Max",xval:this.meta[0].max,yval:this.meta[1].max},{name:"Sum",xval:this.meta[0].sum,yval:this.meta[1].sum},{name:"Mean",xval:this.meta[0].mean,yval:this.meta[1].mean},{name:"Median",xval:this.meta[0].median,yval:this.meta[1].median}]}))},changeChartSettings:function(){var c=this;newGraphSettings=this.getGraphSettings();this.log("newGraphSettings:",newGraphSettings);_.extend(this.chartConfig,newGraphSettings);this.log("this.chartConfig:",this.chartConfig);this.plot.updateConfig(this.chartConfig,false);if(c.data&&c.meta){c.showLoadingIndicator("Rendering...",function(){c.plot.render(c.data,c.meta);c.hideLoadingIndicator()})}else{this.renderPlot()}},postProcessDataFetchResponse:function(c){this.postProcessData(c.data);this.postProcessMeta(c.meta)},postProcessData:function(d){var c=this;if(c.data){_.each(d,function(f,e){c.data[e]=c.data[e].concat(f)})}else{c.data=d}},postProcessMeta:function(e){var c=this,d=this.dataset.metadata_column_types;if(c.meta){_.each(e,function(g,f){var k=c.meta[f],i=d[f];c.log(f+" postprocessing meta:",g);k.count+=(g.count)?(g.count):(0);c.log(f,"count:",k.count);if((i==="int")||(i==="float")){k.min=Math.min(g.min,k.min);k.max=Math.max(g.max,k.max);k.sum=g.sum+k.sum;k.mean=(k.count)?(k.sum/k.count):(null);var h=c.data[f].slice().sort(),j=Math.floor(h.length/2);if(h.length%2===0){k.median=((h[j]+h[(j+1)])/2)}else{k.median=h[j]}}})}else{c.meta=e;c.log("initial meta:",c.meta)}},getDataSettings:function(){var d=this.getColumnSelections(),c=[];this.log("columnSelections:",d);c=[d.X.colIndex,d.Y.colIndex];if(this.$dataSettingsPanel.find("#include-id-checkbox").attr("checked")){c.push(d.ID.colIndex)}var e={data_type:"raw_data",columns:"["+c+"]"};this.log("params:",e);return e},getColumnSelections:function(){var c={};this.$dataSettingsPanel.find("div.column-select select").each(function(){var d=$(this),e=parseInt(d.val(),10)-1;c[d.attr("name")]={colIndex:e,colName:d.children('[value="'+e+'"]').text()}});return c},getGraphSettings:function(){var e={},f=this.getColumnSelections();e.datapointSize=this.$chartSettingsPanel.find("#datapointSize.numeric-slider-input").find(".slider").slider("value");e.width=this.$chartSettingsPanel.find("#width.numeric-slider-input").find(".slider").slider("value");e.height=this.$chartSettingsPanel.find("#height.numeric-slider-input").find(".slider").slider("value");var d=this.$chartSettingsPanel.find("input#X-axis-label").val(),c=this.$chartSettingsPanel.find("input#Y-axis-label").val();e.xLabel=(d==="X")?(f.X.colName):(d);e.yLabel=(c==="Y")?(f.Y.colName):(c);e.animDuration=10;if(this.$chartSettingsPanel.find("#animDuration.checkbox-input").is(":checked")){e.animDuration=500}this.log("graphSettings:",e);return e},toString:function(){return"ScatterplotControlForm("+((this.dataset)?(this.dataset.id):(""))+")"}});b.templates={form:Handlebars.templates["template-visualization-scatterplotControlForm"],statsTable:Handlebars.templates["template-visualization-statsTable"],chartSettings:Handlebars.templates["template-visualization-chartSettings"]};return{LazyDataLoader:LazyDataLoader,TwoVarScatterplot:a,ScatterplotControlForm:b}});
\ No newline at end of file
+function TwoVarScatterplot(d){var b=10,f=7,e=10,c=8,a=5;this.log=function(){if(this.debugging&&console&&console.debug){var g=Array.prototype.slice.call(arguments);g.unshift(this.toString());console.debug.apply(console,g)}};this.log("new TwoVarScatterplot:",d);this.defaults={id:"TwoVarScatterplot",containerSelector:"body",maxDataPoints:30000,datapointSize:4,animDuration:500,xNumTicks:10,yNumTicks:10,xAxisLabelBumpY:40,yAxisLabelBumpX:-35,width:400,height:400,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,xMin:null,xMax:null,yMin:null,yMax:null,xLabel:"X",yLabel:"Y"};this.config=_.extend({},this.defaults,d);this.log("intial config:",this.config);this.updateConfig=function(g,h){_.extend(this.config,g);this.log(this+".updateConfig:",this.config)};this.toString=function(){return this.config.id};this.translateStr=function(g,h){return"translate("+g+","+h+")"};this.rotateStr=function(h,g,i){return"rotate("+h+","+g+","+i+")"};this.adjustChartDimensions=function(j,h,g,i){j=j||0;h=h||0;g=g||0;i=i||0;this.svg.attr("width",this.config.width+(this.config.marginRight+h)+(this.config.marginLeft+i)).attr("height",this.config.height+(this.config.marginTop+j)+(this.config.marginBottom+g)).style("display","block");this.content=this.svg.select("g.content").attr("transform",this.translateStr(this.config.marginLeft+i,this.config.marginTop+j))};this.preprocessData=function(i,h,g){return(i.length>this.config.maxDataPoints)?(i.slice(0,this.config.maxDataPoints)):(i)};this.findMinMaxes=function(g,i,h){this.xMin=this.config.xMin||(h)?(h[0].min):(d3.min(g));this.xMax=this.config.xMax||(h)?(h[0].max):(d3.max(g));this.yMin=this.config.yMin||(h)?(h[1].min):(d3.min(i));this.yMax=this.config.yMax||(h)?(h[1].max):(d3.max(i))};this.setUpScales=function(){this.xScale=d3.scale.linear().domain([this.xMin,this.xMax]).range([0,this.config.width]),this.yScale=d3.scale.linear().domain([this.yMin,this.yMax]).range([this.config.height,0])};this.setUpXAxis=function(){this.xAxisFn=d3.svg.axis().scale(this.xScale).ticks(this.config.xNumTicks).orient("bottom");this.xAxis.attr("transform",this.translateStr(0,this.config.height)).call(this.xAxisFn);var g=d3.max(_.map([this.xMin,this.xMax],function(h){return(String(h)).length}));if(g>=a){this.xAxis.selectAll("g").filter(":nth-child(odd)").style("display","none")}this.log("this.config.xLabel:",this.config.xLabel);this.xAxisLabel.attr("x",this.config.width/2).attr("y",this.config.xAxisLabelBumpY).attr("text-anchor","middle").text(this.config.xLabel);this.log("xAxisLabel:",this.xAxisLabel)};this.setUpYAxis=function(){this.yAxisFn=d3.svg.axis().scale(this.yScale).ticks(this.config.yNumTicks).orient("left");this.yAxis.call(this.yAxisFn);var g=this.yAxis.selectAll("text").filter(function(k,j){return j!==0});this.log("yTickLabels:",g);this.yLongestLabel=d3.max(g[0].map(function(k,j){return(d3.select(k).text()).length}))||0;var h=b+(this.yLongestLabel*f)+c+e;this.config.yAxisLabelBumpX=-(h-e);if(this.config.marginLeft<h){var i=(h)-this.config.marginLeft;i=(i<0)?(0):(i);this.adjustChartDimensions(0,0,0,i)}this.yAxisLabel.attr("x",this.config.yAxisLabelBumpX).attr("y",this.config.height/2).attr("text-anchor","middle").attr("transform",this.rotateStr(-90,this.config.yAxisLabelBumpX,this.config.height/2)).text(this.config.yLabel)};this.renderGrid=function(){this.vGridLines=this.content.selectAll("line.v-grid-line").data(this.xScale.ticks(this.xAxisFn.ticks()[0]));this.vGridLines.enter().append("svg:line").classed("grid-line v-grid-line",true);this.vGridLines.attr("x1",this.xScale).attr("y1",0).attr("x2",this.xScale).attr("y2",this.config.height);this.vGridLines.exit().remove();this.hGridLines=this.content.selectAll("line.h-grid-line").data(this.yScale.ticks(this.yAxisFn.ticks()[0]));this.hGridLines.enter().append("svg:line").classed("grid-line h-grid-line",true);this.hGridLines.attr("x1",0).attr("y1",this.yScale).attr("x2",this.config.width).attr("y2",this.yScale);this.hGridLines.exit().remove()};this.glyphEnterState=function(g){};this.glyphFinalState=function(g){};this.glyphExitState=function(g){};this.renderDatapoints=function(g,j,h){this.log(this+".renderDatapoints",arguments);var i=0;this.datapoints=this.addDatapoints(g,j,h,".glyph");this.datapoints.exit().each(function(){i+=1}).transition().duration(this.config.animDuration).attr("cy",this.config.height).attr("r",0).remove();this.log(i," glyphs removed")};this.addDatapoints=function(k,i,g,j){this.log(this+".addDatapoints",arguments);var n=this,m=0,o=function(q,p){return n.xScale(k[p])},h=function(q,p){return n.yScale(i[p])};var l=this.content.selectAll(j);this.log("existing datapoints:",l);l=l.data(k);m=0;l.enter().append("svg:circle").each(function(){m+=1}).classed("glyph",true).attr("cx",o).attr("cy",h).attr("r",0);this.log(m," new glyphs created");m=0;l.transition().duration(this.config.animDuration).each(function(){m+=1}).attr("cx",o).attr("cy",h).attr("r",n.config.datapointSize);this.log(m," existing glyphs transitioned");if(g){l.attr("data",function(q,p){return(g[p])})}l.attr("svg:title",function(q,p){return((g)?(g[p]+": "):(""))+k[p]+", "+i[p]});l.on("mouseover",function(r,p){var q=d3.select(this);q.style("fill","red").style("fill-opacity",1);n.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",q.attr("cx")).attr("y1",q.attr("cy")).attr("x2",0).attr("y2",q.attr("cy")).classed("hoverline",true);n.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",q.attr("cx")).attr("y1",q.attr("cy")).attr("x2",q.attr("cx")).attr("y2",n.config.height).classed("hoverline",true)}).on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",0.2);d3.selectAll(".hoverline").remove()});return l};this.render=function(i,j){this.log(this+".render",arguments);this.log("\t config:",this.config);var g=i[0],k=i[1],h=(i.length>2)?(i[2]):(undefined);g=this.preprocessData(g);k=this.preprocessData(k);this.log("xCol len",g.length,"yCol len",k.length);this.findMinMaxes(g,k,j);this.setUpScales();if(!this.svg){this.svg=d3.select("svg").attr("class","chart")}if(!this.content){this.content=this.svg.append("svg:g").attr("class","content").attr("id",this.config.id)}this.adjustChartDimensions();if(!this.xAxis){this.xAxis=this.content.append("g").attr("class","axis").attr("id","x-axis")}if(!this.xAxisLabel){this.xAxisLabel=this.xAxis.append("text").attr("class","axis-label").attr("id","x-axis-label")}if(!this.yAxis){this.yAxis=this.content.append("g").attr("class","axis").attr("id","y-axis")}if(!this.yAxisLabel){this.yAxisLabel=this.yAxis.append("text").attr("class","axis-label").attr("id","y-axis-label")}this.setUpXAxis();this.setUpYAxis();this.renderGrid();this.renderDatapoints(g,k,h)}};
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-chartControl.js
--- /dev/null
+++ b/static/scripts/templates/compiled/template-visualization-chartControl.js
@@ -0,0 +1,38 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['template-visualization-chartControl'] = template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+ var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+
+ return " checked=\"true\"";}
+
+ buffer += "<p class=\"help-text\">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n <div id=\"datapointSize\" class=\"form-input numeric-slider-input\">\n <label for=\"datapointSize\">Size of data point: </label>\n <div class=\"slider-output\">";
+ foundHelper = helpers.datapointSize;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.datapointSize; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id=\"animDuration\" class=\"form-input checkbox-input\">\n <label for=\"animated\">Animate graph transitions?: </label>\n <input type=\"checkbox\" id=\"animated\"\n class=\"checkbox control\"";
+ stack1 = depth0.animDuration;
+ stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += " />\n <p class=\"form-help help-text-small\">\n Uncheck this to disable the animations used on the graph\n </p>\n </div>\n\n <div id=\"width\" class=\"form-input numeric-slider-input\">\n <label for=\"width\">Graph width: </label>\n <div class=\"slider-output\">";
+ foundHelper = helpers.width;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.width; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id=\"height\" class=\"form-input numeric-slider-input\">\n <label for=\"height\">Graph height: </label>\n <div class=\"slider-output\">";
+ foundHelper = helpers.height;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.height; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id=\"X-axis-label\"class=\"text-input form-input\">\n <label for=\"X-axis-label\">Re-label the X axis: </label>\n <input type=\"text\" name=\"X-axis-label\" id=\"X-axis-label\" value=\"";
+ foundHelper = helpers.xLabel;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.xLabel; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\" />\n <p class=\"form-help help-text-small\"></p>\n </div>\n\n <div id=\"Y-axis-label\" class=\"text-input form-input\">\n <label for=\"Y-axis-label\">Re-label the Y axis: </label>\n <input type=\"text\" name=\"Y-axis-label\" id=\"Y-axis-label\" value=\"";
+ foundHelper = helpers.yLabel;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.yLabel; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\" />\n <p class=\"form-help help-text-small\"></p>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />";
+ return buffer;});
+})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-chartDisplay.js
--- /dev/null
+++ b/static/scripts/templates/compiled/template-visualization-chartDisplay.js
@@ -0,0 +1,18 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['template-visualization-chartDisplay'] = template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+ var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression;
+
+
+ buffer += "<svg width=\"";
+ foundHelper = helpers.width;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.width; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\" height=\"";
+ foundHelper = helpers.height;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.height; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\"></svg>";
+ return buffer;});
+})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-chartSettings.js
--- a/static/scripts/templates/compiled/template-visualization-chartSettings.js
+++ /dev/null
@@ -1,38 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-chartSettings'] = template(function (Handlebars,depth0,helpers,partials,data) {
- helpers = helpers || Handlebars.helpers;
- var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this;
-
-function program1(depth0,data) {
-
-
- return " checked=\"true\"";}
-
- buffer += "<p class=\"help-text\">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n \n <div id=\"datapointSize\" class=\"form-input numeric-slider-input\">\n <label for=\"datapointSize\">Size of data point: </label>\n <div class=\"slider-output\">";
- foundHelper = helpers.datapointSize;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.datapointSize; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id=\"animDuration\" class=\"form-input checkbox-input\">\n <label for=\"animated\">Animate graph transitions?: </label>\n <input type=\"checkbox\" id=\"animated\"\n class=\"checkbox control\"";
- stack1 = depth0.animDuration;
- stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += " />\n <p class=\"form-help help-text-small\">\n Uncheck this to disable the animations used on the graph\n </p>\n </div>\n\n <div id=\"width\" class=\"form-input numeric-slider-input\">\n <label for=\"width\">Graph width: </label>\n <div class=\"slider-output\">";
- foundHelper = helpers.width;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.width; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id=\"height\" class=\"form-input numeric-slider-input\">\n <label for=\"height\">Graph height: </label>\n <div class=\"slider-output\">";
- foundHelper = helpers.height;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.height; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including graph margins and axes)\n </p>\n </div>\n\n <div id=\"X-axis-label\"class=\"text-input form-input\">\n <label for=\"X-axis-label\">Re-label the X axis: </label>\n <input type=\"text\" name=\"X-axis-label\" id=\"X-axis-label\" value=\"";
- foundHelper = helpers.xLabel;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.xLabel; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "\" />\n <p class=\"form-help help-text-small\"></p>\n </div>\n\n <div id=\"Y-axis-label\" class=\"text-input form-input\">\n <label for=\"Y-axis-label\">Re-label the Y axis: </label>\n <input type=\"text\" name=\"Y-axis-label\" id=\"Y-axis-label\" value=\"";
- foundHelper = helpers.yLabel;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.yLabel; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "\" />\n <p class=\"form-help help-text-small\"></p>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />";
- return buffer;});
-})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-dataControl.js
--- /dev/null
+++ b/static/scripts/templates/compiled/template-visualization-dataControl.js
@@ -0,0 +1,65 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['template-visualization-dataControl'] = template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1, foundHelper;
+ buffer += "\n <option value=\"";
+ foundHelper = helpers.index;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\">";
+ foundHelper = helpers.name;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</option>\n ";
+ return buffer;}
+
+function program3(depth0,data) {
+
+ var buffer = "", stack1, foundHelper;
+ buffer += "\n <option value=\"";
+ foundHelper = helpers.index;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\">";
+ foundHelper = helpers.name;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</option>\n ";
+ return buffer;}
+
+function program5(depth0,data) {
+
+ var buffer = "", stack1, foundHelper;
+ buffer += "\n <option value=\"";
+ foundHelper = helpers.index;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "\">";
+ foundHelper = helpers.name;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</option>\n ";
+ return buffer;}
+
+ buffer += "<p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n ";
+ buffer += "\n <div class=\"column-select\">\n <label for=\"X-select\">Data column for X: </label>\n <select name=\"X\" id=\"X-select\">\n ";
+ stack1 = depth0.numericColumns;
+ stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </select>\n </div>\n <div class=\"column-select\">\n <label for=\"Y-select\">Data column for Y: </label>\n <select name=\"Y\" id=\"Y-select\">\n ";
+ stack1 = depth0.numericColumns;
+ stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(3, program3, data)});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </select>\n </div>\n\n ";
+ buffer += "\n <div id=\"include-id\">\n <label for=\"include-id-checkbox\">Include a third column as data point IDs?</label>\n <input type=\"checkbox\" name=\"include-id\" id=\"include-id-checkbox\" />\n <p class=\"help-text-small\">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class=\"column-select\" style=\"display: none\">\n <label for=\"ID-select\">Data column for IDs: </label>\n <select name=\"ID\" id=\"ID-select\">\n ";
+ stack1 = depth0.allColumns;
+ stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </select>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />\n <div class=\"clear\"></div>";
+ return buffer;});
+})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-scatterplotControlForm.js
--- a/static/scripts/templates/compiled/template-visualization-scatterplotControlForm.js
+++ b/static/scripts/templates/compiled/template-visualization-scatterplotControlForm.js
@@ -2,75 +2,30 @@
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['template-visualization-scatterplotControlForm'] = template(function (Handlebars,depth0,helpers,partials,data) {
helpers = helpers || Handlebars.helpers;
- var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this;
+ var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression;
-function program1(depth0,data) {
-
- var buffer = "", stack1, foundHelper;
- buffer += "\n <option value=\"";
- foundHelper = helpers.index;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "\">";
- foundHelper = helpers.name;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</option>\n ";
- return buffer;}
-function program3(depth0,data) {
-
- var buffer = "", stack1, foundHelper;
- buffer += "\n <option value=\"";
- foundHelper = helpers.index;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "\">";
- foundHelper = helpers.name;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</option>\n ";
- return buffer;}
-
-function program5(depth0,data) {
-
- var buffer = "", stack1, foundHelper;
- buffer += "\n <option value=\"";
- foundHelper = helpers.index;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "\">";
- foundHelper = helpers.name;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</option>\n ";
- return buffer;}
-
- buffer += "\n\n<ul class=\"nav nav-tabs\">\n <li class=\"active\"><a data-toggle=\"tab\" href=\"#data-settings\">Data Controls</a></li>\n <li><a data-toggle=\"tab\" href=\"#chart-settings\">Plot Controls</a></li>\n <li><a data-toggle=\"tab\" href=\"#chart-stats\">Statistics</a></li>\n <li><a data-toggle=\"tab\" href=\"#chart\">Chart</a></li>\n</ul>\n\n";
- buffer += "\n<div class=\"tab-content\">\n<div id=\"data-settings\" class=\"tab-pane active\">\n\n <p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n \n ";
- buffer += "\n <div class=\"column-select\">\n <label for=\"X-select\">Data column for X: </label>\n <select name=\"X\" id=\"X-select\">\n ";
- stack1 = depth0.numericColumns;
- stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n <div class=\"column-select\">\n <label for=\"Y-select\">Data column for Y: </label>\n <select name=\"Y\" id=\"Y-select\">\n ";
- stack1 = depth0.numericColumns;
- stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(3, program3, data)});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n \n ";
- buffer += "\n <div id=\"include-id\">\n <label for=\"include-id-checkbox\">Include a third column as data point IDs?</label>\n <input type=\"checkbox\" name=\"include-id\" id=\"include-id-checkbox\" />\n <p class=\"help-text-small\">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class=\"column-select\" style=\"display: none\">\n <label for=\"ID-select\">Data column for IDs: </label>\n <select name=\"ID\" id=\"ID-select\">\n ";
- stack1 = depth0.allColumns;
- stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n \n <input id=\"render-button\" type=\"button\" value=\"Draw\" />\n <input id=\"save-button\" type=\"button\" value=\"Download Plot as SVG\" style=\"display: none;\" />\n <div class=\"clear\"></div>\n</div>\n\n<div id=\"chart-settings\" class=\"tab-pane\">\n</div>\n\n<div id=\"chart-stats\" class=\"tab-pane\">\n</div>\n</div>";
- buffer += "\n\n";
- buffer += "\n<div id=\"loading-indicator\" style=\"display: none;\">\n <img class=\"loading-img\" src=\"";
+ buffer += "\n\n<div class=\"scatterplot-container chart-container tabbable tabs-left\">\n ";
+ buffer += "\n <ul class=\"nav nav-tabs\">\n ";
+ buffer += "\n <li class=\"active\"><a href=\"#data-control\" data-toggle=\"tab\" class=\"tooltip\"\n title=\"Use this tab to change which data are used\">Data Controls</a></li>\n <li><a href=\"#chart-control\" data-toggle=\"tab\" class=\"tooltip\"\n title=\"Use this tab to change how the chart is drawn\">Chart Controls</a></li>\n <li><a href=\"#stats-display\" data-toggle=\"tab\" class=\"tooltip\"\n title=\"This tab will display overall statistics for your data\">Statistics</a></li>\n <li><a href=\"#chart-display\" data-toggle=\"tab\" class=\"tooltip\"\n title=\"This tab will display the chart\">Chart</a>\n ";
+ buffer += "\n <div id=\"loading-indicator\" style=\"display: none;\">\n <img class=\"loading-img\" src=\"";
foundHelper = helpers.loadingIndicatorImagePath;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.loadingIndicatorImagePath; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "\" />\n <span class=\"loading-message\">";
+ buffer += escapeExpression(stack1) + "\" />\n <span class=\"loading-message\">";
foundHelper = helpers.message;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.message; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</span>\n</div>";
+ buffer += escapeExpression(stack1) + "</span>\n </div>\n </li>\n </ul>\n\n ";
+ buffer += "\n <div class=\"tab-content\">\n ";
+ buffer += "\n <div id=\"data-control\" class=\"tab-pane active\">\n ";
+ buffer += "\n </div>\n \n ";
+ buffer += "\n <div id=\"chart-control\" class=\"tab-pane\">\n ";
+ buffer += "\n </div>\n\n ";
+ buffer += "\n <div id=\"stats-display\" class=\"tab-pane\">\n ";
+ buffer += "\n </div>\n\n ";
+ buffer += "\n <div id=\"chart-display\" class=\"tab-pane\">\n ";
+ buffer += "\n </div>\n\n </div>";
+ buffer += "\n</div>";
return buffer;});
})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-statsDisplay.js
--- /dev/null
+++ b/static/scripts/templates/compiled/template-visualization-statsDisplay.js
@@ -0,0 +1,31 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['template-visualization-statsDisplay'] = template(function (Handlebars,depth0,helpers,partials,data) {
+ helpers = helpers || Handlebars.helpers;
+ var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = "", stack1, foundHelper;
+ buffer += "\n <tr><td>";
+ foundHelper = helpers.name;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</td><td>";
+ foundHelper = helpers.xval;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.xval; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</td><td>";
+ foundHelper = helpers.yval;
+ if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
+ else { stack1 = depth0.yval; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
+ buffer += escapeExpression(stack1) + "</td></tr>\n </tr>\n ";
+ return buffer;}
+
+ buffer += "<p class=\"help-text\">By column:</p>\n <table id=\"chart-stats-table\">\n <thead><th></th><th>X</th><th>Y</th></thead>\n ";
+ stack1 = depth0.stats;
+ stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
+ if(stack1 || stack1 === 0) { buffer += stack1; }
+ buffer += "\n </table>";
+ return buffer;});
+})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/compiled/template-visualization-statsTable.js
--- a/static/scripts/templates/compiled/template-visualization-statsTable.js
+++ /dev/null
@@ -1,31 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-statsTable'] = template(function (Handlebars,depth0,helpers,partials,data) {
- helpers = helpers || Handlebars.helpers;
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
-
-function program1(depth0,data) {
-
- var buffer = "", stack1, foundHelper;
- buffer += "\n <tr><td>";
- foundHelper = helpers.name;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</td><td>";
- foundHelper = helpers.xval;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.xval; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</td><td>";
- foundHelper = helpers.yval;
- if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
- else { stack1 = depth0.yval; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
- buffer += escapeExpression(stack1) + "</td></tr>\n </tr>\n ";
- return buffer;}
-
- buffer += "<p class=\"help-text\">By column:</p>\n <table id=\"chart-stats-table\">\n <thead><th></th><th>X</th><th>Y</th></thead>\n ";
- stack1 = depth0.stats;
- stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </table>";
- return buffer;});
-})();
\ No newline at end of file
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/templates/visualization-templates.html
--- a/static/scripts/templates/visualization-templates.html
+++ b/static/scripts/templates/visualization-templates.html
@@ -1,22 +1,59 @@
<script type="text/template" class="template-visualization" id="template-visualization-scatterplotControlForm">
-{{! main controls }}
+{{! main layout }}
-<ul class="nav nav-tabs">
- <li class="active"><a data-toggle="tab" href="#data-settings">Data Controls</a></li>
- <li><a data-toggle="tab" href="#chart-settings">Plot Controls</a></li>
- <li><a data-toggle="tab" href="#chart-stats">Statistics</a></li>
- <li><a data-toggle="tab" href="#chart">Chart</a></li>
-</ul>
+<div class="scatterplot-container chart-container tabbable tabs-left">
+ {{! tab buttons/headers using Bootstrap }}
+ <ul class="nav nav-tabs">
+ {{! start with the data controls as the displayed tab }}
+ <li class="active"><a href="#data-control" data-toggle="tab" class="tooltip"
+ title="Use this tab to change which data are used">Data Controls</a></li>
+ <li><a href="#chart-control" data-toggle="tab" class="tooltip"
+ title="Use this tab to change how the chart is drawn">Chart Controls</a></li>
+ <li><a href="#stats-display" data-toggle="tab" class="tooltip"
+ title="This tab will display overall statistics for your data">Statistics</a></li>
+ <li><a href="#chart-display" data-toggle="tab" class="tooltip"
+ title="This tab will display the chart">Chart</a>
+ {{! loading indicator - initially hidden }}
+ <div id="loading-indicator" style="display: none;">
+ <img class="loading-img" src="{{loadingIndicatorImagePath}}" />
+ <span class="loading-message">{{message}}</span>
+ </div>
+ </li>
+ </ul>
-{{! data settings }}
-<div class="tab-content">
-<div id="data-settings" class="tab-pane active">
+ {{! data form, chart config form, stats, and chart all get their own tab }}
+ <div class="tab-content">
+ {{! ---------------------------- tab for data settings form }}
+ <div id="data-control" class="tab-pane active">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for chart graphics control form }}
+ <div id="chart-control" class="tab-pane">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for data statistics }}
+ <div id="stats-display" class="tab-pane">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for actual chart }}
+ <div id="chart-display" class="tab-pane">
+ {{! chart rendered separately }}
+ </div>
+
+ </div>{{! end .tab-content }}
+</div>{{! end .chart-control }}
+</script>
+
+<script type="text/template" class="template-visualization" id="template-visualization-dataControl"><p class="help-text">
Use the following controls to change the data used by the chart.
Use the 'Draw' button to render (or re-render) the chart with the current settings.
</p>
-
+
{{! column selector containers }}
<div class="column-select"><label for="X-select">Data column for X: </label>
@@ -34,7 +71,7 @@
{{/each}}
</select></div>
-
+
{{! optional id column }}
<div id="include-id"><label for="include-id-checkbox">Include a third column as data point IDs?</label>
@@ -52,47 +89,19 @@
{{/each}}
</select></div>
-
+
<input id="render-button" type="button" value="Draw" />
- <input id="save-button" type="button" value="Download Plot as SVG" style="display: none;" /><div class="clear"></div>
-</div>
-
-<div id="chart-settings" class="tab-pane">
-</div>
-
-<div id="chart-stats" class="tab-pane">
-</div>
-</div>{{! class="tab-content" }}
-
-{{! loading indicator - initially hidden }}
-<div id="loading-indicator" style="display: none;">
- <img class="loading-img" src="{{loadingIndicatorImagePath}}" />
- <span class="loading-message">{{message}}</span>
-</div>
-
</script>
-<script type="text/template" class="template-visualization" id="template-visualization-statsTable">
- <p class="help-text">By column:</p>
- <table id="chart-stats-table">
- <thead><th></th><th>X</th><th>Y</th></thead>
- {{#each stats}}
- <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
- </tr>
- {{/each}}
- </table>
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-chartSettings">
-
+<script type="text/template" class="template-visualization" id="template-visualization-chartControl"><p class="help-text">
Use the following controls to how the chart is displayed.
The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
Move the focus between controls by using the tab or shift+tab keys on your keyboard.
Use the 'Draw' button to render (or re-render) the chart with the current settings.
</p>
-
+
<div id="datapointSize" class="form-input numeric-slider-input"><label for="datapointSize">Size of data point: </label><div class="slider-output">{{datapointSize}}</div>
@@ -143,3 +152,18 @@
<input id="render-button" type="button" value="Draw" /></script>
+
+<script type="text/template" class="template-visualization" id="template-visualization-statsDisplay">
+ <p class="help-text">By column:</p>
+ <table id="chart-stats-table">
+ <thead><th></th><th>X</th><th>Y</th></thead>
+ {{#each stats}}
+ <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
+ </tr>
+ {{/each}}
+ </table>
+</script>
+
+<script type="text/template" class="template-visualization" id="template-visualization-chartDisplay">
+ <svg width="{{width}}" height="{{height}}"></svg>
+</script>
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/utils/LazyDataLoader.js
--- a/static/scripts/utils/LazyDataLoader.js
+++ b/static/scripts/utils/LazyDataLoader.js
@@ -1,3 +1,4 @@
+//==============================================================================
/*
TODO:
?? superclass dataloader, subclass lazydataloader??
@@ -213,3 +214,5 @@
loader.initialize( config );
return loader;
}
+
+//==============================================================================
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff static/scripts/viz/scatterplot.js
--- a/static/scripts/viz/scatterplot.js
+++ b/static/scripts/viz/scatterplot.js
@@ -1,18 +1,3 @@
-define([
- "../libs/underscore",
-
- "../mvc/base-mvc",
- "../utils/LazyDataLoader",
- "../templates/compiled/template-visualization-scatterplotControlForm",
- "../templates/compiled/template-visualization-statsTable",
- "../templates/compiled/template-visualization-chartSettings",
-
- "../libs/d3",
-
- "../libs/bootstrap",
- "../libs/jquery/jquery-ui"
-], function(){
-
/* =============================================================================
todo:
outside this:
@@ -106,8 +91,8 @@
yNumTicks : 10,
xAxisLabelBumpY : 40,
yAxisLabelBumpX : -35,
- width : 500,
- height : 500,
+ width : 400,
+ height : 400,
//TODO: anyway to make this a sub-obj?
marginTop : 50,
marginRight : 50,
@@ -123,11 +108,14 @@
yLabel : "Y"
};
this.config = _.extend( {}, this.defaults, config );
+ this.log( 'intial config:', this.config );
this.updateConfig = function( newConfig, rerender ){
// setter for chart config
//TODO: validate here
_.extend( this.config, newConfig );
+ this.log( this + '.updateConfig:', this.config );
+ //TODO: implement rerender flag
};
// ........................................................ helpers
@@ -143,20 +131,6 @@
};
// ........................................................ initial element creation
- //NOTE: called on new
- this.svg = d3.select( this.config.containerSelector )
- .append( "svg:svg" ).attr( "class", "chart" );
-
- this.content = this.svg.append( "svg:g" ).attr( "class", "content" ).attr( 'id', this.config.id );
-
- this.xAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'x-axis' );
- this.xAxisLabel = this.xAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'x-axis-label' );
-
- this.yAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'y-axis' );
- this.yAxisLabel = this.yAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'y-axis-label' );
-
- //this.log( 'built svg:', d3.selectAll( 'svg' ) );
-
this.adjustChartDimensions = function( top, right, bottom, left ){
//this.log( this + '.adjustChartDimensions', arguments );
top = top || 0;
@@ -186,8 +160,8 @@
return ( data.length > this.config.maxDataPoints )? ( data.slice( 0, this.config.maxDataPoints ) ): ( data );
};
- this.setUpDomains = function( xCol, yCol, meta ){
- //this.log( this + '.setUpDomains', arguments );
+ this.findMinMaxes = function( xCol, yCol, meta ){
+ //this.log( this + '.findMinMaxes', arguments );
// configuration takes priority, otherwise meta (from the server) if passed, last-resort: compute it here
this.xMin = this.config.xMin || ( meta )?( meta[0].min ):( d3.min( xCol ) );
this.xMax = this.config.xMax || ( meta )?( meta[0].max ):( d3.max( xCol ) );
@@ -220,22 +194,22 @@
.call( this.xAxisFn );
//this.log( 'xAxis:', this.xAxis );
- this.xLongestLabel = d3.max( _.map( [ this.xMin, this.xMax ],
+ //TODO: adjust ticks when tick labels are long - move odds down and extend tick line
+ // (for now) hide them
+ var xLongestTickLabel = d3.max( _.map( [ this.xMin, this.xMax ],
function( number ){ return ( String( number ) ).length; } ) );
- //this.log( 'xLongestLabel:', this.xLongestLabel );
-
- if( this.xLongestLabel >= X_LABEL_TOO_LONG_AT ){
- //TODO: adjust ticks when tick labels are long - move odds down and extend tick line
- // (for now) hide them
+ //this.log( 'xLongestTickLabel:', xLongestTickLabel );
+ if( xLongestTickLabel >= X_LABEL_TOO_LONG_AT ){
this.xAxis.selectAll( 'g' ).filter( ':nth-child(odd)' ).style( 'display', 'none' );
}
+ this.log( 'this.config.xLabel:', this.config.xLabel );
this.xAxisLabel// = xAxis.select( 'text#x-axis-label' )
.attr( 'x', this.config.width / 2 )
.attr( 'y', this.config.xAxisLabelBumpY )
.attr( 'text-anchor', 'middle' )
.text( this.config.xLabel );
- //this.log( 'xAxisLabel:', this.xAxisLabel );
+ this.log( 'xAxisLabel:', this.xAxisLabel );
};
this.setUpYAxis = function(){
@@ -248,9 +222,10 @@
.call( this.yAxisFn );
//this.log( 'yAxis:', this.yAxis );
+ // a too complicated section for increasing the left margin when tick labels are long
// get the tick labels for the y axis
var yTickLabels = this.yAxis.selectAll( 'text' ).filter( function( e, i ){ return i !== 0; } );
- //this.log( 'yTickLabels:', yTickLabels );
+ this.log( 'yTickLabels:', yTickLabels );
// get the longest label length (or 0 if no labels)
this.yLongestLabel = d3.max(
@@ -412,7 +387,7 @@
}
// titles
- newDatapoints.attr( 'title', function( d, i ){
+ newDatapoints.attr( 'svg:title', function( d, i ){
return (( ids )?( ids[ i ] + ': ' ):( '' )) + newXCol[ i ] + ', ' + newYCol[ i ];
});
@@ -462,6 +437,9 @@
this.render = function( columnData, meta ){
this.log( this + '.render', arguments );
+ this.log( '\t config:', this.config );
+
+ // prepare the data
//pre: columns passed are numeric
//pre: at least two columns are passed
//assume: first column is x, second column is y, any remaining aren't used
@@ -475,12 +453,32 @@
yCol = this.preprocessData( yCol );
this.log( 'xCol len', xCol.length, 'yCol len', yCol.length );
- this.setUpDomains( xCol, yCol, meta );
+ this.findMinMaxes( xCol, yCol, meta );
//this.log( 'xMin, xMax, yMin, yMax:', this.xMin, this.xMax, this.yMin, this.yMax );
+ this.setUpScales();
+
+ // build the svg dom infrastructure
+ if( !this.svg ){ this.svg = d3.select( 'svg' ).attr( "class", "chart" ); }
+ if( !this.content ){
+ this.content = this.svg.append( "svg:g" ).attr( "class", "content" ).attr( 'id', this.config.id );
+ }
+ //this.log( 'svg:', this.svg );
+ //this.log( 'content:', this.content );
+
+ this.adjustChartDimensions();
- this.setUpScales();
- this.adjustChartDimensions();
-
+ if( !this.xAxis ){ this.xAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'x-axis' ); }
+ if( !this.xAxisLabel ){
+ this.xAxisLabel = this.xAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'x-axis-label' );
+ }
+ //this.log( 'xAxis:', this.xAxis, 'xAxisLabel:', this.xAxisLabel );
+
+ if( !this.yAxis ){ this.yAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'y-axis' ); }
+ if( !this.yAxisLabel ){
+ this.yAxisLabel = this.yAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'y-axis-label' );
+ }
+ //this.log( 'yAxis:', this.yAxis, 'yAxisLabel:', this.yAxisLabel );
+
this.setUpXAxis();
this.setUpYAxis();
@@ -490,510 +488,3 @@
}
//==============================================================================
-/**
- * Scatterplot control UI as a backbone view
- * handles:
- * getting the desired data
- * configuring the plot display
- * showing (general) statistics
- *
- * initialize attributes REQUIRES a dataset and an apiDatasetsURL
- */
-var ScatterplotControlForm = BaseView.extend( LoggableMixin ).extend({
- //logger : console,
- className : 'scatterplot-settings-form',
-
- dataLoadDelay : 500,
- dataLoadSize : 3001,
-
- loadingIndicatorImage : 'loading_large_white_bg.gif',
-
- initialize : function( attributes ){
- this.log( this + '.initialize, attributes:', attributes );
-
- this.dataset = null;
- this.chartConfig = null;
- this.plot = null;
- this.loader = null;
-
- this.$statsPanel = null;
- this.$chartSettingsPanel = null;
- this.$dataSettingsPanel = null;
- this.dataFetch = null;
-
- this.initializeFromAttributes( attributes );
- this.initializeChart( attributes );
- this.initializeDataLoader( attributes );
- },
-
- initializeFromAttributes : function( attributes ){
- // required settings: ensure certain vars we need are passed in attributes
- if( !attributes || !attributes.dataset ){
- throw( "ScatterplotView requires a dataset" );
- } else {
- this.dataset = attributes.dataset;
- }
- if( jQuery.type( this.dataset.metadata_column_types ) === 'string' ){
- this.dataset.metadata_column_types = this.dataset.metadata_column_types.split( ', ' );
- }
- this.log( 'dataset:', this.dataset );
-
- // passed from mako helper
- //TODO: integrate to galaxyPaths
- if( !attributes.apiDatasetsURL ){
- throw( "ScatterplotView requires a apiDatasetsURL" );
- } else {
- this.dataURL = attributes.apiDatasetsURL + '/' + this.dataset.id + '?';
- }
- this.log( 'this.dataURL:', this.dataURL );
- },
-
- initializeChart : function( attributes ){
- // set up the basic chart infrastructure and config (if any)
- this.chartConfig = attributes.chartConfig || {};
- if( this.logger ){ this.chartConfig.debugging = true; }
- this.log( 'initial chartConfig:', this.chartConfig );
- this.plot = new TwoVarScatterplot( this.chartConfig );
- this.chartConfig = this.plot.config;
- },
-
- initializeDataLoader : function( attributes ){
- // set up data loader
- var view = this;
- this.loader = new LazyDataLoader({
- logger : ( this.logger )?( this.logger ):( null ),
- // we'll generate this when columns are chosen
- url : null,
- start : attributes.start || 0,
- //NOTE: metadata_data_lines can be null (so we won't know the total)
- total : attributes.total || this.dataset.metadata_data_lines,
- delay : this.dataLoadDelay,
- size : this.dataLoadSize,
-
- buildUrl : function( start, size ){
- // currently VERY SPECIFIC to using data_providers.py start_val, max_vals params
- return this.url + '&' + jQuery.param({
- start_val: start,
- max_vals: size
- });
- }
- });
- $( this.loader ).bind( 'error', function( event, status, error ){
- view.log( 'ERROR:', status, error );
- alert( 'ERROR fetching data:\n' + status + '\n' + error );
- view.hideLoadingIndicator();
- });
- },
-
- // ------------------------------------------------------------------------- CONTROLS RENDERING
- render : function(){
- // render the data/chart control forms (but not the chart)
- var view = this,
- formData = {
- config : this.chartConfig,
- allColumns : [],
- numericColumns : [],
- loadingIndicatorImagePath : galaxy_paths.get( 'image_path' ) + '/' + this.loadingIndicatorImage
- };
-
- //TODO: isNumericColumn
- // gather column indeces (from metadata_column_types) and names (from metadata_columnnames)
- _.each( this.dataset.metadata_column_types, function( type, index ){
- // label with the name if available (fall back on 'column <index>')
- var name = 'column ' + ( index + 1 );
- if( view.dataset.metadata_column_names ){
- name = view.dataset.metadata_column_names[ index ];
- }
-
- // filter numeric columns to their own list
- if( type === 'int' || type === 'float' ){
- formData.numericColumns.push({ index: ( index + 1 ), name: name });
- }
- formData.allColumns.push({ index: ( index + 1 ), name: name });
- });
- //TODO: other vals: max_vals, start_val, pagination (plot-settings)
-
- // render template and set up panels, store refs
- this.$el.append( ScatterplotControlForm.templates.form( formData ) );
-
- this.$dataSettingsPanel = this.$el.find( '.tab-pane#data-settings' );
- this.$chartSettingsPanel = this._render_chartSettings();
- this.$statsPanel = this.$el.find( '.tab-pane#chart-stats' );
-
- // preset to column selectors if they were passed in the config
- this.$dataSettingsPanel.find( '#X-select' ).val( this.chartConfig.xColumn );
- this.$dataSettingsPanel.find( '#Y-select' ).val( this.chartConfig.yColumn );
- if( this.chartConfig.idColumn !== undefined ){
- this.$dataSettingsPanel.find( '#include-id-checkbox' )
- .attr( 'checked', true ).trigger( 'change' );
- this.$dataSettingsPanel.find( '#ID-select' ).val( this.chartConfig.idColumn );
- }
-
- //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-settings"]' ).tab( 'show' );
-
- //TODO:?? add autoRender=1 to query maybe?
- if( this.chartConfig.xColumn && this.chartConfig.yColumn ){
- this.renderPlot();
- }
- return this;
- },
-
- //TODO: seems like a function of dataset or metadata
- isColumnNumeric : function( index ){
- if( ( index >= 0 ) && ( index < this.dataset.metadata_column_types.length ) ){
- var columnType = this.dataset.metadata_column_types[ index ];
- return ( columnType === 'int' || columnType === 'float' );
- }
- return false;
- },
-
- _render_chartSettings : function(){
- // chart settings panel
- var chartControl = this,
- $chartSettingsPanel = this.$el.find( '.tab-pane#chart-settings' ),
- // limits for controls (by control/chartConfig id)
- //TODO: move into TwoVarScatterplot
- controlRanges = {
- 'datapointSize' : { min: 2, max: 10, step: 1 },
- 'width' : { min: 200, max: 800, step: 20 },
- 'height' : { min: 200, max: 800, step: 20 }
- };
-
- // render the html
- $chartSettingsPanel.append( ScatterplotControlForm.templates.chartSettings( this.chartConfig ) );
-
- // set up js on sliders
- $chartSettingsPanel.find( '.numeric-slider-input' ).each( function(){
- var $this = $( this ),
- $output = $this.find( '.slider-output' ),
- $slider = $this.find( '.slider' ),
- id = $this.attr( 'id' );
- //chartControl.log( 'slider set up', 'this:', $this, 'slider:', $slider, 'id', id );
-
- // what to do when the slider changes: update display and update chartConfig
- function onSliderChange(){
- var $this = $( this ),
- newValue = $this.slider( 'value' );
- //chartControl.log( 'slider change', 'this:', $this, 'output:', $output, 'value', newValue );
- $output.text( newValue );
- //chartControl.chartConfig[ id ] = newValue;
- }
-
- $slider.slider( _.extend( controlRanges[ id ], {
- value : chartControl.chartConfig[ id ],
- change : onSliderChange,
- slide : onSliderChange
- }));
- });
-
- return $chartSettingsPanel;
- },
-
- // ------------------------------------------------------------------------- EVENTS
- events : {
- 'change #include-id-checkbox' : 'toggleThirdColumnSelector',
- 'click #data-settings #render-button' : 'renderPlot',
- 'click #chart-settings #render-button' : 'changeChartSettings'
- },
-
- toggleThirdColumnSelector : function(){
- // show/hide the id selector on the data settings panel
- this.$el.find( 'select[name="ID"]' ).parent().toggle();
- },
-
- showLoadingIndicator : function( message, callback ){
- // display the loading indicator over the tab panels if hidden, update message (if passed)
- message = message || '';
- var indicator = this.$el.find( 'div#loading-indicator' );
- messageBox = indicator.find( '.loading-message' );
-
- if( indicator.is( ':visible' ) ){
- if( message ){
- messageBox.fadeOut( 'fast', function(){
- messageBox.text( message );
- messageBox.fadeIn( 'fast', callback );
- });
- } else {
- callback();
- }
-
- } else {
- if( message ){ messageBox.text( message ); }
- indicator.fadeIn( 'fast', callback );
- }
- },
-
- hideLoadingIndicator : function( callback ){
- this.$el.find( 'div#loading-indicator' ).fadeOut( 'fast', callback );
- },
-
- // ------------------------------------------------------------------------- GRAPH/STATS RENDERING
- renderPlot : function(){
- // fetch the data, (re-)render the chart
- //TODO: separate data fetch
- var view = this;
-
- // this is a complete re-render, so clear the prev. data
- view.data = null;
- view.meta = null;
-
- // update the chartConfig (here and plot) using graph settings
- //TODO: separate and improve (used in changeChartSettings too)
- _.extend( this.chartConfig, this.getGraphSettings() );
- this.log( 'this.chartConfig:', this.chartConfig );
- this.plot.updateConfig( this.chartConfig, false );
-
- // build the url with the current data settings
- this.loader.url = this.dataURL + '&' + jQuery.param( this.getDataSettings() );
- this.log( 'this.loader, url:', this.loader.url, 'total:', this.loader.total );
-
- // bind the new data event to: aggregate data, update the chart and stats with new data
- $( this.loader ).bind( 'loaded.new', function( event, response ){
- view.log( view + ' loaded.new', response );
-
- // aggregate data and meta
- view.postProcessDataFetchResponse( response );
- view.log( 'postprocessed data:', view.data, 'meta:', view.meta );
-
- // update the graph and stats
- view.showLoadingIndicator( 'Rendering...', function(){
- view.$el.find( 'ul.nav' ).find( 'a[href="#chart-stats"]' ).tab( 'show' );
-
- view.plot.render( view.data, view.meta );
- view.renderStats( view.data, view.meta );
- view.hideLoadingIndicator();
- });
- });
- // when all data loaded - unbind (or we'll start doubling event handlers)
- $( this.loader ).bind( 'complete', function( event, data ){
- view.log( 'complete', data );
- $( view.loader ).unbind();
- });
-
- // begin loading the data
- view.showLoadingIndicator( 'Fetching data...', function(){ view.loader.load(); });
- },
-
- renderStats : function(){
- // render the stats table in the stats panel
- //TODO: there's a better way
- this.$statsPanel.html( ScatterplotControlForm.templates.statsTable({
- stats: [
- { name: 'Count', xval: this.meta[0].count, yval: this.meta[1].count },
- { name: 'Min', xval: this.meta[0].min, yval: this.meta[1].min },
- { name: 'Max', xval: this.meta[0].max, yval: this.meta[1].max },
- { name: 'Sum', xval: this.meta[0].sum, yval: this.meta[1].sum },
- { name: 'Mean', xval: this.meta[0].mean, yval: this.meta[1].mean },
- { name: 'Median', xval: this.meta[0].median, yval: this.meta[1].median }
- ]
- }));
- },
-
- changeChartSettings : function(){
- // re-render the chart with new chart settings and OLD data
- var view = this;
- newGraphSettings = this.getGraphSettings();
-
- // update the chart config from the chartSettings panel controls
- this.log( 'newGraphSettings:', newGraphSettings );
- _.extend( this.chartConfig, newGraphSettings );
- this.log( 'this.chartConfig:', this.chartConfig );
- this.plot.updateConfig( this.chartConfig, false );
-
- // if there's current data, call plot.render with it (no data fetch)
- if( view.data && view.meta ){
- view.showLoadingIndicator( 'Rendering...', function(){
- view.plot.render( view.data, view.meta );
- view.hideLoadingIndicator();
- });
-
- // no current data, call renderPlot instead (which will fetch data)
- } else {
- this.renderPlot();
- }
- },
-
- // ------------------------------------------------------------------------- DATA AGGREGATION
- postProcessDataFetchResponse : function( response ){
- // the loader only returns new data - it's up to this to munge the fetches together properly
- //TODO: we're now storing data in two places: loader and here
- // can't we reduce incoming data into loader.data[0]? are there concurrency problems?
- this.postProcessData( response.data );
- this.postProcessMeta( response.meta );
- },
-
- postProcessData : function( newData ){
- // stack the column data on top of each other into this.data
- var view = this;
-
- // if we already have data: aggregate
- if( view.data ){
- _.each( newData, function( newColData, colIndex ){
- //view.log( colIndex + ' data:', newColData );
- //TODO??: time, space efficiency of this?
- view.data[ colIndex ] = view.data[ colIndex ].concat( newColData );
- });
-
- // otherwise: assign (first load)
- } else {
- view.data = newData;
- }
- },
-
- postProcessMeta : function( newMeta ){
- // munge the meta data (stats) from the server fetches together
- //pre: this.data must be preprocessed (needed for medians)
- var view = this,
- colTypes = this.dataset.metadata_column_types;
-
- // if we already have meta: aggregate
- if( view.meta ){
- _.each( newMeta, function( newColMeta, colIndex ){
- var colMeta = view.meta[ colIndex ],
- colType = colTypes[ colIndex ];
- view.log( colIndex + ' postprocessing meta:', newColMeta );
- //view.log( colIndex + ' old meta:',
- // 'min:', colMeta.min,
- // 'max:', colMeta.max,
- // 'sum:', colMeta.sum,
- // 'mean:', colMeta.mean,
- // 'median:', colMeta.median
- //);
-
- //!TODO: at what point are we getting int/float overflow on these?!
- //??: need to be null safe?
- colMeta.count += ( newColMeta.count )?( newColMeta.count ):( 0 );
- view.log( colIndex, 'count:', colMeta.count );
-
- if( ( colType === 'int' ) || ( colType === 'float' ) ){
- //view.log( colIndex + ' incoming meta:',
- // 'min:', newColMeta.min,
- // 'max:', newColMeta.max,
- // 'sum:', newColMeta.sum,
- // 'mean:', newColMeta.mean,
- // 'median:', newColMeta.median
- //);
-
- colMeta.min = Math.min( newColMeta.min, colMeta.min );
- colMeta.max = Math.max( newColMeta.max, colMeta.max );
- colMeta.sum = newColMeta.sum + colMeta.sum;
- colMeta.mean = ( colMeta.count )?( colMeta.sum / colMeta.count ):( null );
-
- // median's a pain bc of sorting (requires the data as well)
- var sortedCol = view.data[ colIndex ].slice().sort(),
- middleIndex = Math.floor( sortedCol.length / 2 );
-
- if( sortedCol.length % 2 === 0 ){
- colMeta.median = ( ( sortedCol[ middleIndex ] + sortedCol[( middleIndex + 1 )] ) / 2 );
-
- } else {
- colMeta.median = sortedCol[ middleIndex ];
- }
-
- //view.log( colIndex + ' new meta:',
- // 'min:', colMeta.min,
- // 'max:', colMeta.max,
- // 'sum:', colMeta.sum,
- // 'mean:', colMeta.mean,
- // 'median:', colMeta.median
- //);
- }
- });
-
- // otherwise: assign (first load)
- } else {
- view.meta = newMeta;
- view.log( 'initial meta:', view.meta );
- }
- },
-
- // ------------------------------------------------------------------------- GET DATA/GRAPH SETTINGS
- getDataSettings : function(){
- // parse the column values for both indeces (for the data fetch) and names (for the graph)
- var columnSelections = this.getColumnSelections(),
- columns = [];
- this.log( 'columnSelections:', columnSelections );
-
- //TODO: validate columns - minimally: we can assume either set by selectors or via a good query string
-
- // get column indices for params, include the desired ID column (if any)
- columns = [ columnSelections.X.colIndex, columnSelections.Y.colIndex ];
- if( this.$dataSettingsPanel.find( '#include-id-checkbox' ).attr( 'checked' ) ){
- columns.push( columnSelections.ID.colIndex );
- }
-
- //TODO: other vals: max, start, page
-
- var params = {
- data_type : 'raw_data',
- columns : '[' + columns + ']'
- };
- this.log( 'params:', params );
- return params;
- },
-
- getColumnSelections : function(){
- // gets the current user-selected values for which columns to fetch from the data settings panel
- // returns a map: { column-select name (eg. X) : { colIndex : column-selector val,
- // colName : selected option text }, ... }
- var selections = {};
- this.$dataSettingsPanel.find( 'div.column-select select' ).each( function(){
- var $this = $( this ),
- val = parseInt( $this.val(), 10 ) - 1;
- selections[ $this.attr( 'name' ) ] = {
- colIndex : val,
- colName : $this.children( '[value="' + val + '"]' ).text()
- };
- });
- return selections;
- },
-
- getGraphSettings : function(){
- // gets the user-selected chartConfig from the chart settings panel
- var settings = {},
- colSelections = this.getColumnSelections();
- //this.log( 'colSelections:', colSelections );
-
- //TODO: simplify with keys and loop
- settings.datapointSize = this.$chartSettingsPanel.find( '#datapointSize.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
- settings.width = this.$chartSettingsPanel.find( '#width.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
- settings.height = this.$chartSettingsPanel.find( '#height.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
-
- // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName
- //TODO: a little confusing
- var chartSettingsXLabel = this.$chartSettingsPanel.find( 'input#X-axis-label' ).val(),
- chartSettingsYLabel = this.$chartSettingsPanel.find( 'input#Y-axis-label' ).val();
- settings.xLabel = ( chartSettingsXLabel === 'X' )?
- ( colSelections.X.colName ):( chartSettingsXLabel );
- settings.yLabel = ( chartSettingsYLabel === 'Y' )?
- ( colSelections.Y.colName ):( chartSettingsYLabel );
-
- settings.animDuration = 10;
- if( this.$chartSettingsPanel.find( '#animDuration.checkbox-input' ).is( ':checked' ) ){
- settings.animDuration = 500;
- }
-
- this.log( 'graphSettings:', settings );
- return settings;
- },
-
- toString : function(){
- return 'ScatterplotControlForm(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')';
- }
-});
-
-ScatterplotControlForm.templates = {
- form : Handlebars.templates[ 'template-visualization-scatterplotControlForm' ],
- statsTable : Handlebars.templates[ 'template-visualization-statsTable' ],
- chartSettings : Handlebars.templates[ 'template-visualization-chartSettings' ]
-};
-
-//==============================================================================
-return {
- LazyDataLoader : LazyDataLoader,
- TwoVarScatterplot : TwoVarScatterplot,
- ScatterplotControlForm : ScatterplotControlForm
-};});
diff -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 -r 9bf411ee2476c21c4c3fce6a5bbabf508f7505ff templates/visualization/scatterplot.mako
--- a/templates/visualization/scatterplot.mako
+++ b/templates/visualization/scatterplot.mako
@@ -12,21 +12,7 @@
/*TODO: use/move into base.less*/
* { margin: 0px; padding: 0px; }
-/* -------------------------------------------- layout */
-.column {
- position:relative;
- overflow: auto;
-}
-
-.left-column {
- float: left;
- width: 40%;
-}
-
-.right-column {
- margin-left: 41%;
-}
-
+/* -------------------------------------------- general layout */
div.tab-pane {
padding: 8px;
}
@@ -39,6 +25,7 @@
#chart-header {
padding : 8px;
background-color: #ebd9b2;
+ margin-bottom: 16px;
}
#chart-header .subtitle {
@@ -48,90 +35,98 @@
font-size: small;
}
-/* -------------------------------------------- all controls */
-#chart-settings-form {
+/* -------------------------------------------- main layout */
+#scatterplot {
/*from width + margin of chart?*/
- padding-top: 1em;
}
-#chart-settings-form input[type=button],
-#chart-settings-form select {
+.scatterplot-container .tab-pane {
+}
+
+/* -------------------------------------------- all controls */
+
+#scatterplot input[type=button],
+#scatterplot select {
width: 100%;
max-width: 256px;
margin-bottom: 8px;
}
-#chart-settings-form .help-text,
-#chart-settings-form .help-text-small {
+#scatterplot .help-text,
+#scatterplot .help-text-small {
color: grey;
}
-#chart-settings-form .help-text {
+#scatterplot .help-text {
padding-bottom: 16px;
}
-#chart-settings-form .help-text-small {
+#scatterplot .help-text-small {
padding: 4px;
font-size: smaller;
}
-#chart-settings-form > * {
+#scatterplot > * {
}
-#chart-settings-form input[value=Draw] {
+#scatterplot input[value=Draw] {
display: block;
margin-top: 16px;
}
+#scatterplot .numeric-slider-input {
+ max-width: 70%;
+}
+
/* -------------------------------------------- data controls */
/* -------------------------------------------- chart controls */
-#chart-settings .form-input {
+#chart-control .form-input {
/*display: table-row;*/
}
-#chart-settings label {
+#chart-control label {
/*text-align: right;*/
margin-bottom: 8px;
/*display: table-cell;*/
}
-#chart-settings .slider {
+#chart-control .slider {
/*display: table-cell;*/
height: 8px;
display: block;
margin: 8px 0px 0px 8px;
}
-#chart-settings .slider-output {
+#chart-control .slider-output {
/*display: table-cell;*/
float: right;
}
-#chart-settings input[type="text"] {
+#chart-control input[type="text"] {
border: 1px solid lightgrey;
}
/* -------------------------------------------- statistics */
-#chart-stats table#chart-stats-table {
+#stats-display table#chart-stats-table {
width: 100%;
}
-#chart-stats #chart-stats-table th {
+#stats-display #chart-stats-table th {
width: 30%;
padding: 4px;
font-weight: bold;
color: grey;
}
-#chart-stats #chart-stats-table td {
+#stats-display #chart-stats-table td {
border: solid lightgrey;
border-width: 1px 0px 0px 1px;
padding: 4px;
}
-#chart-stats #chart-stats-table td:nth-child(1) {
+#stats-display #chart-stats-table td:nth-child(1) {
border-width: 1px 0px 0px 0px;
padding-right: 1em;
text-align: right;
@@ -141,19 +136,12 @@
/* -------------------------------------------- load indicators */
#loading-indicator {
- z-index: 2;
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- background-color: white;
- padding: 32px 0 0 32px;
+ margin: 12px 0px 0px 8px;
}
-#chart-settings-form #loading-indicator .loading-message {
- margin-left: 10px;
+#scatterplot #loading-indicator .loading-message {
font-style: italic;
+ font-size: smaller;
color: grey;
}
@@ -208,18 +196,37 @@
<%def name="javascripts()">
${parent.javascripts()}
-${h.js( "libs/require" )}
+${h.js(
+
+ "libs/underscore",
+ "libs/jquery/jquery-ui",
+ "libs/d3",
+
+ "mvc/base-mvc",
+ "utils/LazyDataLoader",
+ "viz/scatterplot"
+)}
+
+${h.templates(
+ "../../templates/compiled/template-visualization-scatterplotControlForm",
+ "../../templates/compiled/template-visualization-dataControl",
+ "../../templates/compiled/template-visualization-chartControl",
+ "../../templates/compiled/template-visualization-chartDisplay",
+ "../../templates/compiled/template-visualization-statsDisplay"
+)}
+
+${h.js(
+ "mvc/visualizations/scatterplotControlForm",
+)}
<script type="text/javascript">
-require.config({ baseUrl : "${h.url_for( '/static/scripts' )}", });
+$(function(){
-require([ "viz/scatterplot" ], function( scatterplot ){
-
var hda = ${h.to_json_string( hda )},
historyID = '${historyID}',
querySettings = ${h.to_json_string( kwargs )},
chartConfig = _.extend( querySettings, {
- containerSelector : '#chart-holder',
+ containerSelector : '#chart',
//TODO: move to ScatterplotControlForm.initialize
marginTop : ( querySettings.marginTop > 20 )?( querySettings.marginTop ):( 20 ),
@@ -229,18 +236,29 @@
});
//console.debug( querySettings );
- var settingsForm = new scatterplot.ScatterplotControlForm({
+ var settingsForm = new ScatterplotControlForm({
dataset : hda,
apiDatasetsURL : "${h.url_for( controller='/api/datasets' )}",
- el : $( '#chart-settings-form' ),
+ el : $( '#scatterplot' ),
chartConfig : chartConfig
}).render();
});
+</script>
+</%def>
+<%def name="body()">
+ <!--dataset info-->
+ <div id="chart-header" class="header">
+ <h2 class="title">Scatterplot of '${hda['name']}'</h2>
+ <p class="subtitle">${hda['misc_info']}</p>
+ </div>
+ <div id="scatterplot" class="scatterplot-control-form"></div>
+</%def>
+
+<script type="text/javascript">
function make_abs_box( top, left, x, y, id ){
- console.debug( top, left, x, y, id );
var ARROW_SIZE = 8,
ARROW_COLOR = 'grey',
DIST_TO_POINT = 4,
@@ -296,26 +314,4 @@
//console.debug( boxContainer );
return boxContainer;
}
-
-
</script>
-</%def>
-
-<%def name="body()">
- <!--dataset info-->
- <div id="chart-header" class="header">
- <h2 class="title">Scatterplot of '${hda['name']}'</h2>
- <p class="subtitle">${hda['misc_info']}</p>
- </div>
- <div class="outer-container">
- <!--plot controls-->
- <div id="chart-settings-form" class="column left-column"></div>
- <!--plot-->
- <div class="column right-column">
- <div id="chart-holder" class="inner-container"></div>
- </div>
- <div style="clear: both;"></div>
- </div>
- <div id="test"></div>
-
-</%def>
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.
1
0
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/8143d7f7b944/
changeset: 8143d7f7b944
user: jgoecks
date: 2012-11-27 00:26:42
summary: Remove debugging statement.
affected #: 1 file
diff -r 1c5aa1094c417760f0c48225f5a3dd8b71245b0e -r 8143d7f7b94449889c1d590a3d72a3d20de291e3 templates/grid_base.mako
--- a/templates/grid_base.mako
+++ b/templates/grid_base.mako
@@ -564,7 +564,6 @@
// HACK: use a simple string to separate the elements in the
// response: (1) table body; (2) number of pages in table; and (3) message.
var parsed_response_text = response_text.split("*****");
- console.log(parsed_response_text);
// Update grid body and footer.
$('#grid-table-body').html(parsed_response_text[0]);
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.
1
0
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/1c5aa1094c41/
changeset: 1c5aa1094c41
user: jgoecks
date: 2012-11-27 00:25:52
summary: Some grid framework fixes.
affected #: 5 files
diff -r 8420c9a0f7308bbbd79c0a0db29dab063ff9070a -r 1c5aa1094c417760f0c48225f5a3dd8b71245b0e lib/galaxy/web/framework/helpers/grids.py
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -20,6 +20,7 @@
title = ""
exposed = True
model_class = None
+ show_item_checkboxes = False
template = "grid_base.mako"
async_template = "grid_base_async.mako"
use_async = False
@@ -273,7 +274,8 @@
message = message,
use_panels=self.use_panels,
webapp=webapp,
- show_item_checkboxes = ( kwargs.get( 'show_item_checkboxes', '' ) in ['True', 'true'] ),
+ show_item_checkboxes = ( self.show_item_checkboxes or
+ kwargs.get( 'show_item_checkboxes', '' ) in [ 'True', 'true' ] ),
# Pass back kwargs so that grid template can set and use args without
# grid explicitly having to pass them.
kwargs=kwargs )
diff -r 8420c9a0f7308bbbd79c0a0db29dab063ff9070a -r 1c5aa1094c417760f0c48225f5a3dd8b71245b0e lib/galaxy/webapps/galaxy/controllers/page.py
--- a/lib/galaxy/webapps/galaxy/controllers/page.py
+++ b/lib/galaxy/webapps/galaxy/controllers/page.py
@@ -91,6 +91,7 @@
return item.name
# Grid definition.
+ show_item_checkboxes = True
template = "/page/select_items_grid.mako"
async_template = "/page/select_items_grid_async.mako"
default_filter = { "deleted" : "False" , "sharing" : "All" }
diff -r 8420c9a0f7308bbbd79c0a0db29dab063ff9070a -r 1c5aa1094c417760f0c48225f5a3dd8b71245b0e templates/grid_base.mako
--- a/templates/grid_base.mako
+++ b/templates/grid_base.mako
@@ -564,10 +564,13 @@
// HACK: use a simple string to separate the elements in the
// response: (1) table body; (2) number of pages in table; and (3) message.
var parsed_response_text = response_text.split("*****");
+ console.log(parsed_response_text);
// Update grid body and footer.
$('#grid-table-body').html(parsed_response_text[0]);
- $('#grid-table-footer').html(parsed_response_text[1]);
+ // FIXME: this does not work at all; what's needed is a function
+ // that updates page links when number of pages changes.
+ //$('#grid-table-footer').html(parsed_response_text[1]);
// Trigger custom event to indicate grid body has changed.
$('#grid-table-body').trigger('update');
@@ -906,7 +909,7 @@
%>
Page:
% if min_page > 1:
- <span class='page-link'><a href="${url( page=1 )}" page_num="1">1</a></span> ...
+ <span class='page-link' id="page-link-1"><a href="${url( page=1 )}" page_num="1">1</a></span> ...
% endif
%for page_index in range(min_page, max_page + 1):
%if page_index == cur_page_num:
@@ -918,7 +921,7 @@
%endfor
%if max_page < num_pages:
...
- <span class='page-link'><a href="${url( page=num_pages )}" page_num="${num_pages}">${num_pages}</a></span>
+ <span class='page-link' id="page-link-${num_pages}"><a href="${url( page=num_pages )}" page_num="${num_pages}">${num_pages}</a></span>
%endif
</span>
diff -r 8420c9a0f7308bbbd79c0a0db29dab063ff9070a -r 1c5aa1094c417760f0c48225f5a3dd8b71245b0e templates/page/select_items_grid.mako
--- a/templates/page/select_items_grid.mako
+++ b/templates/page/select_items_grid.mako
@@ -1,7 +1,7 @@
## Template generates a grid that enables user to select items.
<%namespace file="../grid_base.mako" import="*" />
-${javascripts()}
+${grid_javascripts()}
${stylesheets()}
${render_grid_header( grid, False )}
${render_grid_table( grid, show_item_checkboxes=True )}
diff -r 8420c9a0f7308bbbd79c0a0db29dab063ff9070a -r 1c5aa1094c417760f0c48225f5a3dd8b71245b0e templates/page/select_items_grid_async.mako
--- a/templates/page/select_items_grid_async.mako
+++ b/templates/page/select_items_grid_async.mako
@@ -2,7 +2,7 @@
<%namespace file="/display_common.mako" import="render_message" />
## Always show item checkboxes so that users can select items.
-${render_grid_table_body_contents( grid, show_item_checkboxes=True )}
+${render_grid_table_body_contents( grid, show_item_checkboxes=show_item_checkboxes )}
*****
${num_pages}
*****
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.
1
0
commit/galaxy-central: natefoo: Explicitly import galaxy.util.shed_util in galaxy.tool_shed that was causing a failure to start on test.
by Bitbucket 26 Nov '12
by Bitbucket 26 Nov '12
26 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/8420c9a0f730/
changeset: 8420c9a0f730
user: natefoo
date: 2012-11-26 19:45:14
summary: Explicitly import galaxy.util.shed_util in galaxy.tool_shed that was causing a failure to start on test.
affected #: 1 file
diff -r f03725b8272bd13aa3fc693b9fcb20d2f0f4c28f -r 8420c9a0f7308bbbd79c0a0db29dab063ff9070a lib/galaxy/tool_shed/__init__.py
--- a/lib/galaxy/tool_shed/__init__.py
+++ b/lib/galaxy/tool_shed/__init__.py
@@ -2,7 +2,7 @@
Classes encapsulating the management of repositories installed from Galaxy tool sheds.
"""
import os
-from galaxy.util.shed_util import *
+import galaxy.util.shed_util
from galaxy.model.orm import *
from galaxy import eggs
@@ -27,7 +27,7 @@
ElementInclude.include( root )
tool_path = root.get( 'tool_path', None )
if tool_path:
- tool_shed = clean_tool_shed_url( tool_shed_repository.tool_shed )
+ tool_shed = galaxy.util.shed_util.clean_tool_shed_url( tool_shed_repository.tool_shed )
relative_path = os.path.join( tool_path,
tool_shed,
'repos',
@@ -44,13 +44,13 @@
.order_by( self.model.ToolShedRepository.table.c.id ):
relative_install_dir = self.get_repository_install_dir( tool_shed_repository )
if relative_install_dir:
- installed_repository_dict = load_installed_datatypes( self.app, tool_shed_repository, relative_install_dir )
+ installed_repository_dict = galaxy.util.shed_util.load_installed_datatypes( self.app, tool_shed_repository, relative_install_dir )
if installed_repository_dict:
self.installed_repository_dicts.append( installed_repository_dict )
def load_proprietary_converters_and_display_applications( self, deactivate=False ):
for installed_repository_dict in self.installed_repository_dicts:
if installed_repository_dict[ 'converter_path' ]:
- load_installed_datatype_converters( self.app, installed_repository_dict, deactivate=deactivate )
+ galaxy.util.shed_util.load_installed_datatype_converters( self.app, installed_repository_dict, deactivate=deactivate )
if installed_repository_dict[ 'display_path' ]:
- load_installed_display_applications( self.app, installed_repository_dict, deactivate=deactivate )
-
\ No newline at end of file
+ galaxy.util.shed_util.load_installed_display_applications( self.app, installed_repository_dict, deactivate=deactivate )
+
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.
1
0
4 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/ec3b5956130f/
changeset: ec3b5956130f
user: jmchilton
date: 2012-10-31 18:18:55
summary: Fix small bug in exception handler in lib/galaxy/jobs/handler.py. The bug was preventing exceptions in dynamic job runner methods from being percolated up into the logs.
affected #: 1 file
diff -r 4ceab232dc02a5a99eca0b24c0038c490d49a392 -r ec3b5956130f767d6f5d9c07c6a64dafd128351f lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -397,6 +397,11 @@
def put( self, job_wrapper ):
try:
runner_name = self.__get_runner_name( job_wrapper )
+ except Exception:
+ log.exception( 'Failed to generate job runner name' )
+ job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
+ return
+ try:
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and isinstance(job_wrapper, TaskWrapper):
#DBTODO Refactor
log.debug( "dispatching task %s, of job %d, to %s runner" %( job_wrapper.task_id, job_wrapper.job_id, runner_name ) )
https://bitbucket.org/galaxy/galaxy-central/changeset/0206d3a8a4d0/
changeset: 0206d3a8a4d0
user: jmchilton
date: 2012-10-31 21:04:33
summary: Allow dynamic job runner rules to pass error messages back to users via job failure message. To do this simply raise galaxy.jobs.mapper.JobMappingException(user_messsage) from a rule method where user_message is the desired message.
affected #: 2 files
diff -r ec3b5956130f767d6f5d9c07c6a64dafd128351f -r 0206d3a8a4d03b2447ab1ef09cd2a2015ce4c3ae lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -17,6 +17,7 @@
# States for running a job. These are NOT the same as data states
JOB_WAIT, JOB_ERROR, JOB_INPUT_ERROR, JOB_INPUT_DELETED, JOB_READY, JOB_DELETED, JOB_ADMIN_DELETED = 'wait', 'error', 'input_error', 'input_deleted', 'ready', 'deleted', 'admin_deleted'
+DEFAULT_JOB_PUT_FAILURE_MESSAGE = 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.'
class JobHandler( object ):
"""
@@ -41,6 +42,7 @@
a JobRunner.
"""
STOP_SIGNAL = object()
+
def __init__( self, app, dispatcher ):
"""Start the job manager"""
self.app = app
@@ -397,9 +399,13 @@
def put( self, job_wrapper ):
try:
runner_name = self.__get_runner_name( job_wrapper )
- except Exception:
- log.exception( 'Failed to generate job runner name' )
- job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
+ except Exception, e:
+ failure_message = getattr(e, 'failure_message', DEFAULT_JOB_PUT_FAILURE_MESSAGE )
+ if failure_message == DEFAULT_JOB_PUT_FAILURE_MESSAGE:
+ log.exception( 'Failed to generate job runner name' )
+ else:
+ log.debug( "Intentionally failing job with message (%s)" % failure_message )
+ job_wrapper.fail( failure_message )
return
try:
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and isinstance(job_wrapper, TaskWrapper):
@@ -410,7 +416,7 @@
self.job_runners[runner_name].put( job_wrapper )
except KeyError:
log.error( 'put(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
- job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
+ job_wrapper.fail( DEFAULT_JOB_PUT_FAILURE_MESSAGE )
def stop( self, job ):
"""
@@ -452,7 +458,7 @@
self.job_runners[runner_name].recover( job, job_wrapper )
except KeyError:
log.error( 'recover(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
- job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
+ job_wrapper.fail( DEFAULT_JOB_PUT_FAILURE_MESSAGE )
def shutdown( self ):
for runner in self.job_runners.itervalues():
diff -r ec3b5956130f767d6f5d9c07c6a64dafd128351f -r 0206d3a8a4d03b2447ab1ef09cd2a2015ce4c3ae lib/galaxy/jobs/mapper.py
--- a/lib/galaxy/jobs/mapper.py
+++ b/lib/galaxy/jobs/mapper.py
@@ -8,6 +8,12 @@
DYNAMIC_RUNNER_PREFIX = "dynamic:///"
+class JobMappingException( Exception ):
+
+ def __init__( self, failure_message ):
+ self.failure_message = failure_message
+
+
class JobRunnerMapper( object ):
"""
This class is responsible to managing the mapping of jobs
https://bitbucket.org/galaxy/galaxy-central/changeset/6f3b4e88fc21/
changeset: 6f3b4e88fc21
user: jmchilton
date: 2012-11-15 05:23:19
summary: Merge latest galaxy-central to resolve conflict introdcued with 73e05bc.
affected #: 227 files
Diff too large to display.
https://bitbucket.org/galaxy/galaxy-central/changeset/f03725b8272b/
changeset: f03725b8272b
user: natefoo
date: 2012-11-26 19:36:09
summary: Merged in jmchilton/galaxy-central-dynamic-job-runner-enhancements (pull request #82)
affected #: 2 files
diff -r d0e7bd064cf9a2b991f793c1dc7720430906174f -r f03725b8272bd13aa3fc693b9fcb20d2f0f4c28f lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -17,6 +17,7 @@
# States for running a job. These are NOT the same as data states
JOB_WAIT, JOB_ERROR, JOB_INPUT_ERROR, JOB_INPUT_DELETED, JOB_READY, JOB_DELETED, JOB_ADMIN_DELETED, JOB_USER_OVER_QUOTA = 'wait', 'error', 'input_error', 'input_deleted', 'ready', 'deleted', 'admin_deleted', 'user_over_quota'
+DEFAULT_JOB_PUT_FAILURE_MESSAGE = 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.'
class JobHandler( object ):
"""
@@ -41,6 +42,7 @@
a JobRunner.
"""
STOP_SIGNAL = object()
+
def __init__( self, app, dispatcher ):
"""Start the job manager"""
self.app = app
@@ -462,6 +464,15 @@
def put( self, job_wrapper ):
try:
runner_name = self.__get_runner_name( job_wrapper )
+ except Exception, e:
+ failure_message = getattr(e, 'failure_message', DEFAULT_JOB_PUT_FAILURE_MESSAGE )
+ if failure_message == DEFAULT_JOB_PUT_FAILURE_MESSAGE:
+ log.exception( 'Failed to generate job runner name' )
+ else:
+ log.debug( "Intentionally failing job with message (%s)" % failure_message )
+ job_wrapper.fail( failure_message )
+ return
+ try:
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and isinstance(job_wrapper, TaskWrapper):
#DBTODO Refactor
log.debug( "dispatching task %s, of job %d, to %s runner" %( job_wrapper.task_id, job_wrapper.job_id, runner_name ) )
@@ -470,7 +481,7 @@
self.job_runners[runner_name].put( job_wrapper )
except KeyError:
log.error( 'put(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
- job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
+ job_wrapper.fail( DEFAULT_JOB_PUT_FAILURE_MESSAGE )
def stop( self, job ):
"""
@@ -512,7 +523,7 @@
self.job_runners[runner_name].recover( job, job_wrapper )
except KeyError:
log.error( 'recover(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
- job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
+ job_wrapper.fail( DEFAULT_JOB_PUT_FAILURE_MESSAGE )
def shutdown( self ):
for runner in self.job_runners.itervalues():
diff -r d0e7bd064cf9a2b991f793c1dc7720430906174f -r f03725b8272bd13aa3fc693b9fcb20d2f0f4c28f lib/galaxy/jobs/mapper.py
--- a/lib/galaxy/jobs/mapper.py
+++ b/lib/galaxy/jobs/mapper.py
@@ -8,6 +8,12 @@
DYNAMIC_RUNNER_PREFIX = "dynamic:///"
+class JobMappingException( Exception ):
+
+ def __init__( self, failure_message ):
+ self.failure_message = failure_message
+
+
class JobRunnerMapper( object ):
"""
This class is responsible to managing the mapping of jobs
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.
1
0
commit/galaxy-central: greg: Don't allow reviewing empty repositories in the tool shed.
by Bitbucket 26 Nov '12
by Bitbucket 26 Nov '12
26 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/d0e7bd064cf9/
changeset: d0e7bd064cf9
user: greg
date: 2012-11-26 18:09:23
summary: Don't allow reviewing empty repositories in the tool shed.
affected #: 3 files
diff -r 593c0d6c3d447aaa79c61fea2688b6091b44dda5 -r d0e7bd064cf9a2b991f793c1dc7720430906174f templates/webapps/community/repository/manage_repository.mako
--- a/templates/webapps/community/repository/manage_repository.mako
+++ b/templates/webapps/community/repository/manage_repository.mako
@@ -21,7 +21,7 @@
can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated
can_reset_all_metadata = not is_deprecated and is_admin and len( repo ) > 0
has_readme = metadata and 'readme' in metadata
- can_review_repository = not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
+ can_review_repository = not is_new and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
reviewing_repository = cntrller and cntrller == 'repository_review'
if can_push:
diff -r 593c0d6c3d447aaa79c61fea2688b6091b44dda5 -r d0e7bd064cf9a2b991f793c1dc7720430906174f templates/webapps/community/repository/view_repository.mako
--- a/templates/webapps/community/repository/view_repository.mako
+++ b/templates/webapps/community/repository/view_repository.mako
@@ -20,7 +20,7 @@
browse_label = 'Browse repository tip files'
has_readme = metadata and 'readme' in metadata
reviewing_repository = cntrller and cntrller == 'repository_review'
- can_review_repository = not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
+ can_review_repository = not is_new and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
%><%!
diff -r 593c0d6c3d447aaa79c61fea2688b6091b44dda5 -r d0e7bd064cf9a2b991f793c1dc7720430906174f templates/webapps/community/repository/view_tool_metadata.mako
--- a/templates/webapps/community/repository/view_tool_metadata.mako
+++ b/templates/webapps/community/repository/view_tool_metadata.mako
@@ -8,6 +8,7 @@
from urllib import quote_plus
is_admin = trans.user_is_admin()
is_new = repository.is_new( trans.app )
+ is_deprecated = repository.deprecated
can_contact_owner = trans.user and trans.user != repository.user
can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository )
can_upload = can_push
@@ -21,7 +22,7 @@
else:
browse_label = 'Browse repository tip files'
has_readme = metadata and 'readme' in metadata
- can_review_repository = trans.app.security_agent.user_can_review_repositories( trans.user )
+ can_review_repository = not is_new and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user )
%><%!
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.
1
0
26 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/593c0d6c3d44/
changeset: 593c0d6c3d44
user: greg
date: 2012-11-26 17:43:38
summary: Fixes for tool shed functional tests.
affected #: 3 files
diff -r 770580cf77ccd49f2a8c2aab2b559c45a318db51 -r 593c0d6c3d447aaa79c61fea2688b6091b44dda5 test/tool_shed/base/test_db_util.py
--- a/test/tool_shed/base/test_db_util.py
+++ b/test/tool_shed/base/test_db_util.py
@@ -1,9 +1,6 @@
import galaxy.webapps.community.model as model
from galaxy.model.orm import *
from galaxy.webapps.community.model.mapping import context as sa_session
-from base.twilltestcase import *
-from sqlalchemy import desc
-import sys
def delete_obj( obj ):
sa_session.delete( obj )
diff -r 770580cf77ccd49f2a8c2aab2b559c45a318db51 -r 593c0d6c3d447aaa79c61fea2688b6091b44dda5 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -1,5 +1,4 @@
from base.twilltestcase import *
-from tool_shed.base.test_db_util import *
class ShedTwillTestCase( TwillTestCase ):
def setUp( self ):
@@ -37,7 +36,7 @@
self.check_for_strings( strings_displayed, strings_not_displayed )
def check_for_valid_tools( self, repository ):
self.manage_repository( repository )
- self.check_page_for_string( '<b>Valid tools</b><i> - click the name to preview the tool' )
+ self.check_page_for_string( 'Valid tools' )
def check_repository_changelog( self, repository, strings_displayed=[], strings_not_displayed=[] ):
url = '/repository/view_changelog?id=%s' % self.security.encode_id( repository.id )
self.visit_url( url )
@@ -149,6 +148,6 @@
self.visit_url( '/upload/upload?repository_id=%s' % self.security.encode_id( repository.id ) )
for key in kwargs:
tc.fv( "1", key, kwargs[ key ] )
- tc.formfile( "1", "file_data", filename )
+ tc.formfile( "1", "file_data", self.get_filename( filename ) )
tc.submit( "upload_button" )
self.check_for_strings( strings_displayed, strings_not_displayed )
diff -r 770580cf77ccd49f2a8c2aab2b559c45a318db51 -r 593c0d6c3d447aaa79c61fea2688b6091b44dda5 test/tool_shed/functional/test_0000_basic_repository_features.py
--- a/test/tool_shed/functional/test_0000_basic_repository_features.py
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -1,8 +1,3 @@
-import tempfile, time, re, tempfile, os, shutil
-import galaxy.webapps.community.model
-from galaxy.util import parse_xml, string_as_bool
-from galaxy.util.shed_util import clean_tool_shed_url
-from galaxy.model.orm import *
from tool_shed.base.twilltestcase import *
from tool_shed.base.test_db_util import *
@@ -19,8 +14,6 @@
repository_name = 'filter'
repository_description = "Galaxy's filter tool"
repository_long_description = "Long description of Galaxy's filter tool"
-files_path = os.path.abspath( os.path.join( "test", "tool_shed", "test_data" ) )
-filter_filename = os.path.join( files_path, "filtering_1.1.0.tar" )
class TestCreateRepository( ShedTwillTestCase ):
@@ -65,8 +58,9 @@
def test_0030_upload_tarball( self ):
"""Upload filtering_1.1.0.tar to the repository"""
repository = get_repository_by_name( repository_name, admin_username )
- self.upload( repository, filter_filename, \
- strings_displayed=[ "The file '%s' has been successfully uploaded to the repository." % filter_filename ], \
+ self.upload( repository,
+ 'filtering_1.1.0.tar',
+ strings_displayed=[ "has been successfully uploaded to the repository." ],
commit_message="Uploaded filtering 1.1.0" )
self.check_for_valid_tools( repository )
latest_repository_metadata = self.get_latest_repository_metadata_for_repository( repository )
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.
1
0
commit/galaxy-central: greg: Change name of first tool shed functional test file.
by Bitbucket 26 Nov '12
by Bitbucket 26 Nov '12
26 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/770580cf77cc/
changeset: 770580cf77cc
user: greg
date: 2012-11-26 17:24:39
summary: Change name of first tool shed functional test file.
affected #: 2 files
diff -r a27a0f7d3ac90dfabd1050e26b9216eeb9ebc46b -r 770580cf77ccd49f2a8c2aab2b559c45a318db51 test/tool_shed/functional/test_0000_basic_repository_features.py
--- /dev/null
+++ b/test/tool_shed/functional/test_0000_basic_repository_features.py
@@ -0,0 +1,94 @@
+import tempfile, time, re, tempfile, os, shutil
+import galaxy.webapps.community.model
+from galaxy.util import parse_xml, string_as_bool
+from galaxy.util.shed_util import clean_tool_shed_url
+from galaxy.model.orm import *
+from tool_shed.base.twilltestcase import *
+from tool_shed.base.test_db_util import *
+
+admin_user = None
+admin_user_private_role = None
+admin_email = 'test(a)bx.psu.edu'
+admin_username = 'admin-user'
+
+regular_user = None
+regular_user_private_role = None
+regular_email = 'test-1(a)bx.psu.edu'
+regular_username = 'user1'
+
+repository_name = 'filter'
+repository_description = "Galaxy's filter tool"
+repository_long_description = "Long description of Galaxy's filter tool"
+files_path = os.path.abspath( os.path.join( "test", "tool_shed", "test_data" ) )
+filter_filename = os.path.join( files_path, "filtering_1.1.0.tar" )
+
+class TestCreateRepository( ShedTwillTestCase ):
+
+ def test_0000_initiate_users( self ):
+ """Create necessary users and login as an admin user."""
+ self.login( email=regular_email, username=regular_username )
+ regular_user = get_user( regular_email )
+ assert regular_user is not None, 'Problem retrieving user with email %s from the database' % regular_email
+ regular_user_private_role = get_private_role( regular_user )
+ self.logout()
+ self.login( email=admin_email, username=admin_username )
+ admin_user = get_user( admin_email )
+ assert admin_user is not None, 'Problem retrieving user with email %s from the database' % admin_email
+ admin_user_private_role = get_private_role( admin_user )
+ def test_0005_create_categories( self ):
+ """Create a category"""
+ self.create_category( 'Text Manipulation', 'Tools for manipulating text' )
+ self.create_category( 'Text Analysis', 'Tools for analyzing text' )
+ def test_0010_create_repository( self ):
+ """Create a repository"""
+ strings_displayed = [ '<div class="toolFormTitle">Repository %s</div>' % "'%s'" % repository_name, \
+ 'Repository %s has been created' % "'%s'" % repository_name ]
+ self.create_repository( repository_name, repository_description, \
+ repository_long_description=repository_long_description, \
+ categories=[ 'Text Manipulation' ], \
+ strings_displayed=strings_displayed )
+ def test_0015_edit_repository( self ):
+ """Edit the repository name, description, and long description"""
+ repository = get_repository_by_name( repository_name, admin_username )
+ new_name = "renamed_filter"
+ new_description = "Edited filter tool"
+ new_long_description = "Edited long description"
+ self.edit_repository_information( repository, repo_name=new_name, description=new_description, long_description=new_long_description )
+ def test_0020_change_repository_category( self ):
+ """Change the category of a repository"""
+ repository = get_repository_by_name( repository_name, admin_username )
+ self.edit_repository_categories( repository, categories_to_add=[ "Text Analysis" ], categories_to_remove=[ "Text Manipulation" ] )
+# def test_0025_grant_write_access( self ):
+# '''Grant write access to another user'''
+# repository = get_repository_by_name( repository_name, admin_username )
+# self.grant_write_access( repository, usernames=[ regular_username ] )
+ def test_0030_upload_tarball( self ):
+ """Upload filtering_1.1.0.tar to the repository"""
+ repository = get_repository_by_name( repository_name, admin_username )
+ self.upload( repository, filter_filename, \
+ strings_displayed=[ "The file '%s' has been successfully uploaded to the repository." % filter_filename ], \
+ commit_message="Uploaded filtering 1.1.0" )
+ self.check_for_valid_tools( repository )
+ latest_repository_metadata = self.get_latest_repository_metadata_for_repository( repository )
+ changeset_revision = latest_repository_metadata.changeset_revision
+ self.check_repository_changelog( repository, strings_displayed=[ 'Repository metadata is associated with this change set.' ] )
+ self.set_repository_malicious( repository, strings_displayed=[ 'The repository tip has been defined as malicious.' ] )
+ self.unset_repository_malicious( repository, strings_displayed=[ 'The repository tip has been defined as <b>not</b> malicious.' ] )
+ self.load_display_tool_page( repository, tool_xml_filename='filtering.xml', \
+ changeset_revision=changeset_revision, \
+ strings_displayed=[ 'Filter (version 1.1.0)', "c1=='chr1'" ], \
+ strings_not_displayed=[] )
+ tool = latest_repository_metadata.metadata[ 'tools' ][0]
+ metadata_strings_displayed = [ tool[ 'guid' ], tool[ 'version' ], tool[ 'id' ], tool[ 'name' ], tool[ 'description' ], changeset_revision ]
+ self.check_for_tool_metadata( repository, changeset_revision, 'Filter1', strings_displayed=metadata_strings_displayed )
+ def test_0035_repository_browse_page( self ):
+ '''Visit the repository browse page'''
+ repository = get_repository_by_name( repository_name, admin_username )
+ self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] )
+ def test_0040_visit_clone_url_via_hgweb( self ):
+ '''Visit the repository clone URL via hgweb'''
+ repository = get_repository_by_name( repository_name, admin_username )
+ latest_changeset_revision = self.get_latest_repository_metadata_for_repository( repository )
+ self.display_repository_clone_page( admin_username, \
+ repository_name, \
+ strings_displayed=[ 'Uploaded filtering 1.1.0', latest_changeset_revision.changeset_revision ] )
diff -r a27a0f7d3ac90dfabd1050e26b9216eeb9ebc46b -r 770580cf77ccd49f2a8c2aab2b559c45a318db51 test/tool_shed/functional/test_0000_create_repository.py
--- a/test/tool_shed/functional/test_0000_create_repository.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import tempfile, time, re, tempfile, os, shutil
-import galaxy.webapps.community.model
-from galaxy.util import parse_xml, string_as_bool
-from galaxy.util.shed_util import clean_tool_shed_url
-from galaxy.model.orm import *
-from tool_shed.base.twilltestcase import *
-from tool_shed.base.test_db_util import *
-
-admin_user = None
-admin_user_private_role = None
-admin_email = 'test(a)bx.psu.edu'
-admin_username = 'admin-user'
-
-regular_user = None
-regular_user_private_role = None
-regular_email = 'test-1(a)bx.psu.edu'
-regular_username = 'user1'
-
-repository_name = 'filter'
-repository_description = "Galaxy's filter tool"
-repository_long_description = "Long description of Galaxy's filter tool"
-files_path = os.path.abspath( os.path.join( "test", "tool_shed", "test_data" ) )
-filter_filename = os.path.join( files_path, "filtering_1.1.0.tar" )
-
-class TestCreateRepository( ShedTwillTestCase ):
-
- def test_0000_initiate_users( self ):
- """Create necessary users and login as an admin user."""
- self.login( email=regular_email, username=regular_username )
- regular_user = get_user( regular_email )
- assert regular_user is not None, 'Problem retrieving user with email %s from the database' % regular_email
- regular_user_private_role = get_private_role( regular_user )
- self.logout()
- self.login( email=admin_email, username=admin_username )
- admin_user = get_user( admin_email )
- assert admin_user is not None, 'Problem retrieving user with email %s from the database' % admin_email
- admin_user_private_role = get_private_role( admin_user )
- def test_0005_create_categories( self ):
- """Create a category"""
- self.create_category( 'Text Manipulation', 'Tools for manipulating text' )
- self.create_category( 'Text Analysis', 'Tools for analyzing text' )
- def test_0010_create_repository( self ):
- """Create a repository"""
- strings_displayed = [ '<div class="toolFormTitle">Repository %s</div>' % "'%s'" % repository_name, \
- 'Repository %s has been created' % "'%s'" % repository_name ]
- self.create_repository( repository_name, repository_description, \
- repository_long_description=repository_long_description, \
- categories=[ 'Text Manipulation' ], \
- strings_displayed=strings_displayed )
- def test_0015_edit_repository( self ):
- """Edit the repository name, description, and long description"""
- repository = get_repository_by_name( repository_name, admin_username )
- new_name = "renamed_filter"
- new_description = "Edited filter tool"
- new_long_description = "Edited long description"
- self.edit_repository_information( repository, repo_name=new_name, description=new_description, long_description=new_long_description )
- def test_0020_change_repository_category( self ):
- """Change the category of a repository"""
- repository = get_repository_by_name( repository_name, admin_username )
- self.edit_repository_categories( repository, categories_to_add=[ "Text Analysis" ], categories_to_remove=[ "Text Manipulation" ] )
-# def test_0025_grant_write_access( self ):
-# '''Grant write access to another user'''
-# repository = get_repository_by_name( repository_name, admin_username )
-# self.grant_write_access( repository, usernames=[ regular_username ] )
- def test_0030_upload_tarball( self ):
- """Upload filtering_1.1.0.tar to the repository"""
- repository = get_repository_by_name( repository_name, admin_username )
- self.upload( repository, filter_filename, \
- strings_displayed=[ "The file '%s' has been successfully uploaded to the repository." % filter_filename ], \
- commit_message="Uploaded filtering 1.1.0" )
- self.check_for_valid_tools( repository )
- latest_repository_metadata = self.get_latest_repository_metadata_for_repository( repository )
- changeset_revision = latest_repository_metadata.changeset_revision
- self.check_repository_changelog( repository, strings_displayed=[ 'Repository metadata is associated with this change set.' ] )
- self.set_repository_malicious( repository, strings_displayed=[ 'The repository tip has been defined as malicious.' ] )
- self.unset_repository_malicious( repository, strings_displayed=[ 'The repository tip has been defined as <b>not</b> malicious.' ] )
- self.load_display_tool_page( repository, tool_xml_filename='filtering.xml', \
- changeset_revision=changeset_revision, \
- strings_displayed=[ 'Filter (version 1.1.0)', "c1=='chr1'" ], \
- strings_not_displayed=[] )
- tool = latest_repository_metadata.metadata[ 'tools' ][0]
- metadata_strings_displayed = [ tool[ 'guid' ], tool[ 'version' ], tool[ 'id' ], tool[ 'name' ], tool[ 'description' ], changeset_revision ]
- self.check_for_tool_metadata( repository, changeset_revision, 'Filter1', strings_displayed=metadata_strings_displayed )
- def test_0035_repository_browse_page( self ):
- '''Visit the repository browse page'''
- repository = get_repository_by_name( repository_name, admin_username )
- self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] )
- def test_0040_visit_clone_url_via_hgweb( self ):
- '''Visit the repository clone URL via hgweb'''
- repository = get_repository_by_name( repository_name, admin_username )
- latest_changeset_revision = self.get_latest_repository_metadata_for_repository( repository )
- self.display_repository_clone_page( admin_username, \
- repository_name, \
- strings_displayed=[ 'Uploaded filtering 1.1.0', latest_changeset_revision.changeset_revision ] )
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.
1
0
commit/galaxy-central: greg: Style fix for rendering tool dependency information when installing tool shed repositories.
by Bitbucket 26 Nov '12
by Bitbucket 26 Nov '12
26 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/a27a0f7d3ac9/
changeset: a27a0f7d3ac9
user: greg
date: 2012-11-26 16:44:32
summary: Style fix for rendering tool dependency information when installing tool shed repositories.
affected #: 2 files
diff -r a627f341d01ae4a9741164981ff2e5300353354f -r a27a0f7d3ac90dfabd1050e26b9216eeb9ebc46b lib/galaxy/util/shed_util.py
--- a/lib/galaxy/util/shed_util.py
+++ b/lib/galaxy/util/shed_util.py
@@ -484,18 +484,6 @@
if ctx_file_name == config_file:
return get_named_tmpfile_from_ctx( changeset_ctx, ctx_file, dir )
return None
-def get_headers( fname, sep, count=60, is_multi_byte=False ):
- """Returns a list with the first 'count' lines split by 'sep'."""
- headers = []
- for idx, line in enumerate( file( fname ) ):
- line = line.rstrip( '\n\r' )
- if is_multi_byte:
- line = unicode( line, 'utf-8' )
- sep = sep.encode( 'utf-8' )
- headers.append( line.split( sep ) )
- if idx == count:
- break
- return headers
def get_converter_and_display_paths( registration_elem, relative_install_dir ):
"""Find the relative path to data type converters and display applications included in installed tool shed repositories."""
converter_path = None
@@ -544,6 +532,18 @@
ctx_rev = response.read()
response.close()
return ctx_rev
+def get_headers( fname, sep, count=60, is_multi_byte=False ):
+ """Returns a list with the first 'count' lines split by 'sep'."""
+ headers = []
+ for idx, line in enumerate( file( fname ) ):
+ line = line.rstrip( '\n\r' )
+ if is_multi_byte:
+ line = unicode( line, 'utf-8' )
+ sep = sep.encode( 'utf-8' )
+ headers.append( line.split( sep ) )
+ if idx == count:
+ break
+ return headers
def get_installed_tool_shed_repository( trans, id ):
"""Get a repository on the Galaxy side from the database via id"""
return trans.sa_session.query( trans.model.ToolShedRepository ).get( trans.security.decode_id( id ) )
diff -r a627f341d01ae4a9741164981ff2e5300353354f -r a27a0f7d3ac90dfabd1050e26b9216eeb9ebc46b templates/admin/tool_shed_repository/common.mako
--- a/templates/admin/tool_shed_repository/common.mako
+++ b/templates/admin/tool_shed_repository/common.mako
@@ -90,8 +90,18 @@
</div></div><div style="clear: both"></div>
- <div class="form-row">
- <table class="grid">
+ <div class="form-row">
+ <style type="text/css">
+ #dependency_table{ table-layout:fixed;
+ width:100%;
+ overflow-wrap:normal;
+ overflow:hidden;
+ border:0px;
+ word-break:keep-all;
+ word-wrap:break-word;
+ line-break:strict; }
+ </style>
+ <table class="grid" id="dependency_table"><tr><td colspan="4" bgcolor="#D8D8D8"><b>Tool dependencies</b></td></tr><%
env_settings_heaader_row_displayed = False
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.
1
0
commit/galaxy-central: inithello: Removed lastz and bowtie from tool_conf.xml.main
by Bitbucket 26 Nov '12
by Bitbucket 26 Nov '12
26 Nov '12
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/a627f341d01a/
changeset: a627f341d01a
user: inithello
date: 2012-11-26 16:20:38
summary: Removed lastz and bowtie from tool_conf.xml.main
affected #: 1 file
diff -r 87828175dfe7475aa846f383a5038e5ca4f1e312 -r a627f341d01ae4a9741164981ff2e5300353354f tool_conf.xml.main
--- a/tool_conf.xml.main
+++ b/tool_conf.xml.main
@@ -259,13 +259,10 @@
</section><section name="NGS: Mapping" id="ngs_mapping"><label text="Illumina" id="illumina"/>
- <tool file="sr_mapping/bowtie_wrapper.xml" /><label text="Roche-454" id="roche_454"/>
- <tool file="sr_mapping/lastz_wrapper.xml" /><tool file="metag_tools/megablast_wrapper.xml" /><tool file="metag_tools/megablast_xml_parser.xml" /><label text="AB-SOLiD" id="ab_solid"/>
- <tool file="sr_mapping/bowtie_color_wrapper.xml" /></section><section name="NGS: SAM Tools" id="samtools"><tool file="samtools/sam_bitwise_flag_filter.xml" />
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.
1
0