galaxy-commits
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 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: Display messages for repositories in the tool shed that have dependencies on deprecated repositories.
by commits-noreply@bitbucket.org 15 Jan '14
by commits-noreply@bitbucket.org 15 Jan '14
15 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9fe1a6fc375b/
Changeset: 9fe1a6fc375b
User: greg
Date: 2014-01-15 21:35:41
Summary: Display messages for repositories in the tool shed that have dependencies on deprecated repositories.
Affected #: 6 files
diff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f -r 9fe1a6fc375b3fad14dac271ed8f85e02fc6ef6d lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -971,8 +971,9 @@
@web.require_login( "deprecate repository" )
def deprecate( self, trans, **kwd ):
"""Mark a repository in the tool shed as deprecated or not deprecated."""
- # Marking a repository in the tool shed as deprecated has no effect on any downloadable changeset revisions that may be associated with the
- # repository. Revisions are not marked as not downlaodable because those that have installed the repository must be allowed to get updates.
+ # Marking a repository in the tool shed as deprecated has no effect on any downloadable changeset
+ # revisions that may be associated with the repository. Revisions are not marked as not downlaodable
+ # because those that have installed the repository must be allowed to get updates.
message = kwd.get( 'message', '' )
status = kwd.get( 'status', 'done' )
repository_id = kwd.get( 'id', None )
@@ -2287,7 +2288,9 @@
status = 'warning'
else:
# Handle messaging for orphan tool dependency definitions.
- orphan_message = tool_dependency_util.generate_message_for_orphan_tool_dependencies( trans, repository, metadata )
+ orphan_message = tool_dependency_util.generate_message_for_orphan_tool_dependencies( trans,
+ repository,
+ metadata )
if orphan_message:
message += orphan_message
status = 'warning'
@@ -2302,14 +2305,23 @@
skip_tool_tests_check_box = CheckboxField( 'skip_tool_tests', checked=skip_tool_tests_checked )
categories = suc.get_categories( trans )
selected_categories = [ rca.category_id for rca in repository.categories ]
- containers_dict = container_util.build_repository_containers_for_tool_shed( trans, repository, changeset_revision, repository_dependencies, repository_metadata )
+ containers_dict = container_util.build_repository_containers_for_tool_shed( trans,
+ repository,
+ changeset_revision,
+ repository_dependencies,
+ repository_metadata )
heads = suc.get_repository_heads( repo )
+ deprecated_repository_dependency_tups = \
+ repository_dependency_util.get_repository_dependency_tups_from_repository_metadata( trans.app,
+ repository_metadata,
+ deprecated_only=True )
return trans.fill_template( '/webapps/tool_shed/repository/manage_repository.mako',
repo_name=repo_name,
description=description,
long_description=long_description,
current_allow_push_list=current_allow_push_list,
allow_push_select_field=allow_push_select_field,
+ deprecated_repository_dependency_tups=deprecated_repository_dependency_tups,
repo=repo,
heads=heads,
repository=repository,
diff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f -r 9fe1a6fc375b3fad14dac271ed8f85e02fc6ef6d lib/tool_shed/galaxy_install/installed_repository_manager.py
--- a/lib/tool_shed/galaxy_install/installed_repository_manager.py
+++ b/lib/tool_shed/galaxy_install/installed_repository_manager.py
@@ -76,7 +76,7 @@
# Get the list of repository dependencies for this repository.
status = self.install_model.ToolShedRepository.installation_status.INSTALLED
repository_dependency_tups = \
- repository_dependency_util.get_repository_dependency_tups_for_repository( self.app, repository, status=status )
+ repository_dependency_util.get_repository_dependency_tups_for_installed_repository( self.app, repository, status=status )
# Add an entry to self.installed_repository_dependencies_of_installed_repositories.
if repository_tup not in self.installed_repository_dependencies_of_installed_repositories:
debug_msg = "Adding an entry for revision %s of repository %s owned by %s " % ( installed_changeset_revision, name, owner )
@@ -132,7 +132,7 @@
debug_msg += "to repository_dependencies_of_installed_repositories."
log.debug( debug_msg )
repository_dependency_tups = \
- repository_dependency_util.get_repository_dependency_tups_for_repository( self.app, repository, status=None )
+ repository_dependency_util.get_repository_dependency_tups_for_installed_repository( self.app, repository, status=None )
self.repository_dependencies_of_installed_repositories[ repository_tup ] = repository_dependency_tups
def add_entry_to_runtime_tool_dependencies_of_installed_tool_dependencies( self, tool_dependency ):
diff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f -r 9fe1a6fc375b3fad14dac271ed8f85e02fc6ef6d lib/tool_shed/util/repository_dependency_util.py
--- a/lib/tool_shed/util/repository_dependency_util.py
+++ b/lib/tool_shed/util/repository_dependency_util.py
@@ -303,10 +303,13 @@
key_rd_dicts_to_be_processed=None, all_repository_dependencies=None,
handled_key_rd_dicts=None, circular_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.
+ 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 handled_key_rd_dicts is None:
handled_key_rd_dicts = []
@@ -331,8 +334,8 @@
all_repository_dependencies = initialize_all_repository_dependencies( current_repository_key,
repository_dependencies_dict,
all_repository_dependencies )
- # Handle the repository dependencies defined in the current repository, if any, and populate the various repository dependency objects for
- # this round of processing.
+ # Handle the repository dependencies defined in the current repository, if any, and populate
+ # the various repository dependency objects for this round of processing.
current_repository_key_rd_dicts, key_rd_dicts_to_be_processed, handled_key_rd_dicts, all_repository_dependencies = \
populate_repository_dependency_objects_for_processing( trans,
current_repository_key,
@@ -375,10 +378,41 @@
all_repository_dependencies = prune_invalid_repository_dependencies( all_repository_dependencies )
return all_repository_dependencies
-def get_repository_dependency_tups_for_repository( app, repository, dependency_tups=None, status=None ):
+def get_repository_dependency_tups_from_repository_metadata( app, repository_metadata, deprecated_only=False ):
+ """
+ Return a list of of tuples defining repository objects required by the received repository. The returned
+ list defines the entire repository dependency tree. This method is called only from the Tool Shed.
+ """
+ dependency_tups = []
+ if repository_metadata is not None:
+ metadata = repository_metadata.metadata
+ if metadata:
+ repository_dependencies_dict = metadata.get( 'repository_dependencies', None )
+ if repository_dependencies_dict is not None:
+ repository_dependency_tups = repository_dependencies_dict.get( 'repository_dependencies', None )
+ if repository_dependency_tups is not None:
+ # The value of repository_dependency_tups is a list of repository dependency tuples like this:
+ # ['http://localhost:9009', 'package_samtools_0_1_18', 'devteam', 'ef37fc635cb9', 'False', 'False']
+ for repository_dependency_tup in repository_dependency_tups:
+ toolshed, name, owner, changeset_revision, pir, oicct = \
+ common_util.parse_repository_dependency_tuple( repository_dependency_tup )
+ repository = suc.get_repository_by_name_and_owner( app, name, owner )
+ if repository:
+ if deprecated_only:
+ if repository.deprecated:
+ dependency_tups.append( repository_dependency_tup )
+ else:
+ dependency_tups.append( repository_dependency_tup )
+ else:
+ log.debug( "Cannot locate repository %s owned by %s for inclusion in repository dependency tups." % \
+ ( name, owner ) )
+ return dependency_tups
+
+def get_repository_dependency_tups_for_installed_repository( app, repository, dependency_tups=None, status=None ):
"""
Return a list of of tuples defining tool_shed_repository objects (whose status can be anything) required by the
- received repository. The returned list defines the entire repository dependency tree.
+ received repository. The returned list defines the entire repository dependency tree. This method is called
+ only from Galaxy.
"""
if dependency_tups is None:
dependency_tups = []
@@ -393,7 +427,9 @@
continue
if required_repository_tup not in dependency_tups:
dependency_tups.append( required_repository_tup )
- return get_repository_dependency_tups_for_repository( app, required_repository, dependency_tups=dependency_tups )
+ return get_repository_dependency_tups_for_installed_repository( app,
+ required_repository,
+ dependency_tups=dependency_tups )
return dependency_tups
def get_repository_tuple_for_installed_repository_manager( repository ):
@@ -657,12 +693,16 @@
containers_dict[ 'missing_repository_dependencies' ] = None
return containers_dict
-def populate_repository_dependency_objects_for_processing( trans, current_repository_key, repository_dependencies_dict, key_rd_dicts_to_be_processed,
- handled_key_rd_dicts, circular_repository_dependencies, all_repository_dependencies ):
+def populate_repository_dependency_objects_for_processing( trans, current_repository_key, repository_dependencies_dict,
+ key_rd_dicts_to_be_processed, handled_key_rd_dicts,
+ circular_repository_dependencies, all_repository_dependencies ):
"""
- The process that discovers all repository dependencies for a specified repository's changeset revision uses this method to populate the following items
- for the current processing loop: filtered_current_repository_key_rd_dicts, key_rd_dicts_to_be_processed, handled_key_rd_dicts, all_repository_dependencies.
- Each processing loop may discover more repository dependencies, so this method is repeatedly called until all repository dependencies have been discovered.
+ The process that discovers all repository dependencies for a specified repository's
+ changeset revision uses this method to populate the following items for the current
+ processing loop: filtered_current_repository_key_rd_dicts, key_rd_dicts_to_be_processed,
+ handled_key_rd_dicts, all_repository_dependencies. Each processing loop may discover
+ more repository dependencies, so this method is repeatedly called until all repository
+ dependencies have been discovered.
"""
current_repository_key_rd_dicts = []
filtered_current_repository_key_rd_dicts = []
@@ -673,13 +713,15 @@
if current_repository_key_rd_dicts and current_repository_key:
# Remove all repository dependencies that point to a revision within its own repository.
current_repository_key_rd_dicts = remove_ropository_dependency_reference_to_self( current_repository_key_rd_dicts )
- current_repository_key_rd_dicts = get_updated_changeset_revisions_for_repository_dependencies( trans, current_repository_key_rd_dicts )
+ current_repository_key_rd_dicts = get_updated_changeset_revisions_for_repository_dependencies( trans,
+ current_repository_key_rd_dicts )
for key_rd_dict in current_repository_key_rd_dicts:
# Filter out repository dependencies that are required only if compiling the dependent repository's tool dependency.
key_rd_dict = filter_only_if_compiling_contained_td( key_rd_dict )
if key_rd_dict:
is_circular = False
- if not in_key_rd_dicts( key_rd_dict, handled_key_rd_dicts ) and not in_key_rd_dicts( key_rd_dict, key_rd_dicts_to_be_processed ):
+ if not in_key_rd_dicts( key_rd_dict, handled_key_rd_dicts ) and not in_key_rd_dicts( key_rd_dict,
+ key_rd_dicts_to_be_processed ):
filtered_current_repository_key_rd_dicts.append( key_rd_dict )
repository_dependency = key_rd_dict[ current_repository_key ]
if current_repository_key in all_repository_dependencies:
@@ -708,8 +750,10 @@
def prune_invalid_repository_dependencies( repository_dependencies ):
"""
- Eliminate all invalid entries in the received repository_dependencies dictionary. An entry is invalid if the value_list of the key/value pair is
- empty. This occurs when an invalid combination of tool shed, name , owner, changeset_revision is used and a repository_metadata record is not found.
+ Eliminate all invalid entries in the received repository_dependencies dictionary. An entry
+ is invalid if the value_list of the key/value pair is empty. This occurs when an invalid
+ combination of tool shed, name , owner, changeset_revision is used and a repository_metadata
+ record is not found.
"""
valid_repository_dependencies = {}
description = repository_dependencies.get( 'description', None )
@@ -752,7 +796,9 @@
toolshed, name, owner, changeset_revision, prior_installation_required, only_if_compiling_contained_td = \
common_util.parse_repository_dependency_tuple( repository_dependency )
if rd_toolshed == toolshed and rd_name == name and rd_owner == owner:
- log.debug( "Removing repository dependency for repository %s owned by %s since it refers to a revision within itself." % ( name, owner ) )
+ debug_msg = "Removing repository dependency for repository %s owned by %s " % ( name, owner )
+ debug_msg += 'since it refers to a revision within itself.'
+ log.debug( debug_msg )
else:
new_key_rd_dict = {}
new_key_rd_dict[ key ] = repository_dependency
diff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f -r 9fe1a6fc375b3fad14dac271ed8f85e02fc6ef6d templates/webapps/tool_shed/common/common.mako
--- a/templates/webapps/tool_shed/common/common.mako
+++ b/templates/webapps/tool_shed/common/common.mako
@@ -51,6 +51,23 @@
</script></%def>
+<%def name="render_deprecated_repository_dependencies_message( deprecated_repository_dependency_tups )">
+ <div class="warningmessage">
+ <%
+ from tool_shed.util.common_util import parse_repository_dependency_tuple
+ msg = '<ul>'
+ for deprecated_repository_dependency_tup in deprecated_repository_dependency_tups:
+ toolshed, name, owner, changeset_revision, pir, oicct = \
+ parse_repository_dependency_tuple( deprecated_repository_dependency_tup )
+ msg += '<li>Revision <b>%s</b> of repository <b>%s</b> owned by <b>%s</b></li>' % \
+ ( changeset_revision, name, owner )
+ msg += '</ul>'
+ %>
+ This repository depends upon the following deprecated repositories<br/>
+ ${msg}
+ </div>
+</%def>
+
<%def name="render_star_rating( name, rating, disabled=False )"><%
if disabled:
@@ -87,6 +104,20 @@
</div></%def>
+<%def name="render_multiple_heads_message( heads )">
+ <div class="warningmessage">
+ <%
+ from tool_shed.util.shed_util_common import get_revision_label_from_ctx
+ heads_str = ''
+ for ctx in heads:
+ heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx, include_date=True )
+ %>
+ Contact the administrator of this Tool Shed as soon as possible and let them know that
+ this repository has the following multiple heads which must be merged.<br/>
+ ${heads_str}
+ </div>
+</%def>
+
<%def name="render_review_comment( comment_text )"><style type="text/css">
#reviews_table{ table-layout:fixed;
diff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f -r 9fe1a6fc375b3fad14dac271ed8f85e02fc6ef6d templates/webapps/tool_shed/repository/manage_repository.mako
--- a/templates/webapps/tool_shed/repository/manage_repository.mako
+++ b/templates/webapps/tool_shed/repository/manage_repository.mako
@@ -87,6 +87,14 @@
multiple_heads = len( heads ) > 1
else:
multiple_heads = False
+
+ if repository_metadata is None:
+ revision_installable = False
+ else:
+ if repository_metadata.downloadable is None:
+ revision_installable = 'unknown'
+ else:
+ revision_installable = repository_metadata.downloadable
%><%!
@@ -119,18 +127,12 @@
<div class="warningmessage">
This repository has been marked as deprecated, so some tool shed features may be restricted.
</div>
-%elif multiple_heads:
- <div class="warningmessage">
- <%
- from tool_shed.util.shed_util_common import get_revision_label_from_ctx
- heads_str = ''
- for ctx in heads:
- heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx, include_date=True )
- %>
- Contact the administrator of this Tool Shed as soon as possible and let them know that
- this repository has the following multiple heads which must be merged.<br/>
- ${heads_str}
- </div>
+%endif:
+%if multiple_heads:
+ ${render_multiple_heads_message( heads )}
+%endif
+%if deprecated_repository_dependency_tups:
+ ${render_deprecated_repository_dependencies_message( deprecated_repository_dependency_tups )}
%endif
%if len( changeset_revision_select_field.options ) > 1:
@@ -207,7 +209,11 @@
${repository.user.username | h}
</div><div class="form-row">
- <label>Times downloaded:</label>
+ <label>This revision can be installed:</label>
+ ${revision_installable}
+ </div>
+ <div class="form-row">
+ <label>Times cloned / installed:</label>
${repository.times_downloaded | h}
</div>
%if is_admin:
diff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f -r 9fe1a6fc375b3fad14dac271ed8f85e02fc6ef6d templates/webapps/tool_shed/repository/view_repository.mako
--- a/templates/webapps/tool_shed/repository/view_repository.mako
+++ b/templates/webapps/tool_shed/repository/view_repository.mako
@@ -30,6 +30,14 @@
multiple_heads = len( heads ) > 1
else:
multiple_heads = False
+
+ if repository_metadata is None:
+ revision_installable = False
+ else:
+ if repository_metadata.downloadable is None:
+ revision_installable = 'unknown'
+ else:
+ revision_installable = repository_metadata.downloadable
%><%!
@@ -66,18 +74,12 @@
<div class="warningmessage">
This repository has been marked as deprecated, so some tool shed features may be restricted.
</div>
-%elif multiple_heads:
- <div class="warningmessage">
- <%
- from tool_shed.util.shed_util_common import get_revision_label_from_ctx
- heads_str = ''
- for ctx in heads:
- heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx, include_date=True )
- %>
- Contact the administrator of this Tool Shed as soon as possible and let them know that
- this repository has the following multiple heads which must be merged.<br/>
- ${heads_str}
- </div>
+%endif:
+%if multiple_heads:
+ ${render_multiple_heads_message( heads )}
+%endif
+%if deprecated_repository_dependency_tups:
+ ${render_deprecated_repository_dependencies_message( deprecated_repository_dependency_tups )}
%endif
%if len( changeset_revision_select_field.options ) > 1:
@@ -142,7 +144,11 @@
${repository.user.username | h}
</div><div class="form-row">
- <label>Times downloaded:</label>
+ <label>This revision can be installed:</label>
+ ${revision_installable}
+ </div>
+ <div class="form-row">
+ <label>Times cloned / installed:</label>
${repository.times_downloaded}
</div>
%if trans.user_is_admin():
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/73dcfbfc744c/
Changeset: 73dcfbfc744c
Branch: stable
User: dan
Date: 2013-12-12 23:09:29
Summary: When setting external metadata using _handle_metadata_externally (e.g. local and lwr runners) use the requirments of the set metadata tool to determine dependencies. Resolves issue seen with missing samtools command when samtools is not in the path, but has been installed with the dependency system.
Affected #: 4 files
diff -r a96ca602209ca2d4d732cced9530a62b3e4134a8 -r 73dcfbfc744c2336749df0276a5a727d672c6add lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -585,6 +585,9 @@
tool_xml_text = """
<tool id="__SET_METADATA__" name="Set External Metadata" version="1.0.1" tool_type="set_metadata"><type class="SetMetadataTool" module="galaxy.tools"/>
+ <requirements>
+ <requirement type="package">samtools</requirement>
+ </requirements><action module="galaxy.tools.actions.metadata" class="SetMetadataToolAction"/><command>$__SET_EXTERNAL_METADATA_COMMAND_LINE__</command><inputs>
diff -r a96ca602209ca2d4d732cced9530a62b3e4134a8 -r 73dcfbfc744c2336749df0276a5a727d672c6add lib/galaxy/jobs/runners/__init__.py
--- a/lib/galaxy/jobs/runners/__init__.py
+++ b/lib/galaxy/jobs/runners/__init__.py
@@ -196,7 +196,7 @@
log.exception( "from_work_dir specified a location not in the working directory: %s, %s" % ( source_file, job_wrapper.working_directory ) )
return output_pairs
- def _handle_metadata_externally(self, job_wrapper):
+ def _handle_metadata_externally( self, job_wrapper, resolve_requirements=False ):
"""
Set metadata externally. Used by the local and lwr job runners where this
shouldn't be attached to command-line to execute.
@@ -211,6 +211,12 @@
#we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior
kwds={ 'overwrite' : False } )
log.debug( 'executing external set_meta script for job %d: %s' % ( job_wrapper.job_id, external_metadata_script ) )
+ if resolve_requirements:
+ dependency_shell_commands = self.app.datatypes_registry.set_external_metadata_tool.build_dependency_shell_commands()
+ if dependency_shell_commands:
+ if isinstance( dependency_shell_commands, list ):
+ dependency_shell_commands = "&&".join( dependency_shell_commands )
+ external_metadata_script = "%s&&%s" % ( dependency_shell_commands, external_metadata_script )
external_metadata_proc = subprocess.Popen( args=external_metadata_script,
shell=True,
env=os.environ,
diff -r a96ca602209ca2d4d732cced9530a62b3e4134a8 -r 73dcfbfc744c2336749df0276a5a727d672c6add lib/galaxy/jobs/runners/local.py
--- a/lib/galaxy/jobs/runners/local.py
+++ b/lib/galaxy/jobs/runners/local.py
@@ -110,7 +110,7 @@
job_wrapper.fail( "failure running job", exception=True )
log.exception("failure running job %d" % job_wrapper.job_id)
return
- self._handle_metadata_externally( job_wrapper )
+ self._handle_metadata_externally( job_wrapper, resolve_requirements=True )
# Finish the job!
try:
job_wrapper.finish( stdout, stderr, exit_code )
diff -r a96ca602209ca2d4d732cced9530a62b3e4134a8 -r 73dcfbfc744c2336749df0276a5a727d672c6add lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -154,7 +154,7 @@
job_wrapper.fail( message, exception=True )
log.exception("failure running job %d" % job_wrapper.job_id)
return
- self._handle_metadata_externally( job_wrapper )
+ self._handle_metadata_externally( job_wrapper, resolve_requirements=True )
# Finish the job
try:
job_wrapper.finish( stdout, stderr )
https://bitbucket.org/galaxy/galaxy-central/commits/4e280310697b/
Changeset: 4e280310697b
Branch: stable
User: jmchilton
Date: 2013-12-13 22:43:26
Summary: Use null object pattern to simplify logic related to no dependency manager being set.
Fixes bug introduced in 4241eff related to dependency_manager being None if tool_dependency_dir is not set.
Affected #: 4 files
diff -r 73dcfbfc744c2336749df0276a5a727d672c6add -r 4e280310697b806aa0e08c80f342e2c93f1558f0 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -704,10 +704,7 @@
if self.command_line and self.command_line.startswith( 'python' ):
self.galaxy_lib_dir = os.path.abspath( "lib" ) # cwd = galaxy root
# Shell fragment to inject dependencies
- if self.app.config.use_tool_dependencies:
- self.dependency_shell_commands = self.tool.build_dependency_shell_commands()
- else:
- self.dependency_shell_commands = None
+ self.dependency_shell_commands = self.tool.build_dependency_shell_commands()
# We need command_line persisted to the db in order for Galaxy to re-queue the job
# if the server was stopped and restarted before the job finished
job.command_line = self.command_line
@@ -1451,10 +1448,7 @@
if self.command_line and self.command_line.startswith( 'python' ):
self.galaxy_lib_dir = os.path.abspath( "lib" ) # cwd = galaxy root
# Shell fragment to inject dependencies
- if self.app.config.use_tool_dependencies:
- self.dependency_shell_commands = self.tool.build_dependency_shell_commands()
- else:
- self.dependency_shell_commands = None
+ self.dependency_shell_commands = self.tool.build_dependency_shell_commands()
# We need command_line persisted to the db in order for Galaxy to re-queue the job
# if the server was stopped and restarted before the job finished
task.command_line = self.command_line
diff -r 73dcfbfc744c2336749df0276a5a727d672c6add -r 4e280310697b806aa0e08c80f342e2c93f1558f0 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -40,7 +40,7 @@
from galaxy.tools.actions import DefaultToolAction
from galaxy.tools.actions.data_source import DataSourceToolAction
from galaxy.tools.actions.data_manager import DataManagerToolAction
-from galaxy.tools.deps import DependencyManager, INDETERMINATE_DEPENDENCY
+from galaxy.tools.deps import build_dependency_manager
from galaxy.tools.deps.requirements import parse_requirements_from_xml
from galaxy.tools.parameters import check_param, params_from_strings, params_to_strings
from galaxy.tools.parameters.basic import (BaseURLToolParameter,
@@ -696,14 +696,7 @@
return stored.latest_workflow
def init_dependency_manager( self ):
- if self.app.config.use_tool_dependencies:
- dependency_manager_kwds = {
- 'default_base_path': self.app.config.tool_dependency_dir,
- 'conf_file': self.app.config.dependency_resolvers_config_file,
- }
- self.dependency_manager = DependencyManager( **dependency_manager_kwds )
- else:
- self.dependency_manager = None
+ self.dependency_manager = build_dependency_manager( self.app.config )
@property
def sa_session( self ):
diff -r 73dcfbfc744c2336749df0276a5a727d672c6add -r 4e280310697b806aa0e08c80f342e2c93f1558f0 lib/galaxy/tools/deps/__init__.py
--- a/lib/galaxy/tools/deps/__init__.py
+++ b/lib/galaxy/tools/deps/__init__.py
@@ -15,6 +15,31 @@
from galaxy.util.submodules import submodules
+def build_dependency_manager( config ):
+ if config.use_tool_dependencies:
+ dependency_manager_kwds = {
+ 'default_base_path': config.tool_dependency_dir,
+ 'conf_file': config.dependency_resolvers_config_file,
+ }
+ dependency_manager = DependencyManager( **dependency_manager_kwds )
+ else:
+ dependency_manager = NullDependencyManager()
+
+ return dependency_manager
+
+
+class NullDependencyManager( object ):
+
+ def uses_tool_shed_dependencies(self):
+ return False
+
+ def dependency_shell_commands( self, requirements, **kwds ):
+ return []
+
+ def find_dep( self, name, version=None, type='package', **kwds ):
+ return INDETERMINATE_DEPENDENCY
+
+
class DependencyManager( object ):
"""
A DependencyManager attempts to resolve named and versioned dependencies by
diff -r 73dcfbfc744c2336749df0276a5a727d672c6add -r 4e280310697b806aa0e08c80f342e2c93f1558f0 lib/tool_shed/util/common_install_util.py
--- a/lib/tool_shed/util/common_install_util.py
+++ b/lib/tool_shed/util/common_install_util.py
@@ -458,7 +458,7 @@
tool_dependency = tool_dependencies[ index ]
if tool_dependency.can_install:
try:
- dependencies_ignored = app.toolbox.dependency_manager and not app.toolbox.dependency_manager.uses_tool_shed_dependencies()
+ dependencies_ignored = not app.toolbox.dependency_manager.uses_tool_shed_dependencies()
if dependencies_ignored:
log.info("Skipping package %s, tool shed dependency resolver not enabled." % package_name)
# Tool dependency resolves have been configured and they do not
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: dannon: Tweaks to scripts/api/common.py to fix JSONDecodeError removal and clean up code.
by commits-noreply@bitbucket.org 15 Jan '14
by commits-noreply@bitbucket.org 15 Jan '14
15 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d2f73445ce7a/
Changeset: d2f73445ce7a
User: dannon
Date: 2014-01-15 15:05:38
Summary: Tweaks to scripts/api/common.py to fix JSONDecodeError removal and clean up code.
Affected #: 1 file
diff -r d0302907cbbfbfbc8561bd3b2fe4a667161e67ff -r d2f73445ce7ada2f13947aa7c7c8506743d40d3f scripts/api/common.py
--- a/scripts/api/common.py
+++ b/scripts/api/common.py
@@ -1,3 +1,6 @@
+"""
+Common methods used by the API sample scripts.
+"""
import json
import logging
import os
@@ -9,17 +12,16 @@
sys.path = new_path
from galaxy import eggs
-import pkg_resources
+eggs.require( "pycrypto" )
-pkg_resources.require( "pycrypto" )
from Crypto.Cipher import Blowfish
-from Crypto.Util.randpool import RandomPool
-from Crypto.Util import number
log = logging.getLogger( __name__ )
def make_url( api_key, url, args=None ):
- # Adds the API Key to the URL if it's not already there.
+ """
+ Adds the API Key to the URL if it's not already there.
+ """
if args is None:
args = []
argsep = '&'
@@ -30,29 +32,37 @@
return url + argsep + '&'.join( [ '='.join( t ) for t in args ] )
def get( api_key, url ):
- # Do the actual GET.
+ """
+ Do the actual GET.
+ """
url = make_url( api_key, url )
try:
return json.loads( urllib2.urlopen( url ).read() )
- except json.decoder.JSONDecodeError, e:
- print "URL did not return JSON data"
+ except ValueError, e:
+ print "URL did not return JSON data: %s" % e
sys.exit(1)
def post( api_key, url, data ):
- # Do the actual POST.
+ """
+ Do the actual POST.
+ """
url = make_url( api_key, url )
req = urllib2.Request( url, headers = { 'Content-Type': 'application/json' }, data = json.dumps( data ) )
return json.loads( urllib2.urlopen( req ).read() )
def put( api_key, url, data ):
- # Do the actual PUT
+ """
+ Do the actual PUT
+ """
url = make_url( api_key, url )
req = urllib2.Request( url, headers = { 'Content-Type': 'application/json' }, data = json.dumps( data ))
req.get_method = lambda: 'PUT'
return json.loads( urllib2.urlopen( req ).read() )
def __del( api_key, url, data ):
- # Do the actual DELETE
+ """
+ Do the actual DELETE
+ """
url = make_url( api_key, url )
req = urllib2.Request( url, headers = { 'Content-Type': 'application/json' }, data = json.dumps( data ))
req.get_method = lambda: 'DELETE'
@@ -60,7 +70,9 @@
def display( api_key, url, return_formatted=True ):
- # Sends an API GET request and acts as a generic formatter for the JSON response.
+ """
+ Sends an API GET request and acts as a generic formatter for the JSON response.
+ """
try:
r = get( api_key, url )
except urllib2.HTTPError, e:
@@ -97,8 +109,10 @@
print 'response is unknown type: %s' % type( r )
def submit( api_key, url, data, return_formatted=True ):
- # Sends an API POST request and acts as a generic formatter for the JSON response.
- # 'data' will become the JSON payload read by Galaxy.
+ """
+ Sends an API POST request and acts as a generic formatter for the JSON response.
+ 'data' will become the JSON payload read by Galaxy.
+ """
try:
r = post( api_key, url, data )
except urllib2.HTTPError, e:
@@ -131,8 +145,10 @@
print r
def update( api_key, url, data, return_formatted=True ):
- # Sends an API PUT request and acts as a generic formatter for the JSON response.
- # 'data' will become the JSON payload read by Galaxy.
+ """
+ Sends an API PUT request and acts as a generic formatter for the JSON response.
+ 'data' will become the JSON payload read by Galaxy.
+ """
try:
r = put( api_key, url, data )
except urllib2.HTTPError, e:
@@ -147,10 +163,12 @@
print 'Response'
print '--------'
print r
-
+
def delete( api_key, url, data, return_formatted=True ):
- # Sends an API DELETE request and acts as a generic formatter for the JSON response.
- # 'data' will become the JSON payload read by Galaxy.
+ """
+ Sends an API DELETE request and acts as a generic formatter for the JSON response.
+ 'data' will become the JSON payload read by Galaxy.
+ """
try:
r = __del( api_key, url, data )
except urllib2.HTTPError, e:
@@ -166,12 +184,14 @@
print '--------'
print r
-# utility method to encode ID's
def encode_id( config_id_secret, obj_id ):
+ """
+ utility method to encode ID's
+ """
id_cipher = Blowfish.new( config_id_secret )
# Convert to string
s = str( obj_id )
- # Pad to a multiple of 8 with leading "!"
+ # Pad to a multiple of 8 with leading "!"
s = ( "!" * ( 8 - len(s) % 8 ) ) + s
# Encrypt
return id_cipher.encrypt( s ).encode( 'hex' )
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: Fix tool configuration reload for changes in 5f6c670.
by commits-noreply@bitbucket.org 15 Jan '14
by commits-noreply@bitbucket.org 15 Jan '14
15 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d0302907cbbf/
Changeset: d0302907cbbf
User: inithello
Date: 2014-01-15 14:54:33
Summary: Fix tool configuration reload for changes in 5f6c670.
Affected #: 1 file
diff -r 833d5e848479b04c0e90923b9dc4c168d78b4d7e -r d0302907cbbfbfbc8561bd3b2fe4a667161e67ff templates/admin/reload_tool.mako
--- a/templates/admin/reload_tool.mako
+++ b/templates/admin/reload_tool.mako
@@ -1,5 +1,6 @@
<%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" />
+<% from galaxy.tools import Tool, ToolSection %><script type="text/javascript">
$().ready(function() {
@@ -26,13 +27,13 @@
</label><select name="tool_id">
%for key, val in toolbox.tool_panel.items():
- %if key.startswith( 'tool' ):
+ %if isinstance( val, Tool ):
<option value="${val.id}">${val.name}</option>
- %elif key.startswith( 'section' ):
+ %elif isinstance( val, ToolSection ):
<optgroup label="${val.name}"><% section = val %>
%for section_key, section_val in section.elems.items():
- %if section_key.startswith( 'tool' ):
+ %if isinstance( section_val, Tool ):
<% selected_str = "" %>
%if section_val.id == tool_id:
<% selected_str = " selected=\"selected\"" %>
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: dannon: Fix object not found wording.
by commits-noreply@bitbucket.org 15 Jan '14
by commits-noreply@bitbucket.org 15 Jan '14
15 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/833d5e848479/
Changeset: 833d5e848479
User: dannon
Date: 2014-01-15 14:46:53
Summary: Fix object not found wording.
Affected #: 1 file
diff -r fa698b544051c61a6b895f04840309a7807ce5a7 -r 833d5e848479b04c0e90923b9dc4c168d78b4d7e lib/galaxy/exceptions/error_codes.json
--- a/lib/galaxy/exceptions/error_codes.json
+++ b/lib/galaxy/exceptions/error_codes.json
@@ -52,6 +52,6 @@
{
"name": "USER_OBJECT_NOT_FOUND",
"code": 404001,
- "message": "No such object not found."
+ "message": "No such object found."
}
]
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
5 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3cafaba9aaf2/
Changeset: 3cafaba9aaf2
User: jmchilton
Date: 2014-01-10 22:10:52
Summary: Pull extract_payload_from_request out of expose_api.
Needs to be used in new version of API decorator and resulted in too much nesting in there the way it was.
Affected #: 1 file
diff -r 4ab2017348e25958bcd8babd8e110f574dff24df -r 3cafaba9aaf288d0abf257032db225fc52ca7287 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -103,6 +103,35 @@
return decorator
return argcatcher
+
+def __extract_payload_from_request(trans, func, kwargs):
+ content_type = trans.request.headers['content-type']
+ if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
+ # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
+ # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
+ # are not a part of the request body. This is a problem because it's not possible to differentiate between values
+ # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
+ # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
+ # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
+ payload = kwargs.copy()
+ named_args, _, _, _ = inspect.getargspec(func)
+ for arg in named_args:
+ payload.pop(arg, None)
+ for k, v in payload.iteritems():
+ if isinstance(v, (str, unicode)):
+ try:
+ payload[k] = from_json_string(v)
+ except:
+ # may not actually be json, just continue
+ pass
+ payload = util.recursively_stringify_dictionary_keys( payload )
+ else:
+ # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
+ # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
+ # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
+ payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
+ return payload
+
def expose_api_raw( func ):
"""
Expose this function via the API but don't dump the results
@@ -140,35 +169,8 @@
error_message = "API Authentication Required for this request"
return error
if trans.request.body:
- def extract_payload_from_request(trans, func, kwargs):
- content_type = trans.request.headers['content-type']
- if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
- # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
- # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
- # are not a part of the request body. This is a problem because it's not possible to differentiate between values
- # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
- # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
- # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
- payload = kwargs.copy()
- named_args, _, _, _ = inspect.getargspec(func)
- for arg in named_args:
- payload.pop(arg, None)
- for k, v in payload.iteritems():
- if isinstance(v, (str, unicode)):
- try:
- payload[k] = from_json_string(v)
- except:
- # may not actually be json, just continue
- pass
- payload = util.recursively_stringify_dictionary_keys( payload )
- else:
- # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
- # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
- # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
- payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
- return payload
try:
- kwargs['payload'] = extract_payload_from_request(trans, func, kwargs)
+ kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
except ValueError:
error_status = '400 Bad Request'
error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
https://bitbucket.org/galaxy/galaxy-central/commits/39b2ae68b153/
Changeset: 39b2ae68b153
User: jmchilton
Date: 2014-01-10 22:10:52
Summary: Introduce new generation of API decorator...
Add improved error handling. Introduce error code.
Use new decorator with tested methods in histories API.
Affected #: 5 files
diff -r 3cafaba9aaf288d0abf257032db225fc52ca7287 -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -6,33 +6,66 @@
eggs.require( "Paste" )
from paste import httpexceptions
+from ..exceptions import error_codes
+
class MessageException( Exception ):
"""
- Exception to make throwing errors from deep in controllers easier
+ Exception to make throwing errors from deep in controllers easier.
"""
- def __init__( self, err_msg, type="info" ):
- self.err_msg = err_msg
+ # status code to be set when used with API.
+ status_code = 400
+ # Error code information embedded into API json responses.
+ err_code = error_codes.UNKNOWN
+
+ def __init__( self, err_msg=None, type="info", **extra_error_info ):
+ self.err_msg = err_msg or self.err_code.default_error_message
self.type = type
+ self.extra_error_info = extra_error_info
+
def __str__( self ):
return self.err_msg
+
class ItemDeletionException( MessageException ):
pass
+
class ItemAccessibilityException( MessageException ):
- pass
+ status_code = 403
+ err_code = error_codes.USER_CANNOT_ACCESS_ITEM
+
class ItemOwnershipException( MessageException ):
- pass
+ status_code = 403
+ err_code = error_codes.USER_DOES_NOT_OWN_ITEM
+
+
+class DuplicatedSlugException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_SLUG_DUPLICATE
+
+
+class ObjectAttributeInvalidException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_OBJECT_ATTRIBUTE_INVALID
+
+
+class ObjectAttributeMissingException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_OBJECT_ATTRIBUTE_MISSING
+
class ActionInputError( MessageException ):
def __init__( self, err_msg, type="error" ):
super( ActionInputError, self ).__init__( err_msg, type )
-class ObjectNotFound( Exception ):
+
+class ObjectNotFound( MessageException ):
""" Accessed object was not found """
- pass
+ status_code = 404
+ err_code = error_codes.USER_OBJECT_NOT_FOUND
+
class ObjectInvalid( Exception ):
""" Accessed object store ID is invalid """
diff -r 3cafaba9aaf288d0abf257032db225fc52ca7287 -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 lib/galaxy/exceptions/error_codes.py
--- /dev/null
+++ b/lib/galaxy/exceptions/error_codes.py
@@ -0,0 +1,33 @@
+# Error codes are provided as a convience to Galaxy API clients, but at this
+# time they do represent part of the more stable interface. They can change
+# without warning between releases.
+UNKNOWN_ERROR_MESSAGE = "Unknown error occurred while processing request."
+
+
+class ErrorCode( object ):
+
+ def __init__( self, code, default_error_message ):
+ self.code = code
+ self.default_error_message = default_error_message or UNKNOWN_ERROR_MESSAGE
+
+ def __str__( self ):
+ return str( self.default_error_message )
+
+ def __int__( self ):
+ return int( self.code )
+
+# TODO: Guidelines for error message langauge?
+UNKNOWN = ErrorCode(0, UNKNOWN_ERROR_MESSAGE)
+
+USER_CANNOT_RUN_AS = ErrorCode(400001, "User does not have permissions to run jobs as another user.")
+USER_INVALID_RUN_AS = ErrorCode(400002, "Invalid run_as request - run_as user does not exist.")
+USER_INVALID_JSON = ErrorCode(400003, "Your request did not appear to be valid JSON, please consult the API documentation.")
+USER_OBJECT_ATTRIBUTE_INVALID = ErrorCode(400004, "Attempted to create or update object with invalid attribute value.")
+USER_OBJECT_ATTRIBUTE_MISSING = ErrorCode(400005, "Attempted to create object without required attribute.")
+USER_SLUG_DUPLICATE = ErrorCode(400006, "Slug must be unique per user.")
+
+USER_NO_API_KEY = ErrorCode(403001, "API Authentication Required for this request")
+USER_CANNOT_ACCESS_ITEM = ErrorCode(403002, "User cannot access specified item.")
+USER_DOES_NOT_OWN_ITEM = ErrorCode(403003, "User does not own specified item.")
+
+USER_OBJECT_NOT_FOUND = ErrorCode(404001, "No such object not found.")
diff -r 3cafaba9aaf288d0abf257032db225fc52ca7287 -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -15,3 +15,7 @@
from framework import expose_api_raw
from framework import expose_api_raw_anonymous
from framework.base import httpexceptions
+
+# TODO: Drop and make these the default.
+from framework import _future_expose_api
+from framework import _future_expose_api_anonymous
diff -r 3cafaba9aaf288d0abf257032db225fc52ca7287 -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -10,9 +10,9 @@
import socket
import string
import time
-
+from traceback import format_exc
+from Cookie import CookieError
from functools import wraps
-from Cookie import CookieError
pkg_resources.require( "Cheetah" )
from Cheetah.Template import Template
@@ -23,6 +23,7 @@
from galaxy import util
from galaxy.exceptions import MessageException
+from galaxy.exceptions import error_codes
from galaxy.util import asbool
from galaxy.util import safe_str_cmp
from galaxy.util.backports.importlib import import_module
@@ -212,6 +213,143 @@
decorator.exposed = True
return decorator
+API_RESPONSE_CONTENT_TYPE = "application/json"
+
+
+def __api_error_message( trans, **kwds ):
+ exception = kwds.get( "exception", None )
+ if exception:
+ # If we are passed a MessageException use err_msg.
+ default_error_code = getattr( exception, "err_code", error_codes.UNKNOWN )
+ default_error_message = getattr( exception, "err_msg", default_error_code.default_error_message )
+ extra_error_info = getattr( exception, 'extra_error_info', {} )
+ if not isinstance( extra_error_info, dict ):
+ extra_error_info = {}
+ else:
+ default_error_message = "Error processing API request."
+ default_error_code = error_codes.UNKNOWN
+ extra_error_info = {}
+ traceback_string = kwds.get( "traceback", "No traceback available." )
+ err_msg = kwds.get( "err_msg", default_error_message )
+ error_code_object = kwds.get( "err_code", default_error_code )
+ try:
+ error_code = error_code_object.code
+ except AttributeError:
+ # Some sort of bad error code sent in, logic failure on part of
+ # Galaxy developer.
+ error_code = error_codes.UNKNOWN.code
+ # Would prefer the terminology of error_code and error_message, but
+ # err_msg used a good number of places already. Might as well not change
+ # it?
+ error_response = dict( err_msg=err_msg, err_code=error_code, **extra_error_info )
+ if trans.debug: # TODO: Should admins get to see traceback as well?
+ error_response[ "traceback" ] = traceback_string
+ return error_response
+
+
+def __api_error_response( trans, **kwds ):
+ error_dict = __api_error_message( trans, **kwds )
+ exception = kwds.get( "exception", None )
+ # If we are given an status code directly - use it - otherwise check
+ # the exception for a status_code attribute.
+ if "status_code" in kwds:
+ status_code = int( kwds.get( "status_code" ) )
+ elif hasattr( exception, "status_code" ):
+ status_code = int( exception.status_code )
+ else:
+ status_code = 500
+ response = trans.response
+ if not response.status or str(response.status).startswith("20"):
+ # Unset status code appears to be string '200 OK', if anything
+ # non-success (i.e. not 200 or 201) has been set, do not override
+ # underlying controller.
+ response.status = status_code
+ return to_json_string( error_dict )
+
+
+# TODO: rename as expose_api and make default.
+def _future_expose_api_anonymous( func, to_json=True ):
+ """
+ Expose this function via the API but don't require a set user.
+ """
+ return _future_expose_api( func, to_json=to_json, user_required=False )
+
+
+# TODO: rename as expose_api and make default.
+def _future_expose_api( func, to_json=True, user_required=True ):
+ """
+ Expose this function via the API.
+ """
+ @wraps(func)
+ def decorator( self, trans, *args, **kwargs ):
+ if trans.error_message:
+ # TODO: Document this branch, when can this happen,
+ # I don't understand it.
+ return __api_error_response( trans, err_msg=trans.error_message )
+ if user_required and trans.anonymous:
+ error_code = error_codes.USER_NO_API_KEY
+ # Use error codes default error message.
+ return __api_error_response( trans, err_code=error_code, status_code=403 )
+ if trans.request.body:
+ try:
+ kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
+ except ValueError:
+ error_code = error_codes.USER_INVALID_JSON
+ return __api_error_response( trans, status_code=400, err_code=error_code )
+
+ trans.response.set_content_type( API_RESPONSE_CONTENT_TYPE )
+ # send 'do not cache' headers to handle IE's caching of ajax get responses
+ trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
+ # TODO: Refactor next block out into a helper procedure.
+ # Perform api_run_as processing, possibly changing identity
+ if 'payload' in kwargs and 'run_as' in kwargs['payload']:
+ if not trans.user_can_do_run_as():
+ error_code = error_codes.USER_CANNOT_RUN_AS
+ return __api_error_response( trans, err_code=error_code, status_code=403 )
+ try:
+ decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
+ except TypeError:
+ error_message = "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
+ error_code = error_codes.USER_INVALID_RUN_AS
+ return __api_error_response( trans, err_code=error_code, err_msg=error_message, status_code=400)
+ try:
+ user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
+ trans.api_inherit_admin = trans.user_is_admin()
+ trans.set_user(user)
+ except:
+ error_code = error_codes.USER_INVALID_RUN_AS
+ return __api_error_response( trans, err_code=error_code, status_code=400 )
+ try:
+ rval = func( self, trans, *args, **kwargs)
+ if to_json and trans.debug:
+ rval = to_json_string( rval, indent=4, sort_keys=True )
+ elif to_json:
+ rval = to_json_string( rval )
+ return rval
+ except MessageException as e:
+ traceback_string = format_exc()
+ return __api_error_response( trans, exception=e, traceback=traceback_string )
+ except paste.httpexceptions.HTTPException:
+ # TODO: Allow to pass or format for the API???
+ raise # handled
+ except Exception as e:
+ traceback_string = format_exc()
+ error_message = 'Uncaught exception in exposed API method:'
+ log.exception( error_message )
+ return __api_error_response(
+ trans,
+ status_code=500,
+ exception=e,
+ traceback=traceback_string,
+ err_msg=error_message,
+ err_code=error_codes.UNKNOWN
+ )
+ if not hasattr(func, '_orig'):
+ decorator._orig = func
+ decorator.exposed = True
+ return decorator
+
+
def require_admin( func ):
@wraps(func)
def decorator( self, trans, *args, **kwargs ):
diff -r 3cafaba9aaf288d0abf257032db225fc52ca7287 -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -9,6 +9,8 @@
from paste.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPInternalServerError, HTTPException
from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
from galaxy.util import string_as_bool, restore_text
from galaxy.util.sanitize_html import sanitize_html
from galaxy.web.base.controller import BaseAPIController, UsesHistoryMixin, UsesTagsMixin
@@ -20,7 +22,7 @@
class HistoriesController( BaseAPIController, UsesHistoryMixin, UsesTagsMixin ):
- @web.expose_api_anonymous
+ @expose_api_anonymous
def index( self, trans, deleted='False', **kwd ):
"""
index( trans, deleted='False' )
@@ -152,7 +154,7 @@
return history_data
- @web.expose_api
+ @expose_api
def create( self, trans, payload, **kwd ):
"""
create( trans, payload )
https://bitbucket.org/galaxy/galaxy-central/commits/b41d51baaae1/
Changeset: b41d51baaae1
User: jmchilton
Date: 2014-01-10 22:10:52
Summary: Add functional tests for pages API.
Touch up pages API in response to test cases. Update pages API to use newer API decorator.
Affected #: 5 files
diff -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 lib/galaxy/webapps/galaxy/api/page_revisions.py
--- a/lib/galaxy/webapps/galaxy/api/page_revisions.py
+++ b/lib/galaxy/webapps/galaxy/api/page_revisions.py
@@ -2,8 +2,9 @@
API for updating Galaxy Pages
"""
import logging
-from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import SharableItemSecurityMixin, BaseAPIController, SharableMixin
+from galaxy import exceptions
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.util.sanitize_html import sanitize_html
@@ -12,7 +13,7 @@
class PageRevisionsController( BaseAPIController, SharableItemSecurityMixin, UsesAnnotations, SharableMixin ):
- @web.expose_api
+ @expose_api
def index( self, trans, page_id, **kwd ):
"""
index( self, trans, page_id, **kwd )
@@ -24,14 +25,16 @@
:rtype: list
:returns: dictionaries containing different revisions of the page
"""
+ page = self._get_page( trans, page_id )
+ self._verify_page_ownership( trans, page )
+
r = trans.sa_session.query( trans.app.model.PageRevision ).filter_by( page_id=trans.security.decode_id(page_id) )
out = []
for page in r:
- if self.security_check( trans, page, True, True ):
- out.append( self.encode_all_ids( trans, page.to_dict(), True) )
+ out.append( self.encode_all_ids( trans, page.to_dict(), True) )
return out
- @web.expose_api
+ @expose_api
def create( self, trans, page_id, payload, **kwd ):
"""
create( self, trans, page_id, payload **kwd )
@@ -46,39 +49,42 @@
:rtype: dictionary
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
"""
- error_str = ""
+ content = payload.get("content", None)
+ if not content:
+ raise exceptions.ObjectAttributeMissingException("content undefined or empty")
- if not page_id:
- error_str = "page_id is required"
- elif not payload.get("content", None):
- error_str = "content is required"
+ page = self._get_page( trans, page_id )
+ self._verify_page_ownership( trans, page )
+
+ if 'title' in payload:
+ title = payload['title']
else:
+ title = page.title
- # Create the new stored page
+ content = sanitize_html( content, 'utf-8', 'text/html' )
+
+ page_revision = trans.app.model.PageRevision()
+ page_revision.title = title
+ page_revision.page = page
+ page.latest_revision = page_revision
+ page_revision.content = content
+
+ # Persist
+ session = trans.sa_session
+ session.flush()
+
+ return page_revision.to_dict( view="element" )
+
+ def _get_page( self, trans, page_id ):
+ page = None
+ try:
page = trans.sa_session.query( trans.app.model.Page ).get( trans.security.decode_id(page_id) )
- if page is None:
- return { "error" : "page not found"}
+ except Exception:
+ pass
+ if not page:
+ raise exceptions.ObjectNotFound()
+ return page
- if not self.security_check( trans, page, True, True ):
- return { "error" : "page not found"}
-
- if 'title' in payload:
- title = payload['title']
- else:
- title = page.title
-
- content = payload.get("content", "")
- content = sanitize_html( content, 'utf-8', 'text/html' )
-
- page_revision = trans.app.model.PageRevision()
- page_revision.title = title
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
- # Persist
- session = trans.sa_session
- session.flush()
-
- return { "success" : "revision posted" }
-
- return { "error" : error_str }
+ def _verify_page_ownership( self, trans, page ):
+ if not self.security_check( trans, page, True, True ):
+ raise exceptions.ItemOwnershipException()
diff -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 lib/galaxy/webapps/galaxy/api/pages.py
--- a/lib/galaxy/webapps/galaxy/api/pages.py
+++ b/lib/galaxy/webapps/galaxy/api/pages.py
@@ -2,8 +2,9 @@
API for updating Galaxy Pages
"""
import logging
-from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import SharableItemSecurityMixin, BaseAPIController, SharableMixin
+from galaxy import exceptions
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.util.sanitize_html import sanitize_html
@@ -12,7 +13,7 @@
class PagesController( BaseAPIController, SharableItemSecurityMixin, UsesAnnotations, SharableMixin ):
- @web.expose_api
+ @expose_api
def index( self, trans, deleted=False, **kwd ):
"""
index( self, trans, deleted=False, **kwd )
@@ -47,7 +48,7 @@
return out
- @web.expose_api
+ @expose_api
def create( self, trans, payload, **kwd ):
"""
create( self, trans, payload, **kwd )
@@ -64,45 +65,41 @@
:returns: Dictionary return of the Page.to_dict call
"""
user = trans.get_user()
- error_str = ""
if not payload.get("title", None):
- error_str = "Page name is required"
+ raise exceptions.ObjectAttributeMissingException( "Page name is required" )
elif not payload.get("slug", None):
- error_str = "Page id is required"
+ raise exceptions.ObjectAttributeMissingException( "Page id is required" )
elif not self._is_valid_slug( payload["slug"] ):
- error_str = "Page identifier must consist of only lowercase letters, numbers, and the '-' character"
+ raise exceptions.ObjectAttributeInvalidException( "Page identifier must consist of only lowercase letters, numbers, and the '-' character" )
elif trans.sa_session.query( trans.app.model.Page ).filter_by( user=user, slug=payload["slug"], deleted=False ).first():
- error_str = "Page id must be unique"
- else:
+ raise exceptions.DuplicatedSlugException( "Page slug must be unique" )
- content = payload.get("content", "")
- content = sanitize_html( content, 'utf-8', 'text/html' )
+ content = payload.get("content", "")
+ content = sanitize_html( content, 'utf-8', 'text/html' )
- # Create the new stored page
- page = trans.app.model.Page()
- page.title = payload['title']
- page.slug = payload['slug']
- page_annotation = sanitize_html( payload.get( "annotation", "" ), 'utf-8', 'text/html' )
- self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
- page.user = user
- # And the first (empty) page revision
- page_revision = trans.app.model.PageRevision()
- page_revision.title = payload['title']
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
- # Persist
- session = trans.sa_session
- session.add( page )
- session.flush()
+ # Create the new stored page
+ page = trans.app.model.Page()
+ page.title = payload['title']
+ page.slug = payload['slug']
+ page_annotation = sanitize_html( payload.get( "annotation", "" ), 'utf-8', 'text/html' )
+ self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
+ page.user = user
+ # And the first (empty) page revision
+ page_revision = trans.app.model.PageRevision()
+ page_revision.title = payload['title']
+ page_revision.page = page
+ page.latest_revision = page_revision
+ page_revision.content = content
+ # Persist
+ session = trans.sa_session
+ session.add( page )
+ session.flush()
- rval = self.encode_all_ids( trans, page.to_dict(), True )
- return rval
+ rval = self.encode_all_ids( trans, page.to_dict(), True )
+ return rval
- return { "error" : error_str }
-
- @web.expose_api
+ @expose_api
def delete( self, trans, id, **kwd ):
"""
delete( self, trans, id, **kwd )
@@ -114,22 +111,14 @@
:rtype: dict
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
"""
- page_id = id
- try:
- page = trans.sa_session.query(self.app.model.Page).get(trans.security.decode_id(page_id))
- except Exception, e:
- return { "error" : "Page with ID='%s' can not be found\n Exception: %s" % (page_id, str( e )) }
+ page = self._get_page( trans, id )
- # check to see if user has permissions to selected workflow
- if page.user != trans.user and not trans.user_is_admin():
- return { "error" : "Workflow is not owned by or shared with current user" }
-
- #Mark a workflow as deleted
+ #Mark a page as deleted
page.deleted = True
trans.sa_session.flush()
- return { "success" : "Deleted", "id" : page_id }
+ return '' # TODO: Figure out what to return on DELETE, document in guidelines!
- @web.expose_api
+ @expose_api
def show( self, trans, id, **kwd ):
"""
show( self, trans, id, **kwd )
@@ -141,8 +130,22 @@
:rtype: dict
:returns: Dictionary return of the Page.to_dict call with the 'content' field populated by the most recent revision
"""
- page = trans.sa_session.query( trans.app.model.Page ).get( trans.security.decode_id( id ) )
+ page = self._get_page( trans, id )
self.security_check( trans, page, check_ownership=False, check_accessible=True)
rval = self.encode_all_ids( trans, page.to_dict(), True )
rval['content'] = page.latest_revision.content
return rval
+
+ def _get_page( self, trans, id ): # Fetches page object and verifies security.
+ try:
+ page = trans.sa_session.query( trans.app.model.Page ).get( trans.security.decode_id( id ) )
+ except Exception:
+ page = None
+
+ if not page:
+ raise exceptions.ObjectNotFound()
+
+ if page.user != trans.user and not trans.user_is_admin():
+ raise exceptions.ItemOwnershipException()
+
+ return page
diff -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 test/base/api.py
--- a/test/base/api.py
+++ b/test/base/api.py
@@ -60,6 +60,12 @@
for key in keys:
assert key in response, "Response [%s] does not contain key [%s]" % ( response, key )
+ def _assert_error_code_is( self, response, error_code ):
+ if hasattr( response, "json" ):
+ response = response.json()
+ self._assert_has_keys( response, "err_code" )
+ self.assertEquals( response[ "err_code" ], int( error_code ) )
+
def _random_key( self ): # Used for invalid request testing...
return "1234567890123456"
diff -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 test/functional/api/test_page_revisions.py
--- /dev/null
+++ b/test/functional/api/test_page_revisions.py
@@ -0,0 +1,35 @@
+from galaxy.exceptions import error_codes
+from functional.api.pages import BasePageApiTestCase
+
+
+class PageRevisionsApiTestCase( BasePageApiTestCase ):
+
+ def test_create( self ):
+ page_json = self._create_valid_page_with_slug( "pr1" )
+ revision_data = dict( content="<p>NewContent!</p>" )
+ page_revision_response = self._post( "pages/%s/revisions" % page_json[ 'id' ], data=revision_data )
+ self._assert_status_code_is( page_revision_response, 200 )
+ page_revision_json = page_revision_response.json()
+ self._assert_has_keys( page_revision_json, 'id', 'content' )
+
+ def test_403_if_create_revision_on_unowned_page( self ):
+ page_json = self._create_valid_page_as( "pr2(a)bx.psu.edu", "pr2" )
+ revision_data = dict( content="<p>NewContent!</p>" )
+ page_revision_response = self._post( "pages/%s/revisions" % page_json[ 'id' ], data=revision_data )
+ self._assert_status_code_is( page_revision_response, 403 )
+
+ def test_revision_index( self ):
+ page_json = self._create_valid_page_with_slug( "pr3" )
+ revision_data = dict( content="<p>NewContent!</p>" )
+ revisions_url = "pages/%s/revisions" % page_json[ 'id' ]
+ self._post( revisions_url, data=revision_data )
+ revisions_response = self._get( revisions_url )
+ self._assert_status_code_is( revisions_response, 200 )
+ revisions_json = revisions_response.json()
+ assert len( revisions_json ) == 2 # Original revision and new one
+
+ def test_404_if_index_unknown_page( self ):
+ revisions_url = "pages/%s/revisions" % self._random_key()
+ revisions_response = self._get( revisions_url )
+ self._assert_status_code_is( revisions_response, 404 )
+ self._assert_error_code_is( revisions_response, error_codes.USER_OBJECT_NOT_FOUND )
diff -r 39b2ae68b153ce701e821b0f4609c8ba8f0f5ad5 -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 test/functional/api/test_pages.py
--- /dev/null
+++ b/test/functional/api/test_pages.py
@@ -0,0 +1,105 @@
+from galaxy.exceptions import error_codes
+from base import api
+from base.interactor import delete_request
+
+from operator import itemgetter
+
+
+class BasePageApiTestCase( api.ApiTestCase ):
+
+ def _create_valid_page_with_slug( self, slug ):
+ page_request = self._test_page_payload( slug=slug )
+ page_response = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response, 200 )
+ return page_response.json()
+
+ def _create_valid_page_as( self, other_email, slug ):
+ run_as_user = self._setup_user( other_email )
+ page_request = self._test_page_payload( slug=slug )
+ page_request[ "run_as" ] = run_as_user[ "id" ]
+ page_response = self._post( "pages", page_request, admin=True )
+ self._assert_status_code_is( page_response, 200 )
+ return page_response.json()
+
+ def _test_page_payload( self, **kwds ):
+ request = dict(
+ slug="mypage",
+ title="MY PAGE",
+ content="<p>Page!</p>",
+ )
+ request.update( **kwds )
+ return request
+
+
+class PageApiTestCase( BasePageApiTestCase ):
+
+ def test_create( self ):
+ response_json = self._create_valid_page_with_slug( "mypage" )
+ self._assert_has_keys( response_json, "slug", "title", "id" )
+
+ def test_index( self ):
+ create_response_json = self._create_valid_page_with_slug( "indexpage" )
+ assert self._users_index_has_page_with_id( create_response_json[ "id" ] )
+
+ def test_index_doesnt_show_unavailable_pages( self ):
+ create_response_json = self._create_valid_page_as( "others_page_index(a)bx.psu.edu", "otherspageindex" )
+ assert not self._users_index_has_page_with_id( create_response_json[ "id" ] )
+
+ def test_cannot_create_pages_with_same_slug( self ):
+ page_request = self._test_page_payload( slug="mypage1" )
+ page_response_1 = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response_1, 200 )
+ page_response_2 = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response_2, 400 )
+ self._assert_error_code_is( page_response_2, error_codes.USER_SLUG_DUPLICATE )
+
+ def test_page_requires_name( self ):
+ page_request = self._test_page_payload()
+ del page_request[ 'title' ]
+ page_response = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response, 400 )
+ self._assert_error_code_is( page_response, error_codes.USER_OBJECT_ATTRIBUTE_MISSING )
+
+ def test_page_requires_slug( self ):
+ page_request = self._test_page_payload()
+ del page_request[ 'slug' ]
+ page_response = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response, 400 )
+
+ def test_delete( self ):
+ response_json = self._create_valid_page_with_slug( "testdelete" )
+ delete_response = delete_request( self._api_url( "pages/%s" % response_json[ 'id' ], use_key=True ) )
+ self._assert_status_code_is( delete_response, 200 )
+
+ def test_404_on_delete_unknown_page( self ):
+ delete_response = delete_request( self._api_url( "pages/%s" % self._random_key(), use_key=True ) )
+ self._assert_status_code_is( delete_response, 404 )
+ self._assert_error_code_is( delete_response, error_codes.USER_OBJECT_NOT_FOUND )
+
+ def test_403_on_delete_unowned_page( self ):
+ page_response = self._create_valid_page_as( "others_page(a)bx.psu.edu", "otherspage" )
+ delete_response = delete_request( self._api_url( "pages/%s" % page_response[ "id" ], use_key=True ) )
+ self._assert_status_code_is( delete_response, 403 )
+ self._assert_error_code_is( delete_response, error_codes.USER_DOES_NOT_OWN_ITEM )
+
+ def test_show( self ):
+ response_json = self._create_valid_page_with_slug( "pagetoshow" )
+ show_response = self._get( "pages/%s" % response_json['id'] )
+ self._assert_status_code_is( show_response, 200 )
+ show_json = show_response.json()
+ self._assert_has_keys( show_json, "slug", "title", "id" )
+ self.assertEquals( show_json["slug"], "pagetoshow" )
+ self.assertEquals( show_json["title"], "MY PAGE" )
+ self.assertEquals( show_json["content"], "<p>Page!</p>" )
+
+ def test_403_on_unowner_show( self ):
+ response_json = self._create_valid_page_as( "others_page_show(a)bx.psu.edu", "otherspageshow" )
+ show_response = self._get( "pages/%s" % response_json['id'] )
+ self._assert_status_code_is( show_response, 403 )
+ self._assert_error_code_is( show_response, error_codes.USER_DOES_NOT_OWN_ITEM )
+
+ def _users_index_has_page_with_id( self, id ):
+ index_response = self._get( "pages" )
+ self._assert_status_code_is( index_response, 200 )
+ pages = index_response.json()
+ return id in map( itemgetter( "id" ), pages )
https://bitbucket.org/galaxy/galaxy-central/commits/503836d4cea3/
Changeset: 503836d4cea3
User: jmchilton
Date: 2014-01-10 22:10:52
Summary: Parse error codes/messages from new JSON file.
Same python interface to this data, but the data can now be reused by clients in other languages (e.g. potentially automating the creation of typed Java exceptions).
Affected #: 2 files
diff -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 -r 503836d4cea399f366a02f7e48b75095a8bb876f lib/galaxy/exceptions/error_codes.json
--- /dev/null
+++ b/lib/galaxy/exceptions/error_codes.json
@@ -0,0 +1,57 @@
+[
+ {
+ "name": "UNKNOWN",
+ "code": 0,
+ "message": "Unknown error occurred while processing request."
+ },
+ {
+ "name": "USER_CANNOT_RUN_AS",
+ "code": 400001,
+ "message": "User does not have permissions to run jobs as another user."
+ },
+ {
+ "name": "USER_INVALID_RUN_AS",
+ "code": 400002,
+ "message": "Invalid run_as request - run_as user does not exist."
+ },
+ {
+ "name": "USER_INVALID_JSON",
+ "code": 400003,
+ "message": "Your request did not appear to be valid JSON, please consult the API documentation."
+ },
+ {
+ "name": "USER_OBJECT_ATTRIBUTE_INVALID",
+ "code": 400004,
+ "message": "Attempted to create or update object with invalid attribute value."
+ },
+ {
+ "name": "USER_OBJECT_ATTRIBUTE_MISSING",
+ "code": 400005,
+ "message": "Attempted to create object without required attribute."
+ },
+ {
+ "name": "USER_SLUG_DUPLICATE",
+ "code": 400006,
+ "message": "Slug must be unique per user."
+ },
+ {
+ "name": "USER_NO_API_KEY",
+ "code": 403001,
+ "message": "API Authentication Required for this request"
+ },
+ {
+ "name": "USER_CANNOT_ACCESS_ITEM",
+ "code": 403002,
+ "message": "User cannot access specified item."
+ },
+ {
+ "name": "USER_DOES_NOT_OWN_ITEM",
+ "code": 403003,
+ "message": "User does not own specified item."
+ },
+ {
+ "name": "USER_OBJECT_NOT_FOUND",
+ "code": 404001,
+ "message": "No such object not found."
+ }
+]
diff -r b41d51baaae16c4d6e7c4fb20a28cf9aeb5f1f43 -r 503836d4cea399f366a02f7e48b75095a8bb876f lib/galaxy/exceptions/error_codes.py
--- a/lib/galaxy/exceptions/error_codes.py
+++ b/lib/galaxy/exceptions/error_codes.py
@@ -1,3 +1,6 @@
+from pkg_resources import resource_string
+from json import loads
+
# Error codes are provided as a convience to Galaxy API clients, but at this
# time they do represent part of the more stable interface. They can change
# without warning between releases.
@@ -16,18 +19,14 @@
def __int__( self ):
return int( self.code )
-# TODO: Guidelines for error message langauge?
-UNKNOWN = ErrorCode(0, UNKNOWN_ERROR_MESSAGE)
+ @staticmethod
+ def from_dict( entry ):
+ name = entry.get("name")
+ code = entry.get("code")
+ message = entry.get("message")
+ return ( name, ErrorCode( code, message ) )
-USER_CANNOT_RUN_AS = ErrorCode(400001, "User does not have permissions to run jobs as another user.")
-USER_INVALID_RUN_AS = ErrorCode(400002, "Invalid run_as request - run_as user does not exist.")
-USER_INVALID_JSON = ErrorCode(400003, "Your request did not appear to be valid JSON, please consult the API documentation.")
-USER_OBJECT_ATTRIBUTE_INVALID = ErrorCode(400004, "Attempted to create or update object with invalid attribute value.")
-USER_OBJECT_ATTRIBUTE_MISSING = ErrorCode(400005, "Attempted to create object without required attribute.")
-USER_SLUG_DUPLICATE = ErrorCode(400006, "Slug must be unique per user.")
-
-USER_NO_API_KEY = ErrorCode(403001, "API Authentication Required for this request")
-USER_CANNOT_ACCESS_ITEM = ErrorCode(403002, "User cannot access specified item.")
-USER_DOES_NOT_OWN_ITEM = ErrorCode(403003, "User does not own specified item.")
-
-USER_OBJECT_NOT_FOUND = ErrorCode(404001, "No such object not found.")
+error_codes_json = resource_string( __name__, 'error_codes.json' )
+for entry in loads( error_codes_json ):
+ name, error_code_obj = ErrorCode.from_dict( entry )
+ globals()[ name ] = error_code_obj
https://bitbucket.org/galaxy/galaxy-central/commits/fa698b544051/
Changeset: fa698b544051
User: dannon
Date: 2014-01-15 14:30:38
Summary: Merged in jmchilton/galaxy-central-fork-1 (pull request #294)
New API Decorator
Affected #: 11 files
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -6,33 +6,66 @@
eggs.require( "Paste" )
from paste import httpexceptions
+from ..exceptions import error_codes
+
class MessageException( Exception ):
"""
- Exception to make throwing errors from deep in controllers easier
+ Exception to make throwing errors from deep in controllers easier.
"""
- def __init__( self, err_msg, type="info" ):
- self.err_msg = err_msg
+ # status code to be set when used with API.
+ status_code = 400
+ # Error code information embedded into API json responses.
+ err_code = error_codes.UNKNOWN
+
+ def __init__( self, err_msg=None, type="info", **extra_error_info ):
+ self.err_msg = err_msg or self.err_code.default_error_message
self.type = type
+ self.extra_error_info = extra_error_info
+
def __str__( self ):
return self.err_msg
+
class ItemDeletionException( MessageException ):
pass
+
class ItemAccessibilityException( MessageException ):
- pass
+ status_code = 403
+ err_code = error_codes.USER_CANNOT_ACCESS_ITEM
+
class ItemOwnershipException( MessageException ):
- pass
+ status_code = 403
+ err_code = error_codes.USER_DOES_NOT_OWN_ITEM
+
+
+class DuplicatedSlugException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_SLUG_DUPLICATE
+
+
+class ObjectAttributeInvalidException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_OBJECT_ATTRIBUTE_INVALID
+
+
+class ObjectAttributeMissingException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_OBJECT_ATTRIBUTE_MISSING
+
class ActionInputError( MessageException ):
def __init__( self, err_msg, type="error" ):
super( ActionInputError, self ).__init__( err_msg, type )
-class ObjectNotFound( Exception ):
+
+class ObjectNotFound( MessageException ):
""" Accessed object was not found """
- pass
+ status_code = 404
+ err_code = error_codes.USER_OBJECT_NOT_FOUND
+
class ObjectInvalid( Exception ):
""" Accessed object store ID is invalid """
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/exceptions/error_codes.json
--- /dev/null
+++ b/lib/galaxy/exceptions/error_codes.json
@@ -0,0 +1,57 @@
+[
+ {
+ "name": "UNKNOWN",
+ "code": 0,
+ "message": "Unknown error occurred while processing request."
+ },
+ {
+ "name": "USER_CANNOT_RUN_AS",
+ "code": 400001,
+ "message": "User does not have permissions to run jobs as another user."
+ },
+ {
+ "name": "USER_INVALID_RUN_AS",
+ "code": 400002,
+ "message": "Invalid run_as request - run_as user does not exist."
+ },
+ {
+ "name": "USER_INVALID_JSON",
+ "code": 400003,
+ "message": "Your request did not appear to be valid JSON, please consult the API documentation."
+ },
+ {
+ "name": "USER_OBJECT_ATTRIBUTE_INVALID",
+ "code": 400004,
+ "message": "Attempted to create or update object with invalid attribute value."
+ },
+ {
+ "name": "USER_OBJECT_ATTRIBUTE_MISSING",
+ "code": 400005,
+ "message": "Attempted to create object without required attribute."
+ },
+ {
+ "name": "USER_SLUG_DUPLICATE",
+ "code": 400006,
+ "message": "Slug must be unique per user."
+ },
+ {
+ "name": "USER_NO_API_KEY",
+ "code": 403001,
+ "message": "API Authentication Required for this request"
+ },
+ {
+ "name": "USER_CANNOT_ACCESS_ITEM",
+ "code": 403002,
+ "message": "User cannot access specified item."
+ },
+ {
+ "name": "USER_DOES_NOT_OWN_ITEM",
+ "code": 403003,
+ "message": "User does not own specified item."
+ },
+ {
+ "name": "USER_OBJECT_NOT_FOUND",
+ "code": 404001,
+ "message": "No such object not found."
+ }
+]
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/exceptions/error_codes.py
--- /dev/null
+++ b/lib/galaxy/exceptions/error_codes.py
@@ -0,0 +1,32 @@
+from pkg_resources import resource_string
+from json import loads
+
+# Error codes are provided as a convience to Galaxy API clients, but at this
+# time they do represent part of the more stable interface. They can change
+# without warning between releases.
+UNKNOWN_ERROR_MESSAGE = "Unknown error occurred while processing request."
+
+
+class ErrorCode( object ):
+
+ def __init__( self, code, default_error_message ):
+ self.code = code
+ self.default_error_message = default_error_message or UNKNOWN_ERROR_MESSAGE
+
+ def __str__( self ):
+ return str( self.default_error_message )
+
+ def __int__( self ):
+ return int( self.code )
+
+ @staticmethod
+ def from_dict( entry ):
+ name = entry.get("name")
+ code = entry.get("code")
+ message = entry.get("message")
+ return ( name, ErrorCode( code, message ) )
+
+error_codes_json = resource_string( __name__, 'error_codes.json' )
+for entry in loads( error_codes_json ):
+ name, error_code_obj = ErrorCode.from_dict( entry )
+ globals()[ name ] = error_code_obj
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -15,3 +15,7 @@
from framework import expose_api_raw
from framework import expose_api_raw_anonymous
from framework.base import httpexceptions
+
+# TODO: Drop and make these the default.
+from framework import _future_expose_api
+from framework import _future_expose_api_anonymous
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -10,9 +10,9 @@
import socket
import string
import time
-
+from traceback import format_exc
+from Cookie import CookieError
from functools import wraps
-from Cookie import CookieError
pkg_resources.require( "Cheetah" )
from Cheetah.Template import Template
@@ -23,6 +23,7 @@
from galaxy import util
from galaxy.exceptions import MessageException
+from galaxy.exceptions import error_codes
from galaxy.util import asbool
from galaxy.util import safe_str_cmp
from galaxy.util.backports.importlib import import_module
@@ -103,6 +104,35 @@
return decorator
return argcatcher
+
+def __extract_payload_from_request(trans, func, kwargs):
+ content_type = trans.request.headers['content-type']
+ if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
+ # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
+ # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
+ # are not a part of the request body. This is a problem because it's not possible to differentiate between values
+ # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
+ # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
+ # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
+ payload = kwargs.copy()
+ named_args, _, _, _ = inspect.getargspec(func)
+ for arg in named_args:
+ payload.pop(arg, None)
+ for k, v in payload.iteritems():
+ if isinstance(v, (str, unicode)):
+ try:
+ payload[k] = from_json_string(v)
+ except:
+ # may not actually be json, just continue
+ pass
+ payload = util.recursively_stringify_dictionary_keys( payload )
+ else:
+ # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
+ # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
+ # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
+ payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
+ return payload
+
def expose_api_raw( func ):
"""
Expose this function via the API but don't dump the results
@@ -140,35 +170,8 @@
error_message = "API Authentication Required for this request"
return error
if trans.request.body:
- def extract_payload_from_request(trans, func, kwargs):
- content_type = trans.request.headers['content-type']
- if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'):
- # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
- # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
- # are not a part of the request body. This is a problem because it's not possible to differentiate between values
- # which are a part of the request body, and therefore should be a part of the payload, and values which should not be
- # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from
- # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods.
- payload = kwargs.copy()
- named_args, _, _, _ = inspect.getargspec(func)
- for arg in named_args:
- payload.pop(arg, None)
- for k, v in payload.iteritems():
- if isinstance(v, (str, unicode)):
- try:
- payload[k] = from_json_string(v)
- except:
- # may not actually be json, just continue
- pass
- payload = util.recursively_stringify_dictionary_keys( payload )
- else:
- # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check
- # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding
- # such as multipart/form-data. Leaving it as is for backward compatibility, just in case.
- payload = util.recursively_stringify_dictionary_keys( from_json_string( trans.request.body ) )
- return payload
try:
- kwargs['payload'] = extract_payload_from_request(trans, func, kwargs)
+ kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
except ValueError:
error_status = '400 Bad Request'
error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
@@ -210,6 +213,143 @@
decorator.exposed = True
return decorator
+API_RESPONSE_CONTENT_TYPE = "application/json"
+
+
+def __api_error_message( trans, **kwds ):
+ exception = kwds.get( "exception", None )
+ if exception:
+ # If we are passed a MessageException use err_msg.
+ default_error_code = getattr( exception, "err_code", error_codes.UNKNOWN )
+ default_error_message = getattr( exception, "err_msg", default_error_code.default_error_message )
+ extra_error_info = getattr( exception, 'extra_error_info', {} )
+ if not isinstance( extra_error_info, dict ):
+ extra_error_info = {}
+ else:
+ default_error_message = "Error processing API request."
+ default_error_code = error_codes.UNKNOWN
+ extra_error_info = {}
+ traceback_string = kwds.get( "traceback", "No traceback available." )
+ err_msg = kwds.get( "err_msg", default_error_message )
+ error_code_object = kwds.get( "err_code", default_error_code )
+ try:
+ error_code = error_code_object.code
+ except AttributeError:
+ # Some sort of bad error code sent in, logic failure on part of
+ # Galaxy developer.
+ error_code = error_codes.UNKNOWN.code
+ # Would prefer the terminology of error_code and error_message, but
+ # err_msg used a good number of places already. Might as well not change
+ # it?
+ error_response = dict( err_msg=err_msg, err_code=error_code, **extra_error_info )
+ if trans.debug: # TODO: Should admins get to see traceback as well?
+ error_response[ "traceback" ] = traceback_string
+ return error_response
+
+
+def __api_error_response( trans, **kwds ):
+ error_dict = __api_error_message( trans, **kwds )
+ exception = kwds.get( "exception", None )
+ # If we are given an status code directly - use it - otherwise check
+ # the exception for a status_code attribute.
+ if "status_code" in kwds:
+ status_code = int( kwds.get( "status_code" ) )
+ elif hasattr( exception, "status_code" ):
+ status_code = int( exception.status_code )
+ else:
+ status_code = 500
+ response = trans.response
+ if not response.status or str(response.status).startswith("20"):
+ # Unset status code appears to be string '200 OK', if anything
+ # non-success (i.e. not 200 or 201) has been set, do not override
+ # underlying controller.
+ response.status = status_code
+ return to_json_string( error_dict )
+
+
+# TODO: rename as expose_api and make default.
+def _future_expose_api_anonymous( func, to_json=True ):
+ """
+ Expose this function via the API but don't require a set user.
+ """
+ return _future_expose_api( func, to_json=to_json, user_required=False )
+
+
+# TODO: rename as expose_api and make default.
+def _future_expose_api( func, to_json=True, user_required=True ):
+ """
+ Expose this function via the API.
+ """
+ @wraps(func)
+ def decorator( self, trans, *args, **kwargs ):
+ if trans.error_message:
+ # TODO: Document this branch, when can this happen,
+ # I don't understand it.
+ return __api_error_response( trans, err_msg=trans.error_message )
+ if user_required and trans.anonymous:
+ error_code = error_codes.USER_NO_API_KEY
+ # Use error codes default error message.
+ return __api_error_response( trans, err_code=error_code, status_code=403 )
+ if trans.request.body:
+ try:
+ kwargs['payload'] = __extract_payload_from_request(trans, func, kwargs)
+ except ValueError:
+ error_code = error_codes.USER_INVALID_JSON
+ return __api_error_response( trans, status_code=400, err_code=error_code )
+
+ trans.response.set_content_type( API_RESPONSE_CONTENT_TYPE )
+ # send 'do not cache' headers to handle IE's caching of ajax get responses
+ trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
+ # TODO: Refactor next block out into a helper procedure.
+ # Perform api_run_as processing, possibly changing identity
+ if 'payload' in kwargs and 'run_as' in kwargs['payload']:
+ if not trans.user_can_do_run_as():
+ error_code = error_codes.USER_CANNOT_RUN_AS
+ return __api_error_response( trans, err_code=error_code, status_code=403 )
+ try:
+ decoded_user_id = trans.security.decode_id( kwargs['payload']['run_as'] )
+ except TypeError:
+ error_message = "Malformed user id ( %s ) specified, unable to decode." % str( kwargs['payload']['run_as'] )
+ error_code = error_codes.USER_INVALID_RUN_AS
+ return __api_error_response( trans, err_code=error_code, err_msg=error_message, status_code=400)
+ try:
+ user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id )
+ trans.api_inherit_admin = trans.user_is_admin()
+ trans.set_user(user)
+ except:
+ error_code = error_codes.USER_INVALID_RUN_AS
+ return __api_error_response( trans, err_code=error_code, status_code=400 )
+ try:
+ rval = func( self, trans, *args, **kwargs)
+ if to_json and trans.debug:
+ rval = to_json_string( rval, indent=4, sort_keys=True )
+ elif to_json:
+ rval = to_json_string( rval )
+ return rval
+ except MessageException as e:
+ traceback_string = format_exc()
+ return __api_error_response( trans, exception=e, traceback=traceback_string )
+ except paste.httpexceptions.HTTPException:
+ # TODO: Allow to pass or format for the API???
+ raise # handled
+ except Exception as e:
+ traceback_string = format_exc()
+ error_message = 'Uncaught exception in exposed API method:'
+ log.exception( error_message )
+ return __api_error_response(
+ trans,
+ status_code=500,
+ exception=e,
+ traceback=traceback_string,
+ err_msg=error_message,
+ err_code=error_codes.UNKNOWN
+ )
+ if not hasattr(func, '_orig'):
+ decorator._orig = func
+ decorator.exposed = True
+ return decorator
+
+
def require_admin( func ):
@wraps(func)
def decorator( self, trans, *args, **kwargs ):
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -9,6 +9,8 @@
from paste.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPInternalServerError, HTTPException
from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
from galaxy.util import string_as_bool, restore_text
from galaxy.util.sanitize_html import sanitize_html
from galaxy.web.base.controller import BaseAPIController, UsesHistoryMixin, UsesTagsMixin
@@ -20,7 +22,7 @@
class HistoriesController( BaseAPIController, UsesHistoryMixin, UsesTagsMixin ):
- @web.expose_api_anonymous
+ @expose_api_anonymous
def index( self, trans, deleted='False', **kwd ):
"""
index( trans, deleted='False' )
@@ -152,7 +154,7 @@
return history_data
- @web.expose_api
+ @expose_api
def create( self, trans, payload, **kwd ):
"""
create( trans, payload )
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/webapps/galaxy/api/page_revisions.py
--- a/lib/galaxy/webapps/galaxy/api/page_revisions.py
+++ b/lib/galaxy/webapps/galaxy/api/page_revisions.py
@@ -2,8 +2,9 @@
API for updating Galaxy Pages
"""
import logging
-from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import SharableItemSecurityMixin, BaseAPIController, SharableMixin
+from galaxy import exceptions
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.util.sanitize_html import sanitize_html
@@ -12,7 +13,7 @@
class PageRevisionsController( BaseAPIController, SharableItemSecurityMixin, UsesAnnotations, SharableMixin ):
- @web.expose_api
+ @expose_api
def index( self, trans, page_id, **kwd ):
"""
index( self, trans, page_id, **kwd )
@@ -24,14 +25,16 @@
:rtype: list
:returns: dictionaries containing different revisions of the page
"""
+ page = self._get_page( trans, page_id )
+ self._verify_page_ownership( trans, page )
+
r = trans.sa_session.query( trans.app.model.PageRevision ).filter_by( page_id=trans.security.decode_id(page_id) )
out = []
for page in r:
- if self.security_check( trans, page, True, True ):
- out.append( self.encode_all_ids( trans, page.to_dict(), True) )
+ out.append( self.encode_all_ids( trans, page.to_dict(), True) )
return out
- @web.expose_api
+ @expose_api
def create( self, trans, page_id, payload, **kwd ):
"""
create( self, trans, page_id, payload **kwd )
@@ -46,39 +49,42 @@
:rtype: dictionary
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
"""
- error_str = ""
+ content = payload.get("content", None)
+ if not content:
+ raise exceptions.ObjectAttributeMissingException("content undefined or empty")
- if not page_id:
- error_str = "page_id is required"
- elif not payload.get("content", None):
- error_str = "content is required"
+ page = self._get_page( trans, page_id )
+ self._verify_page_ownership( trans, page )
+
+ if 'title' in payload:
+ title = payload['title']
else:
+ title = page.title
- # Create the new stored page
+ content = sanitize_html( content, 'utf-8', 'text/html' )
+
+ page_revision = trans.app.model.PageRevision()
+ page_revision.title = title
+ page_revision.page = page
+ page.latest_revision = page_revision
+ page_revision.content = content
+
+ # Persist
+ session = trans.sa_session
+ session.flush()
+
+ return page_revision.to_dict( view="element" )
+
+ def _get_page( self, trans, page_id ):
+ page = None
+ try:
page = trans.sa_session.query( trans.app.model.Page ).get( trans.security.decode_id(page_id) )
- if page is None:
- return { "error" : "page not found"}
+ except Exception:
+ pass
+ if not page:
+ raise exceptions.ObjectNotFound()
+ return page
- if not self.security_check( trans, page, True, True ):
- return { "error" : "page not found"}
-
- if 'title' in payload:
- title = payload['title']
- else:
- title = page.title
-
- content = payload.get("content", "")
- content = sanitize_html( content, 'utf-8', 'text/html' )
-
- page_revision = trans.app.model.PageRevision()
- page_revision.title = title
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
- # Persist
- session = trans.sa_session
- session.flush()
-
- return { "success" : "revision posted" }
-
- return { "error" : error_str }
+ def _verify_page_ownership( self, trans, page ):
+ if not self.security_check( trans, page, True, True ):
+ raise exceptions.ItemOwnershipException()
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 lib/galaxy/webapps/galaxy/api/pages.py
--- a/lib/galaxy/webapps/galaxy/api/pages.py
+++ b/lib/galaxy/webapps/galaxy/api/pages.py
@@ -2,8 +2,9 @@
API for updating Galaxy Pages
"""
import logging
-from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import SharableItemSecurityMixin, BaseAPIController, SharableMixin
+from galaxy import exceptions
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.util.sanitize_html import sanitize_html
@@ -12,7 +13,7 @@
class PagesController( BaseAPIController, SharableItemSecurityMixin, UsesAnnotations, SharableMixin ):
- @web.expose_api
+ @expose_api
def index( self, trans, deleted=False, **kwd ):
"""
index( self, trans, deleted=False, **kwd )
@@ -47,7 +48,7 @@
return out
- @web.expose_api
+ @expose_api
def create( self, trans, payload, **kwd ):
"""
create( self, trans, payload, **kwd )
@@ -64,45 +65,41 @@
:returns: Dictionary return of the Page.to_dict call
"""
user = trans.get_user()
- error_str = ""
if not payload.get("title", None):
- error_str = "Page name is required"
+ raise exceptions.ObjectAttributeMissingException( "Page name is required" )
elif not payload.get("slug", None):
- error_str = "Page id is required"
+ raise exceptions.ObjectAttributeMissingException( "Page id is required" )
elif not self._is_valid_slug( payload["slug"] ):
- error_str = "Page identifier must consist of only lowercase letters, numbers, and the '-' character"
+ raise exceptions.ObjectAttributeInvalidException( "Page identifier must consist of only lowercase letters, numbers, and the '-' character" )
elif trans.sa_session.query( trans.app.model.Page ).filter_by( user=user, slug=payload["slug"], deleted=False ).first():
- error_str = "Page id must be unique"
- else:
+ raise exceptions.DuplicatedSlugException( "Page slug must be unique" )
- content = payload.get("content", "")
- content = sanitize_html( content, 'utf-8', 'text/html' )
+ content = payload.get("content", "")
+ content = sanitize_html( content, 'utf-8', 'text/html' )
- # Create the new stored page
- page = trans.app.model.Page()
- page.title = payload['title']
- page.slug = payload['slug']
- page_annotation = sanitize_html( payload.get( "annotation", "" ), 'utf-8', 'text/html' )
- self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
- page.user = user
- # And the first (empty) page revision
- page_revision = trans.app.model.PageRevision()
- page_revision.title = payload['title']
- page_revision.page = page
- page.latest_revision = page_revision
- page_revision.content = content
- # Persist
- session = trans.sa_session
- session.add( page )
- session.flush()
+ # Create the new stored page
+ page = trans.app.model.Page()
+ page.title = payload['title']
+ page.slug = payload['slug']
+ page_annotation = sanitize_html( payload.get( "annotation", "" ), 'utf-8', 'text/html' )
+ self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
+ page.user = user
+ # And the first (empty) page revision
+ page_revision = trans.app.model.PageRevision()
+ page_revision.title = payload['title']
+ page_revision.page = page
+ page.latest_revision = page_revision
+ page_revision.content = content
+ # Persist
+ session = trans.sa_session
+ session.add( page )
+ session.flush()
- rval = self.encode_all_ids( trans, page.to_dict(), True )
- return rval
+ rval = self.encode_all_ids( trans, page.to_dict(), True )
+ return rval
- return { "error" : error_str }
-
- @web.expose_api
+ @expose_api
def delete( self, trans, id, **kwd ):
"""
delete( self, trans, id, **kwd )
@@ -114,22 +111,14 @@
:rtype: dict
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
"""
- page_id = id
- try:
- page = trans.sa_session.query(self.app.model.Page).get(trans.security.decode_id(page_id))
- except Exception, e:
- return { "error" : "Page with ID='%s' can not be found\n Exception: %s" % (page_id, str( e )) }
+ page = self._get_page( trans, id )
- # check to see if user has permissions to selected workflow
- if page.user != trans.user and not trans.user_is_admin():
- return { "error" : "Workflow is not owned by or shared with current user" }
-
- #Mark a workflow as deleted
+ #Mark a page as deleted
page.deleted = True
trans.sa_session.flush()
- return { "success" : "Deleted", "id" : page_id }
+ return '' # TODO: Figure out what to return on DELETE, document in guidelines!
- @web.expose_api
+ @expose_api
def show( self, trans, id, **kwd ):
"""
show( self, trans, id, **kwd )
@@ -141,8 +130,22 @@
:rtype: dict
:returns: Dictionary return of the Page.to_dict call with the 'content' field populated by the most recent revision
"""
- page = trans.sa_session.query( trans.app.model.Page ).get( trans.security.decode_id( id ) )
+ page = self._get_page( trans, id )
self.security_check( trans, page, check_ownership=False, check_accessible=True)
rval = self.encode_all_ids( trans, page.to_dict(), True )
rval['content'] = page.latest_revision.content
return rval
+
+ def _get_page( self, trans, id ): # Fetches page object and verifies security.
+ try:
+ page = trans.sa_session.query( trans.app.model.Page ).get( trans.security.decode_id( id ) )
+ except Exception:
+ page = None
+
+ if not page:
+ raise exceptions.ObjectNotFound()
+
+ if page.user != trans.user and not trans.user_is_admin():
+ raise exceptions.ItemOwnershipException()
+
+ return page
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 test/base/api.py
--- a/test/base/api.py
+++ b/test/base/api.py
@@ -60,6 +60,12 @@
for key in keys:
assert key in response, "Response [%s] does not contain key [%s]" % ( response, key )
+ def _assert_error_code_is( self, response, error_code ):
+ if hasattr( response, "json" ):
+ response = response.json()
+ self._assert_has_keys( response, "err_code" )
+ self.assertEquals( response[ "err_code" ], int( error_code ) )
+
def _random_key( self ): # Used for invalid request testing...
return "1234567890123456"
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 test/functional/api/test_page_revisions.py
--- /dev/null
+++ b/test/functional/api/test_page_revisions.py
@@ -0,0 +1,35 @@
+from galaxy.exceptions import error_codes
+from functional.api.pages import BasePageApiTestCase
+
+
+class PageRevisionsApiTestCase( BasePageApiTestCase ):
+
+ def test_create( self ):
+ page_json = self._create_valid_page_with_slug( "pr1" )
+ revision_data = dict( content="<p>NewContent!</p>" )
+ page_revision_response = self._post( "pages/%s/revisions" % page_json[ 'id' ], data=revision_data )
+ self._assert_status_code_is( page_revision_response, 200 )
+ page_revision_json = page_revision_response.json()
+ self._assert_has_keys( page_revision_json, 'id', 'content' )
+
+ def test_403_if_create_revision_on_unowned_page( self ):
+ page_json = self._create_valid_page_as( "pr2(a)bx.psu.edu", "pr2" )
+ revision_data = dict( content="<p>NewContent!</p>" )
+ page_revision_response = self._post( "pages/%s/revisions" % page_json[ 'id' ], data=revision_data )
+ self._assert_status_code_is( page_revision_response, 403 )
+
+ def test_revision_index( self ):
+ page_json = self._create_valid_page_with_slug( "pr3" )
+ revision_data = dict( content="<p>NewContent!</p>" )
+ revisions_url = "pages/%s/revisions" % page_json[ 'id' ]
+ self._post( revisions_url, data=revision_data )
+ revisions_response = self._get( revisions_url )
+ self._assert_status_code_is( revisions_response, 200 )
+ revisions_json = revisions_response.json()
+ assert len( revisions_json ) == 2 # Original revision and new one
+
+ def test_404_if_index_unknown_page( self ):
+ revisions_url = "pages/%s/revisions" % self._random_key()
+ revisions_response = self._get( revisions_url )
+ self._assert_status_code_is( revisions_response, 404 )
+ self._assert_error_code_is( revisions_response, error_codes.USER_OBJECT_NOT_FOUND )
diff -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 -r fa698b544051c61a6b895f04840309a7807ce5a7 test/functional/api/test_pages.py
--- /dev/null
+++ b/test/functional/api/test_pages.py
@@ -0,0 +1,105 @@
+from galaxy.exceptions import error_codes
+from base import api
+from base.interactor import delete_request
+
+from operator import itemgetter
+
+
+class BasePageApiTestCase( api.ApiTestCase ):
+
+ def _create_valid_page_with_slug( self, slug ):
+ page_request = self._test_page_payload( slug=slug )
+ page_response = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response, 200 )
+ return page_response.json()
+
+ def _create_valid_page_as( self, other_email, slug ):
+ run_as_user = self._setup_user( other_email )
+ page_request = self._test_page_payload( slug=slug )
+ page_request[ "run_as" ] = run_as_user[ "id" ]
+ page_response = self._post( "pages", page_request, admin=True )
+ self._assert_status_code_is( page_response, 200 )
+ return page_response.json()
+
+ def _test_page_payload( self, **kwds ):
+ request = dict(
+ slug="mypage",
+ title="MY PAGE",
+ content="<p>Page!</p>",
+ )
+ request.update( **kwds )
+ return request
+
+
+class PageApiTestCase( BasePageApiTestCase ):
+
+ def test_create( self ):
+ response_json = self._create_valid_page_with_slug( "mypage" )
+ self._assert_has_keys( response_json, "slug", "title", "id" )
+
+ def test_index( self ):
+ create_response_json = self._create_valid_page_with_slug( "indexpage" )
+ assert self._users_index_has_page_with_id( create_response_json[ "id" ] )
+
+ def test_index_doesnt_show_unavailable_pages( self ):
+ create_response_json = self._create_valid_page_as( "others_page_index(a)bx.psu.edu", "otherspageindex" )
+ assert not self._users_index_has_page_with_id( create_response_json[ "id" ] )
+
+ def test_cannot_create_pages_with_same_slug( self ):
+ page_request = self._test_page_payload( slug="mypage1" )
+ page_response_1 = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response_1, 200 )
+ page_response_2 = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response_2, 400 )
+ self._assert_error_code_is( page_response_2, error_codes.USER_SLUG_DUPLICATE )
+
+ def test_page_requires_name( self ):
+ page_request = self._test_page_payload()
+ del page_request[ 'title' ]
+ page_response = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response, 400 )
+ self._assert_error_code_is( page_response, error_codes.USER_OBJECT_ATTRIBUTE_MISSING )
+
+ def test_page_requires_slug( self ):
+ page_request = self._test_page_payload()
+ del page_request[ 'slug' ]
+ page_response = self._post( "pages", page_request )
+ self._assert_status_code_is( page_response, 400 )
+
+ def test_delete( self ):
+ response_json = self._create_valid_page_with_slug( "testdelete" )
+ delete_response = delete_request( self._api_url( "pages/%s" % response_json[ 'id' ], use_key=True ) )
+ self._assert_status_code_is( delete_response, 200 )
+
+ def test_404_on_delete_unknown_page( self ):
+ delete_response = delete_request( self._api_url( "pages/%s" % self._random_key(), use_key=True ) )
+ self._assert_status_code_is( delete_response, 404 )
+ self._assert_error_code_is( delete_response, error_codes.USER_OBJECT_NOT_FOUND )
+
+ def test_403_on_delete_unowned_page( self ):
+ page_response = self._create_valid_page_as( "others_page(a)bx.psu.edu", "otherspage" )
+ delete_response = delete_request( self._api_url( "pages/%s" % page_response[ "id" ], use_key=True ) )
+ self._assert_status_code_is( delete_response, 403 )
+ self._assert_error_code_is( delete_response, error_codes.USER_DOES_NOT_OWN_ITEM )
+
+ def test_show( self ):
+ response_json = self._create_valid_page_with_slug( "pagetoshow" )
+ show_response = self._get( "pages/%s" % response_json['id'] )
+ self._assert_status_code_is( show_response, 200 )
+ show_json = show_response.json()
+ self._assert_has_keys( show_json, "slug", "title", "id" )
+ self.assertEquals( show_json["slug"], "pagetoshow" )
+ self.assertEquals( show_json["title"], "MY PAGE" )
+ self.assertEquals( show_json["content"], "<p>Page!</p>" )
+
+ def test_403_on_unowner_show( self ):
+ response_json = self._create_valid_page_as( "others_page_show(a)bx.psu.edu", "otherspageshow" )
+ show_response = self._get( "pages/%s" % response_json['id'] )
+ self._assert_status_code_is( show_response, 403 )
+ self._assert_error_code_is( show_response, error_codes.USER_DOES_NOT_OWN_ITEM )
+
+ def _users_index_has_page_with_id( self, id ):
+ index_response = self._get( "pages" )
+ self._assert_status_code_is( index_response, 200 )
+ pages = index_response.json()
+ return id in map( itemgetter( "id" ), 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: greg: Fix messaging broken in my last commit - thanks to Nicola Soranzo!
by commits-noreply@bitbucket.org 15 Jan '14
by commits-noreply@bitbucket.org 15 Jan '14
15 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/89dc1fd43e7b/
Changeset: 89dc1fd43e7b
User: greg
Date: 2014-01-15 02:05:28
Summary: Fix messaging broken in my last commit - thanks to Nicola Soranzo!
Affected #: 1 file
diff -r 55a2d1cced87aa2401a99ff3bc33d759f9b6e82c -r 89dc1fd43e7ba1156580e59bb310d09d95ccdb94 lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -2002,8 +2002,8 @@
','.join( util.listify( changeset_revisions ) ) ) )
return trans.response.send_redirect( url )
else:
- message = 'Repository installation is not possible due to an invalid Galaxy URL: <b>%s</b>. '
- message += 'You may need to enable cookies in your browser. ' % galaxy_url
+ message = 'Repository installation is not possible due to an invalid Galaxy URL: <b>%s</b>. ' % galaxy_url
+ message += 'You may need to enable cookies in your browser. '
status = 'error'
return trans.response.send_redirect( web.url_for( controller='repository',
action='browse_valid_categories',
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/77e5cbd3bcfe/
Changeset: 77e5cbd3bcfe
User: jmchilton
Date: 2014-01-14 23:02:11
Summary: Fix R env handling in fabric_util broken with 864f8f4.
Thanks BjoernGruening.
Affected #: 1 file
diff -r 027eb5827babc5a6b07b6c3e098bacd09185adf4 -r 77e5cbd3bcfebf50ce463c0b320a51e288f00a1e lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -409,7 +409,7 @@
with lcd( current_dir ):
with settings( warn_only=True ):
for tarball_name in tarball_names:
- cmd = '''PATH=$PATH:$R_HOME/bin; export PATH; R_LIBS=$INSTALL_DIR; export R_LIBS; &&
+ cmd = '''PATH=$PATH:$R_HOME/bin; export PATH; R_LIBS=$INSTALL_DIR; export R_LIBS;
Rscript -e "install.packages(c('%s'),lib='$INSTALL_DIR', repos=NULL, dependencies=FALSE)"''' % ( str( tarball_name ) )
cmd = install_environment.build_command( td_common_util.evaluate_template( cmd, install_dir ) )
return_code = handle_command( app, tool_dependency, install_dir, cmd )
https://bitbucket.org/galaxy/galaxy-central/commits/55a2d1cced87/
Changeset: 55a2d1cced87
User: jmchilton
Date: 2014-01-14 23:05:01
Summary: Merge latest.
Affected #: 1 file
diff -r 77e5cbd3bcfebf50ce463c0b320a51e288f00a1e -r 55a2d1cced87aa2401a99ff3bc33d759f9b6e82c 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
@@ -497,9 +497,10 @@
suc.repository_was_previously_installed( trans, tool_shed_url, name, tmp_repo_info_tuple )
if installed_repository:
current_changeset_revision = str( installed_repository.changeset_revision )
- message = 'Revision %s of repository %s owned by %s has already been installed.'
+ message = 'Revision <b>%s</b> of repository <b>%s</b> owned by <b>%s</b> has already been installed.' % \
+ ( latest_downloadable_revision, name, owner )
if current_changeset_revision != latest_downloadable_revision:
- message += ' The current changeset revision is %s.' % current_changeset_revision
+ message += ' The current changeset revision is <b>%s</b>.' % current_changeset_revision
status = 'error'
else:
# Install the latest downloadable revision of the repository.
@@ -508,10 +509,10 @@
( name, owner, latest_downloadable_revision, web.url_for( '/', qualified=True ) ) )
return trans.response.send_redirect( url )
else:
- message = 'Cannot locate installed tool shed repository with encoded id %s.' % str( repository_id )
+ message = 'Cannot locate installed tool shed repository with encoded id <b>%s</b>.' % str( repository_id )
status = 'error'
else:
- message = 'The request parameters did not include the required encoded id of installed repository.'
+ message = 'The request parameters did not include the required encoded <b>id</b> of installed repository.'
status = 'error'
return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
action='browse_repositories',
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: Improved messaging when upgrading an installed repository in Galaxy.
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9165ed69a678/
Changeset: 9165ed69a678
User: greg
Date: 2014-01-14 22:54:09
Summary: Improved messaging when upgrading an installed repository in Galaxy.
Affected #: 1 file
diff -r 027eb5827babc5a6b07b6c3e098bacd09185adf4 -r 9165ed69a678010aff37fdc93f3a25661aa44e79 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
@@ -497,9 +497,10 @@
suc.repository_was_previously_installed( trans, tool_shed_url, name, tmp_repo_info_tuple )
if installed_repository:
current_changeset_revision = str( installed_repository.changeset_revision )
- message = 'Revision %s of repository %s owned by %s has already been installed.'
+ message = 'Revision <b>%s</b> of repository <b>%s</b> owned by <b>%s</b> has already been installed.' % \
+ ( latest_downloadable_revision, name, owner )
if current_changeset_revision != latest_downloadable_revision:
- message += ' The current changeset revision is %s.' % current_changeset_revision
+ message += ' The current changeset revision is <b>%s</b>.' % current_changeset_revision
status = 'error'
else:
# Install the latest downloadable revision of the repository.
@@ -508,10 +509,10 @@
( name, owner, latest_downloadable_revision, web.url_for( '/', qualified=True ) ) )
return trans.response.send_redirect( url )
else:
- message = 'Cannot locate installed tool shed repository with encoded id %s.' % str( repository_id )
+ message = 'Cannot locate installed tool shed repository with encoded id <b>%s</b>.' % str( repository_id )
status = 'error'
else:
- message = 'The request parameters did not include the required encoded id of installed repository.'
+ message = 'The request parameters did not include the required encoded <b>id</b> of installed repository.'
status = 'error'
return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
action='browse_repositories',
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6cba7c5f3635/
Changeset: 6cba7c5f3635
User: carlfeberhard
Date: 2014-01-14 22:41:20
Summary: Histories API: apply None rule from 5c5e543 to history update
Affected #: 1 file
diff -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 -r 6cba7c5f36358f7e71168cff6118e15642d01cef lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -344,6 +344,8 @@
)
validated_payload = {}
for key, val in payload.items():
+ if val is None:
+ continue
if key in ( 'name', 'genome_build', 'annotation' ):
validated_payload[ key ] = self.validate_and_sanitize_basestring( key, val )
if key in ( 'deleted', 'published' ):
https://bitbucket.org/galaxy/galaxy-central/commits/027eb5827bab/
Changeset: 027eb5827bab
User: carlfeberhard
Date: 2014-01-14 22:41:37
Summary: merge
Affected #: 1 file
diff -r 6cba7c5f36358f7e71168cff6118e15642d01cef -r 027eb5827babc5a6b07b6c3e098bacd09185adf4 templates/webapps/galaxy/tool_form.mako
--- a/templates/webapps/galaxy/tool_form.mako
+++ b/templates/webapps/galaxy/tool_form.mako
@@ -268,12 +268,12 @@
<%def name="row_for_rerun()">
%if trans.app.config.track_jobs_in_database and tool_state.rerun_remap_job_id is not None:
- <div class="form-row">
+ <div id="remap-row" class="form-row"><input type="checkbox" name="rerun_remap_job_id" value="${tool_state.rerun_remap_job_id}"> Resume dependencies from this job
<div class="toolParamHelp" style="clear: both;">
The previous run of this tool failed and other tools were waiting for it to finish successfully, use this option to resume those tools using the outputs of this tool run.
</div>
- <div>
+ </div><div style="clear: both;"></div>
%endif
</%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
commit/galaxy-central: jmchilton: Close remap div in tool_form.
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9eb10fd69c98/
Changeset: 9eb10fd69c98
User: jmchilton
Date: 2014-01-14 22:40:27
Summary: Close remap div in tool_form.
Fixes up the GUI slightly and is needed for me to selectively disable feature downstream.
Affected #: 1 file
diff -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 -r 9eb10fd69c986fcd01a4ca0393e28ac18b1c3518 templates/webapps/galaxy/tool_form.mako
--- a/templates/webapps/galaxy/tool_form.mako
+++ b/templates/webapps/galaxy/tool_form.mako
@@ -268,12 +268,12 @@
<%def name="row_for_rerun()">
%if trans.app.config.track_jobs_in_database and tool_state.rerun_remap_job_id is not None:
- <div class="form-row">
+ <div id="remap-row" class="form-row"><input type="checkbox" name="rerun_remap_job_id" value="${tool_state.rerun_remap_job_id}"> Resume dependencies from this job
<div class="toolParamHelp" style="clear: both;">
The previous run of this tool failed and other tools were waiting for it to finish successfully, use this option to resume those tools using the outputs of this tool run.
</div>
- <div>
+ </div><div style="clear: both;"></div>
%endif
</%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
commit/galaxy-central: greg: Add the ability to install the latest installable revision of a repository that is already installed into Galaxy via the installed repository's pop-up menu on the Manage installed Tool Shed repositories grid in Galaxy. This is a new grid operation that is available only for those repositories that have a revision upgrade available in the Tool Shed.
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/be5f0c5df96f/
Changeset: be5f0c5df96f
User: greg
Date: 2014-01-14 22:37:56
Summary: Add the ability to install the latest installable revision of a repository that is already installed into Galaxy via the installed repository's pop-up menu on the Manage installed Tool Shed repositories grid in Galaxy. This is a new grid operation that is available only for those repositories that have a revision upgrade available in the Tool Shed.
Affected #: 5 files
diff -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 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
@@ -106,10 +106,11 @@
if operation == "activate or reinstall":
repository = suc.get_installed_tool_shed_repository( trans, kwd[ 'id' ] )
if repository.uninstalled:
- # Since we're reinstalling the repository we need to find the latest changeset revision to which it can be updated so that we
- # can reset the metadata if necessary. This will ensure that information about repository dependencies and tool dependencies
- # will be current. Only allow selecting a different section in the tool panel if the repository was uninstalled and it contained
- # tools that should be displayed in the tool panel.
+ # Since we're reinstalling the repository we need to find the latest changeset revision to which it can
+ # be updated so that we can reset the metadata if necessary. This will ensure that information about
+ # repository dependencies and tool dependencies will be current. Only allow selecting a different section
+ # in the tool panel if the repository was uninstalled and it contained tools that should be displayed in
+ # the tool panel.
changeset_revision_dict = repository_util.get_update_to_changeset_revision_and_ctx_rev( trans, repository )
current_changeset_revision = changeset_revision_dict.get( 'changeset_revision', None )
current_ctx_rev = changeset_revision_dict.get( 'ctx_rev', None )
@@ -121,11 +122,12 @@
**kwd ) )
else:
# The uninstalled repository has updates available in the tool shed.
- updated_repo_info_dict = self.get_updated_repository_information( trans=trans,
- repository_id=trans.security.encode_id( repository.id ),
- repository_name=repository.name,
- repository_owner=repository.owner,
- changeset_revision=current_changeset_revision )
+ updated_repo_info_dict = \
+ self.get_updated_repository_information( trans=trans,
+ repository_id=trans.security.encode_id( repository.id ),
+ repository_name=repository.name,
+ repository_owner=repository.owner,
+ changeset_revision=current_changeset_revision )
json_repo_info_dict = json.to_json_string( updated_repo_info_dict )
encoded_repo_info_dict = encoding_util.tool_shed_encode( json_repo_info_dict )
kwd[ 'latest_changeset_revision' ] = current_changeset_revision
@@ -150,6 +152,10 @@
return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
action='deactivate_or_uninstall_repository',
**kwd ) )
+ if operation == "install latest revision":
+ return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
+ action='install_latest_repository_revision',
+ **kwd ) )
return self.installed_repository_grid( trans, **kwd )
@web.expose
@@ -460,6 +466,61 @@
@web.expose
@web.require_admin
+ def install_latest_repository_revision( self, trans, **kwd ):
+ """Install the latest installable revision of a repository that has been previously installed."""
+ message = kwd.get( 'message', '' )
+ status = kwd.get( 'status', 'done' )
+ repository_id = kwd.get( 'id', None )
+ if repository_id is not None:
+ repository = suc.get_installed_tool_shed_repository( trans, repository_id )
+ if repository is not None:
+ tool_shed_url = suc.get_url_from_tool_shed( trans.app, repository.tool_shed )
+ name = str( repository.name )
+ owner = str( repository.owner )
+ url = suc.url_join( tool_shed_url,
+ 'repository/get_latest_downloadable_changeset_revision?galaxy_url=%s&name=%s&owner=%s' % \
+ ( web.url_for( '/', qualified=True ), name, owner ) )
+ raw_text = common_util.tool_shed_get( trans.app, tool_shed_url, url )
+ latest_downloadable_revision = json.from_json_string( raw_text )
+ if latest_downloadable_revision == suc.INITIAL_CHANGELOG_HASH:
+ message = 'Error retrieving the latest downloadable revision for this repository via the url <b>%s</b>.' % url
+ status = 'error'
+ else:
+ # Make sure the latest changeset_revision of the repository has not already been installed.
+ # Updates to installed repository revisions may have occurred, so make sure to locate the
+ # appropriate repository revision if one exists. We need to create a temporary repo_info_tuple
+ # with the following entries to handle this.
+ # ( description, clone_url, changeset_revision, ctx_rev, owner, repository_dependencies, tool_dependencies )
+ tmp_clone_url = suc.url_join( tool_shed_url, 'repos', owner, name )
+ tmp_repo_info_tuple = ( None, tmp_clone_url, latest_downloadable_revision, None, owner, None, None )
+ installed_repository, installed_changeset_revision = \
+ suc.repository_was_previously_installed( trans, tool_shed_url, name, tmp_repo_info_tuple )
+ if installed_repository:
+ current_changeset_revision = str( installed_repository.changeset_revision )
+ message = 'Revision %s of repository %s owned by %s has already been installed.'
+ if current_changeset_revision != latest_downloadable_revision:
+ message += ' The current changeset revision is %s.' % current_changeset_revision
+ status = 'error'
+ else:
+ # Install the latest downloadable revision of the repository.
+ url = suc.url_join( tool_shed_url,
+ 'repository/install_repositories_by_revision?name=%s&owner=%s&changeset_revisions=%s&galaxy_url=%s' % \
+ ( name, owner, latest_downloadable_revision, web.url_for( '/', qualified=True ) ) )
+ return trans.response.send_redirect( url )
+ else:
+ message = 'Cannot locate installed tool shed repository with encoded id %s.' % str( repository_id )
+ status = 'error'
+ else:
+ message = 'The request parameters did not include the required encoded id of installed repository.'
+ status = 'error'
+ return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
+ action='browse_repositories',
+ message=message,
+ status=status ) )
+
+
+ @web.expose
+ @web.require_admin
def install_tool_dependencies( self, trans, **kwd ):
message = kwd.get( 'message', '' )
status = kwd.get( 'status', 'done' )
@@ -600,12 +661,17 @@
def manage_repository( self, trans, **kwd ):
message = kwd.get( 'message', '' )
status = kwd.get( 'status', 'done' )
- repository_id = kwd[ 'id' ]
+ repository_id = kwd.get( 'id', None )
+ if repository_id is None:
+ return trans.show_error_message( 'Missing required encoded repository id.' )
operation = kwd.get( 'operation', None )
repository = suc.get_installed_tool_shed_repository( trans, repository_id )
- if not repository:
+ if repository is None:
return trans.show_error_message( 'Invalid repository specified.' )
tool_shed_url = suc.get_url_from_tool_shed( trans.app, repository.tool_shed )
+ name = str( repository.name )
+ owner = str( repository.owner )
+ installed_changeset_revision = str( repository.installed_changeset_revision )
if repository.status in [ trans.install_model.ToolShedRepository.installation_status.CLONING ]:
tool_shed_repository_ids = [ repository_id ]
return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
@@ -615,12 +681,12 @@
# Send a request to the tool shed to install the repository.
url = suc.url_join( tool_shed_url,
'repository/install_repositories_by_revision?name=%s&owner=%s&changeset_revisions=%s&galaxy_url=%s' % \
- ( repository.name, repository.owner, repository.installed_changeset_revision, ( web.url_for( '/', qualified=True ) ) ) )
+ ( name, owner, installed_changeset_revision, ( web.url_for( '/', qualified=True ) ) ) )
return trans.response.send_redirect( url )
description = kwd.get( 'description', repository.description )
shed_tool_conf, tool_path, relative_install_dir = suc.get_tool_panel_config_tool_path_install_dir( trans.app, repository )
if relative_install_dir:
- repo_files_dir = os.path.abspath( os.path.join( tool_path, relative_install_dir, repository.name ) )
+ repo_files_dir = os.path.abspath( os.path.join( tool_path, relative_install_dir, name ) )
else:
repo_files_dir = None
if repository.in_error_state:
@@ -841,7 +907,7 @@
# Get the information necessary to install each repository.
url = suc.url_join( tool_shed_url,
'repository/get_repository_information?repository_ids=%s&changeset_revisions=%s' % \
- ( repository_ids, changeset_revisions ) )
+ ( str( repository_ids ), str( changeset_revisions ) ) )
raw_text = common_util.tool_shed_get( trans.app, tool_shed_url, url )
repo_information_dict = json.from_json_string( raw_text )
for encoded_repo_info_dict in repo_information_dict.get( 'repo_info_dicts', [] ):
diff -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -1581,28 +1581,52 @@
items=functional_test_results )
@web.json
+ def get_latest_downloadable_changeset_revision( self, trans, **kwd ):
+ """
+ Return the latest installable changeset revision for the repository associated with the received
+ name and owner. This method is called from Galaxy when attempting to install the latest revision
+ of an installed repository.
+ """
+ repository_name = kwd.get( 'name', None )
+ repository_owner = kwd.get( 'owner', None )
+ if repository_name is not None and repository_owner is not None:
+ repository = suc.get_repository_by_name_and_owner( trans.app, repository_name, repository_owner )
+ if repository:
+ repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) )
+ return suc.get_latest_downloadable_changeset_revision( trans, repository, repo )
+ return suc.INITIAL_CHANGELOG_HASH
+
+ @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.
+ 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 = suc.get_repository_by_name_and_owner( trans.app, repository_name, repository_owner )
- if repository:
- repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans,
- trans.security.encode_id( repository.id ),
- changeset_revision )
- if repository_metadata:
- metadata = repository_metadata.metadata
- if metadata:
- return readme_util.build_readme_files_dict( trans, repository, changeset_revision, repository_metadata.metadata )
+ repository_name = kwd.get( 'name', None )
+ repository_owner = kwd.get( 'owner', None )
+ changeset_revision = kwd.get( 'changeset_revision', None )
+ if repository_name is not None and repository_owner is not None and changeset_revision is not None:
+ repository = suc.get_repository_by_name_and_owner( trans.app, repository_name, repository_owner )
+ if repository:
+ repository_metadata = \
+ suc.get_repository_metadata_by_changeset_revision( trans,
+ trans.security.encode_id( repository.id ),
+ changeset_revision )
+ if repository_metadata:
+ metadata = repository_metadata.metadata
+ if metadata:
+ return readme_util.build_readme_files_dict( trans,
+ repository,
+ changeset_revision,
+ repository_metadata.metadata )
return {}
@web.json
def get_repository_dependencies( self, trans, **kwd ):
- """Return an encoded dictionary of all repositories upon which the contents of the received repository depends."""
+ """
+ Return an encoded dictionary of all repositories upon which the contents of the received repository
+ depends.
+ """
name = kwd.get( 'name', None )
owner = kwd.get( 'owner', None )
changeset_revision = kwd.get( 'changeset_revision', None )
@@ -1630,8 +1654,8 @@
@web.json
def get_repository_information( self, trans, repository_ids, changeset_revisions, **kwd ):
"""
- Generate a list of dictionaries, each of which contains the information about a repository that will be necessary for installing it into
- a local Galaxy instance.
+ Generate a list of dictionaries, each of which contains the information about a repository that will
+ be necessary for installing it into a local Galaxy instance.
"""
includes_tools = False
includes_tools_for_display_in_tool_panel = False
@@ -1665,8 +1689,8 @@
@web.json
def get_required_repo_info_dict( self, trans, encoded_str=None ):
"""
- Retrieve and return a dictionary that includes a list of dictionaries that each contain all of the information needed to install the list of
- repositories defined by the received encoded_str.
+ Retrieve and return a dictionary that includes a list of dictionaries that each contain all of the
+ information needed to install the list of repositories defined by the received encoded_str.
"""
repo_info_dict = {}
if encoded_str:
@@ -1689,7 +1713,10 @@
@web.expose
def get_tool_dependencies( self, trans, **kwd ):
- """Handle a request from a Galaxy instance to get the tool_dependencies entry from the metadata for a specified changeset revision."""
+ """
+ Handle a request from a Galaxy instance to get the tool_dependencies entry from the metadata
+ for a specified changeset revision.
+ """
name = kwd.get( 'name', None )
owner = kwd.get( 'owner', None )
changeset_revision = kwd.get( 'changeset_revision', None )
@@ -1705,7 +1732,10 @@
@web.expose
def get_tool_dependencies_config_contents( self, trans, **kwd ):
- """Handle a request from a Galaxy instance to get the tool_dependencies.xml file contents for a specified changeset revision."""
+ """
+ Handle a request from a Galaxy instance to get the tool_dependencies.xml file contents for a
+ specified changeset revision.
+ """
name = kwd.get( 'name', None )
owner = kwd.get( 'owner', None )
changeset_revision = kwd.get( 'changeset_revision', None )
@@ -1726,8 +1756,8 @@
@web.expose
def get_tool_versions( self, trans, **kwd ):
"""
- For each valid /downloadable change set (up to the received changeset_revision) in the repository's change log, append the change
- set's tool_versions dictionary to the list that will be returned.
+ For each valid /downloadable change set (up to the received changeset_revision) in the repository's
+ change log, append the changeset tool_versions dictionary to the list that will be returned.
"""
name = kwd[ 'name' ]
owner = kwd[ 'owner' ]
@@ -1751,7 +1781,10 @@
@web.json
def get_updated_repository_information( self, trans, name, owner, changeset_revision, **kwd ):
- """Generate a dictionary that contains the information about a repository that is necessary for installing it into a local Galaxy instance."""
+ """
+ Generate a dictionary that contains the information about a repository that is necessary for installing
+ it into a local Galaxy instance.
+ """
repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
repository_id = trans.security.encode_id( repository.id )
repository_clone_url = suc.generate_clone_url_for_repository_in_tool_shed( trans, repository )
@@ -1828,7 +1861,10 @@
repo_info_dict=repo_info_dict )
def get_versions_of_tool( self, trans, repository, repository_metadata, guid ):
- """Return the tool lineage in descendant order for the received guid contained in the received repsitory_metadata.tool_versions."""
+ """
+ Return the tool lineage in descendant order for the received guid contained in the received
+ repsitory_metadata.tool_versions.
+ """
encoded_id = trans.security.encode_id( repository.id )
repo_dir = repository.repo_path( trans.app )
repo = hg.repository( suc.get_configured_ui(), repo_dir )
@@ -1846,7 +1882,9 @@
current_child_guid = parent_guid
# Get all descendant guids of the received guid.
current_parent_guid = guid
- for changeset in suc.reversed_lower_upper_bounded_changelog( repo, repository_metadata.changeset_revision, repository.tip( trans.app ) ):
+ for changeset in suc.reversed_lower_upper_bounded_changelog( repo,
+ repository_metadata.changeset_revision,
+ repository.tip( trans.app ) ):
ctx = repo.changectx( changeset )
rm = suc.get_repository_metadata_by_changeset_revision( trans, encoded_id, str( ctx ) )
if rm:
@@ -1943,8 +1981,9 @@
@web.expose
def install_repositories_by_revision( self, trans, **kwd ):
"""
- Send the list of repository_ids and changeset_revisions to Galaxy so it can begin the installation process. If the value of
- repository_ids is not received, then the name and owner of a single repository must be received to install a single repository.
+ Send the list of repository_ids and changeset_revisions to Galaxy so it can begin the installation
+ process. If the value of repository_ids is not received, then the name and owner of a single repository
+ must be received to install a single repository.
"""
repository_ids = kwd.get( 'repository_ids', None )
changeset_revisions = kwd.get( 'changeset_revisions', None )
@@ -1958,10 +1997,13 @@
# Redirect back to local Galaxy to perform install.
url = suc.url_join( galaxy_url,
'admin_toolshed/prepare_for_install?tool_shed_url=%s&repository_ids=%s&changeset_revisions=%s' % \
- ( web.url_for( '/', qualified=True ), ','.join( util.listify( repository_ids ) ), ','.join( util.listify( changeset_revisions ) ) ) )
+ ( web.url_for( '/', qualified=True ),
+ ','.join( util.listify( repository_ids ) ),
+ ','.join( util.listify( changeset_revisions ) ) ) )
return trans.response.send_redirect( url )
else:
- message = 'Repository installation is not possible due to an invalid Galaxy URL: <b>%s</b>. You may need to enable cookies in your browser. ' % galaxy_url
+ message = 'Repository installation is not possible due to an invalid Galaxy URL: <b>%s</b>. '
+ message += 'You may need to enable cookies in your browser. ' % galaxy_url
status = 'error'
return trans.response.send_redirect( web.url_for( controller='repository',
action='browse_valid_categories',
diff -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py
--- a/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py
+++ b/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py
@@ -131,73 +131,99 @@
args = { self.key: val }
accepted_filters.append( grids.GridColumnFilter( label, args) )
return accepted_filters
+
# Grid definition
title = "Installed tool shed repositories"
model_class = tool_shed_install.ToolShedRepository
template='/admin/tool_shed_repository/grid.mako'
default_sort_key = "name"
columns = [
- ToolShedStatusColumn( "",
- attach_popup=False ),
- NameColumn( "Name",
+ ToolShedStatusColumn( label="" ),
+ NameColumn( label="Name",
key="name",
link=( lambda item: iff( item.status in [ tool_shed_install.ToolShedRepository.installation_status.CLONING ],
None,
dict( operation="manage_repository", id=item.id ) ) ),
attach_popup=True ),
- DescriptionColumn( "Description" ),
- OwnerColumn( "Owner" ),
- RevisionColumn( "Revision" ),
- StatusColumn( "Installation Status",
+ DescriptionColumn( label="Description" ),
+ OwnerColumn( label="Owner" ),
+ RevisionColumn( label="Revision" ),
+ StatusColumn( label="Installation Status",
filterable="advanced" ),
- ToolShedColumn( "Tool shed" ),
+ ToolShedColumn( label="Tool shed" ),
# Columns that are valid for filtering but are not visible.
- DeletedColumn( "Status",
+ DeletedColumn( label="Status",
key="deleted",
visible=False,
filterable="advanced" )
]
columns.append( grids.MulticolFilterColumn( "Search repository name",
- cols_to_filter=[ columns[0] ],
+ cols_to_filter=[ columns[ 1 ] ],
key="free-text-search",
visible=False,
filterable="standard" ) )
global_actions = [
- grids.GridAction( "Update tool shed status",
- dict( controller='admin_toolshed', action='update_tool_shed_status_for_installed_repository', all_installed_repositories=True ) )
+ grids.GridAction( label="Update tool shed status",
+ url_args=dict( controller='admin_toolshed',
+ action='update_tool_shed_status_for_installed_repository',
+ all_installed_repositories=True ),
+ inbound=False )
]
- operations = [ grids.GridOperation( "Update tool shed status",
+ operations = [ grids.GridOperation( label="Update tool shed status",
+ condition=( lambda item: not item.deleted ),
allow_multiple=False,
- condition=( lambda item: not item.deleted ),
- async_compatible=False,
- url_args=dict( controller='admin_toolshed', action='browse_repositories', operation='update tool shed status' ) ),
- grids.GridOperation( "Get updates",
+ url_args=dict( controller='admin_toolshed',
+ action='browse_repositories',
+ operation='update tool shed status' ) ),
+ grids.GridOperation( label="Get updates",
+ condition=( lambda item: \
+ not item.deleted and \
+ item.revision_update_available and \
+ item.status not in \
+ [ tool_shed_install.ToolShedRepository.installation_status.ERROR,
+ tool_shed_install.ToolShedRepository.installation_status.NEW ] ),
allow_multiple=False,
- condition=( lambda item: not item.deleted and item.revision_update_available and item.status not in \
- [ tool_shed_install.ToolShedRepository.installation_status.ERROR, tool_shed_install.ToolShedRepository.installation_status.NEW ] ),
- async_compatible=False,
- url_args=dict( controller='admin_toolshed', action='browse_repositories', operation='get updates' ) ),
- grids.GridOperation( "Install",
+ url_args=dict( controller='admin_toolshed',
+ action='browse_repositories',
+ operation='get updates' ) ),
+ grids.GridOperation( label="Install latest revision",
+ condition=( lambda item: item.upgrade_available ),
allow_multiple=False,
- condition=( lambda item: not item.deleted and item.status == tool_shed_install.ToolShedRepository.installation_status.NEW ),
- async_compatible=False,
- url_args=dict( controller='admin_toolshed', action='manage_repository', operation='install' ) ),
- grids.GridOperation( "Deactivate or uninstall",
+ url_args=dict( controller='admin_toolshed',
+ action='browse_repositories',
+ operation='install latest revision' ) ),
+ grids.GridOperation( label="Install",
+ condition=( lambda item: \
+ not item.deleted and \
+ item.status == tool_shed_install.ToolShedRepository.installation_status.NEW ),
allow_multiple=False,
- condition=( lambda item: not item.deleted and item.status not in \
- [ tool_shed_install.ToolShedRepository.installation_status.ERROR, tool_shed_install.ToolShedRepository.installation_status.NEW ] ),
- async_compatible=False,
- url_args=dict( controller='admin_toolshed', action='browse_repositories', operation='deactivate or uninstall' ) ),
- grids.GridOperation( "Reset to install",
+ url_args=dict( controller='admin_toolshed',
+ action='manage_repository',
+ operation='install' ) ),
+ grids.GridOperation( label="Deactivate or uninstall",
+ condition=( lambda item: \
+ not item.deleted and \
+ item.status not in \
+ [ tool_shed_install.ToolShedRepository.installation_status.ERROR,
+ tool_shed_install.ToolShedRepository.installation_status.NEW ] ),
allow_multiple=False,
- condition=( lambda item: ( item.status == tool_shed_install.ToolShedRepository.installation_status.ERROR ) ),
- async_compatible=False,
- url_args=dict( controller='admin_toolshed', action='browse_repositories', operation='reset to install' ) ),
- grids.GridOperation( "Activate or reinstall",
+ url_args=dict( controller='admin_toolshed',
+ action='browse_repositories',
+ operation='deactivate or uninstall' ) ),
+ grids.GridOperation( label="Reset to install",
+ condition=( lambda item: \
+ ( item.status == tool_shed_install.ToolShedRepository.installation_status.ERROR ) ),
allow_multiple=False,
+ url_args=dict( controller='admin_toolshed',
+ action='browse_repositories',
+ operation='reset to install' ) ),
+ grids.GridOperation( label="Activate or reinstall",
condition=( lambda item: item.deleted ),
- async_compatible=False,
- url_args=dict( controller='admin_toolshed', action='browse_repositories', operation='activate or reinstall' ) ) ]
+ allow_multiple=False,
+ target=None,
+ url_args=dict( controller='admin_toolshed',
+ action='browse_repositories',
+ operation='activate or reinstall' ) ) ]
standard_filters = []
default_filter = dict( deleted="False" )
num_rows_per_page = 50
diff -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 lib/tool_shed/util/common_install_util.py
--- a/lib/tool_shed/util/common_install_util.py
+++ b/lib/tool_shed/util/common_install_util.py
@@ -224,8 +224,9 @@
def get_installed_and_missing_repository_dependencies_for_new_install( trans, repo_info_tuple ):
"""
- Parse the received repository_dependencies dictionary that is associated with a repository being installed into Galaxy for the first time
- and attempt to determine repository dependencies that are already installed and those that are not.
+ Parse the received repository_dependencies dictionary that is associated with a repository being
+ installed into Galaxy for the first time and attempt to determine repository dependencies that are
+ already installed and those that are not.
"""
missing_repository_dependencies = {}
installed_repository_dependencies = {}
@@ -236,20 +237,25 @@
if repository_dependencies:
description = repository_dependencies[ 'description' ]
root_key = repository_dependencies[ 'root_key' ]
- # The repository dependencies container will include only the immediate repository dependencies of this repository, so the container will be
- # only a single level in depth.
+ # The repository dependencies container will include only the immediate repository dependencies of
+ # this repository, so the container will be only a single level in depth.
for key, rd_tups in repository_dependencies.items():
if key in [ 'description', 'root_key' ]:
continue
for rd_tup in rd_tups:
tool_shed, name, owner, changeset_revision, prior_installation_required, only_if_compiling_contained_td = \
common_util.parse_repository_dependency_tuple( rd_tup )
- # Updates to installed repository revisions may have occurred, so make sure to locate the appropriate repository revision if one exists.
- # We need to create a temporary repo_info_tuple that includes the correct repository owner which we get from the current rd_tup. The current
- # tuple looks like: ( description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, installed_td )
+ # Updates to installed repository revisions may have occurred, so make sure to locate the
+ # appropriate repository revision if one exists. We need to create a temporary repo_info_tuple
+ # that includes the correct repository owner which we get from the current rd_tup. The current
+ # tuple looks like: ( description, repository_clone_url, changeset_revision, ctx_rev, repository_owner,
+ # repository_dependencies, installed_td )
tmp_clone_url = suc.generate_clone_url_from_repo_info_tup( rd_tup )
tmp_repo_info_tuple = ( None, tmp_clone_url, changeset_revision, None, owner, None, None )
- repository, installed_changeset_revision = suc.repository_was_previously_installed( trans, tool_shed, name, tmp_repo_info_tuple )
+ repository, installed_changeset_revision = suc.repository_was_previously_installed( trans,
+ tool_shed,
+ name,
+ tmp_repo_info_tuple )
if repository:
new_rd_tup = [ tool_shed,
name,
@@ -263,10 +269,11 @@
if new_rd_tup not in installed_rd_tups:
installed_rd_tups.append( new_rd_tup )
else:
- # A repository dependency that is not installed will not be considered missing if it's value for only_if_compiling_contained_td is
- # True This is because this type of repository dependency will only be considered at the time that the specified tool dependency
- # is being installed, and even then only if the compiled binary of the tool dependency could not be installed due to the unsupported
- # installation environment.
+ # A repository dependency that is not installed will not be considered missing if it's value
+ # for only_if_compiling_contained_td is True This is because this type of repository dependency
+ # will only be considered at the time that the specified tool dependency is being installed, and
+ # even then only if the compiled binary of the tool dependency could not be installed due to the
+ # unsupported installation environment.
if not util.asbool( only_if_compiling_contained_td ):
if new_rd_tup not in missing_rd_tups:
missing_rd_tups.append( new_rd_tup )
@@ -280,8 +287,8 @@
None,
'Never installed' ]
if not util.asbool( only_if_compiling_contained_td ):
- # A repository dependency that is not installed will not be considered missing if it's value for only_if_compiling_contained_td is
- # True - see above...
+ # A repository dependency that is not installed will not be considered missing if it's value for
+ # only_if_compiling_contained_td is True - see above...
if new_rd_tup not in missing_rd_tups:
missing_rd_tups.append( new_rd_tup )
if installed_rd_tups:
@@ -295,7 +302,10 @@
return installed_repository_dependencies, missing_repository_dependencies
def get_installed_and_missing_tool_dependencies_for_installing_repository( trans, tool_shed_url, tool_dependencies_dict ):
- """Return the lists of installed tool dependencies and missing tool dependencies for a set of repositories being installed into Galaxy."""
+ """
+ Return the lists of installed tool dependencies and missing tool dependencies for a set of repositories
+ being installed into Galaxy.
+ """
installed_tool_dependencies = {}
missing_tool_dependencies = {}
if tool_dependencies_dict:
diff -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e -r be5f0c5df96fba1a12e3e96fdb6f1805703f0b73 lib/tool_shed/util/shed_util_common.py
--- a/lib/tool_shed/util/shed_util_common.py
+++ b/lib/tool_shed/util/shed_util_common.py
@@ -1622,10 +1622,12 @@
def repository_was_previously_installed( trans, tool_shed_url, repository_name, repo_info_tuple ):
"""
- Find out if a repository is already installed into Galaxy - there are several scenarios where this is necessary. For example, this method
- will handle the case where the repository was previously installed using an older changeset_revsion, but later the repository was updated
- in the tool shed and now we're trying to install the latest changeset revision of the same repository instead of updating the one that was
- previously installed. We'll look in the database instead of on disk since the repository may be currently uninstalled.
+ Find out if a repository is already installed into Galaxy - there are several scenarios where this
+ is necessary. For example, this method will handle the case where the repository was previously
+ installed using an older changeset_revsion, but later the repository was updated in the tool shed
+ and now we're trying to install the latest changeset revision of the same repository instead of
+ updating the one that was previously installed. We'll look in the database instead of on disk since
+ the repository may be currently uninstalled.
"""
description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies = \
get_repo_info_tuple_contents( repo_info_tuple )
@@ -1638,8 +1640,8 @@
changeset_revision )
if tool_shed_repository:
return tool_shed_repository, changeset_revision
- # Get all previous changeset revisions from the tool shed for the repository back to, but excluding, the previous valid changeset
- # revision to see if it was previously installed using one of them.
+ # Get all previous changeset revisions from the tool shed for the repository back to, but excluding,
+ # the previous valid changeset revision to see if it was previously installed using one of them.
url = url_join( tool_shed_url,
'repository/previous_changeset_revisions?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s' % \
( url_for( '/', qualified=True ), repository_name, repository_owner, 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: carlfeberhard: Visualizations Registry: update unit test to remove expected visualizations, small fixes
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8ac00a14c7fd/
Changeset: 8ac00a14c7fd
User: carlfeberhard
Date: 2014-01-14 21:52:01
Summary: Visualizations Registry: update unit test to remove expected visualizations, small fixes
Affected #: 4 files
diff -r c5fbdfd31b387056d1dfdad5e9e356c0020c5cec -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -19,8 +19,6 @@
__TODO__ = """
BUGS:
- anon users clicking a viz link gets 'must be' msg in galaxy_main (w/ masthead)
- should not show visualizations (no icon)?
newick files aren't being sniffed prop? - datatype is txt
have parsers create objects instead of dicts
@@ -29,12 +27,9 @@
some confused vocabulary in docs, var names
tests:
anding, grouping, not
- has_dataprovider
user is admin
data_sources:
lists of
-add description element to visualization.
-
user_pref for ordering/ex/inclusion of particular visualizations
"""
@@ -258,6 +253,7 @@
if test_fn( target_object, test_result ):
#log.debug( '\t test passed' )
return True
+
return False
def get_visualization_url( self, trans, target_object, visualization_name, param_data ):
diff -r c5fbdfd31b387056d1dfdad5e9e356c0020c5cec -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e static/scripts/mvc/dataset/hda-edit.js
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -250,7 +250,7 @@
});
// No need for popup menu because there's a single visualization.
- if( _.keys( visualizations ).length === 1 ) {
+ if( visualizations.length === 1 ) {
var onlyVisualization = visualizations[0];
$icon.attr( 'data-original-title', _l( 'Visualize in ' ) + onlyVisualization.html );
$icon.attr( 'href', onlyVisualization.href );
diff -r c5fbdfd31b387056d1dfdad5e9e356c0020c5cec -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e static/scripts/packed/mvc/dataset/hda-edit.js
--- a/static/scripts/packed/mvc/dataset/hda-edit.js
+++ b/static/scripts/packed/mvc/dataset/hda-edit.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var i=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(_.keys(g).length===1){var h=g[0];i.attr("data-original-title",_l("Visualize in ")+h.html);i.attr("href",h.href)}else{var j=[];_.each(g,function(k){k.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:k.href});return false}return true};j.push(k);return false});PopupMenu.create(i,j)}return i},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h,scratchbook:true});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i),scratchbook:true})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m,scratchbook:true})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var i=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(g.length===1){var h=g[0];i.attr("data-original-title",_l("Visualize in ")+h.html);i.attr("href",h.href)}else{var j=[];_.each(g,function(k){k.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:k.href});return false}return true};j.push(k);return false});PopupMenu.create(i,j)}return i},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h,scratchbook:true});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i),scratchbook:true})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m,scratchbook:true})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
diff -r c5fbdfd31b387056d1dfdad5e9e356c0020c5cec -r 8ac00a14c7fd8fcf263184aeab4786a66cdb4b0e test/unit/visualizations/registry/test_VisualizationsRegistry.py
--- a/test/unit/visualizations/registry/test_VisualizationsRegistry.py
+++ b/test/unit/visualizations/registry/test_VisualizationsRegistry.py
@@ -49,18 +49,8 @@
template_cache_dir=template_cache_dir )
expected_plugins_path = os.path.join( glx_dir, vis_reg_path )
- expected_plugin_names = [
- 'circster',
- 'graphview',
- 'phyloviz',
- 'scatterplot',
- 'sweepster',
- 'trackster',
- ]
-
self.assertEqual( plugin_mgr.base_url, 'visualizations' )
self.assertItemsEqual( plugin_mgr.directories, [ expected_plugins_path ] )
- self.assertItemsEqual( plugin_mgr.plugins.keys(), expected_plugin_names )
scatterplot = plugin_mgr.plugins[ 'scatterplot' ]
self.assertEqual( scatterplot.name, 'scatterplot' )
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8244a5142f90/
Changeset: 8244a5142f90
User: jmchilton
Date: 2014-01-14 21:43:39
Summary: Add tool execution unit test for rerun_remapping.
Affected #: 2 files
diff -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 -r 8244a5142f90a0d7f1c8b2f2f8886e7d30e0240d test/unit/tools/test_execution.py
--- a/test/unit/tools/test_execution.py
+++ b/test/unit/tools/test_execution.py
@@ -89,6 +89,18 @@
runtool_btn="dummy",
)
assert template == "tool_executed.mako"
+ # Didn't specify a rerun_remap_id so this should be None
+ assert self.tool_action.execution_call_args[ 0 ][ "rerun_remap_job_id" ] is None
+
+ def test_remap_job( self ):
+ self.__init_tool( SIMPLE_TOOL_CONTENTS )
+ template, template_vars = self.__handle_with_incoming(
+ param1="moo",
+ rerun_remap_job_id=self.app.security.encode_id(123),
+ runtool_btn="dummy",
+ )
+ assert template == "tool_executed.mako"
+ assert self.tool_action.execution_call_args[ 0 ][ "rerun_remap_job_id" ] == 123
def test_repeat_state_updates( self ):
self.__init_tool( REPEAT_TOOL_CONTENTS )
diff -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 -r 8244a5142f90a0d7f1c8b2f2f8886e7d30e0240d test/unit/tools_support.py
--- a/test/unit/tools_support.py
+++ b/test/unit/tools_support.py
@@ -9,6 +9,7 @@
import shutil
from galaxy.util.bunch import Bunch
+from galaxy.web.security import SecurityHelper
import galaxy.model
from galaxy.model import mapping
@@ -54,6 +55,7 @@
self.model[ module_member_name ] = module_member
self.toolbox = None
self.object_store = None
+ self.security = SecurityHelper(id_secret="testing")
class MockContext(object):
https://bitbucket.org/galaxy/galaxy-central/commits/c5fbdfd31b38/
Changeset: c5fbdfd31b38
User: jmchilton
Date: 2014-01-14 21:43:39
Summary: Refactor tool so incoming dict isn't passed to __handle_tool_execute.
Slightly confusing that state params and raw incoming passed to that method, so pull out rerun_remap_job_id sooner and just pass that along (it was the only incoming was used for). Use the oppertunity to isolate potential errors with decoding rerun_remap_job_id and include more informative error message.
Add unit test to test invalid rerun_remap_job_ids.
Affected #: 2 files
diff -r 8244a5142f90a0d7f1c8b2f2f8886e7d30e0240d -r c5fbdfd31b387056d1dfdad5e9e356c0020c5cec lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1847,6 +1847,14 @@
that is why this is not just called for_api.
"""
all_pages = ( process_state == "populate" ) # If process_state = update, handle all pages at once.
+ rerun_remap_job_id = None
+ if 'rerun_remap_job_id' in incoming:
+ try:
+ rerun_remap_job_id = trans.app.security.decode_id( incoming[ 'rerun_remap_job_id' ] )
+ except Exception:
+ message = 'Failure executing tool (attempting to rerun invalid job).'
+ return 'message.mako', dict( status='error', message=message, refresh_frames=[] )
+
state, state_new = self.__fetch_state( trans, incoming, history, all_pages=all_pages )
if state_new:
# This feels a bit like a hack. It allows forcing full processing
@@ -1874,7 +1882,7 @@
return "tool_form.mako", dict( errors=errors, tool_state=state, incoming=incoming, error_message=error_message )
# If we've completed the last page we can execute the tool
elif all_pages or state.page == self.last_page:
- return self.__handle_tool_execute( trans, incoming, params, history )
+ return self.__handle_tool_execute( trans, rerun_remap_job_id, params, history )
# Otherwise move on to the next page
else:
return self.__handle_page_advance( trans, state, errors )
@@ -1882,11 +1890,8 @@
def __should_refresh_state( self, incoming ):
return not( 'runtool_btn' in incoming or 'URL' in incoming or 'ajax_upload' in incoming )
- def __handle_tool_execute( self, trans, incoming, params, history ):
+ def __handle_tool_execute( self, trans, rerun_remap_job_id, params, history ):
try:
- rerun_remap_job_id = None
- if 'rerun_remap_job_id' in incoming:
- rerun_remap_job_id = trans.app.security.decode_id(incoming['rerun_remap_job_id'])
_, out_data = self.execute( trans, incoming=params, history=history, rerun_remap_job_id=rerun_remap_job_id )
except httpexceptions.HTTPFound, e:
#if it's a paste redirect exception, pass it up the stack
diff -r 8244a5142f90a0d7f1c8b2f2f8886e7d30e0240d -r c5fbdfd31b387056d1dfdad5e9e356c0020c5cec test/unit/tools/test_execution.py
--- a/test/unit/tools/test_execution.py
+++ b/test/unit/tools/test_execution.py
@@ -102,6 +102,17 @@
assert template == "tool_executed.mako"
assert self.tool_action.execution_call_args[ 0 ][ "rerun_remap_job_id" ] == 123
+ def test_invalid_remap_job( self ):
+ self.__init_tool( SIMPLE_TOOL_CONTENTS )
+ template, template_vars = self.__handle_with_incoming(
+ param1="moo",
+ rerun_remap_job_id='123', # Not encoded
+ runtool_btn="dummy",
+ )
+ assert template == "message.mako"
+ assert template_vars[ "status" ] == "error"
+ assert "invalid job" in template_vars[ "message" ]
+
def test_repeat_state_updates( self ):
self.__init_tool( REPEAT_TOOL_CONTENTS )
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: Visualizations Registry: remove linkText config val (use visualization name attribute), change render_location to render_target, disable circster; fix rendering of visualization button in HDA when only one visualization
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/bb0b6a7e73d7/
Changeset: bb0b6a7e73d7
User: carlfeberhard
Date: 2014-01-14 21:26:44
Summary: Visualizations Registry: remove linkText config val (use visualization name attribute), change render_location to render_target, disable circster; fix rendering of visualization button in HDA when only one visualization
Affected #: 9 files
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 config/plugins/visualizations/circster/config/circster.xml
--- a/config/plugins/visualizations/circster/config/circster.xml
+++ b/config/plugins/visualizations/circster/config/circster.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE visualization SYSTEM "../../visualization.dtd">
-<visualization name="circster">
+<visualization name="Circster" disabled="true"><data_sources><data_source><model_class>HistoryDatasetAssociation</model_class>
@@ -24,5 +24,5 @@
<!-- template_root and template are currently ignored for the 'built-in' visualizations --><template_root>webapps/galaxy/visualization</template_root><template>circster.mako</template>
- <render_location>_top</render_location>
+ <render_target>_top</render_target></visualization>
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 config/plugins/visualizations/phyloviz/config/phyloviz.xml
--- a/config/plugins/visualizations/phyloviz/config/phyloviz.xml
+++ b/config/plugins/visualizations/phyloviz/config/phyloviz.xml
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE visualization SYSTEM "../../visualization.dtd">
-<visualization name="phyloviz">
+<visualization name="Phyloviz"><data_sources><data_source><model_class>HistoryDatasetAssociation</model_class><test type="isinstance" test_attr="datatype" result_type="datatype">data.Newick</test><test type="isinstance" test_attr="datatype" result_type="datatype">data.Nexus</test>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">xml.Phyloxml</test><to_param param_attr="id">dataset_id</to_param></data_source></data_sources>
@@ -16,5 +17,5 @@
<!-- template_root and template are currently ignored for the 'built-in' visualizations --><template_root>webapps/galaxy/visualization</template_root><template>phyloviz.mako</template>
- <render_location>_top</render_location>
+ <render_target>_top</render_target></visualization>
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 config/plugins/visualizations/scatterplot/config/scatterplot.xml
--- a/config/plugins/visualizations/scatterplot/config/scatterplot.xml
+++ b/config/plugins/visualizations/scatterplot/config/scatterplot.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE visualization SYSTEM "../../visualization.dtd">
-<visualization name="scatterplot">
+<visualization name="Scatterplot"><data_sources><data_source><model_class>HistoryDatasetAssociation</model_class>
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 config/plugins/visualizations/sweepster/config/sweepster.xml
--- a/config/plugins/visualizations/sweepster/config/sweepster.xml
+++ b/config/plugins/visualizations/sweepster/config/sweepster.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE visualization SYSTEM "../../visualization.dtd">
-<visualization name="sweepster" disabled="true">
+<visualization name="Sweepster" disabled="true"><data_sources><data_source><model_class>HistoryDatasetAssociation</model_class>
@@ -23,5 +23,5 @@
<!-- template_root and template are currently ignored for the 'built-in' visualizations --><template_root>webapps/galaxy/visualization</template_root><template>sweepster.mako</template>
- <render_location>_top</render_location>
+ <render_target>_top</render_target></visualization>
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 config/plugins/visualizations/trackster/config/trackster.xml
--- a/config/plugins/visualizations/trackster/config/trackster.xml
+++ b/config/plugins/visualizations/trackster/config/trackster.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE visualization SYSTEM "../../visualization.dtd">
-<visualization name="trackster">
+<visualization name="Trackster"><!--not tested yet --><data_sources><data_source>
@@ -26,5 +26,5 @@
<!-- template_root and template are currently ignored for the 'built-in' visualizations --><template_root>webapps/galaxy/visualization/tracks</template_root><template>browser.mako</template>
- <render_location>_top</render_location>
+ <render_target>_top</render_target></visualization>
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 config/plugins/visualizations/visualization.dtd
--- a/config/plugins/visualizations/visualization.dtd
+++ b/config/plugins/visualizations/visualization.dtd
@@ -1,7 +1,7 @@
<!-- each visualization must have a template (all other elements are optional) -->
-<!ELEMENT visualization (description*,data_sources*,params*,template_root*,template,link_text*,render_location*)>
+<!ELEMENT visualization (description*,data_sources*,params*,template_root*,template,render_target*)><!-- visualization
- name: some name for the visualization (e.g. 'trackster', 'scatterplot', etc.) REQUIRED
+ name: the title/display name of the visualization (e.g. 'Trackster', 'Fastq Stats', etc.) REQUIRED
disabled: if included (value does not matter), this attribute will prevent the visualization being loaded
--><!ATTLIST visualization
@@ -128,10 +128,8 @@
<!ELEMENT template_root (#PCDATA)><!-- template: the template used to render the visualization. REQUIRED --><!ELEMENT template (#PCDATA)>
-<!-- link_text: the text component of an html anchor displayed when the registry builds the link information -->
-<!ELEMENT link_text (#PCDATA)>
-<!-- render_location: used as the target attribute of the link to the visualization.
+<!-- render_target: used as the target attribute of the link to the visualization.
Can be 'galaxy_main', '_top', '_blank'. DEFAULT: 'galaxy_main'
--><!-- TODO: rename -> render_target -->
-<!ELEMENT render_location (#PCDATA)>
+<!ELEMENT render_target (#PCDATA)>
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -219,16 +219,13 @@
param_data = data_source[ 'to_params' ]
url = self.get_visualization_url( trans, target_object, visualization_name, param_data )
- link_text = visualization.config.get( 'link_text', None )
- if not link_text:
- # default to visualization name, titlecase, and replace underscores
- link_text = visualization_name.title().replace( '_', ' ' )
- render_location = visualization.config.get( 'render_location', 'galaxy_main' )
+ display_name = visualization.config.get( 'name', None )
+ render_target = visualization.config.get( 'render_target', 'galaxy_main' )
# remap some of these vars for direct use in ui.js, PopupMenu (e.g. text->html)
return {
'href' : url,
- 'html' : link_text,
- 'target': render_location
+ 'html' : display_name,
+ 'target': render_target
}
return None
@@ -362,7 +359,7 @@
-- what provides the data
-- what information needs to be added to the query string
"""
- VALID_RENDER_LOCATIONS = [ 'galaxy_main', '_top', '_blank' ]
+ VALID_RENDER_TARGETS = [ 'galaxy_main', '_top', '_blank' ]
def __init__( self, debug=False ):
self.debug = debug
@@ -397,6 +394,12 @@
if 'disabled' in xml_tree.attrib:
return None
+ # a text display name for end user links
+ returned[ 'name' ] = xml_tree.attrib.get( 'name', None )
+ if not returned[ 'name' ]:
+ raise ParsingException( 'visualization needs a name attribute' )
+ print returned[ 'name' ]
+
# a (for now) text description of what the visualization does
description = xml_tree.find( 'description' )
returned[ 'description' ] = description.text.strip() if description is not None else None
@@ -458,14 +461,14 @@
if link_text != None and link_text.text:
returned[ 'link_text' ] = link_text
- # render_location: where in the browser to open the rendered visualization
+ # render_target: where in the browser to open the rendered visualization
# defaults to: galaxy_main
- render_location = xml_tree.find( 'render_location' )
- if( ( render_location != None and render_location.text )
- and ( render_location.text in self.VALID_RENDER_LOCATIONS ) ):
- returned[ 'render_location' ] = render_location.text
+ render_target = xml_tree.find( 'render_target' )
+ if( ( render_target != None and render_target.text )
+ and ( render_target.text in self.VALID_RENDER_TARGETS ) ):
+ returned[ 'render_target' ] = render_target.text
else:
- returned[ 'render_location' ] = 'galaxy_main'
+ returned[ 'render_target' ] = 'galaxy_main'
# consider unifying the above into it's own element and parsing method
return returned
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 static/scripts/mvc/dataset/hda-edit.js
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -251,8 +251,9 @@
// No need for popup menu because there's a single visualization.
if( _.keys( visualizations ).length === 1 ) {
- $icon.attr( 'title', _.keys( visualizations )[0] );
- $icon.attr( 'href', _.values( visualizations )[0] );
+ var onlyVisualization = visualizations[0];
+ $icon.attr( 'data-original-title', _l( 'Visualize in ' ) + onlyVisualization.html );
+ $icon.attr( 'href', onlyVisualization.href );
// >1: Populate menu dict with visualization fns, make the popupmenu
} else {
diff -r fb1c758d0eb990f2ce59de422d1184647f051cc8 -r bb0b6a7e73d7154377b414939b9fa44da1fb6306 static/scripts/packed/mvc/dataset/hda-edit.js
--- a/static/scripts/packed/mvc/dataset/hda-edit.js
+++ b/static/scripts/packed/mvc/dataset/hda-edit.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var h=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(_.keys(g).length===1){h.attr("title",_.keys(g)[0]);h.attr("href",_.values(g)[0])}else{var i=[];_.each(g,function(j){j.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:j.href});return false}return true};i.push(j);return false});PopupMenu.create(h,i)}return h},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h,scratchbook:true});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i),scratchbook:true})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m,scratchbook:true})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var i=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(_.keys(g).length===1){var h=g[0];i.attr("data-original-title",_l("Visualize in ")+h.html);i.attr("href",h.href)}else{var j=[];_.each(g,function(k){k.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:k.href});return false}return true};j.push(k);return false});PopupMenu.create(i,j)}return i},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h,scratchbook:true});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i),scratchbook:true})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m,scratchbook:true})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jmchilton: Fix workflow editor for changes in 5f6c670.
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/fb1c758d0eb9/
Changeset: fb1c758d0eb9
User: jmchilton
Date: 2014-01-14 21:14:33
Summary: Fix workflow editor for changes in 5f6c670.
Affected #: 1 file
diff -r 22757ed4622ee1b975d966e9fe18ce6d83d2b909 -r fb1c758d0eb990f2ce59de422d1184647f051cc8 templates/webapps/galaxy/workflow/editor.mako
--- a/templates/webapps/galaxy/workflow/editor.mako
+++ b/templates/webapps/galaxy/workflow/editor.mako
@@ -291,6 +291,8 @@
</%def><%def name="left_panel()">
+ <% from galaxy.tools import Tool, ToolSection, ToolSectionLabel %>
+
<div class="unified-panel-header" unselectable="on"><div class='unified-panel-header-inner'>
${n_('Tools')}
@@ -306,9 +308,9 @@
<div class="toolSectionList">
%for key, val in app.toolbox.tool_panel.items():
<div class="toolSectionWrapper">
- %if key.startswith( 'tool' ):
+ %if isinstance( val, Tool ):
${render_tool( val, False )}
- %elif key.startswith( 'section' ) and val.elems:
+ %elif isinstance( val, ToolSection ) and val.elems:
<% section = val %><div class="toolSectionTitle" id="title_${section.id}"><span>${section.name}</span>
@@ -316,15 +318,15 @@
<div id="${section.id}" class="toolSectionBody"><div class="toolSectionBg">
%for section_key, section_val in section.elems.items():
- %if section_key.startswith( 'tool' ):
+ %if isinstance( section_val, Tool ):
${render_tool( section_val, True )}
- %elif section_key.startswith( 'label' ):
+ %elif isinstance( section_val, ToolSectionLabel ):
${render_label( section_val )}
%endif
%endfor
</div></div>
- %elif key.startswith( 'label' ):
+ %elif isinstance( val, ToolSectionLabel ):
${render_label( val )}
%endif
</div>
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
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8fca48f410f7/
Changeset: 8fca48f410f7
User: dan
Date: 2014-01-14 19:39:59
Summary: Fix for history api set_as_current().
Affected #: 1 file
diff -r 28575f5bd33b555cf594247f7706679df425085b -r 8fca48f410f7996cb79318118f3799856d1d3628 lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -118,7 +118,7 @@
return history_data
@web.expose_api
- def set_as_current( self, trans, id, *kwd ):
+ def set_as_current( self, trans, id, **kwd ):
"""
set_as_current( trans, id, **kwd )
* POST /api/histories/{id}/set_as_current:
https://bitbucket.org/galaxy/galaxy-central/commits/a96ca602209c/
Changeset: a96ca602209c
Branch: stable
User: dan
Date: 2014-01-14 19:39:59
Summary: Fix for history api set_as_current().
Affected #: 1 file
diff -r 97fd1b009c86ac992716ce6e81d45cb20c06a3c1 -r a96ca602209ca2d4d732cced9530a62b3e4134a8 lib/galaxy/webapps/galaxy/api/histories.py
--- a/lib/galaxy/webapps/galaxy/api/histories.py
+++ b/lib/galaxy/webapps/galaxy/api/histories.py
@@ -117,7 +117,7 @@
return history_data
@web.expose_api
- def set_as_current( self, trans, id, *kwd ):
+ def set_as_current( self, trans, id, **kwd ):
"""
set_as_current( trans, id, **kwd )
* POST /api/histories/{id}/set_as_current:
https://bitbucket.org/galaxy/galaxy-central/commits/22757ed4622e/
Changeset: 22757ed4622e
User: dan
Date: 2014-01-14 19:40:43
Summary: Merge stable
Affected #: 1 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
7 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c3a90a311c55/
Changeset: c3a90a311c55
User: martenson
Date: 2014-01-09 18:58:21
Summary: remove visualization and workflows menu items for anonymous users that can’t use them
Affected #: 2 files
diff -r 2d525856226f0115a5f8dea5e7fedfae2fcbcf8a -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a static/scripts/galaxy.menu.js
--- a/static/scripts/galaxy.menu.js
+++ b/static/scripts/galaxy.menu.js
@@ -38,13 +38,16 @@
//
// Workflow tab.
//
- var tab_workflow = new mod_masthead.GalaxyMastheadTab({
- id : "workflow",
- title : "Workflow",
- content : "workflow"
+ if (this.options.user.valid)
+ {
+ var tab_workflow = new mod_masthead.GalaxyMastheadTab({
+ id : "workflow",
+ title : "Workflow",
+ content : "workflow"
- });
- this.masthead.append(tab_workflow);
+ });
+ this.masthead.append(tab_workflow);
+ }
//
// 'Shared Items' or Libraries tab.
@@ -116,22 +119,25 @@
//
// Visualization tab.
//
- var tab_visualization = new mod_masthead.GalaxyMastheadTab({
- id : "visualization",
- title : "Visualization",
- content : "visualization/list"
- });
- tab_visualization.add({
- title : "New Track Browser",
- content : "visualization/trackster",
- target : "_frame"
- });
- tab_visualization.add({
- title : "Saved Visualizations",
- content : "visualization/list",
- target : "_frame"
- });
- this.masthead.append(tab_visualization);
+ if (this.options.user.valid)
+ {
+ var tab_visualization = new mod_masthead.GalaxyMastheadTab({
+ id : "visualization",
+ title : "Visualization",
+ content : "visualization/list"
+ });
+ tab_visualization.add({
+ title : "New Track Browser",
+ content : "visualization/trackster",
+ target : "_frame"
+ });
+ tab_visualization.add({
+ title : "Saved Visualizations",
+ content : "visualization/list",
+ target : "_frame"
+ });
+ this.masthead.append(tab_visualization);
+ }
//
// Cloud menu.
diff -r 2d525856226f0115a5f8dea5e7fedfae2fcbcf8a -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a static/scripts/packed/galaxy.menu.js
--- a/static/scripts/packed/galaxy.menu.js
+++ b/static/scripts/packed/galaxy.menu.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var d=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index"});this.masthead.append(d);var c=new b.GalaxyMastheadTab({id:"workflow",title:"Workflow",content:"workflow"});this.masthead.append(c);var g=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index"});g.add({title:"Data Libraries",content:"library/index"});g.add({title:"New Libraries",content:"library/list",divider:true});g.add({title:"Published Histories",content:"history/list_published"});g.add({title:"Published Workflows",content:"workflow/list_published"});g.add({title:"Published Visualizations",content:"visualization/list_published"});g.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(g);if(this.options.user.requests){var h=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});h.add({title:"Sequencing Requests",content:"requests/index"});h.add({title:"Find Samples",content:"requests/find_samples_index"});h.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(h)}var k=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list"});k.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});k.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"});this.masthead.append(k);if(this.options.enable_cloud_launch){var e=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});e.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(e)}if(this.options.is_admin_user){var f=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only"});this.masthead.append(f)}var j=new b.GalaxyMastheadTab({id:"help",title:"Help"});if(this.options.biostar_url){j.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});j.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}j.add({title:"Support",content:this.options.support_url,target:"_blank"});j.add({title:"Search",content:this.options.search_url,target:"_blank"});j.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});j.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});j.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});j.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){j.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(j);if(!this.options.user.valid){var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only"});i.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){i.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(i)}else{var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only"});i.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){i.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{i.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});i.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});i.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}i.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});i.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});i.add({title:"Saved Pages",content:"page/list",target:"_top"});i.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){i.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(i)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
+define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var d=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index"});this.masthead.append(d);if(this.options.user.valid){var c=new b.GalaxyMastheadTab({id:"workflow",title:"Workflow",content:"workflow"});this.masthead.append(c)}var g=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index"});g.add({title:"Data Libraries",content:"library/index"});g.add({title:"New Libraries",content:"library/list",divider:true});g.add({title:"Published Histories",content:"history/list_published"});g.add({title:"Published Workflows",content:"workflow/list_published"});g.add({title:"Published Visualizations",content:"visualization/list_published"});g.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(g);if(this.options.user.requests){var h=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});h.add({title:"Sequencing Requests",content:"requests/index"});h.add({title:"Find Samples",content:"requests/find_samples_index"});h.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(h)}if(this.options.user.valid){var k=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list"});k.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});k.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"});this.masthead.append(k)}if(this.options.enable_cloud_launch){var e=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});e.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(e)}if(this.options.is_admin_user){var f=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only"});this.masthead.append(f)}var j=new b.GalaxyMastheadTab({id:"help",title:"Help"});if(this.options.biostar_url){j.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});j.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}j.add({title:"Support",content:this.options.support_url,target:"_blank"});j.add({title:"Search",content:this.options.search_url,target:"_blank"});j.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});j.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});j.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});j.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){j.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(j);if(!this.options.user.valid){var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only"});i.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){i.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(i)}else{var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only"});i.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){i.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{i.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});i.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});i.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}i.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});i.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});i.add({title:"Saved Pages",content:"page/list",target:"_top"});i.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){i.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(i)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/a2a02033743a/
Changeset: a2a02033743a
User: martenson
Date: 2014-01-13 18:26:30
Summary: disable visualization and workflows menu items for anonymous users that can’t use them, show notification of clicked, also add possibility to use titles in menu (shown by default on hoover)
Affected #: 6 files
diff -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a -r a2a02033743a08357cf73cb7812006c4a69b7fb2 static/scripts/galaxy.masthead.js
--- a/static/scripts/galaxy.masthead.js
+++ b/static/scripts/galaxy.masthead.js
@@ -3,7 +3,7 @@
*/
// dependencies
-define(["utils/galaxy.utils"], function(mod_utils) {
+define(["utils/galaxy.utils", "libs/toastr"], function(mod_utils, mod_toastr) {
// masthead
var GalaxyMasthead = Backbone.View.extend(
@@ -259,7 +259,9 @@
type : 'url',
scratchbook : false,
on_unload : null,
- visible : true
+ visible : true,
+ disabled : false,
+ title_attribute : ''
},
// location
@@ -288,6 +290,12 @@
// add template for tab
this.setElement($(this._template(this.options)));
+ // disable menu items that are not available to anonymous user
+ // also show title to explain why they are disabled
+ if (this.options.disabled){
+ $(this.el).find('.root').addClass('disabled');
+ }
+
// visiblity
if (!this.options.visible)
this.hide();
@@ -371,9 +379,14 @@
// prevent default
e.preventDefault();
+ if (this.options.disabled){
+ mod_toastr.info('Please <a href="/user/create">register</a> to use this feature');
+ return
+ }
+
// check for menu options
if (!this.$menu) {
- Galaxy.frame.add(this.options);
+ Galaxy.frame.add(this.options);
}
},
@@ -400,7 +413,7 @@
// start template
var tmpl = '<ul id="' + options.id + '" class="nav navbar-nav" border="0" cellspacing="0">' +
'<li class="root dropdown" style="">' +
- '<a class="head dropdown-toggle" data-toggle="dropdown" target="' + options.target + '" href="' + options.content + '">' +
+ '<a class="head dropdown-toggle" data-toggle="dropdown" target="' + options.target + '" href="' + options.content + '" title="' + options.title_attribute + '">' +
options.title + '<b class="symbol"></b>' +
'</a>' +
'</li>' +
diff -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a -r a2a02033743a08357cf73cb7812006c4a69b7fb2 static/scripts/galaxy.menu.js
--- a/static/scripts/galaxy.menu.js
+++ b/static/scripts/galaxy.menu.js
@@ -31,23 +31,28 @@
var tab_analysis = new mod_masthead.GalaxyMastheadTab({
id : "analysis",
title : "Analyze Data",
- content : "root/index"
+ content : "root/index",
+ title_attribute : 'Main analysis page'
});
this.masthead.append(tab_analysis);
//
// Workflow tab.
//
- if (this.options.user.valid)
- {
- var tab_workflow = new mod_masthead.GalaxyMastheadTab({
- id : "workflow",
- title : "Workflow",
- content : "workflow"
- });
- this.masthead.append(tab_workflow);
+ var workflow_options = {
+ id : "workflow",
+ title : "Workflow",
+ content : "workflow",
+ title_attribute : 'Chain tools into workflows'
}
+ if (!this.options.user.valid){
+ workflow_options.disabled = true; // disable workflows for anonymous users
+ workflow_options.title_attribute = 'Please register to use workflows';
+ }
+
+ var tab_workflow = new mod_masthead.GalaxyMastheadTab(workflow_options);
+ this.masthead.append(tab_workflow);
//
// 'Shared Items' or Libraries tab.
@@ -55,7 +60,8 @@
var tab_shared = new mod_masthead.GalaxyMastheadTab({
id : "shared",
title : "Shared Data",
- content : "library/index"
+ content : "library/index",
+ title_attribute : 'Access public data'
});
tab_shared.add({
@@ -64,7 +70,7 @@
});
tab_shared.add({
- title : "New Libraries",
+ title : "Data Libraries Beta",
content : "library/list",
divider : true
});
@@ -124,7 +130,8 @@
var tab_visualization = new mod_masthead.GalaxyMastheadTab({
id : "visualization",
title : "Visualization",
- content : "visualization/list"
+ content : "visualization/list",
+ title_attribute : 'Visualize data'
});
tab_visualization.add({
title : "New Track Browser",
@@ -137,6 +144,16 @@
target : "_frame"
});
this.masthead.append(tab_visualization);
+ } else
+ {
+ var tab_visualization = new mod_masthead.GalaxyMastheadTab({
+ id : "visualization",
+ title : "Visualization",
+ content : "visualization/list",
+ disabled : true,
+ title_attribute : 'Please register to create visualizations'
+ });
+ this.masthead.append(tab_visualization);
}
//
@@ -165,7 +182,8 @@
id : "admin",
title : "Admin",
content : "admin/index",
- extra_class : "admin-only"
+ extra_class : "admin-only",
+ title_attribute : 'Administer this Galaxy'
});
this.masthead.append(tab_admin);
}
@@ -175,7 +193,8 @@
//
var tab_help = new mod_masthead.GalaxyMastheadTab({
id : "help",
- title : "Help"
+ title : "Help",
+ title_attribute : 'Find help with Galaxy'
});
if (this.options.biostar_url)
{
@@ -238,7 +257,8 @@
var tab_user = new mod_masthead.GalaxyMastheadTab({
id : "user",
title : "User",
- extra_class : "loggedout-only"
+ extra_class : "loggedout-only",
+ title_attribute : 'Account information'
});
// login
@@ -264,7 +284,8 @@
var tab_user = new mod_masthead.GalaxyMastheadTab({
id : "user",
title : "User",
- extra_class : "loggedin-only"
+ extra_class : "loggedin-only",
+ title_attribute : 'Account information'
});
// show user logged in info
diff -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a -r a2a02033743a08357cf73cb7812006c4a69b7fb2 static/scripts/packed/galaxy.masthead.js
--- a/static/scripts/packed/galaxy.masthead.js
+++ b/static/scripts/packed/galaxy.masthead.js
@@ -1,1 +1,1 @@
-define(["utils/galaxy.utils"],function(c){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(f){this.options=f;$("body").off();this.setElement($(this._template(f)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var e=this;window.onbeforeunload=function(){var h="";for(key in e.list){if(e.list[key].options.on_unload){var g=e.list[key].options.on_unload();if(g){h+=g+" "}}}if(h!=""){return h}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(e){return this._add(e,true)},prepend:function(e){return this._add(e,false)},highlight:function(f){var e=$(this.el).find("#"+f+"> li");if(e){e.addClass("active")}},_add:function(h,f){var e=$(this.el).find("#"+h.location);if(e){var g=$(h.el);g.addClass("masthead-item");if(f){e.append(g)}else{e.prepend(g)}this.list.push(h)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(e){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+e.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+e.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(f){if(f){this.options=_.defaults(f,this.options)}this.setElement($(this._template(this.options)));var e=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",e.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(e){$(this.el).find(".icon").removeClass(this.options.icon).addClass(e);this.options.icon=e},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(e){$(this.el).find(".number").text(e)},_template:function(f){var e='<div id="'+f.id+'" class="symbol"><div class="icon fa fa-2x '+f.icon+'"></div>';if(f.with_number){e+='<div class="number"></div>'}e+="</div>";return e}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(e){if(e){this.options=_.defaults(e,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(g){var h={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(g){h=_.defaults(g,h)}if(h.content&&h.content.indexOf("//")===-1){h.content=galaxy_config.root+h.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var f=$(this._templateMenuItem(h));this.$menu.append(f);var e=this;f.on("click",function(i){i.preventDefault();if(e.options.target==="_blank"){return true}Galaxy.frame.add(g)});if(h.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(!this.$menu){Galaxy.frame.add(this.options)}},_templateMenuItem:function(e){return'<li><a href="'+e.content+'" target="'+e.target+'">'+e.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(f){var e='<ul id="'+f.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+f.target+'" href="'+f.content+'">'+f.title+'<b class="symbol"></b></a></li></ul>';return e}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
+define(["utils/galaxy.utils","libs/toastr"],function(c,e){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(g){this.options=g;$("body").off();this.setElement($(this._template(g)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var f=this;window.onbeforeunload=function(){var i="";for(key in f.list){if(f.list[key].options.on_unload){var h=f.list[key].options.on_unload();if(h){i+=h+" "}}}if(i!=""){return i}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(f){return this._add(f,true)},prepend:function(f){return this._add(f,false)},highlight:function(g){var f=$(this.el).find("#"+g+"> li");if(f){f.addClass("active")}},_add:function(i,g){var f=$(this.el).find("#"+i.location);if(f){var h=$(i.el);h.addClass("masthead-item");if(g){f.append(h)}else{f.prepend(h)}this.list.push(i)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(f){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+f.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+f.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(g){if(g){this.options=_.defaults(g,this.options)}this.setElement($(this._template(this.options)));var f=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",f.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(f){$(this.el).find(".icon").removeClass(this.options.icon).addClass(f);this.options.icon=f},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(f){$(this.el).find(".number").text(f)},_template:function(g){var f='<div id="'+g.id+'" class="symbol"><div class="icon fa fa-2x '+g.icon+'"></div>';if(g.with_number){f+='<div class="number"></div>'}f+="</div>";return f}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(f){if(f){this.options=_.defaults(f,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled")}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(h){var i={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(h){i=_.defaults(h,i)}if(i.content&&i.content.indexOf("//")===-1){i.content=galaxy_config.root+i.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var g=$(this._templateMenuItem(i));this.$menu.append(g);var f=this;g.on("click",function(j){j.preventDefault();if(f.options.target==="_blank"){return true}Galaxy.frame.add(h)});if(i.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){e.info('Please <a href="/user/create">register</a> to use this feature');return}if(!this.$menu){Galaxy.frame.add(this.options)}},_templateMenuItem:function(f){return'<li><a href="'+f.content+'" target="'+f.target+'">'+f.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(g){var f='<ul id="'+g.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+g.target+'" href="'+g.content+'" title="'+g.title_attribute+'">'+g.title+'<b class="symbol"></b></a></li></ul>';return f}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
diff -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a -r a2a02033743a08357cf73cb7812006c4a69b7fb2 static/scripts/packed/galaxy.menu.js
--- a/static/scripts/packed/galaxy.menu.js
+++ b/static/scripts/packed/galaxy.menu.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var d=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index"});this.masthead.append(d);if(this.options.user.valid){var c=new b.GalaxyMastheadTab({id:"workflow",title:"Workflow",content:"workflow"});this.masthead.append(c)}var g=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index"});g.add({title:"Data Libraries",content:"library/index"});g.add({title:"New Libraries",content:"library/list",divider:true});g.add({title:"Published Histories",content:"history/list_published"});g.add({title:"Published Workflows",content:"workflow/list_published"});g.add({title:"Published Visualizations",content:"visualization/list_published"});g.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(g);if(this.options.user.requests){var h=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});h.add({title:"Sequencing Requests",content:"requests/index"});h.add({title:"Find Samples",content:"requests/find_samples_index"});h.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(h)}if(this.options.user.valid){var k=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list"});k.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});k.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"});this.masthead.append(k)}if(this.options.enable_cloud_launch){var e=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});e.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(e)}if(this.options.is_admin_user){var f=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only"});this.masthead.append(f)}var j=new b.GalaxyMastheadTab({id:"help",title:"Help"});if(this.options.biostar_url){j.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});j.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}j.add({title:"Support",content:this.options.support_url,target:"_blank"});j.add({title:"Search",content:this.options.search_url,target:"_blank"});j.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});j.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});j.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});j.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){j.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(j);if(!this.options.user.valid){var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only"});i.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){i.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(i)}else{var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only"});i.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){i.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{i.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});i.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});i.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}i.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});i.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});i.add({title:"Saved Pages",content:"page/list",target:"_top"});i.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){i.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(i)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
+define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var d=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index",title_attribute:"Main analysis page"});this.masthead.append(d);var f={id:"workflow",title:"Workflow",content:"workflow",title_attribute:"Chain tools into workflows"};if(!this.options.user.valid){f.disabled=true;f.title_attribute="Please register to use workflows"}var c=new b.GalaxyMastheadTab(f);this.masthead.append(c);var h=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index",title_attribute:"Access public data"});h.add({title:"Data Libraries",content:"library/index"});h.add({title:"Data Libraries Beta",content:"library/list",divider:true});h.add({title:"Published Histories",content:"history/list_published"});h.add({title:"Published Workflows",content:"workflow/list_published"});h.add({title:"Published Visualizations",content:"visualization/list_published"});h.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(h);if(this.options.user.requests){var i=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});i.add({title:"Sequencing Requests",content:"requests/index"});i.add({title:"Find Samples",content:"requests/find_samples_index"});i.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(i)}if(this.options.user.valid){var l=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list",title_attribute:"Visualize data"});l.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});l.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"});this.masthead.append(l)}else{var l=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list",disabled:true,title_attribute:"Please register to create visualizations"});this.masthead.append(l)}if(this.options.enable_cloud_launch){var e=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});e.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(e)}if(this.options.is_admin_user){var g=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only",title_attribute:"Administer this Galaxy"});this.masthead.append(g)}var k=new b.GalaxyMastheadTab({id:"help",title:"Help",title_attribute:"Find help with Galaxy"});if(this.options.biostar_url){k.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});k.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}k.add({title:"Support",content:this.options.support_url,target:"_blank"});k.add({title:"Search",content:this.options.search_url,target:"_blank"});k.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});k.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});k.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});k.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){k.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(k);if(!this.options.user.valid){var j=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only",title_attribute:"Account information"});j.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){j.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(j)}else{var j=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only",title_attribute:"Account information"});j.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){j.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{j.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});j.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});j.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}j.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});j.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});j.add({title:"Saved Pages",content:"page/list",target:"_top"});j.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){j.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(j)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
diff -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a -r a2a02033743a08357cf73cb7812006c4a69b7fb2 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1295,7 +1295,7 @@
.toast-title{font-weight:bold}
.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}
.toast-message a:hover{color:#ccc;text-decoration:none}
-.toast-close-button{position:relative;right:-0.3em;top:-0.3em;float:right;font-size:20px;font-weight:bold;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;filter:alpha(opacity=80);opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=40);opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}
+.toast-close-button{position:relative;right:-0.3em;top:-0.3em;float:right;font-size:20px;font-weight:bold;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;filter:alpha(opacity=100);opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100)}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=100);opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100)}
button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}
.toast-top-full-width{top:0;right:0;width:100%}
.toast-bottom-full-width{bottom:0;right:0;width:100%}
@@ -1304,7 +1304,7 @@
.toast-bottom-right{right:12px;bottom:12px}
.toast-bottom-left{bottom:12px;left:12px}
#toast-container{position:fixed;z-index:999999;}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
-#toast-container>div{margin:4em 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px 3px 3px 3px;-webkit-border-radius:3px 3px 3px 3px;border-radius:3px 3px 3px 3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;filter:alpha(opacity=80);opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}
+#toast-container>div{margin:4em 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px 3px 3px 3px;-webkit-border-radius:3px 3px 3px 3px;border-radius:3px 3px 3px 3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;filter:alpha(opacity=100);opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100)}
#toast-container>:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}
#toast-container>.toast-info{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important}
#toast-container>.toast-error{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important}
diff -r c3a90a311c55e4ec8e95ebb043e7ad6881c06e9a -r a2a02033743a08357cf73cb7812006c4a69b7fb2 static/style/src/less/toastr.less
--- a/static/style/src/less/toastr.less
+++ b/static/style/src/less/toastr.less
@@ -63,14 +63,14 @@
color: @white;
-webkit-text-shadow: 0 1px 0 rgba(255,255,255,1);
text-shadow: 0 1px 0 rgba(255,255,255,1);
- .opacity(0.8);
+ .opacity(1);
&:hover,
&:focus {
color: @black;
text-decoration: none;
cursor: pointer;
- .opacity(0.4);
+ .opacity(1);
}
}
@@ -138,7 +138,7 @@
background-repeat: no-repeat;
.boxShadow(0 0 12px @grey);
color: @white;
- .opacity(0.8);
+ .opacity(1);
}
> :hover {
https://bitbucket.org/galaxy/galaxy-central/commits/6567a77ae7a5/
Changeset: 6567a77ae7a5
User: martenson
Date: 2014-01-13 20:07:00
Summary: galaxy top menu tweaks, titles, notification
Affected #: 4 files
diff -r a2a02033743a08357cf73cb7812006c4a69b7fb2 -r 6567a77ae7a5f5529e07be0f7594df275c6930af static/scripts/galaxy.masthead.js
--- a/static/scripts/galaxy.masthead.js
+++ b/static/scripts/galaxy.masthead.js
@@ -380,7 +380,7 @@
e.preventDefault();
if (this.options.disabled){
- mod_toastr.info('Please <a href="/user/create">register</a> to use this feature');
+ mod_toastr.info('Please <a href="/user/create">register</a> or <a href="/user/login"> log in</a> to use this feature');
return
}
diff -r a2a02033743a08357cf73cb7812006c4a69b7fb2 -r 6567a77ae7a5f5529e07be0f7594df275c6930af static/scripts/galaxy.menu.js
--- a/static/scripts/galaxy.menu.js
+++ b/static/scripts/galaxy.menu.js
@@ -32,7 +32,7 @@
id : "analysis",
title : "Analyze Data",
content : "root/index",
- title_attribute : 'Main analysis page'
+ title_attribute : 'Analysis home view'
});
this.masthead.append(tab_analysis);
@@ -48,7 +48,6 @@
}
if (!this.options.user.valid){
workflow_options.disabled = true; // disable workflows for anonymous users
- workflow_options.title_attribute = 'Please register to use workflows';
}
var tab_workflow = new mod_masthead.GalaxyMastheadTab(workflow_options);
@@ -61,7 +60,7 @@
id : "shared",
title : "Shared Data",
content : "library/index",
- title_attribute : 'Access public data'
+ title_attribute : 'Access published resources'
});
tab_shared.add({
@@ -125,14 +124,21 @@
//
// Visualization tab.
//
- if (this.options.user.valid)
+
+ var visualization_options = {
+ id : "visualization",
+ title : "Visualization",
+ content : "visualization/list",
+ title_attribute : 'Visualize datasets'
+ }
+ if (!this.options.user.valid){
+ visualization_options.disabled = true; // disable visualizations for anonymous users
+ }
+
+ var tab_visualization = new mod_masthead.GalaxyMastheadTab(visualization_options);
+
+ if (this.options.user.valid) //add submenu only when user is logged in
{
- var tab_visualization = new mod_masthead.GalaxyMastheadTab({
- id : "visualization",
- title : "Visualization",
- content : "visualization/list",
- title_attribute : 'Visualize data'
- });
tab_visualization.add({
title : "New Track Browser",
content : "visualization/trackster",
@@ -143,18 +149,8 @@
content : "visualization/list",
target : "_frame"
});
- this.masthead.append(tab_visualization);
- } else
- {
- var tab_visualization = new mod_masthead.GalaxyMastheadTab({
- id : "visualization",
- title : "Visualization",
- content : "visualization/list",
- disabled : true,
- title_attribute : 'Please register to create visualizations'
- });
- this.masthead.append(tab_visualization);
- }
+ }
+ this.masthead.append(tab_visualization);
//
// Cloud menu.
@@ -194,7 +190,7 @@
var tab_help = new mod_masthead.GalaxyMastheadTab({
id : "help",
title : "Help",
- title_attribute : 'Find help with Galaxy'
+ title_attribute : 'Support, contact, and community hubs'
});
if (this.options.biostar_url)
{
@@ -258,7 +254,7 @@
id : "user",
title : "User",
extra_class : "loggedout-only",
- title_attribute : 'Account information'
+ title_attribute : 'Account registration or login'
});
// login
@@ -285,7 +281,7 @@
id : "user",
title : "User",
extra_class : "loggedin-only",
- title_attribute : 'Account information'
+ title_attribute : 'Account preferences and saved data'
});
// show user logged in info
diff -r a2a02033743a08357cf73cb7812006c4a69b7fb2 -r 6567a77ae7a5f5529e07be0f7594df275c6930af static/scripts/packed/galaxy.masthead.js
--- a/static/scripts/packed/galaxy.masthead.js
+++ b/static/scripts/packed/galaxy.masthead.js
@@ -1,1 +1,1 @@
-define(["utils/galaxy.utils","libs/toastr"],function(c,e){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(g){this.options=g;$("body").off();this.setElement($(this._template(g)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var f=this;window.onbeforeunload=function(){var i="";for(key in f.list){if(f.list[key].options.on_unload){var h=f.list[key].options.on_unload();if(h){i+=h+" "}}}if(i!=""){return i}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(f){return this._add(f,true)},prepend:function(f){return this._add(f,false)},highlight:function(g){var f=$(this.el).find("#"+g+"> li");if(f){f.addClass("active")}},_add:function(i,g){var f=$(this.el).find("#"+i.location);if(f){var h=$(i.el);h.addClass("masthead-item");if(g){f.append(h)}else{f.prepend(h)}this.list.push(i)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(f){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+f.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+f.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(g){if(g){this.options=_.defaults(g,this.options)}this.setElement($(this._template(this.options)));var f=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",f.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(f){$(this.el).find(".icon").removeClass(this.options.icon).addClass(f);this.options.icon=f},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(f){$(this.el).find(".number").text(f)},_template:function(g){var f='<div id="'+g.id+'" class="symbol"><div class="icon fa fa-2x '+g.icon+'"></div>';if(g.with_number){f+='<div class="number"></div>'}f+="</div>";return f}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(f){if(f){this.options=_.defaults(f,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled")}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(h){var i={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(h){i=_.defaults(h,i)}if(i.content&&i.content.indexOf("//")===-1){i.content=galaxy_config.root+i.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var g=$(this._templateMenuItem(i));this.$menu.append(g);var f=this;g.on("click",function(j){j.preventDefault();if(f.options.target==="_blank"){return true}Galaxy.frame.add(h)});if(i.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){e.info('Please <a href="/user/create">register</a> to use this feature');return}if(!this.$menu){Galaxy.frame.add(this.options)}},_templateMenuItem:function(f){return'<li><a href="'+f.content+'" target="'+f.target+'">'+f.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(g){var f='<ul id="'+g.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+g.target+'" href="'+g.content+'" title="'+g.title_attribute+'">'+g.title+'<b class="symbol"></b></a></li></ul>';return f}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
+define(["utils/galaxy.utils","libs/toastr"],function(c,e){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(g){this.options=g;$("body").off();this.setElement($(this._template(g)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var f=this;window.onbeforeunload=function(){var i="";for(key in f.list){if(f.list[key].options.on_unload){var h=f.list[key].options.on_unload();if(h){i+=h+" "}}}if(i!=""){return i}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(f){return this._add(f,true)},prepend:function(f){return this._add(f,false)},highlight:function(g){var f=$(this.el).find("#"+g+"> li");if(f){f.addClass("active")}},_add:function(i,g){var f=$(this.el).find("#"+i.location);if(f){var h=$(i.el);h.addClass("masthead-item");if(g){f.append(h)}else{f.prepend(h)}this.list.push(i)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(f){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+f.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+f.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(g){if(g){this.options=_.defaults(g,this.options)}this.setElement($(this._template(this.options)));var f=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",f.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(f){$(this.el).find(".icon").removeClass(this.options.icon).addClass(f);this.options.icon=f},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(f){$(this.el).find(".number").text(f)},_template:function(g){var f='<div id="'+g.id+'" class="symbol"><div class="icon fa fa-2x '+g.icon+'"></div>';if(g.with_number){f+='<div class="number"></div>'}f+="</div>";return f}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(f){if(f){this.options=_.defaults(f,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled")}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(h){var i={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(h){i=_.defaults(h,i)}if(i.content&&i.content.indexOf("//")===-1){i.content=galaxy_config.root+i.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var g=$(this._templateMenuItem(i));this.$menu.append(g);var f=this;g.on("click",function(j){j.preventDefault();if(f.options.target==="_blank"){return true}Galaxy.frame.add(h)});if(i.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){e.info('Please <a href="/user/create">register</a> or <a href="/user/login"> log in</a> to use this feature');return}if(!this.$menu){Galaxy.frame.add(this.options)}},_templateMenuItem:function(f){return'<li><a href="'+f.content+'" target="'+f.target+'">'+f.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(g){var f='<ul id="'+g.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+g.target+'" href="'+g.content+'" title="'+g.title_attribute+'">'+g.title+'<b class="symbol"></b></a></li></ul>';return f}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
diff -r a2a02033743a08357cf73cb7812006c4a69b7fb2 -r 6567a77ae7a5f5529e07be0f7594df275c6930af static/scripts/packed/galaxy.menu.js
--- a/static/scripts/packed/galaxy.menu.js
+++ b/static/scripts/packed/galaxy.menu.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var d=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index",title_attribute:"Main analysis page"});this.masthead.append(d);var f={id:"workflow",title:"Workflow",content:"workflow",title_attribute:"Chain tools into workflows"};if(!this.options.user.valid){f.disabled=true;f.title_attribute="Please register to use workflows"}var c=new b.GalaxyMastheadTab(f);this.masthead.append(c);var h=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index",title_attribute:"Access public data"});h.add({title:"Data Libraries",content:"library/index"});h.add({title:"Data Libraries Beta",content:"library/list",divider:true});h.add({title:"Published Histories",content:"history/list_published"});h.add({title:"Published Workflows",content:"workflow/list_published"});h.add({title:"Published Visualizations",content:"visualization/list_published"});h.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(h);if(this.options.user.requests){var i=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});i.add({title:"Sequencing Requests",content:"requests/index"});i.add({title:"Find Samples",content:"requests/find_samples_index"});i.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(i)}if(this.options.user.valid){var l=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list",title_attribute:"Visualize data"});l.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});l.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"});this.masthead.append(l)}else{var l=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list",disabled:true,title_attribute:"Please register to create visualizations"});this.masthead.append(l)}if(this.options.enable_cloud_launch){var e=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});e.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(e)}if(this.options.is_admin_user){var g=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only",title_attribute:"Administer this Galaxy"});this.masthead.append(g)}var k=new b.GalaxyMastheadTab({id:"help",title:"Help",title_attribute:"Find help with Galaxy"});if(this.options.biostar_url){k.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});k.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}k.add({title:"Support",content:this.options.support_url,target:"_blank"});k.add({title:"Search",content:this.options.search_url,target:"_blank"});k.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});k.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});k.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});k.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){k.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(k);if(!this.options.user.valid){var j=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only",title_attribute:"Account information"});j.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){j.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(j)}else{var j=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only",title_attribute:"Account information"});j.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){j.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{j.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});j.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});j.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}j.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});j.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});j.add({title:"Saved Pages",content:"page/list",target:"_top"});j.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){j.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(j)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
+define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var e=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index",title_attribute:"Analysis home view"});this.masthead.append(e);var g={id:"workflow",title:"Workflow",content:"workflow",title_attribute:"Chain tools into workflows"};if(!this.options.user.valid){g.disabled=true}var d=new b.GalaxyMastheadTab(g);this.masthead.append(d);var i=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index",title_attribute:"Access published resources"});i.add({title:"Data Libraries",content:"library/index"});i.add({title:"Data Libraries Beta",content:"library/list",divider:true});i.add({title:"Published Histories",content:"history/list_published"});i.add({title:"Published Workflows",content:"workflow/list_published"});i.add({title:"Published Visualizations",content:"visualization/list_published"});i.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(i);if(this.options.user.requests){var j=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});j.add({title:"Sequencing Requests",content:"requests/index"});j.add({title:"Find Samples",content:"requests/find_samples_index"});j.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(j)}var c={id:"visualization",title:"Visualization",content:"visualization/list",title_attribute:"Visualize datasets"};if(!this.options.user.valid){c.disabled=true}var m=new b.GalaxyMastheadTab(c);if(this.options.user.valid){m.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});m.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"})}this.masthead.append(m);if(this.options.enable_cloud_launch){var f=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});f.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(f)}if(this.options.is_admin_user){var h=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only",title_attribute:"Administer this Galaxy"});this.masthead.append(h)}var l=new b.GalaxyMastheadTab({id:"help",title:"Help",title_attribute:"Support, contact, and community hubs"});if(this.options.biostar_url){l.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});l.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}l.add({title:"Support",content:this.options.support_url,target:"_blank"});l.add({title:"Search",content:this.options.search_url,target:"_blank"});l.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});l.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});l.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});l.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){l.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(l);if(!this.options.user.valid){var k=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only",title_attribute:"Account registration or login"});k.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){k.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(k)}else{var k=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only",title_attribute:"Account preferences and saved data"});k.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){k.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{k.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});k.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});k.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}k.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});k.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});k.add({title:"Saved Pages",content:"page/list",target:"_top"});k.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){k.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(k)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/d0904bfb2f43/
Changeset: d0904bfb2f43
User: martenson
Date: 2014-01-13 22:51:24
Summary: galaxy top menu tweaks, titles, notification
Affected #: 4 files
diff -r 6567a77ae7a5f5529e07be0f7594df275c6930af -r d0904bfb2f43bca4847b1e3f59b4a57296390cb4 static/scripts/galaxy.masthead.js
--- a/static/scripts/galaxy.masthead.js
+++ b/static/scripts/galaxy.masthead.js
@@ -294,6 +294,7 @@
// also show title to explain why they are disabled
if (this.options.disabled){
$(this.el).find('.root').addClass('disabled');
+ this._attachPopover();
}
// visiblity
@@ -380,8 +381,7 @@
e.preventDefault();
if (this.options.disabled){
- mod_toastr.info('Please <a href="/user/create">register</a> or <a href="/user/login"> log in</a> to use this feature');
- return
+ return // prevent link following if menu item is disabled
}
// check for menu options
@@ -389,7 +389,21 @@
Galaxy.frame.add(this.options);
}
},
-
+
+ _attachPopover : function()
+ {
+ var $popover_element = $(this.el).find('.head');
+ $popover_element.popover({
+ html: true,
+ content: 'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',
+ placement: 'bottom'
+ }).on('shown.bs.popover', function() { // hooking on bootstrap event to automatically hide popovers after delay
+ setTimeout(function() {
+ $popover_element.popover('hide');
+ }, 5000);
+ });
+ },
+
// fill template header
_templateMenuItem: function (options)
{
diff -r 6567a77ae7a5f5529e07be0f7594df275c6930af -r d0904bfb2f43bca4847b1e3f59b4a57296390cb4 static/scripts/libs/bootstrap.js
--- a/static/scripts/libs/bootstrap.js
+++ b/static/scripts/libs/bootstrap.js
@@ -729,4 +729,122 @@
.on('click.bs.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
-}(window.jQuery);
\ No newline at end of file
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.3
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return this.$arrow = this.$arrow || this.tip().find('.arrow')
+ }
+
+ Popover.prototype.tip = function () {
+ if (!this.$tip) this.$tip = $(this.options.template)
+ return this.$tip
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ var old = $.fn.popover
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(jQuery);
diff -r 6567a77ae7a5f5529e07be0f7594df275c6930af -r d0904bfb2f43bca4847b1e3f59b4a57296390cb4 static/scripts/packed/galaxy.masthead.js
--- a/static/scripts/packed/galaxy.masthead.js
+++ b/static/scripts/packed/galaxy.masthead.js
@@ -1,1 +1,1 @@
-define(["utils/galaxy.utils","libs/toastr"],function(c,e){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(g){this.options=g;$("body").off();this.setElement($(this._template(g)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var f=this;window.onbeforeunload=function(){var i="";for(key in f.list){if(f.list[key].options.on_unload){var h=f.list[key].options.on_unload();if(h){i+=h+" "}}}if(i!=""){return i}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(f){return this._add(f,true)},prepend:function(f){return this._add(f,false)},highlight:function(g){var f=$(this.el).find("#"+g+"> li");if(f){f.addClass("active")}},_add:function(i,g){var f=$(this.el).find("#"+i.location);if(f){var h=$(i.el);h.addClass("masthead-item");if(g){f.append(h)}else{f.prepend(h)}this.list.push(i)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(f){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+f.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+f.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(g){if(g){this.options=_.defaults(g,this.options)}this.setElement($(this._template(this.options)));var f=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",f.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(f){$(this.el).find(".icon").removeClass(this.options.icon).addClass(f);this.options.icon=f},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(f){$(this.el).find(".number").text(f)},_template:function(g){var f='<div id="'+g.id+'" class="symbol"><div class="icon fa fa-2x '+g.icon+'"></div>';if(g.with_number){f+='<div class="number"></div>'}f+="</div>";return f}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(f){if(f){this.options=_.defaults(f,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled")}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(h){var i={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(h){i=_.defaults(h,i)}if(i.content&&i.content.indexOf("//")===-1){i.content=galaxy_config.root+i.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var g=$(this._templateMenuItem(i));this.$menu.append(g);var f=this;g.on("click",function(j){j.preventDefault();if(f.options.target==="_blank"){return true}Galaxy.frame.add(h)});if(i.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){e.info('Please <a href="/user/create">register</a> or <a href="/user/login"> log in</a> to use this feature');return}if(!this.$menu){Galaxy.frame.add(this.options)}},_templateMenuItem:function(f){return'<li><a href="'+f.content+'" target="'+f.target+'">'+f.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(g){var f='<ul id="'+g.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+g.target+'" href="'+g.content+'" title="'+g.title_attribute+'">'+g.title+'<b class="symbol"></b></a></li></ul>';return f}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
+define(["utils/galaxy.utils","libs/toastr"],function(c,e){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(g){this.options=g;$("body").off();this.setElement($(this._template(g)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var f=this;window.onbeforeunload=function(){var i="";for(key in f.list){if(f.list[key].options.on_unload){var h=f.list[key].options.on_unload();if(h){i+=h+" "}}}if(i!=""){return i}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(f){return this._add(f,true)},prepend:function(f){return this._add(f,false)},highlight:function(g){var f=$(this.el).find("#"+g+"> li");if(f){f.addClass("active")}},_add:function(i,g){var f=$(this.el).find("#"+i.location);if(f){var h=$(i.el);h.addClass("masthead-item");if(g){f.append(h)}else{f.prepend(h)}this.list.push(i)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(f){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+f.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+f.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(g){if(g){this.options=_.defaults(g,this.options)}this.setElement($(this._template(this.options)));var f=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",f.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(f){$(this.el).find(".icon").removeClass(this.options.icon).addClass(f);this.options.icon=f},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(f){$(this.el).find(".number").text(f)},_template:function(g){var f='<div id="'+g.id+'" class="symbol"><div class="icon fa fa-2x '+g.icon+'"></div>';if(g.with_number){f+='<div class="number"></div>'}f+="</div>";return f}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(f){if(f){this.options=_.defaults(f,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled");this._attachPopover()}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(h){var i={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(h){i=_.defaults(h,i)}if(i.content&&i.content.indexOf("//")===-1){i.content=galaxy_config.root+i.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var g=$(this._templateMenuItem(i));this.$menu.append(g);var f=this;g.on("click",function(j){j.preventDefault();if(f.options.target==="_blank"){return true}Galaxy.frame.add(h)});if(i.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){return}if(!this.$menu){Galaxy.frame.add(this.options)}},_attachPopover:function(){var f=$(this.el).find(".head");f.popover({html:true,content:'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',placement:"bottom"}).on("shown.bs.popover",function(){setTimeout(function(){f.popover("hide")},5000)})},_templateMenuItem:function(f){return'<li><a href="'+f.content+'" target="'+f.target+'">'+f.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(g){var f='<ul id="'+g.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+g.target+'" href="'+g.content+'" title="'+g.title_attribute+'">'+g.title+'<b class="symbol"></b></a></li></ul>';return f}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
diff -r 6567a77ae7a5f5529e07be0f7594df275c6930af -r d0904bfb2f43bca4847b1e3f59b4a57296390cb4 static/scripts/packed/libs/bootstrap.js
--- a/static/scripts/packed/libs/bootstrap.js
+++ b/static/scripts/packed/libs/bootstrap.js
@@ -1,1 +1,1 @@
-+function(b){function a(){var e=document.createElement("bootstrap");var d={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in d){if(e.style[c]!==undefined){return{end:d[c]}}}}b.fn.emulateTransitionEnd=function(e){var d=false,c=this;b(this).one(b.support.transition.end,function(){d=true});var f=function(){if(!d){b(c).trigger(b.support.transition.end)}};setTimeout(f,e);return this};b(function(){b.support.transition=a()})}(window.jQuery);+function(c){var b=function(d){this.element=c(d)};b.prototype.show=function(){var j=this.element;var g=j.closest("ul:not(.dropdown-menu)");var f=j.attr("data-target");if(!f){f=j.attr("href");f=f&&f.replace(/.*(?=#[^\s]*$)/,"")}if(j.parent("li").hasClass("active")){return}var h=g.find(".active:last a")[0];var i=c.Event("show.bs.tab",{relatedTarget:h});j.trigger(i);if(i.isDefaultPrevented()){return}var d=c(f);this.activate(j.parent("li"),g);this.activate(d,d.parent(),function(){j.trigger({type:"shown.bs.tab",relatedTarget:h})})};b.prototype.activate=function(f,e,i){var d=e.find("> .active");var h=i&&c.support.transition&&d.hasClass("fade");function g(){d.removeClass("active").find("> .dropdown-menu > .active").removeClass("active");f.addClass("active");if(h){f[0].offsetWidth;f.addClass("in")}else{f.removeClass("fade")}if(f.parent(".dropdown-menu")){f.closest("li.dropdown").addClass("active")}i&&i()}h?d.one(c.support.transition.end,g).emulateTransitionEnd(150):g();d.removeClass("in")};var a=c.fn.tab;c.fn.tab=function(d){return this.each(function(){var f=c(this);var e=f.data("bs.tab");if(!e){f.data("bs.tab",(e=new b(this)))}if(typeof d=="string"){e[d]()}})};c.fn.tab.Constructor=b;c.fn.tab.noConflict=function(){c.fn.tab=a;return this};c(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(d){d.preventDefault();c(this).tab("show")})}(window.jQuery);+function(c){var b=function(e,d){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null;this.init("tooltip",e,d)};b.DEFAULTS={animation:true,placement:"top",selector:false,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:false,container:"body"};b.prototype.init=function(k,h,f){this.enabled=true;this.type=k;this.$element=c(h);this.options=this.getOptions(f);var j=this.options.trigger.split(" ");for(var g=j.length;g--;){var e=j[g];if(e=="click"){this.$element.on("click."+this.type,this.options.selector,c.proxy(this.toggle,this))}else{if(e!="manual"){var l=e=="hover"?"mouseenter":"focus";var d=e=="hover"?"mouseleave":"blur";this.$element.on(l+"."+this.type,this.options.selector,c.proxy(this.enter,this));this.$element.on(d+"."+this.type,this.options.selector,c.proxy(this.leave,this))}}}this.options.selector?(this._options=c.extend({},this.options,{trigger:"manual",selector:""})):this.fixTitle()};b.prototype.getDefaults=function(){return b.DEFAULTS};b.prototype.getOptions=function(d){d=c.extend({},this.getDefaults(),this.$element.data(),d);if(d.delay&&typeof d.delay=="number"){d.delay={show:d.delay,hide:d.delay}}return d};b.prototype.getDelegateOptions=function(){var d={};var e=this.getDefaults();this._options&&c.each(this._options,function(f,g){if(e[f]!=g){d[f]=g}});return d};b.prototype.enter=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="in";if(!d.options.delay||!d.options.delay.show){return d.show()}d.timeout=setTimeout(function(){if(d.hoverState=="in"){d.show()}},d.options.delay.show)};b.prototype.leave=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="out";if(!d.options.delay||!d.options.delay.hide){return d.hide()}d.timeout=setTimeout(function(){if(d.hoverState=="out"){d.hide()}},d.options.delay.hide)};b.prototype.show=function(){var n=c.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(n);if(n.isDefaultPrevented()){return}var j=this.tip();this.setContent();if(this.options.animation){j.addClass("fade")}var i=typeof this.options.placement=="function"?this.options.placement.call(this,j[0],this.$element[0]):this.options.placement;var r=/\s?auto?\s?/i;var s=r.test(i);if(s){i=i.replace(r,"")||"top"}j.detach().css({top:0,left:0,display:"block"}).addClass(i);this.options.container?j.appendTo(this.options.container):j.insertAfter(this.$element);var o=this.getPosition();var d=j[0].offsetWidth;var l=j[0].offsetHeight;if(s){var h=this.$element.parent();var g=i;var p=document.documentElement.scrollTop||document.body.scrollTop;var q=this.options.container=="body"?window.innerWidth:h.outerWidth();var m=this.options.container=="body"?window.innerHeight:h.outerHeight();var k=this.options.container=="body"?0:h.offset().left;i=i=="bottom"&&o.top+o.height+l-p>m?"top":i=="top"&&o.top-p-l<0?"bottom":i=="right"&&o.right+d>q?"left":i=="left"&&o.left-d<k?"right":i;j.removeClass(g).addClass(i)}var f=this.getCalculatedOffset(i,o,d,l);this.applyPlacement(f,i);this.$element.trigger("shown.bs."+this.type)}};b.prototype.applyPlacement=function(i,j){var g;var k=this.tip();var f=k[0].offsetWidth;var n=k[0].offsetHeight;var e=parseInt(k.css("margin-top"),10);var h=parseInt(k.css("margin-left"),10);if(isNaN(e)){e=0}if(isNaN(h)){h=0}i.top=i.top+e;i.left=i.left+h;k.offset(i).addClass("in");var d=k[0].offsetWidth;var l=k[0].offsetHeight;if(j=="top"&&l!=n){g=true;i.top=i.top+n-l}if(/bottom|top/.test(j)){var m=0;if(i.left<0){m=i.left*-2;i.left=0;k.offset(i);d=k[0].offsetWidth;l=k[0].offsetHeight}this.replaceArrow(m-f+d,d,"left")}else{this.replaceArrow(l-n,l,"top")}if(g){k.offset(i)}};b.prototype.replaceArrow=function(f,e,d){this.arrow().css(d,f?(50*(1-f/e)+"%"):"")};b.prototype.setContent=function(){var e=this.tip();var d=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](d);e.removeClass("fade in top bottom left right")};b.prototype.hide=function(){var f=this;var h=this.tip();var g=c.Event("hide.bs."+this.type);function d(){if(f.hoverState!="in"){h.detach()}}this.$element.trigger(g);if(g.isDefaultPrevented()){return}h.removeClass("in");c.support.transition&&this.$tip.hasClass("fade")?h.one(c.support.transition.end,d).emulateTransitionEnd(150):d();this.$element.trigger("hidden.bs."+this.type);return this};b.prototype.fixTitle=function(){var d=this.$element;if(d.attr("title")||typeof(d.attr("data-original-title"))!="string"){d.attr("data-original-title",d.attr("title")||"").attr("title","")}};b.prototype.hasContent=function(){return this.getTitle()};b.prototype.getPosition=function(){var d=this.$element[0];return c.extend({},(typeof d.getBoundingClientRect=="function")?d.getBoundingClientRect():{width:d.offsetWidth,height:d.offsetHeight},this.$element.offset())};b.prototype.getCalculatedOffset=function(d,g,e,f){return d=="bottom"?{top:g.top+g.height,left:g.left+g.width/2-e/2}:d=="top"?{top:g.top-f,left:g.left+g.width/2-e/2}:d=="left"?{top:g.top+g.height/2-f/2,left:g.left-e}:{top:g.top+g.height/2-f/2,left:g.left+g.width}};b.prototype.getTitle=function(){var f;var d=this.$element;var e=this.options;f=d.attr("data-original-title")||(typeof e.title=="function"?e.title.call(d[0]):e.title);return f};b.prototype.tip=function(){return this.$tip=this.$tip||c(this.options.template)};b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")};b.prototype.validate=function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}};b.prototype.enable=function(){this.enabled=true};b.prototype.disable=function(){this.enabled=false};b.prototype.toggleEnabled=function(){this.enabled=!this.enabled};b.prototype.toggle=function(f){var d=f?c(f.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;d.tip().hasClass("in")?d.leave(d):d.enter(d)};b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var a=c.fn.tooltip;c.fn.tooltip=function(d){return this.each(function(){var g=c(this);var f=g.data("bs.tooltip");var e=typeof d=="object"&&d;if(!f){g.data("bs.tooltip",(f=new b(this,e)))}if(typeof d=="string"){f[d]()}})};c.fn.tooltip.Constructor=b;c.fn.tooltip.noConflict=function(){c.fn.tooltip=a;return this}}(window.jQuery);+function(g){var e=".dropdown-backdrop";var b="[data-toggle=dropdown]";var a=function(i){var h=g(i).on("click.bs.dropdown",this.toggle)};a.prototype.toggle=function(k){var j=g(this);if(j.is(".disabled, :disabled")){return}var i=f(j);var h=i.hasClass("open");d();if(!h){if("ontouchstart" in document.documentElement&&!i.closest(".navbar-nav").length){g('<div class="dropdown-backdrop"/>').insertAfter(g(this)).on("click",d)}i.trigger(k=g.Event("show.bs.dropdown"));if(k.isDefaultPrevented()){return}i.toggleClass("open").trigger("shown.bs.dropdown");j.focus()}return false};a.prototype.keydown=function(l){if(!/(38|40|27)/.test(l.keyCode)){return}var k=g(this);l.preventDefault();l.stopPropagation();if(k.is(".disabled, :disabled")){return}var j=f(k);var i=j.hasClass("open");if(!i||(i&&l.keyCode==27)){if(l.which==27){j.find(b).focus()}return k.click()}var m=g("[role=menu] li:not(.divider):visible a",j);if(!m.length){return}var h=m.index(m.filter(":focus"));if(l.keyCode==38&&h>0){h--}if(l.keyCode==40&&h<m.length-1){h++}if(!~h){h=0}m.eq(h).focus()};function d(){g(e).remove();g(b).each(function(i){var h=f(g(this));if(!h.hasClass("open")){return}h.trigger(i=g.Event("hide.bs.dropdown"));if(i.isDefaultPrevented()){return}h.removeClass("open").trigger("hidden.bs.dropdown")})}function f(j){var h=j.attr("data-target");if(!h){h=j.attr("href");h=h&&/#/.test(h)&&h.replace(/.*(?=#[^\s]*$)/,"")}var i=h&&g(h);return i&&i.length?i:j.parent()}var c=g.fn.dropdown;g.fn.dropdown=function(h){return this.each(function(){var j=g(this);var i=j.data("dropdown");if(!i){j.data("dropdown",(i=new a(this)))}if(typeof h=="string"){i[h].call(j)}})};g.fn.dropdown.Constructor=a;g.fn.dropdown.noConflict=function(){g.fn.dropdown=c;return this};g(document).on("click.bs.dropdown.data-api",d).on("click.bs.dropdown.data-api",".dropdown form",function(h){h.stopPropagation()}).on("click.bs.dropdown.data-api",b,a.prototype.toggle).on("keydown.bs.dropdown.data-api",b+", [role=menu]",a.prototype.keydown)}(window.jQuery);
\ No newline at end of file
++function(b){function a(){var e=document.createElement("bootstrap");var d={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in d){if(e.style[c]!==undefined){return{end:d[c]}}}}b.fn.emulateTransitionEnd=function(e){var d=false,c=this;b(this).one(b.support.transition.end,function(){d=true});var f=function(){if(!d){b(c).trigger(b.support.transition.end)}};setTimeout(f,e);return this};b(function(){b.support.transition=a()})}(window.jQuery);+function(c){var b=function(d){this.element=c(d)};b.prototype.show=function(){var j=this.element;var g=j.closest("ul:not(.dropdown-menu)");var f=j.attr("data-target");if(!f){f=j.attr("href");f=f&&f.replace(/.*(?=#[^\s]*$)/,"")}if(j.parent("li").hasClass("active")){return}var h=g.find(".active:last a")[0];var i=c.Event("show.bs.tab",{relatedTarget:h});j.trigger(i);if(i.isDefaultPrevented()){return}var d=c(f);this.activate(j.parent("li"),g);this.activate(d,d.parent(),function(){j.trigger({type:"shown.bs.tab",relatedTarget:h})})};b.prototype.activate=function(f,e,i){var d=e.find("> .active");var h=i&&c.support.transition&&d.hasClass("fade");function g(){d.removeClass("active").find("> .dropdown-menu > .active").removeClass("active");f.addClass("active");if(h){f[0].offsetWidth;f.addClass("in")}else{f.removeClass("fade")}if(f.parent(".dropdown-menu")){f.closest("li.dropdown").addClass("active")}i&&i()}h?d.one(c.support.transition.end,g).emulateTransitionEnd(150):g();d.removeClass("in")};var a=c.fn.tab;c.fn.tab=function(d){return this.each(function(){var f=c(this);var e=f.data("bs.tab");if(!e){f.data("bs.tab",(e=new b(this)))}if(typeof d=="string"){e[d]()}})};c.fn.tab.Constructor=b;c.fn.tab.noConflict=function(){c.fn.tab=a;return this};c(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(d){d.preventDefault();c(this).tab("show")})}(window.jQuery);+function(c){var b=function(e,d){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null;this.init("tooltip",e,d)};b.DEFAULTS={animation:true,placement:"top",selector:false,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:false,container:"body"};b.prototype.init=function(k,h,f){this.enabled=true;this.type=k;this.$element=c(h);this.options=this.getOptions(f);var j=this.options.trigger.split(" ");for(var g=j.length;g--;){var e=j[g];if(e=="click"){this.$element.on("click."+this.type,this.options.selector,c.proxy(this.toggle,this))}else{if(e!="manual"){var l=e=="hover"?"mouseenter":"focus";var d=e=="hover"?"mouseleave":"blur";this.$element.on(l+"."+this.type,this.options.selector,c.proxy(this.enter,this));this.$element.on(d+"."+this.type,this.options.selector,c.proxy(this.leave,this))}}}this.options.selector?(this._options=c.extend({},this.options,{trigger:"manual",selector:""})):this.fixTitle()};b.prototype.getDefaults=function(){return b.DEFAULTS};b.prototype.getOptions=function(d){d=c.extend({},this.getDefaults(),this.$element.data(),d);if(d.delay&&typeof d.delay=="number"){d.delay={show:d.delay,hide:d.delay}}return d};b.prototype.getDelegateOptions=function(){var d={};var e=this.getDefaults();this._options&&c.each(this._options,function(f,g){if(e[f]!=g){d[f]=g}});return d};b.prototype.enter=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="in";if(!d.options.delay||!d.options.delay.show){return d.show()}d.timeout=setTimeout(function(){if(d.hoverState=="in"){d.show()}},d.options.delay.show)};b.prototype.leave=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="out";if(!d.options.delay||!d.options.delay.hide){return d.hide()}d.timeout=setTimeout(function(){if(d.hoverState=="out"){d.hide()}},d.options.delay.hide)};b.prototype.show=function(){var n=c.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(n);if(n.isDefaultPrevented()){return}var j=this.tip();this.setContent();if(this.options.animation){j.addClass("fade")}var i=typeof this.options.placement=="function"?this.options.placement.call(this,j[0],this.$element[0]):this.options.placement;var r=/\s?auto?\s?/i;var s=r.test(i);if(s){i=i.replace(r,"")||"top"}j.detach().css({top:0,left:0,display:"block"}).addClass(i);this.options.container?j.appendTo(this.options.container):j.insertAfter(this.$element);var o=this.getPosition();var d=j[0].offsetWidth;var l=j[0].offsetHeight;if(s){var h=this.$element.parent();var g=i;var p=document.documentElement.scrollTop||document.body.scrollTop;var q=this.options.container=="body"?window.innerWidth:h.outerWidth();var m=this.options.container=="body"?window.innerHeight:h.outerHeight();var k=this.options.container=="body"?0:h.offset().left;i=i=="bottom"&&o.top+o.height+l-p>m?"top":i=="top"&&o.top-p-l<0?"bottom":i=="right"&&o.right+d>q?"left":i=="left"&&o.left-d<k?"right":i;j.removeClass(g).addClass(i)}var f=this.getCalculatedOffset(i,o,d,l);this.applyPlacement(f,i);this.$element.trigger("shown.bs."+this.type)}};b.prototype.applyPlacement=function(i,j){var g;var k=this.tip();var f=k[0].offsetWidth;var n=k[0].offsetHeight;var e=parseInt(k.css("margin-top"),10);var h=parseInt(k.css("margin-left"),10);if(isNaN(e)){e=0}if(isNaN(h)){h=0}i.top=i.top+e;i.left=i.left+h;k.offset(i).addClass("in");var d=k[0].offsetWidth;var l=k[0].offsetHeight;if(j=="top"&&l!=n){g=true;i.top=i.top+n-l}if(/bottom|top/.test(j)){var m=0;if(i.left<0){m=i.left*-2;i.left=0;k.offset(i);d=k[0].offsetWidth;l=k[0].offsetHeight}this.replaceArrow(m-f+d,d,"left")}else{this.replaceArrow(l-n,l,"top")}if(g){k.offset(i)}};b.prototype.replaceArrow=function(f,e,d){this.arrow().css(d,f?(50*(1-f/e)+"%"):"")};b.prototype.setContent=function(){var e=this.tip();var d=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](d);e.removeClass("fade in top bottom left right")};b.prototype.hide=function(){var f=this;var h=this.tip();var g=c.Event("hide.bs."+this.type);function d(){if(f.hoverState!="in"){h.detach()}}this.$element.trigger(g);if(g.isDefaultPrevented()){return}h.removeClass("in");c.support.transition&&this.$tip.hasClass("fade")?h.one(c.support.transition.end,d).emulateTransitionEnd(150):d();this.$element.trigger("hidden.bs."+this.type);return this};b.prototype.fixTitle=function(){var d=this.$element;if(d.attr("title")||typeof(d.attr("data-original-title"))!="string"){d.attr("data-original-title",d.attr("title")||"").attr("title","")}};b.prototype.hasContent=function(){return this.getTitle()};b.prototype.getPosition=function(){var d=this.$element[0];return c.extend({},(typeof d.getBoundingClientRect=="function")?d.getBoundingClientRect():{width:d.offsetWidth,height:d.offsetHeight},this.$element.offset())};b.prototype.getCalculatedOffset=function(d,g,e,f){return d=="bottom"?{top:g.top+g.height,left:g.left+g.width/2-e/2}:d=="top"?{top:g.top-f,left:g.left+g.width/2-e/2}:d=="left"?{top:g.top+g.height/2-f/2,left:g.left-e}:{top:g.top+g.height/2-f/2,left:g.left+g.width}};b.prototype.getTitle=function(){var f;var d=this.$element;var e=this.options;f=d.attr("data-original-title")||(typeof e.title=="function"?e.title.call(d[0]):e.title);return f};b.prototype.tip=function(){return this.$tip=this.$tip||c(this.options.template)};b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")};b.prototype.validate=function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}};b.prototype.enable=function(){this.enabled=true};b.prototype.disable=function(){this.enabled=false};b.prototype.toggleEnabled=function(){this.enabled=!this.enabled};b.prototype.toggle=function(f){var d=f?c(f.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;d.tip().hasClass("in")?d.leave(d):d.enter(d)};b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var a=c.fn.tooltip;c.fn.tooltip=function(d){return this.each(function(){var g=c(this);var f=g.data("bs.tooltip");var e=typeof d=="object"&&d;if(!f){g.data("bs.tooltip",(f=new b(this,e)))}if(typeof d=="string"){f[d]()}})};c.fn.tooltip.Constructor=b;c.fn.tooltip.noConflict=function(){c.fn.tooltip=a;return this}}(window.jQuery);+function(g){var e=".dropdown-backdrop";var b="[data-toggle=dropdown]";var a=function(i){var h=g(i).on("click.bs.dropdown",this.toggle)};a.prototype.toggle=function(k){var j=g(this);if(j.is(".disabled, :disabled")){return}var i=f(j);var h=i.hasClass("open");d();if(!h){if("ontouchstart" in document.documentElement&&!i.closest(".navbar-nav").length){g('<div class="dropdown-backdrop"/>').insertAfter(g(this)).on("click",d)}i.trigger(k=g.Event("show.bs.dropdown"));if(k.isDefaultPrevented()){return}i.toggleClass("open").trigger("shown.bs.dropdown");j.focus()}return false};a.prototype.keydown=function(l){if(!/(38|40|27)/.test(l.keyCode)){return}var k=g(this);l.preventDefault();l.stopPropagation();if(k.is(".disabled, :disabled")){return}var j=f(k);var i=j.hasClass("open");if(!i||(i&&l.keyCode==27)){if(l.which==27){j.find(b).focus()}return k.click()}var m=g("[role=menu] li:not(.divider):visible a",j);if(!m.length){return}var h=m.index(m.filter(":focus"));if(l.keyCode==38&&h>0){h--}if(l.keyCode==40&&h<m.length-1){h++}if(!~h){h=0}m.eq(h).focus()};function d(){g(e).remove();g(b).each(function(i){var h=f(g(this));if(!h.hasClass("open")){return}h.trigger(i=g.Event("hide.bs.dropdown"));if(i.isDefaultPrevented()){return}h.removeClass("open").trigger("hidden.bs.dropdown")})}function f(j){var h=j.attr("data-target");if(!h){h=j.attr("href");h=h&&/#/.test(h)&&h.replace(/.*(?=#[^\s]*$)/,"")}var i=h&&g(h);return i&&i.length?i:j.parent()}var c=g.fn.dropdown;g.fn.dropdown=function(h){return this.each(function(){var j=g(this);var i=j.data("dropdown");if(!i){j.data("dropdown",(i=new a(this)))}if(typeof h=="string"){i[h].call(j)}})};g.fn.dropdown.Constructor=a;g.fn.dropdown.noConflict=function(){g.fn.dropdown=c;return this};g(document).on("click.bs.dropdown.data-api",d).on("click.bs.dropdown.data-api",".dropdown form",function(h){h.stopPropagation()}).on("click.bs.dropdown.data-api",b,a.prototype.toggle).on("keydown.bs.dropdown.data-api",b+", [role=menu]",a.prototype.keydown)}(window.jQuery);+function(c){var b=function(e,d){this.init("popover",e,d)};if(!c.fn.tooltip){throw new Error("Popover requires tooltip.js")}b.DEFAULTS=c.extend({},c.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'});b.prototype=c.extend({},c.fn.tooltip.Constructor.prototype);b.prototype.constructor=b;b.prototype.getDefaults=function(){return b.DEFAULTS};b.prototype.setContent=function(){var f=this.tip();var e=this.getTitle();var d=this.getContent();f.find(".popover-title")[this.options.html?"html":"text"](e);f.find(".popover-content")[this.options.html?"html":"text"](d);f.removeClass("fade top bottom left right in");if(!f.find(".popover-title").html()){f.find(".popover-title").hide()}};b.prototype.hasContent=function(){return this.getTitle()||this.getContent()};b.prototype.getContent=function(){var d=this.$element;var e=this.options;return d.attr("data-content")||(typeof e.content=="function"?e.content.call(d[0]):e.content)};b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};b.prototype.tip=function(){if(!this.$tip){this.$tip=c(this.options.template)}return this.$tip};var a=c.fn.popover;c.fn.popover=function(d){return this.each(function(){var g=c(this);var f=g.data("bs.popover");var e=typeof d=="object"&&d;if(!f){g.data("bs.popover",(f=new b(this,e)))}if(typeof d=="string"){f[d]()}})};c.fn.popover.Constructor=b;c.fn.popover.noConflict=function(){c.fn.popover=a;return this}}(jQuery);
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/302943e9dfdb/
Changeset: 302943e9dfdb
User: martenson
Date: 2014-01-14 00:04:48
Summary: galaxy top menu disabled items lighter grey….
Affected #: 2 files
diff -r d0904bfb2f43bca4847b1e3f59b4a57296390cb4 -r 302943e9dfdb8df93b0ade4dab1898963b0a3733 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -581,7 +581,7 @@
.navbar-inverse .navbar-text{color:#999}
.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}
.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#181a24}
-.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}
+.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#666;background-color:transparent}
.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}
.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}
.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#1e212d}
@@ -589,7 +589,7 @@
.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}
.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}
.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}
-@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent} .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}
+@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent} .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#666;background-color:transparent}}
.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}
.breadcrumb{padding:8px 15px;margin-bottom:17px;list-style:none;background-color:#f5f5f5;border-radius:3px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}
.breadcrumb>.active{color:#999}
diff -r d0904bfb2f43bca4847b1e3f59b4a57296390cb4 -r 302943e9dfdb8df93b0ade4dab1898963b0a3733 static/style/src/less/galaxy_bootstrap/variables.less
--- a/static/style/src/less/galaxy_bootstrap/variables.less
+++ b/static/style/src/less/galaxy_bootstrap/variables.less
@@ -344,7 +344,7 @@
@navbar-inverse-link-hover-bg: transparent;
@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;
@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);
-@navbar-inverse-link-disabled-color: #444;
+@navbar-inverse-link-disabled-color: #666;
@navbar-inverse-link-disabled-bg: transparent;
// Inverted navbar brand label
https://bitbucket.org/galaxy/galaxy-central/commits/e3d5546bfaab/
Changeset: e3d5546bfaab
User: martenson
Date: 2014-01-14 00:06:51
Summary: toaster removal form masthead
Affected #: 2 files
diff -r 302943e9dfdb8df93b0ade4dab1898963b0a3733 -r e3d5546bfaabc2d0a68e8044d3677b0bfb983831 static/scripts/galaxy.masthead.js
--- a/static/scripts/galaxy.masthead.js
+++ b/static/scripts/galaxy.masthead.js
@@ -3,7 +3,7 @@
*/
// dependencies
-define(["utils/galaxy.utils", "libs/toastr"], function(mod_utils, mod_toastr) {
+define(["utils/galaxy.utils"], function(mod_utils) {
// masthead
var GalaxyMasthead = Backbone.View.extend(
diff -r 302943e9dfdb8df93b0ade4dab1898963b0a3733 -r e3d5546bfaabc2d0a68e8044d3677b0bfb983831 static/scripts/packed/galaxy.masthead.js
--- a/static/scripts/packed/galaxy.masthead.js
+++ b/static/scripts/packed/galaxy.masthead.js
@@ -1,1 +1,1 @@
-define(["utils/galaxy.utils","libs/toastr"],function(c,e){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(g){this.options=g;$("body").off();this.setElement($(this._template(g)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var f=this;window.onbeforeunload=function(){var i="";for(key in f.list){if(f.list[key].options.on_unload){var h=f.list[key].options.on_unload();if(h){i+=h+" "}}}if(i!=""){return i}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(f){return this._add(f,true)},prepend:function(f){return this._add(f,false)},highlight:function(g){var f=$(this.el).find("#"+g+"> li");if(f){f.addClass("active")}},_add:function(i,g){var f=$(this.el).find("#"+i.location);if(f){var h=$(i.el);h.addClass("masthead-item");if(g){f.append(h)}else{f.prepend(h)}this.list.push(i)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(f){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+f.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+f.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(g){if(g){this.options=_.defaults(g,this.options)}this.setElement($(this._template(this.options)));var f=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",f.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(f){$(this.el).find(".icon").removeClass(this.options.icon).addClass(f);this.options.icon=f},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(f){$(this.el).find(".number").text(f)},_template:function(g){var f='<div id="'+g.id+'" class="symbol"><div class="icon fa fa-2x '+g.icon+'"></div>';if(g.with_number){f+='<div class="number"></div>'}f+="</div>";return f}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(f){if(f){this.options=_.defaults(f,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled");this._attachPopover()}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(h){var i={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(h){i=_.defaults(h,i)}if(i.content&&i.content.indexOf("//")===-1){i.content=galaxy_config.root+i.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var g=$(this._templateMenuItem(i));this.$menu.append(g);var f=this;g.on("click",function(j){j.preventDefault();if(f.options.target==="_blank"){return true}Galaxy.frame.add(h)});if(i.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){return}if(!this.$menu){Galaxy.frame.add(this.options)}},_attachPopover:function(){var f=$(this.el).find(".head");f.popover({html:true,content:'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',placement:"bottom"}).on("shown.bs.popover",function(){setTimeout(function(){f.popover("hide")},5000)})},_templateMenuItem:function(f){return'<li><a href="'+f.content+'" target="'+f.target+'">'+f.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(g){var f='<ul id="'+g.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+g.target+'" href="'+g.content+'" title="'+g.title_attribute+'">'+g.title+'<b class="symbol"></b></a></li></ul>';return f}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
+define(["utils/galaxy.utils"],function(c){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(f){this.options=f;$("body").off();this.setElement($(this._template(f)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var e=this;window.onbeforeunload=function(){var h="";for(key in e.list){if(e.list[key].options.on_unload){var g=e.list[key].options.on_unload();if(g){h+=g+" "}}}if(h!=""){return h}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(e){return this._add(e,true)},prepend:function(e){return this._add(e,false)},highlight:function(f){var e=$(this.el).find("#"+f+"> li");if(e){e.addClass("active")}},_add:function(h,f){var e=$(this.el).find("#"+h.location);if(e){var g=$(h.el);g.addClass("masthead-item");if(f){e.append(g)}else{e.prepend(g)}this.list.push(h)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(e){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+e.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+e.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(f){if(f){this.options=_.defaults(f,this.options)}this.setElement($(this._template(this.options)));var e=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",e.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(e){$(this.el).find(".icon").removeClass(this.options.icon).addClass(e);this.options.icon=e},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(e){$(this.el).find(".number").text(e)},_template:function(f){var e='<div id="'+f.id+'" class="symbol"><div class="icon fa fa-2x '+f.icon+'"></div>';if(f.with_number){e+='<div class="number"></div>'}e+="</div>";return e}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(e){if(e){this.options=_.defaults(e,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled");this._attachPopover()}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(g){var h={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(g){h=_.defaults(g,h)}if(h.content&&h.content.indexOf("//")===-1){h.content=galaxy_config.root+h.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var f=$(this._templateMenuItem(h));this.$menu.append(f);var e=this;f.on("click",function(i){i.preventDefault();if(e.options.target==="_blank"){return true}Galaxy.frame.add(g)});if(h.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){return}if(!this.$menu){Galaxy.frame.add(this.options)}},_attachPopover:function(){var e=$(this.el).find(".head");e.popover({html:true,content:'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',placement:"bottom"}).on("shown.bs.popover",function(){setTimeout(function(){e.popover("hide")},5000)})},_templateMenuItem:function(e){return'<li><a href="'+e.content+'" target="'+e.target+'">'+e.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(f){var e='<ul id="'+f.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+f.target+'" href="'+f.content+'" title="'+f.title_attribute+'">'+f.title+'<b class="symbol"></b></a></li></ul>';return e}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/28575f5bd33b/
Changeset: 28575f5bd33b
User: martenson
Date: 2014-01-14 19:36:06
Summary: Merged in martenson/galaxy-central-marten (pull request #293)
remove visualization and workflows menu items for anonymous users that can’t use them
Affected #: 9 files
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/scripts/galaxy.masthead.js
--- a/static/scripts/galaxy.masthead.js
+++ b/static/scripts/galaxy.masthead.js
@@ -259,7 +259,9 @@
type : 'url',
scratchbook : false,
on_unload : null,
- visible : true
+ visible : true,
+ disabled : false,
+ title_attribute : ''
},
// location
@@ -288,6 +290,13 @@
// add template for tab
this.setElement($(this._template(this.options)));
+ // disable menu items that are not available to anonymous user
+ // also show title to explain why they are disabled
+ if (this.options.disabled){
+ $(this.el).find('.root').addClass('disabled');
+ this._attachPopover();
+ }
+
// visiblity
if (!this.options.visible)
this.hide();
@@ -371,12 +380,30 @@
// prevent default
e.preventDefault();
+ if (this.options.disabled){
+ return // prevent link following if menu item is disabled
+ }
+
// check for menu options
if (!this.$menu) {
- Galaxy.frame.add(this.options);
+ Galaxy.frame.add(this.options);
}
},
-
+
+ _attachPopover : function()
+ {
+ var $popover_element = $(this.el).find('.head');
+ $popover_element.popover({
+ html: true,
+ content: 'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',
+ placement: 'bottom'
+ }).on('shown.bs.popover', function() { // hooking on bootstrap event to automatically hide popovers after delay
+ setTimeout(function() {
+ $popover_element.popover('hide');
+ }, 5000);
+ });
+ },
+
// fill template header
_templateMenuItem: function (options)
{
@@ -400,7 +427,7 @@
// start template
var tmpl = '<ul id="' + options.id + '" class="nav navbar-nav" border="0" cellspacing="0">' +
'<li class="root dropdown" style="">' +
- '<a class="head dropdown-toggle" data-toggle="dropdown" target="' + options.target + '" href="' + options.content + '">' +
+ '<a class="head dropdown-toggle" data-toggle="dropdown" target="' + options.target + '" href="' + options.content + '" title="' + options.title_attribute + '">' +
options.title + '<b class="symbol"></b>' +
'</a>' +
'</li>' +
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/scripts/galaxy.menu.js
--- a/static/scripts/galaxy.menu.js
+++ b/static/scripts/galaxy.menu.js
@@ -31,19 +31,26 @@
var tab_analysis = new mod_masthead.GalaxyMastheadTab({
id : "analysis",
title : "Analyze Data",
- content : "root/index"
+ content : "root/index",
+ title_attribute : 'Analysis home view'
});
this.masthead.append(tab_analysis);
//
// Workflow tab.
//
- var tab_workflow = new mod_masthead.GalaxyMastheadTab({
+
+ var workflow_options = {
id : "workflow",
title : "Workflow",
- content : "workflow"
+ content : "workflow",
+ title_attribute : 'Chain tools into workflows'
+ }
+ if (!this.options.user.valid){
+ workflow_options.disabled = true; // disable workflows for anonymous users
+ }
- });
+ var tab_workflow = new mod_masthead.GalaxyMastheadTab(workflow_options);
this.masthead.append(tab_workflow);
//
@@ -52,7 +59,8 @@
var tab_shared = new mod_masthead.GalaxyMastheadTab({
id : "shared",
title : "Shared Data",
- content : "library/index"
+ content : "library/index",
+ title_attribute : 'Access published resources'
});
tab_shared.add({
@@ -61,7 +69,7 @@
});
tab_shared.add({
- title : "New Libraries",
+ title : "Data Libraries Beta",
content : "library/list",
divider : true
});
@@ -116,21 +124,32 @@
//
// Visualization tab.
//
- var tab_visualization = new mod_masthead.GalaxyMastheadTab({
+
+ var visualization_options = {
id : "visualization",
title : "Visualization",
- content : "visualization/list"
- });
- tab_visualization.add({
- title : "New Track Browser",
- content : "visualization/trackster",
- target : "_frame"
- });
- tab_visualization.add({
- title : "Saved Visualizations",
content : "visualization/list",
- target : "_frame"
- });
+ title_attribute : 'Visualize datasets'
+ }
+ if (!this.options.user.valid){
+ visualization_options.disabled = true; // disable visualizations for anonymous users
+ }
+
+ var tab_visualization = new mod_masthead.GalaxyMastheadTab(visualization_options);
+
+ if (this.options.user.valid) //add submenu only when user is logged in
+ {
+ tab_visualization.add({
+ title : "New Track Browser",
+ content : "visualization/trackster",
+ target : "_frame"
+ });
+ tab_visualization.add({
+ title : "Saved Visualizations",
+ content : "visualization/list",
+ target : "_frame"
+ });
+ }
this.masthead.append(tab_visualization);
//
@@ -159,7 +178,8 @@
id : "admin",
title : "Admin",
content : "admin/index",
- extra_class : "admin-only"
+ extra_class : "admin-only",
+ title_attribute : 'Administer this Galaxy'
});
this.masthead.append(tab_admin);
}
@@ -169,7 +189,8 @@
//
var tab_help = new mod_masthead.GalaxyMastheadTab({
id : "help",
- title : "Help"
+ title : "Help",
+ title_attribute : 'Support, contact, and community hubs'
});
if (this.options.biostar_url)
{
@@ -232,7 +253,8 @@
var tab_user = new mod_masthead.GalaxyMastheadTab({
id : "user",
title : "User",
- extra_class : "loggedout-only"
+ extra_class : "loggedout-only",
+ title_attribute : 'Account registration or login'
});
// login
@@ -258,7 +280,8 @@
var tab_user = new mod_masthead.GalaxyMastheadTab({
id : "user",
title : "User",
- extra_class : "loggedin-only"
+ extra_class : "loggedin-only",
+ title_attribute : 'Account preferences and saved data'
});
// show user logged in info
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/scripts/libs/bootstrap.js
--- a/static/scripts/libs/bootstrap.js
+++ b/static/scripts/libs/bootstrap.js
@@ -729,4 +729,122 @@
.on('click.bs.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
-}(window.jQuery);
\ No newline at end of file
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.3
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return this.$arrow = this.$arrow || this.tip().find('.arrow')
+ }
+
+ Popover.prototype.tip = function () {
+ if (!this.$tip) this.$tip = $(this.options.template)
+ return this.$tip
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ var old = $.fn.popover
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(jQuery);
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/scripts/packed/galaxy.masthead.js
--- a/static/scripts/packed/galaxy.masthead.js
+++ b/static/scripts/packed/galaxy.masthead.js
@@ -1,1 +1,1 @@
-define(["utils/galaxy.utils"],function(c){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(f){this.options=f;$("body").off();this.setElement($(this._template(f)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var e=this;window.onbeforeunload=function(){var h="";for(key in e.list){if(e.list[key].options.on_unload){var g=e.list[key].options.on_unload();if(g){h+=g+" "}}}if(h!=""){return h}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(e){return this._add(e,true)},prepend:function(e){return this._add(e,false)},highlight:function(f){var e=$(this.el).find("#"+f+"> li");if(e){e.addClass("active")}},_add:function(h,f){var e=$(this.el).find("#"+h.location);if(e){var g=$(h.el);g.addClass("masthead-item");if(f){e.append(g)}else{e.prepend(g)}this.list.push(h)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(e){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+e.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+e.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(f){if(f){this.options=_.defaults(f,this.options)}this.setElement($(this._template(this.options)));var e=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",e.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(e){$(this.el).find(".icon").removeClass(this.options.icon).addClass(e);this.options.icon=e},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(e){$(this.el).find(".number").text(e)},_template:function(f){var e='<div id="'+f.id+'" class="symbol"><div class="icon fa fa-2x '+f.icon+'"></div>';if(f.with_number){e+='<div class="number"></div>'}e+="</div>";return e}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(e){if(e){this.options=_.defaults(e,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(g){var h={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(g){h=_.defaults(g,h)}if(h.content&&h.content.indexOf("//")===-1){h.content=galaxy_config.root+h.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var f=$(this._templateMenuItem(h));this.$menu.append(f);var e=this;f.on("click",function(i){i.preventDefault();if(e.options.target==="_blank"){return true}Galaxy.frame.add(g)});if(h.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(!this.$menu){Galaxy.frame.add(this.options)}},_templateMenuItem:function(e){return'<li><a href="'+e.content+'" target="'+e.target+'">'+e.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(f){var e='<ul id="'+f.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+f.target+'" href="'+f.content+'">'+f.title+'<b class="symbol"></b></a></li></ul>';return e}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
+define(["utils/galaxy.utils"],function(c){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(f){this.options=f;$("body").off();this.setElement($(this._template(f)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var e=this;window.onbeforeunload=function(){var h="";for(key in e.list){if(e.list[key].options.on_unload){var g=e.list[key].options.on_unload();if(g){h+=g+" "}}}if(h!=""){return h}}},events:{click:"_click",mousedown:function(f){f.preventDefault()}},append:function(e){return this._add(e,true)},prepend:function(e){return this._add(e,false)},highlight:function(f){var e=$(this.el).find("#"+f+"> li");if(e){e.addClass("active")}},_add:function(h,f){var e=$(this.el).find("#"+h.location);if(e){var g=$(h.el);g.addClass("masthead-item");if(f){e.append(g)}else{e.prepend(g)}this.list.push(h)}return null},_click:function(h){var g=$(this.el).find(".popup");if(g){g.hide()}var f=$(h.target).closest(".masthead-item").find(".popup");if($(h.target).hasClass("head")){f.show();this.$background.show()}else{this.$background.hide()}},_template:function(e){return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+e.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+e.brand+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,on_click:function(){alert("clicked")},on_unload:null,visible:true},location:"iconbar",initialize:function(f){if(f){this.options=_.defaults(f,this.options)}this.setElement($(this._template(this.options)));var e=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",e.options.on_click);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(e){$(this.el).find(".icon").removeClass(this.options.icon).addClass(e);this.options.icon=e},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(e){$(this.el).find(".number").text(e)},_template:function(f){var e='<div id="'+f.id+'" class="symbol"><div class="icon fa fa-2x '+f.icon+'"></div>';if(f.with_number){e+='<div class="number"></div>'}e+="</div>";return e}});var d=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,on_unload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(e){if(e){this.options=_.defaults(e,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled");this._attachPopover()}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(g){var h={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(g){h=_.defaults(g,h)}if(h.content&&h.content.indexOf("//")===-1){h.content=galaxy_config.root+h.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var f=$(this._templateMenuItem(h));this.$menu.append(f);var e=this;f.on("click",function(i){i.preventDefault();if(e.options.target==="_blank"){return true}Galaxy.frame.add(g)});if(h.divider){this.$menu.append($(this._templateDivider()))}},_head:function(f){f.preventDefault();if(this.options.disabled){return}if(!this.$menu){Galaxy.frame.add(this.options)}},_attachPopover:function(){var e=$(this.el).find(".head");e.popover({html:true,content:'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',placement:"bottom"}).on("shown.bs.popover",function(){setTimeout(function(){e.popover("hide")},5000)})},_templateMenuItem:function(e){return'<li><a href="'+e.content+'" target="'+e.target+'">'+e.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(f){var e='<ul id="'+f.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+f.target+'" href="'+f.content+'" title="'+f.title_attribute+'">'+f.title+'<b class="symbol"></b></a></li></ul>';return e}});return{GalaxyMasthead:a,GalaxyMastheadTab:d,GalaxyMastheadIcon:b}});
\ No newline at end of file
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/scripts/packed/galaxy.menu.js
--- a/static/scripts/packed/galaxy.menu.js
+++ b/static/scripts/packed/galaxy.menu.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var d=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index"});this.masthead.append(d);var c=new b.GalaxyMastheadTab({id:"workflow",title:"Workflow",content:"workflow"});this.masthead.append(c);var g=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index"});g.add({title:"Data Libraries",content:"library/index"});g.add({title:"New Libraries",content:"library/list",divider:true});g.add({title:"Published Histories",content:"history/list_published"});g.add({title:"Published Workflows",content:"workflow/list_published"});g.add({title:"Published Visualizations",content:"visualization/list_published"});g.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(g);if(this.options.user.requests){var h=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});h.add({title:"Sequencing Requests",content:"requests/index"});h.add({title:"Find Samples",content:"requests/find_samples_index"});h.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(h)}var k=new b.GalaxyMastheadTab({id:"visualization",title:"Visualization",content:"visualization/list"});k.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});k.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"});this.masthead.append(k);if(this.options.enable_cloud_launch){var e=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});e.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(e)}if(this.options.is_admin_user){var f=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only"});this.masthead.append(f)}var j=new b.GalaxyMastheadTab({id:"help",title:"Help"});if(this.options.biostar_url){j.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});j.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}j.add({title:"Support",content:this.options.support_url,target:"_blank"});j.add({title:"Search",content:this.options.search_url,target:"_blank"});j.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});j.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});j.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});j.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){j.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(j);if(!this.options.user.valid){var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only"});i.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){i.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(i)}else{var i=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only"});i.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){i.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{i.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});i.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});i.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}i.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});i.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});i.add({title:"Saved Pages",content:"page/list",target:"_top"});i.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){i.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(i)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
+define(["galaxy.masthead"],function(b){var a=Backbone.Model.extend({options:null,masthead:null,initialize:function(c){this.options=c.config;this.masthead=c.masthead;this.create()},create:function(){var e=new b.GalaxyMastheadTab({id:"analysis",title:"Analyze Data",content:"root/index",title_attribute:"Analysis home view"});this.masthead.append(e);var g={id:"workflow",title:"Workflow",content:"workflow",title_attribute:"Chain tools into workflows"};if(!this.options.user.valid){g.disabled=true}var d=new b.GalaxyMastheadTab(g);this.masthead.append(d);var i=new b.GalaxyMastheadTab({id:"shared",title:"Shared Data",content:"library/index",title_attribute:"Access published resources"});i.add({title:"Data Libraries",content:"library/index"});i.add({title:"Data Libraries Beta",content:"library/list",divider:true});i.add({title:"Published Histories",content:"history/list_published"});i.add({title:"Published Workflows",content:"workflow/list_published"});i.add({title:"Published Visualizations",content:"visualization/list_published"});i.add({title:"Published Pages",content:"page/list_published"});this.masthead.append(i);if(this.options.user.requests){var j=new b.GalaxyMastheadTab({id:"lab",title:"Lab"});j.add({title:"Sequencing Requests",content:"requests/index"});j.add({title:"Find Samples",content:"requests/find_samples_index"});j.add({title:"Help",content:this.options.lims_doc_url});this.masthead.append(j)}var c={id:"visualization",title:"Visualization",content:"visualization/list",title_attribute:"Visualize datasets"};if(!this.options.user.valid){c.disabled=true}var m=new b.GalaxyMastheadTab(c);if(this.options.user.valid){m.add({title:"New Track Browser",content:"visualization/trackster",target:"_frame"});m.add({title:"Saved Visualizations",content:"visualization/list",target:"_frame"})}this.masthead.append(m);if(this.options.enable_cloud_launch){var f=new b.GalaxyMastheadTab({id:"cloud",title:"Cloud",content:"cloudlaunch/index"});f.add({title:"New Cloud Cluster",content:"cloudlaunch/index"});this.masthead.append(f)}if(this.options.is_admin_user){var h=new b.GalaxyMastheadTab({id:"admin",title:"Admin",content:"admin/index",extra_class:"admin-only",title_attribute:"Administer this Galaxy"});this.masthead.append(h)}var l=new b.GalaxyMastheadTab({id:"help",title:"Help",title_attribute:"Support, contact, and community hubs"});if(this.options.biostar_url){l.add({title:"Galaxy Q&A Site",content:this.options.biostar_url_redirect,target:"_blank"});l.add({title:"Ask a question",content:"biostar/biostar_question_redirect",target:"_blank"})}l.add({title:"Support",content:this.options.support_url,target:"_blank"});l.add({title:"Search",content:this.options.search_url,target:"_blank"});l.add({title:"Mailing Lists",content:this.options.mailing_lists,target:"_blank"});l.add({title:"Videos",content:this.options.screencasts_url,target:"_blank"});l.add({title:"Wiki",content:this.options.wiki_url,target:"_blank"});l.add({title:"How to Cite Galaxy",content:this.options.citation_url,target:"_blank"});if(!this.options.terms_url){l.add({title:"Terms and Conditions",content:this.options.terms_url,target:"_blank"})}this.masthead.append(l);if(!this.options.user.valid){var k=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedout-only",title_attribute:"Account registration or login"});k.add({title:"Login",content:"user/login",target:"galaxy_main"});if(this.options.allow_user_creation){k.add({title:"Register",content:"user/create",target:"galaxy_main"})}this.masthead.append(k)}else{var k=new b.GalaxyMastheadTab({id:"user",title:"User",extra_class:"loggedin-only",title_attribute:"Account preferences and saved data"});k.add({title:"Logged in as "+this.options.user.email});if(this.options.use_remote_user&&this.options.remote_user_logout_href){k.add({title:"Logout",content:this.options.remote_user_logout_href,target:"_top"})}else{k.add({title:"Preferences",content:"user?cntrller=user",target:"galaxy_main"});k.add({title:"Custom Builds",content:"user/dbkeys",target:"galaxy_main"});k.add({title:"Logout",content:"user/logout",target:"_top",divider:true})}k.add({title:"Saved Histories",content:"history/list",target:"galaxy_main"});k.add({title:"Saved Datasets",content:"dataset/list",target:"galaxy_main"});k.add({title:"Saved Pages",content:"page/list",target:"_top"});k.add({title:"API Keys",content:"user/api_keys?cntrller=user",target:"galaxy_main"});if(this.options.use_remote_user){k.add({title:"Public Name",content:"user/edit_username?cntrller=user",target:"galaxy_main"})}this.masthead.append(k)}if(this.options.active_view){this.masthead.highlight(this.options.active_view)}}});return{GalaxyMenu:a}});
\ No newline at end of file
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/scripts/packed/libs/bootstrap.js
--- a/static/scripts/packed/libs/bootstrap.js
+++ b/static/scripts/packed/libs/bootstrap.js
@@ -1,1 +1,1 @@
-+function(b){function a(){var e=document.createElement("bootstrap");var d={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in d){if(e.style[c]!==undefined){return{end:d[c]}}}}b.fn.emulateTransitionEnd=function(e){var d=false,c=this;b(this).one(b.support.transition.end,function(){d=true});var f=function(){if(!d){b(c).trigger(b.support.transition.end)}};setTimeout(f,e);return this};b(function(){b.support.transition=a()})}(window.jQuery);+function(c){var b=function(d){this.element=c(d)};b.prototype.show=function(){var j=this.element;var g=j.closest("ul:not(.dropdown-menu)");var f=j.attr("data-target");if(!f){f=j.attr("href");f=f&&f.replace(/.*(?=#[^\s]*$)/,"")}if(j.parent("li").hasClass("active")){return}var h=g.find(".active:last a")[0];var i=c.Event("show.bs.tab",{relatedTarget:h});j.trigger(i);if(i.isDefaultPrevented()){return}var d=c(f);this.activate(j.parent("li"),g);this.activate(d,d.parent(),function(){j.trigger({type:"shown.bs.tab",relatedTarget:h})})};b.prototype.activate=function(f,e,i){var d=e.find("> .active");var h=i&&c.support.transition&&d.hasClass("fade");function g(){d.removeClass("active").find("> .dropdown-menu > .active").removeClass("active");f.addClass("active");if(h){f[0].offsetWidth;f.addClass("in")}else{f.removeClass("fade")}if(f.parent(".dropdown-menu")){f.closest("li.dropdown").addClass("active")}i&&i()}h?d.one(c.support.transition.end,g).emulateTransitionEnd(150):g();d.removeClass("in")};var a=c.fn.tab;c.fn.tab=function(d){return this.each(function(){var f=c(this);var e=f.data("bs.tab");if(!e){f.data("bs.tab",(e=new b(this)))}if(typeof d=="string"){e[d]()}})};c.fn.tab.Constructor=b;c.fn.tab.noConflict=function(){c.fn.tab=a;return this};c(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(d){d.preventDefault();c(this).tab("show")})}(window.jQuery);+function(c){var b=function(e,d){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null;this.init("tooltip",e,d)};b.DEFAULTS={animation:true,placement:"top",selector:false,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:false,container:"body"};b.prototype.init=function(k,h,f){this.enabled=true;this.type=k;this.$element=c(h);this.options=this.getOptions(f);var j=this.options.trigger.split(" ");for(var g=j.length;g--;){var e=j[g];if(e=="click"){this.$element.on("click."+this.type,this.options.selector,c.proxy(this.toggle,this))}else{if(e!="manual"){var l=e=="hover"?"mouseenter":"focus";var d=e=="hover"?"mouseleave":"blur";this.$element.on(l+"."+this.type,this.options.selector,c.proxy(this.enter,this));this.$element.on(d+"."+this.type,this.options.selector,c.proxy(this.leave,this))}}}this.options.selector?(this._options=c.extend({},this.options,{trigger:"manual",selector:""})):this.fixTitle()};b.prototype.getDefaults=function(){return b.DEFAULTS};b.prototype.getOptions=function(d){d=c.extend({},this.getDefaults(),this.$element.data(),d);if(d.delay&&typeof d.delay=="number"){d.delay={show:d.delay,hide:d.delay}}return d};b.prototype.getDelegateOptions=function(){var d={};var e=this.getDefaults();this._options&&c.each(this._options,function(f,g){if(e[f]!=g){d[f]=g}});return d};b.prototype.enter=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="in";if(!d.options.delay||!d.options.delay.show){return d.show()}d.timeout=setTimeout(function(){if(d.hoverState=="in"){d.show()}},d.options.delay.show)};b.prototype.leave=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="out";if(!d.options.delay||!d.options.delay.hide){return d.hide()}d.timeout=setTimeout(function(){if(d.hoverState=="out"){d.hide()}},d.options.delay.hide)};b.prototype.show=function(){var n=c.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(n);if(n.isDefaultPrevented()){return}var j=this.tip();this.setContent();if(this.options.animation){j.addClass("fade")}var i=typeof this.options.placement=="function"?this.options.placement.call(this,j[0],this.$element[0]):this.options.placement;var r=/\s?auto?\s?/i;var s=r.test(i);if(s){i=i.replace(r,"")||"top"}j.detach().css({top:0,left:0,display:"block"}).addClass(i);this.options.container?j.appendTo(this.options.container):j.insertAfter(this.$element);var o=this.getPosition();var d=j[0].offsetWidth;var l=j[0].offsetHeight;if(s){var h=this.$element.parent();var g=i;var p=document.documentElement.scrollTop||document.body.scrollTop;var q=this.options.container=="body"?window.innerWidth:h.outerWidth();var m=this.options.container=="body"?window.innerHeight:h.outerHeight();var k=this.options.container=="body"?0:h.offset().left;i=i=="bottom"&&o.top+o.height+l-p>m?"top":i=="top"&&o.top-p-l<0?"bottom":i=="right"&&o.right+d>q?"left":i=="left"&&o.left-d<k?"right":i;j.removeClass(g).addClass(i)}var f=this.getCalculatedOffset(i,o,d,l);this.applyPlacement(f,i);this.$element.trigger("shown.bs."+this.type)}};b.prototype.applyPlacement=function(i,j){var g;var k=this.tip();var f=k[0].offsetWidth;var n=k[0].offsetHeight;var e=parseInt(k.css("margin-top"),10);var h=parseInt(k.css("margin-left"),10);if(isNaN(e)){e=0}if(isNaN(h)){h=0}i.top=i.top+e;i.left=i.left+h;k.offset(i).addClass("in");var d=k[0].offsetWidth;var l=k[0].offsetHeight;if(j=="top"&&l!=n){g=true;i.top=i.top+n-l}if(/bottom|top/.test(j)){var m=0;if(i.left<0){m=i.left*-2;i.left=0;k.offset(i);d=k[0].offsetWidth;l=k[0].offsetHeight}this.replaceArrow(m-f+d,d,"left")}else{this.replaceArrow(l-n,l,"top")}if(g){k.offset(i)}};b.prototype.replaceArrow=function(f,e,d){this.arrow().css(d,f?(50*(1-f/e)+"%"):"")};b.prototype.setContent=function(){var e=this.tip();var d=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](d);e.removeClass("fade in top bottom left right")};b.prototype.hide=function(){var f=this;var h=this.tip();var g=c.Event("hide.bs."+this.type);function d(){if(f.hoverState!="in"){h.detach()}}this.$element.trigger(g);if(g.isDefaultPrevented()){return}h.removeClass("in");c.support.transition&&this.$tip.hasClass("fade")?h.one(c.support.transition.end,d).emulateTransitionEnd(150):d();this.$element.trigger("hidden.bs."+this.type);return this};b.prototype.fixTitle=function(){var d=this.$element;if(d.attr("title")||typeof(d.attr("data-original-title"))!="string"){d.attr("data-original-title",d.attr("title")||"").attr("title","")}};b.prototype.hasContent=function(){return this.getTitle()};b.prototype.getPosition=function(){var d=this.$element[0];return c.extend({},(typeof d.getBoundingClientRect=="function")?d.getBoundingClientRect():{width:d.offsetWidth,height:d.offsetHeight},this.$element.offset())};b.prototype.getCalculatedOffset=function(d,g,e,f){return d=="bottom"?{top:g.top+g.height,left:g.left+g.width/2-e/2}:d=="top"?{top:g.top-f,left:g.left+g.width/2-e/2}:d=="left"?{top:g.top+g.height/2-f/2,left:g.left-e}:{top:g.top+g.height/2-f/2,left:g.left+g.width}};b.prototype.getTitle=function(){var f;var d=this.$element;var e=this.options;f=d.attr("data-original-title")||(typeof e.title=="function"?e.title.call(d[0]):e.title);return f};b.prototype.tip=function(){return this.$tip=this.$tip||c(this.options.template)};b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")};b.prototype.validate=function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}};b.prototype.enable=function(){this.enabled=true};b.prototype.disable=function(){this.enabled=false};b.prototype.toggleEnabled=function(){this.enabled=!this.enabled};b.prototype.toggle=function(f){var d=f?c(f.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;d.tip().hasClass("in")?d.leave(d):d.enter(d)};b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var a=c.fn.tooltip;c.fn.tooltip=function(d){return this.each(function(){var g=c(this);var f=g.data("bs.tooltip");var e=typeof d=="object"&&d;if(!f){g.data("bs.tooltip",(f=new b(this,e)))}if(typeof d=="string"){f[d]()}})};c.fn.tooltip.Constructor=b;c.fn.tooltip.noConflict=function(){c.fn.tooltip=a;return this}}(window.jQuery);+function(g){var e=".dropdown-backdrop";var b="[data-toggle=dropdown]";var a=function(i){var h=g(i).on("click.bs.dropdown",this.toggle)};a.prototype.toggle=function(k){var j=g(this);if(j.is(".disabled, :disabled")){return}var i=f(j);var h=i.hasClass("open");d();if(!h){if("ontouchstart" in document.documentElement&&!i.closest(".navbar-nav").length){g('<div class="dropdown-backdrop"/>').insertAfter(g(this)).on("click",d)}i.trigger(k=g.Event("show.bs.dropdown"));if(k.isDefaultPrevented()){return}i.toggleClass("open").trigger("shown.bs.dropdown");j.focus()}return false};a.prototype.keydown=function(l){if(!/(38|40|27)/.test(l.keyCode)){return}var k=g(this);l.preventDefault();l.stopPropagation();if(k.is(".disabled, :disabled")){return}var j=f(k);var i=j.hasClass("open");if(!i||(i&&l.keyCode==27)){if(l.which==27){j.find(b).focus()}return k.click()}var m=g("[role=menu] li:not(.divider):visible a",j);if(!m.length){return}var h=m.index(m.filter(":focus"));if(l.keyCode==38&&h>0){h--}if(l.keyCode==40&&h<m.length-1){h++}if(!~h){h=0}m.eq(h).focus()};function d(){g(e).remove();g(b).each(function(i){var h=f(g(this));if(!h.hasClass("open")){return}h.trigger(i=g.Event("hide.bs.dropdown"));if(i.isDefaultPrevented()){return}h.removeClass("open").trigger("hidden.bs.dropdown")})}function f(j){var h=j.attr("data-target");if(!h){h=j.attr("href");h=h&&/#/.test(h)&&h.replace(/.*(?=#[^\s]*$)/,"")}var i=h&&g(h);return i&&i.length?i:j.parent()}var c=g.fn.dropdown;g.fn.dropdown=function(h){return this.each(function(){var j=g(this);var i=j.data("dropdown");if(!i){j.data("dropdown",(i=new a(this)))}if(typeof h=="string"){i[h].call(j)}})};g.fn.dropdown.Constructor=a;g.fn.dropdown.noConflict=function(){g.fn.dropdown=c;return this};g(document).on("click.bs.dropdown.data-api",d).on("click.bs.dropdown.data-api",".dropdown form",function(h){h.stopPropagation()}).on("click.bs.dropdown.data-api",b,a.prototype.toggle).on("keydown.bs.dropdown.data-api",b+", [role=menu]",a.prototype.keydown)}(window.jQuery);
\ No newline at end of file
++function(b){function a(){var e=document.createElement("bootstrap");var d={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in d){if(e.style[c]!==undefined){return{end:d[c]}}}}b.fn.emulateTransitionEnd=function(e){var d=false,c=this;b(this).one(b.support.transition.end,function(){d=true});var f=function(){if(!d){b(c).trigger(b.support.transition.end)}};setTimeout(f,e);return this};b(function(){b.support.transition=a()})}(window.jQuery);+function(c){var b=function(d){this.element=c(d)};b.prototype.show=function(){var j=this.element;var g=j.closest("ul:not(.dropdown-menu)");var f=j.attr("data-target");if(!f){f=j.attr("href");f=f&&f.replace(/.*(?=#[^\s]*$)/,"")}if(j.parent("li").hasClass("active")){return}var h=g.find(".active:last a")[0];var i=c.Event("show.bs.tab",{relatedTarget:h});j.trigger(i);if(i.isDefaultPrevented()){return}var d=c(f);this.activate(j.parent("li"),g);this.activate(d,d.parent(),function(){j.trigger({type:"shown.bs.tab",relatedTarget:h})})};b.prototype.activate=function(f,e,i){var d=e.find("> .active");var h=i&&c.support.transition&&d.hasClass("fade");function g(){d.removeClass("active").find("> .dropdown-menu > .active").removeClass("active");f.addClass("active");if(h){f[0].offsetWidth;f.addClass("in")}else{f.removeClass("fade")}if(f.parent(".dropdown-menu")){f.closest("li.dropdown").addClass("active")}i&&i()}h?d.one(c.support.transition.end,g).emulateTransitionEnd(150):g();d.removeClass("in")};var a=c.fn.tab;c.fn.tab=function(d){return this.each(function(){var f=c(this);var e=f.data("bs.tab");if(!e){f.data("bs.tab",(e=new b(this)))}if(typeof d=="string"){e[d]()}})};c.fn.tab.Constructor=b;c.fn.tab.noConflict=function(){c.fn.tab=a;return this};c(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(d){d.preventDefault();c(this).tab("show")})}(window.jQuery);+function(c){var b=function(e,d){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null;this.init("tooltip",e,d)};b.DEFAULTS={animation:true,placement:"top",selector:false,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:false,container:"body"};b.prototype.init=function(k,h,f){this.enabled=true;this.type=k;this.$element=c(h);this.options=this.getOptions(f);var j=this.options.trigger.split(" ");for(var g=j.length;g--;){var e=j[g];if(e=="click"){this.$element.on("click."+this.type,this.options.selector,c.proxy(this.toggle,this))}else{if(e!="manual"){var l=e=="hover"?"mouseenter":"focus";var d=e=="hover"?"mouseleave":"blur";this.$element.on(l+"."+this.type,this.options.selector,c.proxy(this.enter,this));this.$element.on(d+"."+this.type,this.options.selector,c.proxy(this.leave,this))}}}this.options.selector?(this._options=c.extend({},this.options,{trigger:"manual",selector:""})):this.fixTitle()};b.prototype.getDefaults=function(){return b.DEFAULTS};b.prototype.getOptions=function(d){d=c.extend({},this.getDefaults(),this.$element.data(),d);if(d.delay&&typeof d.delay=="number"){d.delay={show:d.delay,hide:d.delay}}return d};b.prototype.getDelegateOptions=function(){var d={};var e=this.getDefaults();this._options&&c.each(this._options,function(f,g){if(e[f]!=g){d[f]=g}});return d};b.prototype.enter=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="in";if(!d.options.delay||!d.options.delay.show){return d.show()}d.timeout=setTimeout(function(){if(d.hoverState=="in"){d.show()}},d.options.delay.show)};b.prototype.leave=function(e){var d=e instanceof this.constructor?e:c(e.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);clearTimeout(d.timeout);d.hoverState="out";if(!d.options.delay||!d.options.delay.hide){return d.hide()}d.timeout=setTimeout(function(){if(d.hoverState=="out"){d.hide()}},d.options.delay.hide)};b.prototype.show=function(){var n=c.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(n);if(n.isDefaultPrevented()){return}var j=this.tip();this.setContent();if(this.options.animation){j.addClass("fade")}var i=typeof this.options.placement=="function"?this.options.placement.call(this,j[0],this.$element[0]):this.options.placement;var r=/\s?auto?\s?/i;var s=r.test(i);if(s){i=i.replace(r,"")||"top"}j.detach().css({top:0,left:0,display:"block"}).addClass(i);this.options.container?j.appendTo(this.options.container):j.insertAfter(this.$element);var o=this.getPosition();var d=j[0].offsetWidth;var l=j[0].offsetHeight;if(s){var h=this.$element.parent();var g=i;var p=document.documentElement.scrollTop||document.body.scrollTop;var q=this.options.container=="body"?window.innerWidth:h.outerWidth();var m=this.options.container=="body"?window.innerHeight:h.outerHeight();var k=this.options.container=="body"?0:h.offset().left;i=i=="bottom"&&o.top+o.height+l-p>m?"top":i=="top"&&o.top-p-l<0?"bottom":i=="right"&&o.right+d>q?"left":i=="left"&&o.left-d<k?"right":i;j.removeClass(g).addClass(i)}var f=this.getCalculatedOffset(i,o,d,l);this.applyPlacement(f,i);this.$element.trigger("shown.bs."+this.type)}};b.prototype.applyPlacement=function(i,j){var g;var k=this.tip();var f=k[0].offsetWidth;var n=k[0].offsetHeight;var e=parseInt(k.css("margin-top"),10);var h=parseInt(k.css("margin-left"),10);if(isNaN(e)){e=0}if(isNaN(h)){h=0}i.top=i.top+e;i.left=i.left+h;k.offset(i).addClass("in");var d=k[0].offsetWidth;var l=k[0].offsetHeight;if(j=="top"&&l!=n){g=true;i.top=i.top+n-l}if(/bottom|top/.test(j)){var m=0;if(i.left<0){m=i.left*-2;i.left=0;k.offset(i);d=k[0].offsetWidth;l=k[0].offsetHeight}this.replaceArrow(m-f+d,d,"left")}else{this.replaceArrow(l-n,l,"top")}if(g){k.offset(i)}};b.prototype.replaceArrow=function(f,e,d){this.arrow().css(d,f?(50*(1-f/e)+"%"):"")};b.prototype.setContent=function(){var e=this.tip();var d=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](d);e.removeClass("fade in top bottom left right")};b.prototype.hide=function(){var f=this;var h=this.tip();var g=c.Event("hide.bs."+this.type);function d(){if(f.hoverState!="in"){h.detach()}}this.$element.trigger(g);if(g.isDefaultPrevented()){return}h.removeClass("in");c.support.transition&&this.$tip.hasClass("fade")?h.one(c.support.transition.end,d).emulateTransitionEnd(150):d();this.$element.trigger("hidden.bs."+this.type);return this};b.prototype.fixTitle=function(){var d=this.$element;if(d.attr("title")||typeof(d.attr("data-original-title"))!="string"){d.attr("data-original-title",d.attr("title")||"").attr("title","")}};b.prototype.hasContent=function(){return this.getTitle()};b.prototype.getPosition=function(){var d=this.$element[0];return c.extend({},(typeof d.getBoundingClientRect=="function")?d.getBoundingClientRect():{width:d.offsetWidth,height:d.offsetHeight},this.$element.offset())};b.prototype.getCalculatedOffset=function(d,g,e,f){return d=="bottom"?{top:g.top+g.height,left:g.left+g.width/2-e/2}:d=="top"?{top:g.top-f,left:g.left+g.width/2-e/2}:d=="left"?{top:g.top+g.height/2-f/2,left:g.left-e}:{top:g.top+g.height/2-f/2,left:g.left+g.width}};b.prototype.getTitle=function(){var f;var d=this.$element;var e=this.options;f=d.attr("data-original-title")||(typeof e.title=="function"?e.title.call(d[0]):e.title);return f};b.prototype.tip=function(){return this.$tip=this.$tip||c(this.options.template)};b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")};b.prototype.validate=function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}};b.prototype.enable=function(){this.enabled=true};b.prototype.disable=function(){this.enabled=false};b.prototype.toggleEnabled=function(){this.enabled=!this.enabled};b.prototype.toggle=function(f){var d=f?c(f.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;d.tip().hasClass("in")?d.leave(d):d.enter(d)};b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var a=c.fn.tooltip;c.fn.tooltip=function(d){return this.each(function(){var g=c(this);var f=g.data("bs.tooltip");var e=typeof d=="object"&&d;if(!f){g.data("bs.tooltip",(f=new b(this,e)))}if(typeof d=="string"){f[d]()}})};c.fn.tooltip.Constructor=b;c.fn.tooltip.noConflict=function(){c.fn.tooltip=a;return this}}(window.jQuery);+function(g){var e=".dropdown-backdrop";var b="[data-toggle=dropdown]";var a=function(i){var h=g(i).on("click.bs.dropdown",this.toggle)};a.prototype.toggle=function(k){var j=g(this);if(j.is(".disabled, :disabled")){return}var i=f(j);var h=i.hasClass("open");d();if(!h){if("ontouchstart" in document.documentElement&&!i.closest(".navbar-nav").length){g('<div class="dropdown-backdrop"/>').insertAfter(g(this)).on("click",d)}i.trigger(k=g.Event("show.bs.dropdown"));if(k.isDefaultPrevented()){return}i.toggleClass("open").trigger("shown.bs.dropdown");j.focus()}return false};a.prototype.keydown=function(l){if(!/(38|40|27)/.test(l.keyCode)){return}var k=g(this);l.preventDefault();l.stopPropagation();if(k.is(".disabled, :disabled")){return}var j=f(k);var i=j.hasClass("open");if(!i||(i&&l.keyCode==27)){if(l.which==27){j.find(b).focus()}return k.click()}var m=g("[role=menu] li:not(.divider):visible a",j);if(!m.length){return}var h=m.index(m.filter(":focus"));if(l.keyCode==38&&h>0){h--}if(l.keyCode==40&&h<m.length-1){h++}if(!~h){h=0}m.eq(h).focus()};function d(){g(e).remove();g(b).each(function(i){var h=f(g(this));if(!h.hasClass("open")){return}h.trigger(i=g.Event("hide.bs.dropdown"));if(i.isDefaultPrevented()){return}h.removeClass("open").trigger("hidden.bs.dropdown")})}function f(j){var h=j.attr("data-target");if(!h){h=j.attr("href");h=h&&/#/.test(h)&&h.replace(/.*(?=#[^\s]*$)/,"")}var i=h&&g(h);return i&&i.length?i:j.parent()}var c=g.fn.dropdown;g.fn.dropdown=function(h){return this.each(function(){var j=g(this);var i=j.data("dropdown");if(!i){j.data("dropdown",(i=new a(this)))}if(typeof h=="string"){i[h].call(j)}})};g.fn.dropdown.Constructor=a;g.fn.dropdown.noConflict=function(){g.fn.dropdown=c;return this};g(document).on("click.bs.dropdown.data-api",d).on("click.bs.dropdown.data-api",".dropdown form",function(h){h.stopPropagation()}).on("click.bs.dropdown.data-api",b,a.prototype.toggle).on("keydown.bs.dropdown.data-api",b+", [role=menu]",a.prototype.keydown)}(window.jQuery);+function(c){var b=function(e,d){this.init("popover",e,d)};if(!c.fn.tooltip){throw new Error("Popover requires tooltip.js")}b.DEFAULTS=c.extend({},c.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'});b.prototype=c.extend({},c.fn.tooltip.Constructor.prototype);b.prototype.constructor=b;b.prototype.getDefaults=function(){return b.DEFAULTS};b.prototype.setContent=function(){var f=this.tip();var e=this.getTitle();var d=this.getContent();f.find(".popover-title")[this.options.html?"html":"text"](e);f.find(".popover-content")[this.options.html?"html":"text"](d);f.removeClass("fade top bottom left right in");if(!f.find(".popover-title").html()){f.find(".popover-title").hide()}};b.prototype.hasContent=function(){return this.getTitle()||this.getContent()};b.prototype.getContent=function(){var d=this.$element;var e=this.options;return d.attr("data-content")||(typeof e.content=="function"?e.content.call(d[0]):e.content)};b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};b.prototype.tip=function(){if(!this.$tip){this.$tip=c(this.options.template)}return this.$tip};var a=c.fn.popover;c.fn.popover=function(d){return this.each(function(){var g=c(this);var f=g.data("bs.popover");var e=typeof d=="object"&&d;if(!f){g.data("bs.popover",(f=new b(this,e)))}if(typeof d=="string"){f[d]()}})};c.fn.popover.Constructor=b;c.fn.popover.noConflict=function(){c.fn.popover=a;return this}}(jQuery);
\ No newline at end of file
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -581,7 +581,7 @@
.navbar-inverse .navbar-text{color:#999}
.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}
.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#181a24}
-.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}
+.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#666;background-color:transparent}
.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}
.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}
.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#1e212d}
@@ -589,7 +589,7 @@
.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}
.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}
.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}
-@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent} .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}
+@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent} .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#181a24} .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#666;background-color:transparent}}
.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}
.breadcrumb{padding:8px 15px;margin-bottom:17px;list-style:none;background-color:#f5f5f5;border-radius:3px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}
.breadcrumb>.active{color:#999}
@@ -1295,7 +1295,7 @@
.toast-title{font-weight:bold}
.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}
.toast-message a:hover{color:#ccc;text-decoration:none}
-.toast-close-button{position:relative;right:-0.3em;top:-0.3em;float:right;font-size:20px;font-weight:bold;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;filter:alpha(opacity=80);opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=40);opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}
+.toast-close-button{position:relative;right:-0.3em;top:-0.3em;float:right;font-size:20px;font-weight:bold;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;filter:alpha(opacity=100);opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100)}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=100);opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100)}
button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}
.toast-top-full-width{top:0;right:0;width:100%}
.toast-bottom-full-width{bottom:0;right:0;width:100%}
@@ -1304,7 +1304,7 @@
.toast-bottom-right{right:12px;bottom:12px}
.toast-bottom-left{bottom:12px;left:12px}
#toast-container{position:fixed;z-index:999999;}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
-#toast-container>div{margin:4em 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px 3px 3px 3px;-webkit-border-radius:3px 3px 3px 3px;border-radius:3px 3px 3px 3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;filter:alpha(opacity=80);opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}
+#toast-container>div{margin:4em 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px 3px 3px 3px;-webkit-border-radius:3px 3px 3px 3px;border-radius:3px 3px 3px 3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;filter:alpha(opacity=100);opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100)}
#toast-container>:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}
#toast-container>.toast-info{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important}
#toast-container>.toast-error{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important}
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/style/src/less/galaxy_bootstrap/variables.less
--- a/static/style/src/less/galaxy_bootstrap/variables.less
+++ b/static/style/src/less/galaxy_bootstrap/variables.less
@@ -344,7 +344,7 @@
@navbar-inverse-link-hover-bg: transparent;
@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;
@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);
-@navbar-inverse-link-disabled-color: #444;
+@navbar-inverse-link-disabled-color: #666;
@navbar-inverse-link-disabled-bg: transparent;
// Inverted navbar brand label
diff -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 -r 28575f5bd33b555cf594247f7706679df425085b static/style/src/less/toastr.less
--- a/static/style/src/less/toastr.less
+++ b/static/style/src/less/toastr.less
@@ -63,14 +63,14 @@
color: @white;
-webkit-text-shadow: 0 1px 0 rgba(255,255,255,1);
text-shadow: 0 1px 0 rgba(255,255,255,1);
- .opacity(0.8);
+ .opacity(1);
&:hover,
&:focus {
color: @black;
text-decoration: none;
cursor: pointer;
- .opacity(0.4);
+ .opacity(1);
}
}
@@ -138,7 +138,7 @@
background-repeat: no-repeat;
.boxShadow(0 0 12px @grey);
color: @white;
- .opacity(0.8);
+ .opacity(1);
}
> :hover {
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: Log when each phase starts and finishes.
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8917614d21d0/
Changeset: 8917614d21d0
User: inithello
Date: 2014-01-14 18:45:33
Summary: Log when each phase starts and finishes.
Affected #: 1 file
diff -r 0b165ce6fac761a9e48cfa007d7e427c7f4b436c -r 8917614d21d03f4c4c3d83a1e00b302e568ea954 install_and_test_tool_shed_repositories.sh
--- a/install_and_test_tool_shed_repositories.sh
+++ b/install_and_test_tool_shed_repositories.sh
@@ -48,10 +48,12 @@
if [ -f /ToolDepsTest/stage_1_complete ] ; then
rm /ToolDepsTest/stage_1_complete
fi
+ echo "Starting stage 1, tool dependency definitions."
python test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py $* -v --with-nosehtml --html-report-file \
test/install_and_test_tool_shed_repositories/tool_dependency_definitions/run_functional_tests.html \
test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py \
test/functional/test_toolbox.py
+ echo "Stage 1 complete, exit code $?"
touch /ToolDepsTest/stage_1_complete
}
@@ -60,11 +62,13 @@
echo 'Stage 1 did not complete its run, exiting.'
exit 1
fi
+ echo "Starting stage 2, repositories with tools."
# Test installation of repositories that contain valid tools with defined functional tests and a test-data directory containing test files.
python test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py $* -v --with-nosehtml --html-report-file \
test/install_and_test_tool_shed_repositories/repositories_with_tools/run_functional_tests.html \
test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py \
test/functional/test_toolbox.py
+ echo "Stage 2 complete, exit code $?"
rm /ToolDepsTest/stage_1_complete
}
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/97fd1b009c86/
Changeset: 97fd1b009c86
Branch: stable
User: natefoo
Date: 2014-01-14 17:31:11
Summary: Backed out changeset c40ecf876d68
Affected #: 4 files
diff -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e -r 97fd1b009c86ac992716ce6e81d45cb20c06a3c1 lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
--- a/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
@@ -262,7 +262,7 @@
no_tool_dependency_dir_message = "Tool dependencies can be automatically installed only if you set the value of your 'tool_dependency_dir' "
no_tool_dependency_dir_message += "setting in your Galaxy configuration file (universe_wsgi.ini) and restart your Galaxy server."
raise HTTPBadRequest( detail=no_tool_dependency_dir_message )
- new_tool_panel_section_label = payload.get( 'new_tool_panel_section_label', '' )
+ new_tool_panel_section = payload.get( 'new_tool_panel_section_label', '' )
shed_tool_conf = payload.get( 'shed_tool_conf', None )
if shed_tool_conf:
# Get the tool_path setting.
@@ -281,20 +281,15 @@
raise HTTPBadRequest( detail="Missing required parameter 'shed_tool_conf'." )
tool_panel_section_id = payload.get( 'tool_panel_section_id', '' )
if tool_panel_section_id not in [ None, '' ]:
- if tool_panel_section_id not in trans.app.toolbox.tool_panel:
- fixed_tool_panel_section_id = 'section_%s' % tool_panel_section_id
- if fixed_tool_panel_section_id in trans.app.toolbox.tool_panel:
- tool_panel_section_id = fixed_tool_panel_section_id
- else:
- tool_panel_section_id = ''
+ tool_panel_section = trans.app.toolbox.tool_panel[ tool_panel_section_id ]
else:
- tool_panel_section_id = ''
+ tool_panel_section = ''
# Build the dictionary of information necessary for creating tool_shed_repository database records for each repository being installed.
installation_dict = dict( install_repository_dependencies=install_repository_dependencies,
- new_tool_panel_section_label=new_tool_panel_section_label,
+ new_tool_panel_section=new_tool_panel_section,
no_changes_checked=False,
repo_info_dicts=repo_info_dicts,
- tool_panel_section_id=tool_panel_section_id,
+ tool_panel_section=tool_panel_section,
tool_path=tool_path,
tool_shed_url=tool_shed_url )
# Create the tool_shed_repository database records and gather additional information for repository installation.
@@ -311,18 +306,17 @@
install_repository_dependencies=install_repository_dependencies,
install_tool_dependencies=install_tool_dependencies,
message='',
- new_tool_panel_section_label=new_tool_panel_section_label,
+ new_tool_panel_section=new_tool_panel_section,
shed_tool_conf=shed_tool_conf,
status='done',
- tool_panel_section_id=tool_panel_section_id,
+ tool_panel_section=tool_panel_section,
tool_panel_section_keys=tool_panel_section_keys,
tool_path=tool_path,
tool_shed_url=tool_shed_url )
# Prepare the repositories for installation. Even though this method receives a single combination of tool_shed_url, name, owner and
# changeset_revision, there may be multiple repositories for installation at this point because repository dependencies may have added
# additional repositories for installation along with the single specified repository.
- encoded_kwd, query, tool_shed_repositories, encoded_repository_ids = \
- repository_util.initiate_repository_installation( trans, installation_dict )
+ encoded_kwd, query, tool_shed_repositories, encoded_repository_ids = repository_util.initiate_repository_installation( trans, installation_dict )
# Some repositories may have repository dependencies that are required to be installed before the dependent repository, so we'll
# order the list of tsr_ids to ensure all repositories install in the required order.
tsr_ids = [ trans.security.encode_id( tool_shed_repository.id ) for tool_shed_repository in tool_shed_repositories ]
diff -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e -r 97fd1b009c86ac992716ce6e81d45cb20c06a3c1 lib/tool_shed/galaxy_install/repository_util.py
--- a/lib/tool_shed/galaxy_install/repository_util.py
+++ b/lib/tool_shed/galaxy_install/repository_util.py
@@ -244,10 +244,9 @@
tool_panel_section_id = section_dict[ 'id' ]
tool_panel_section_name = section_dict[ 'name' ]
if tool_panel_section_id:
- tool_panel_section_key, tool_panel_section = \
- tool_util.get_or_create_tool_section( trans,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section_label=tool_panel_section_name )
+ tool_panel_section_key, tool_panel_section = tool_util.get_or_create_tool_section( trans,
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section=tool_panel_section_name )
else:
tool_dependencies = None
repo_info_dict = create_repo_info_dict( trans=trans,
@@ -433,10 +432,10 @@
def handle_tool_shed_repositories( trans, installation_dict, using_api=False ):
# The following installation_dict entries are all required.
install_repository_dependencies = installation_dict[ 'install_repository_dependencies' ]
- new_tool_panel_section_label = installation_dict[ 'new_tool_panel_section_label' ]
+ new_tool_panel_section = installation_dict[ 'new_tool_panel_section' ]
no_changes_checked = installation_dict[ 'no_changes_checked' ]
repo_info_dicts = installation_dict[ 'repo_info_dicts' ]
- tool_panel_section_id = installation_dict[ 'tool_panel_section_id' ]
+ tool_panel_section = installation_dict[ 'tool_panel_section' ]
tool_path = installation_dict[ 'tool_path' ]
tool_shed_url = installation_dict[ 'tool_shed_url' ]
created_or_updated_tool_shed_repositories, tool_panel_section_keys, repo_info_dicts, filtered_repo_info_dicts = \
@@ -446,8 +445,8 @@
repo_info_dicts=repo_info_dicts,
install_repository_dependencies=install_repository_dependencies,
no_changes_checked=no_changes_checked,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
+ tool_panel_section=tool_panel_section,
+ new_tool_panel_section=new_tool_panel_section )
return created_or_updated_tool_shed_repositories, tool_panel_section_keys, repo_info_dicts, filtered_repo_info_dicts
def initiate_repository_installation( trans, installation_dict ):
@@ -461,19 +460,18 @@
install_repository_dependencies = installation_dict[ 'install_repository_dependencies' ]
install_tool_dependencies = installation_dict[ 'install_tool_dependencies' ]
message = installation_dict[ 'message' ]
- new_tool_panel_section_label = installation_dict[ 'new_tool_panel_section_label' ]
+ new_tool_panel_section = installation_dict[ 'new_tool_panel_section' ]
shed_tool_conf = installation_dict[ 'shed_tool_conf' ]
status = installation_dict[ 'status' ]
- tool_panel_section_id = installation_dict[ 'tool_panel_section_id' ]
+ tool_panel_section = installation_dict[ 'tool_panel_section' ]
tool_panel_section_keys = installation_dict[ 'tool_panel_section_keys' ]
tool_path = installation_dict[ 'tool_path' ]
tool_shed_url = installation_dict[ 'tool_shed_url' ]
# Handle contained tools.
- if includes_tools_for_display_in_tool_panel and ( new_tool_panel_section_label or tool_panel_section_id ):
- tool_panel_section_key, tool_section = \
- tool_util.handle_tool_panel_section( trans,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
+ if includes_tools_for_display_in_tool_panel and ( new_tool_panel_section or tool_panel_section ):
+ tool_panel_section_key, tool_section = tool_util.handle_tool_panel_section( trans,
+ tool_panel_section=tool_panel_section,
+ new_tool_panel_section=new_tool_panel_section )
else:
tool_panel_section_key = None
tool_section = None
@@ -671,19 +669,16 @@
def order_components_for_installation( trans, tsr_ids, repo_info_dicts, tool_panel_section_keys ):
"""
- Some repositories may have repository dependencies that are required to be installed before the dependent repository.
- This method will inspect the list of repositories about to be installed and make sure to order them appropriately.
- For each repository about to be installed, if required repositories are not contained in the list of repositories about
- to be installed, then they are not considered. Repository dependency definitions that contain circular dependencies
- should not result in an infinite loop, but obviously prior installation will not be handled for one or more of the
- repositories that require prior installation.
+ Some repositories may have repository dependencies that are required to be installed before the dependent repository. This method will inspect the list of
+ repositories about to be installed and make sure to order them appropriately. For each repository about to be installed, if required repositories are not
+ contained in the list of repositories about to be installed, then they are not considered. Repository dependency definitions that contain circular dependencies
+ should not result in an infinite loop, but obviously prior installation will not be handled for one or more of the repositories that require prior installation.
"""
ordered_tsr_ids = []
ordered_repo_info_dicts = []
ordered_tool_panel_section_keys = []
- # Create a dictionary whose keys are the received tsr_ids and whose values are a list of tsr_ids, each of which is
- # contained in the received list of tsr_ids and whose associated repository must be installed prior to the repository
- # associated with the tsr_id key.
+ # Create a dictionary whose keys are the received tsr_ids and whose values are a list of tsr_ids, each of which is contained in the received list of tsr_ids
+ # and whose associated repository must be installed prior to the repository associated with the tsr_id key.
prior_install_required_dict = suc.get_prior_import_or_install_required_dict( trans, tsr_ids, repo_info_dicts )
processed_tsr_ids = []
while len( processed_tsr_ids ) != len( prior_install_required_dict.keys() ):
@@ -695,19 +690,17 @@
for prior_install_required_id in prior_install_required_ids:
if prior_install_required_id not in ordered_tsr_ids:
# Install the associated repository dependency first.
- prior_repo_info_dict, prior_tool_panel_section_key = \
- get_repository_components_for_installation( prior_install_required_id,
- tsr_ids,
- repo_info_dicts,
- tool_panel_section_keys=tool_panel_section_keys )
+ prior_repo_info_dict, prior_tool_panel_section_key = get_repository_components_for_installation( prior_install_required_id,
+ tsr_ids,
+ repo_info_dicts,
+ tool_panel_section_keys=tool_panel_section_keys )
ordered_tsr_ids.append( prior_install_required_id )
ordered_repo_info_dicts.append( prior_repo_info_dict )
ordered_tool_panel_section_keys.append( prior_tool_panel_section_key )
- repo_info_dict, tool_panel_section_key = \
- get_repository_components_for_installation( tsr_id,
- tsr_ids,
- repo_info_dicts,
- tool_panel_section_keys=tool_panel_section_keys )
+ repo_info_dict, tool_panel_section_key = get_repository_components_for_installation( tsr_id,
+ tsr_ids,
+ repo_info_dicts,
+ tool_panel_section_keys=tool_panel_section_keys )
ordered_tsr_ids.append( tsr_id )
ordered_repo_info_dicts.append( repo_info_dict )
ordered_tool_panel_section_keys.append( tool_panel_section_key )
@@ -772,12 +765,11 @@
shed_tool_conf, tool_path, relative_install_dir = suc.get_tool_panel_config_tool_path_install_dir( trans.app, repository )
# Reset the repository attributes to the New state for installation.
if metadata:
- tool_section, tool_panel_section_key = \
- tool_util.handle_tool_panel_selection( trans,
- metadata,
- no_changes_checked=True,
- tool_panel_section_id=None,
- new_tool_panel_section_label=None )
+ tool_section, new_tool_panel_section, tool_panel_section_key = tool_util.handle_tool_panel_selection( trans,
+ metadata,
+ no_changes_checked=True,
+ tool_panel_section=None,
+ new_tool_panel_section=None )
else:
# The tools will be loaded outside of any sections in the tool panel.
tool_panel_section_key = None
diff -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e -r 97fd1b009c86ac992716ce6e81d45cb20c06a3c1 lib/tool_shed/util/repository_dependency_util.py
--- a/lib/tool_shed/util/repository_dependency_util.py
+++ b/lib/tool_shed/util/repository_dependency_util.py
@@ -96,7 +96,7 @@
return True
def create_repository_dependency_objects( trans, tool_path, tool_shed_url, repo_info_dicts, install_repository_dependencies=False,
- no_changes_checked=False, tool_panel_section_id=None, new_tool_panel_section_label=None ):
+ no_changes_checked=False, tool_panel_section=None, new_tool_panel_section=None ):
"""
Discover all repository dependencies and make sure all tool_shed_repository and associated repository_dependency records exist as well as
the dependency relationships between installed repositories. This method is called when uninstalled repositories are being reinstalled.
@@ -174,30 +174,28 @@
# The database record for the tool shed repository currently being processed can be updated. Get the repository metadata
# to see where it was previously located in the tool panel.
if repository_db_record and repository_db_record.metadata:
- tool_section, tool_panel_section_key = \
+ tool_section, new_tool_panel_section, tool_panel_section_key = \
tool_util.handle_tool_panel_selection( trans=trans,
metadata=repository_db_record.metadata,
no_changes_checked=no_changes_checked,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
+ tool_panel_section=tool_panel_section,
+ new_tool_panel_section=new_tool_panel_section )
else:
# We're installing a new tool shed repository that does not yet have a database record.
- tool_panel_section_key, tool_section = \
- tool_util.handle_tool_panel_section( trans,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
- tool_shed_repository = \
- suc.create_or_update_tool_shed_repository( app=trans.app,
- name=name,
- description=description,
- installed_changeset_revision=changeset_revision,
- ctx_rev=ctx_rev,
- repository_clone_url=repository_clone_url,
- metadata_dict={},
- status=trans.model.ToolShedRepository.installation_status.NEW,
- current_changeset_revision=changeset_revision,
- owner=repository_owner,
- dist_to_shed=False )
+ tool_panel_section_key, tool_section = tool_util.handle_tool_panel_section( trans,
+ tool_panel_section=tool_panel_section,
+ new_tool_panel_section=new_tool_panel_section )
+ tool_shed_repository = suc.create_or_update_tool_shed_repository( app=trans.app,
+ name=name,
+ description=description,
+ installed_changeset_revision=changeset_revision,
+ ctx_rev=ctx_rev,
+ repository_clone_url=repository_clone_url,
+ metadata_dict={},
+ status=trans.model.ToolShedRepository.installation_status.NEW,
+ current_changeset_revision=changeset_revision,
+ owner=repository_owner,
+ dist_to_shed=False )
if tool_shed_repository not in all_created_or_updated_tool_shed_repositories:
all_created_or_updated_tool_shed_repositories.append( tool_shed_repository )
# Only append the tool shed repository to the list of created_or_updated_tool_shed_repositories if it is supposed to be installed.
@@ -461,10 +459,9 @@
common_util.parse_repository_dependency_tuple( repository_dependency )
if suc.tool_shed_is_this_tool_shed( toolshed ):
required_repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
- required_repository_metadata = \
- metadata_util.get_repository_metadata_by_repository_id_changeset_revision( trans,
- trans.security.encode_id( required_repository.id ),
- changeset_revision )
+ required_repository_metadata = metadata_util.get_repository_metadata_by_repository_id_changeset_revision( trans,
+ trans.security.encode_id( required_repository.id ),
+ changeset_revision )
if required_repository_metadata:
# The required_repository_metadata changeset_revision is installable.
required_metadata = required_repository_metadata.metadata
diff -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e -r 97fd1b009c86ac992716ce6e81d45cb20c06a3c1 lib/tool_shed/util/tool_util.py
--- a/lib/tool_shed/util/tool_util.py
+++ b/lib/tool_shed/util/tool_util.py
@@ -108,7 +108,7 @@
for k, v in trans.app.toolbox.tool_panel.items():
if isinstance( v, galaxy.tools.ToolSection ):
options.append( ( v.name, v.id ) )
- select_field = SelectField( name='tool_panel_section_id', display='radio' )
+ select_field = SelectField( name='tool_panel_section', display='radio' )
for option_tup in options:
select_field.add_option( option_tup[ 0 ], option_tup[ 1 ] )
return select_field
@@ -461,28 +461,29 @@
fh.close()
return sample_files, deleted_sample_files
-def get_or_create_tool_section( trans, tool_panel_section_id, new_tool_panel_section_label=None ):
- if tool_panel_section_id.startswith( 'section_' ):
- tool_panel_section_key = tool_panel_section_id
- tool_panel_section_id.lstrip( 'section_' )
- else:
- tool_panel_section_key = 'section_%s' % str( tool_panel_section_id )
+def get_or_create_tool_section( trans, tool_panel_section_id, new_tool_panel_section=None ):
+ tool_panel_section_key = 'section_%s' % str( tool_panel_section_id )
if tool_panel_section_key in trans.app.toolbox.tool_panel:
# Appending a tool to an existing section in trans.app.toolbox.tool_panel
tool_section = trans.app.toolbox.tool_panel[ tool_panel_section_key ]
log.debug( "Appending to tool panel section: %s" % str( tool_section.name ) )
else:
# Appending a new section to trans.app.toolbox.tool_panel
- if new_tool_panel_section_label is None:
- # This might add an ugly section label to the tool panel, but, oh well...
- new_tool_panel_section_label = tool_panel_section_id
- elem = XmlET.Element( 'section' )
- elem.attrib[ 'name' ] = new_tool_panel_section_label
- elem.attrib[ 'id' ] = tool_panel_section_id
- elem.attrib[ 'version' ] = ''
- tool_section = galaxy.tools.ToolSection( elem )
- trans.app.toolbox.tool_panel[ tool_panel_section_key ] = tool_section
- log.debug( "Loading new tool panel section: %s" % str( tool_section.name ) )
+ try:
+ new_tool_panel_section_name = new_tool_panel_section.name
+ except:
+ new_tool_panel_section_name = new_tool_panel_section
+ if new_tool_panel_section_name:
+ elem = XmlET.Element( 'section' )
+ elem.attrib[ 'name' ] = new_tool_panel_section_name
+ elem.attrib[ 'id' ] = tool_panel_section_id
+ elem.attrib[ 'version' ] = ''
+ tool_section = galaxy.tools.ToolSection( elem )
+ trans.app.toolbox.tool_panel[ tool_panel_section_key ] = tool_section
+ log.debug( "Loading new tool panel section: %s" % str( tool_section.name ) )
+ else:
+ log.debug( "Unable to create new tool pane section using received new_tool_panel_section: %s" % str( new_tool_panel_section ))
+ return None, None
return tool_panel_section_key, tool_section
def get_tool_path_install_dir( partial_install_dir, shed_tool_conf_dict, tool_dict, config_elems ):
@@ -632,20 +633,16 @@
error = True
return error, message
-def handle_tool_panel_selection( trans, metadata, no_changes_checked, tool_panel_section_id, new_tool_panel_section_label ):
- """
- Handle the selected tool panel location for loading tools included in tool shed repositories when installing
- or reinstalling them.
- """
+def handle_tool_panel_selection( trans, metadata, no_changes_checked, tool_panel_section, new_tool_panel_section ):
+ """Handle the selected tool panel location for loading tools included in tool shed repositories when installing or reinstalling them."""
# Get the location in the tool panel in which each tool was originally loaded.
tool_section = None
tool_panel_section_key = None
if 'tools' in metadata:
# This forces everything to be loaded into the same section (or no section) in the tool panel.
if no_changes_checked:
- # Make sure the no_changes check box overrides the new_tool_panel_section_label if the user checked the check
- # box and entered something into the field.
- new_tool_panel_section_label = None
+ # Make sure the no_changes check box overrides the new_tool_panel_section if the user checked the check box and entered something into the field.
+ new_tool_panel_section = None
if 'tool_panel_section' in metadata:
tool_panel_dict = metadata[ 'tool_panel_section' ]
if not tool_panel_dict:
@@ -659,33 +656,24 @@
original_section_id = tool_section_dict[ 'id' ]
original_section_name = tool_section_dict[ 'name' ]
if original_section_id:
- tool_panel_section_key, tool_section = \
- get_or_create_tool_section( trans,
- tool_panel_section_id=original_section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
+ tool_panel_section_key, tool_section = get_or_create_tool_section( trans,
+ tool_panel_section_id=original_section_id,
+ new_tool_panel_section=new_tool_panel_section )
else:
# The user elected to change the tool panel section to contain the tools.
tool_panel_section_key, tool_section = handle_tool_panel_section( trans,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
- return tool_section, tool_panel_section_key
+ tool_panel_section=tool_panel_section,
+ new_tool_panel_section=new_tool_panel_section )
+ return tool_section, new_tool_panel_section, tool_panel_section_key
-def handle_tool_panel_section( trans, tool_panel_section_id=None, new_tool_panel_section_label=None ):
- """Return a ToolSection object retrieved from the current in-memory tool_panel."""
- # If tool_panel_section_id is received, the section exists in the tool panel. In this case, the value of the
- # received tool_panel_section_id must be the id retrieved from a tool panel config (e.g., tool_conf.xml, which
- # may have getext). If new_tool_panel_section_label is received, a new section will be added to the tool panel.
- if new_tool_panel_section_label:
- section_id = 'section_%s' % str( new_tool_panel_section_label.lower().replace( ' ', '_' ) )
- tool_panel_section_key, tool_section = \
- get_or_create_tool_section( trans,
- tool_panel_section_id=section_id,
- new_tool_panel_section_label=new_tool_panel_section_label )
- elif tool_panel_section_id:
- if tool_panel_section_id.startswith( 'section_' ):
- tool_panel_section_key = str( tool_panel_section_id )
- else:
- tool_panel_section_key = 'section_%s' % str( tool_panel_section_id )
+def handle_tool_panel_section( trans, tool_panel_section=None, new_tool_panel_section=None ):
+ if new_tool_panel_section:
+ section_id = new_tool_panel_section.lower().replace( ' ', '_' )
+ tool_panel_section_key, tool_section = get_or_create_tool_section( trans,
+ tool_panel_section_id=section_id,
+ new_tool_panel_section=new_tool_panel_section )
+ elif tool_panel_section:
+ tool_panel_section_key = 'section_%s' % str( tool_panel_section )
tool_section = trans.app.toolbox.tool_panel[ tool_panel_section_key ]
else:
return None, None
https://bitbucket.org/galaxy/galaxy-central/commits/0b165ce6fac7/
Changeset: 0b165ce6fac7
User: natefoo
Date: 2014-01-14 17:40:40
Summary: Merge backout from stable.
Affected #: 4 files
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: Visualizations Registry: allow config to contain a visualization description
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/cf003cc0101b/
Changeset: cf003cc0101b
User: carlfeberhard
Date: 2014-01-14 17:25:20
Summary: Visualizations Registry: allow config to contain a visualization description
Affected #: 2 files
diff -r 0c20fa551dd90a9d8101bf4f2a8cac3793b9663b -r cf003cc0101b1b07acf8bec871b0e7740ef6b39f config/plugins/visualizations/visualization.dtd
--- a/config/plugins/visualizations/visualization.dtd
+++ b/config/plugins/visualizations/visualization.dtd
@@ -1,10 +1,17 @@
<!-- each visualization must have a template (all other elements are optional) -->
-<!ELEMENT visualization (data_sources*,params*,template_root*,template,link_text*,render_location*)>
-<!-- visualization name (e.g. 'trackster', 'scatterplot', etc.) is required -->
+<!ELEMENT visualization (description*,data_sources*,params*,template_root*,template,link_text*,render_location*)>
+<!-- visualization
+ name: some name for the visualization (e.g. 'trackster', 'scatterplot', etc.) REQUIRED
+ disabled: if included (value does not matter), this attribute will prevent the visualization being loaded
+--><!ATTLIST visualization
name CDATA #REQUIRED
+ disabled CDATA #IMPLIED
>
+<!ELEMENT description (#PCDATA)>
+<!-- a text description of what the visualization does -->
+
<!ELEMENT data_sources (data_source*)><!-- data sources are elements that describe what objects (HDAs, LDDAs, Job, User, etc.)
are applicable to a visualization. Often these are used to fetch applicable links
diff -r 0c20fa551dd90a9d8101bf4f2a8cac3793b9663b -r cf003cc0101b1b07acf8bec871b0e7740ef6b39f lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -397,6 +397,10 @@
if 'disabled' in xml_tree.attrib:
return None
+ # a (for now) text description of what the visualization does
+ description = xml_tree.find( 'description' )
+ returned[ 'description' ] = description.text.strip() if description is not None else None
+
# data_sources are the kinds of objects/data associated with the visualization
# e.g. views on HDAs can use this to find out what visualizations are applicable to them
data_sources = []
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: Visualizations Registry: allow disabling via config file
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0c20fa551dd9/
Changeset: 0c20fa551dd9
User: carlfeberhard
Date: 2014-01-14 16:55:22
Summary: Visualizations Registry: allow disabling via config file
Affected #: 3 files
diff -r c5c57ec1af3ca407ce64e52093173be90a2047b7 -r 0c20fa551dd90a9d8101bf4f2a8cac3793b9663b config/plugins/visualizations/sweepster/config/sweepster.xml
--- a/config/plugins/visualizations/sweepster/config/sweepster.xml
+++ b/config/plugins/visualizations/sweepster/config/sweepster.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE visualization SYSTEM "../../visualization.dtd">
-<visualization name="sweepster">
+<visualization name="sweepster" disabled="true"><data_sources><data_source><model_class>HistoryDatasetAssociation</model_class>
diff -r c5c57ec1af3ca407ce64e52093173be90a2047b7 -r 0c20fa551dd90a9d8101bf4f2a8cac3793b9663b lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -375,7 +375,7 @@
def parse_file( self, xml_filepath ):
"""
Parse the given XML file for visualizations data.
- :returns: tuple of ( `visualization_name`, `visualization` )
+ :returns: visualization config dictionary
"""
try:
xml_tree = galaxy.util.parse_xml( xml_filepath )
@@ -393,6 +393,10 @@
"""
returned = {}
+ # allow manually turning off a vis by checking for a disabled property
+ if 'disabled' in xml_tree.attrib:
+ return None
+
# data_sources are the kinds of objects/data associated with the visualization
# e.g. views on HDAs can use this to find out what visualizations are applicable to them
data_sources = []
diff -r c5c57ec1af3ca407ce64e52093173be90a2047b7 -r 0c20fa551dd90a9d8101bf4f2a8cac3793b9663b lib/galaxy/web/base/pluginframework.py
--- a/lib/galaxy/web/base/pluginframework.py
+++ b/lib/galaxy/web/base/pluginframework.py
@@ -119,7 +119,7 @@
try:
plugin = self.load_plugin( plugin_path )
if not plugin:
- log.warn( '%s, plugin load failed: %s. Skipping...', self, plugin_path )
+ log.warn( '%s, plugin load failed or disabled: %s. Skipping...', self, plugin_path )
#NOTE: prevent silent, implicit overwrite here (two plugins in two diff directories)
#TODO: overwriting may be desired
elif plugin.name in self.plugins:
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
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c40ecf876d68/
Changeset: c40ecf876d68
Branch: stable
User: inithello
Date: 2014-01-14 16:29:33
Summary: Backport fix for selecting a tool panel section when installing a repository using the API.
Affected #: 4 files
diff -r d96d5309b5c52bb43c410c7a943808294bee12c0 -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
--- a/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
@@ -262,7 +262,7 @@
no_tool_dependency_dir_message = "Tool dependencies can be automatically installed only if you set the value of your 'tool_dependency_dir' "
no_tool_dependency_dir_message += "setting in your Galaxy configuration file (universe_wsgi.ini) and restart your Galaxy server."
raise HTTPBadRequest( detail=no_tool_dependency_dir_message )
- new_tool_panel_section = payload.get( 'new_tool_panel_section_label', '' )
+ new_tool_panel_section_label = payload.get( 'new_tool_panel_section_label', '' )
shed_tool_conf = payload.get( 'shed_tool_conf', None )
if shed_tool_conf:
# Get the tool_path setting.
@@ -281,15 +281,20 @@
raise HTTPBadRequest( detail="Missing required parameter 'shed_tool_conf'." )
tool_panel_section_id = payload.get( 'tool_panel_section_id', '' )
if tool_panel_section_id not in [ None, '' ]:
- tool_panel_section = trans.app.toolbox.tool_panel[ tool_panel_section_id ]
+ if tool_panel_section_id not in trans.app.toolbox.tool_panel:
+ fixed_tool_panel_section_id = 'section_%s' % tool_panel_section_id
+ if fixed_tool_panel_section_id in trans.app.toolbox.tool_panel:
+ tool_panel_section_id = fixed_tool_panel_section_id
+ else:
+ tool_panel_section_id = ''
else:
- tool_panel_section = ''
+ tool_panel_section_id = ''
# Build the dictionary of information necessary for creating tool_shed_repository database records for each repository being installed.
installation_dict = dict( install_repository_dependencies=install_repository_dependencies,
- new_tool_panel_section=new_tool_panel_section,
+ new_tool_panel_section_label=new_tool_panel_section_label,
no_changes_checked=False,
repo_info_dicts=repo_info_dicts,
- tool_panel_section=tool_panel_section,
+ tool_panel_section_id=tool_panel_section_id,
tool_path=tool_path,
tool_shed_url=tool_shed_url )
# Create the tool_shed_repository database records and gather additional information for repository installation.
@@ -306,17 +311,18 @@
install_repository_dependencies=install_repository_dependencies,
install_tool_dependencies=install_tool_dependencies,
message='',
- new_tool_panel_section=new_tool_panel_section,
+ new_tool_panel_section_label=new_tool_panel_section_label,
shed_tool_conf=shed_tool_conf,
status='done',
- tool_panel_section=tool_panel_section,
+ tool_panel_section_id=tool_panel_section_id,
tool_panel_section_keys=tool_panel_section_keys,
tool_path=tool_path,
tool_shed_url=tool_shed_url )
# Prepare the repositories for installation. Even though this method receives a single combination of tool_shed_url, name, owner and
# changeset_revision, there may be multiple repositories for installation at this point because repository dependencies may have added
# additional repositories for installation along with the single specified repository.
- encoded_kwd, query, tool_shed_repositories, encoded_repository_ids = repository_util.initiate_repository_installation( trans, installation_dict )
+ encoded_kwd, query, tool_shed_repositories, encoded_repository_ids = \
+ repository_util.initiate_repository_installation( trans, installation_dict )
# Some repositories may have repository dependencies that are required to be installed before the dependent repository, so we'll
# order the list of tsr_ids to ensure all repositories install in the required order.
tsr_ids = [ trans.security.encode_id( tool_shed_repository.id ) for tool_shed_repository in tool_shed_repositories ]
diff -r d96d5309b5c52bb43c410c7a943808294bee12c0 -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e lib/tool_shed/galaxy_install/repository_util.py
--- a/lib/tool_shed/galaxy_install/repository_util.py
+++ b/lib/tool_shed/galaxy_install/repository_util.py
@@ -244,9 +244,10 @@
tool_panel_section_id = section_dict[ 'id' ]
tool_panel_section_name = section_dict[ 'name' ]
if tool_panel_section_id:
- tool_panel_section_key, tool_panel_section = tool_util.get_or_create_tool_section( trans,
- tool_panel_section_id=tool_panel_section_id,
- new_tool_panel_section=tool_panel_section_name )
+ tool_panel_section_key, tool_panel_section = \
+ tool_util.get_or_create_tool_section( trans,
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section_label=tool_panel_section_name )
else:
tool_dependencies = None
repo_info_dict = create_repo_info_dict( trans=trans,
@@ -432,10 +433,10 @@
def handle_tool_shed_repositories( trans, installation_dict, using_api=False ):
# The following installation_dict entries are all required.
install_repository_dependencies = installation_dict[ 'install_repository_dependencies' ]
- new_tool_panel_section = installation_dict[ 'new_tool_panel_section' ]
+ new_tool_panel_section_label = installation_dict[ 'new_tool_panel_section_label' ]
no_changes_checked = installation_dict[ 'no_changes_checked' ]
repo_info_dicts = installation_dict[ 'repo_info_dicts' ]
- tool_panel_section = installation_dict[ 'tool_panel_section' ]
+ tool_panel_section_id = installation_dict[ 'tool_panel_section_id' ]
tool_path = installation_dict[ 'tool_path' ]
tool_shed_url = installation_dict[ 'tool_shed_url' ]
created_or_updated_tool_shed_repositories, tool_panel_section_keys, repo_info_dicts, filtered_repo_info_dicts = \
@@ -445,8 +446,8 @@
repo_info_dicts=repo_info_dicts,
install_repository_dependencies=install_repository_dependencies,
no_changes_checked=no_changes_checked,
- tool_panel_section=tool_panel_section,
- new_tool_panel_section=new_tool_panel_section )
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
return created_or_updated_tool_shed_repositories, tool_panel_section_keys, repo_info_dicts, filtered_repo_info_dicts
def initiate_repository_installation( trans, installation_dict ):
@@ -460,18 +461,19 @@
install_repository_dependencies = installation_dict[ 'install_repository_dependencies' ]
install_tool_dependencies = installation_dict[ 'install_tool_dependencies' ]
message = installation_dict[ 'message' ]
- new_tool_panel_section = installation_dict[ 'new_tool_panel_section' ]
+ new_tool_panel_section_label = installation_dict[ 'new_tool_panel_section_label' ]
shed_tool_conf = installation_dict[ 'shed_tool_conf' ]
status = installation_dict[ 'status' ]
- tool_panel_section = installation_dict[ 'tool_panel_section' ]
+ tool_panel_section_id = installation_dict[ 'tool_panel_section_id' ]
tool_panel_section_keys = installation_dict[ 'tool_panel_section_keys' ]
tool_path = installation_dict[ 'tool_path' ]
tool_shed_url = installation_dict[ 'tool_shed_url' ]
# Handle contained tools.
- if includes_tools_for_display_in_tool_panel and ( new_tool_panel_section or tool_panel_section ):
- tool_panel_section_key, tool_section = tool_util.handle_tool_panel_section( trans,
- tool_panel_section=tool_panel_section,
- new_tool_panel_section=new_tool_panel_section )
+ if includes_tools_for_display_in_tool_panel and ( new_tool_panel_section_label or tool_panel_section_id ):
+ tool_panel_section_key, tool_section = \
+ tool_util.handle_tool_panel_section( trans,
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
else:
tool_panel_section_key = None
tool_section = None
@@ -669,16 +671,19 @@
def order_components_for_installation( trans, tsr_ids, repo_info_dicts, tool_panel_section_keys ):
"""
- Some repositories may have repository dependencies that are required to be installed before the dependent repository. This method will inspect the list of
- repositories about to be installed and make sure to order them appropriately. For each repository about to be installed, if required repositories are not
- contained in the list of repositories about to be installed, then they are not considered. Repository dependency definitions that contain circular dependencies
- should not result in an infinite loop, but obviously prior installation will not be handled for one or more of the repositories that require prior installation.
+ Some repositories may have repository dependencies that are required to be installed before the dependent repository.
+ This method will inspect the list of repositories about to be installed and make sure to order them appropriately.
+ For each repository about to be installed, if required repositories are not contained in the list of repositories about
+ to be installed, then they are not considered. Repository dependency definitions that contain circular dependencies
+ should not result in an infinite loop, but obviously prior installation will not be handled for one or more of the
+ repositories that require prior installation.
"""
ordered_tsr_ids = []
ordered_repo_info_dicts = []
ordered_tool_panel_section_keys = []
- # Create a dictionary whose keys are the received tsr_ids and whose values are a list of tsr_ids, each of which is contained in the received list of tsr_ids
- # and whose associated repository must be installed prior to the repository associated with the tsr_id key.
+ # Create a dictionary whose keys are the received tsr_ids and whose values are a list of tsr_ids, each of which is
+ # contained in the received list of tsr_ids and whose associated repository must be installed prior to the repository
+ # associated with the tsr_id key.
prior_install_required_dict = suc.get_prior_import_or_install_required_dict( trans, tsr_ids, repo_info_dicts )
processed_tsr_ids = []
while len( processed_tsr_ids ) != len( prior_install_required_dict.keys() ):
@@ -690,17 +695,19 @@
for prior_install_required_id in prior_install_required_ids:
if prior_install_required_id not in ordered_tsr_ids:
# Install the associated repository dependency first.
- prior_repo_info_dict, prior_tool_panel_section_key = get_repository_components_for_installation( prior_install_required_id,
- tsr_ids,
- repo_info_dicts,
- tool_panel_section_keys=tool_panel_section_keys )
+ prior_repo_info_dict, prior_tool_panel_section_key = \
+ get_repository_components_for_installation( prior_install_required_id,
+ tsr_ids,
+ repo_info_dicts,
+ tool_panel_section_keys=tool_panel_section_keys )
ordered_tsr_ids.append( prior_install_required_id )
ordered_repo_info_dicts.append( prior_repo_info_dict )
ordered_tool_panel_section_keys.append( prior_tool_panel_section_key )
- repo_info_dict, tool_panel_section_key = get_repository_components_for_installation( tsr_id,
- tsr_ids,
- repo_info_dicts,
- tool_panel_section_keys=tool_panel_section_keys )
+ repo_info_dict, tool_panel_section_key = \
+ get_repository_components_for_installation( tsr_id,
+ tsr_ids,
+ repo_info_dicts,
+ tool_panel_section_keys=tool_panel_section_keys )
ordered_tsr_ids.append( tsr_id )
ordered_repo_info_dicts.append( repo_info_dict )
ordered_tool_panel_section_keys.append( tool_panel_section_key )
@@ -765,11 +772,12 @@
shed_tool_conf, tool_path, relative_install_dir = suc.get_tool_panel_config_tool_path_install_dir( trans.app, repository )
# Reset the repository attributes to the New state for installation.
if metadata:
- tool_section, new_tool_panel_section, tool_panel_section_key = tool_util.handle_tool_panel_selection( trans,
- metadata,
- no_changes_checked=True,
- tool_panel_section=None,
- new_tool_panel_section=None )
+ tool_section, tool_panel_section_key = \
+ tool_util.handle_tool_panel_selection( trans,
+ metadata,
+ no_changes_checked=True,
+ tool_panel_section_id=None,
+ new_tool_panel_section_label=None )
else:
# The tools will be loaded outside of any sections in the tool panel.
tool_panel_section_key = None
diff -r d96d5309b5c52bb43c410c7a943808294bee12c0 -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e lib/tool_shed/util/repository_dependency_util.py
--- a/lib/tool_shed/util/repository_dependency_util.py
+++ b/lib/tool_shed/util/repository_dependency_util.py
@@ -96,7 +96,7 @@
return True
def create_repository_dependency_objects( trans, tool_path, tool_shed_url, repo_info_dicts, install_repository_dependencies=False,
- no_changes_checked=False, tool_panel_section=None, new_tool_panel_section=None ):
+ no_changes_checked=False, tool_panel_section_id=None, new_tool_panel_section_label=None ):
"""
Discover all repository dependencies and make sure all tool_shed_repository and associated repository_dependency records exist as well as
the dependency relationships between installed repositories. This method is called when uninstalled repositories are being reinstalled.
@@ -174,28 +174,30 @@
# The database record for the tool shed repository currently being processed can be updated. Get the repository metadata
# to see where it was previously located in the tool panel.
if repository_db_record and repository_db_record.metadata:
- tool_section, new_tool_panel_section, tool_panel_section_key = \
+ tool_section, tool_panel_section_key = \
tool_util.handle_tool_panel_selection( trans=trans,
metadata=repository_db_record.metadata,
no_changes_checked=no_changes_checked,
- tool_panel_section=tool_panel_section,
- new_tool_panel_section=new_tool_panel_section )
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
else:
# We're installing a new tool shed repository that does not yet have a database record.
- tool_panel_section_key, tool_section = tool_util.handle_tool_panel_section( trans,
- tool_panel_section=tool_panel_section,
- new_tool_panel_section=new_tool_panel_section )
- tool_shed_repository = suc.create_or_update_tool_shed_repository( app=trans.app,
- name=name,
- description=description,
- installed_changeset_revision=changeset_revision,
- ctx_rev=ctx_rev,
- repository_clone_url=repository_clone_url,
- metadata_dict={},
- status=trans.model.ToolShedRepository.installation_status.NEW,
- current_changeset_revision=changeset_revision,
- owner=repository_owner,
- dist_to_shed=False )
+ tool_panel_section_key, tool_section = \
+ tool_util.handle_tool_panel_section( trans,
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
+ tool_shed_repository = \
+ suc.create_or_update_tool_shed_repository( app=trans.app,
+ name=name,
+ description=description,
+ installed_changeset_revision=changeset_revision,
+ ctx_rev=ctx_rev,
+ repository_clone_url=repository_clone_url,
+ metadata_dict={},
+ status=trans.model.ToolShedRepository.installation_status.NEW,
+ current_changeset_revision=changeset_revision,
+ owner=repository_owner,
+ dist_to_shed=False )
if tool_shed_repository not in all_created_or_updated_tool_shed_repositories:
all_created_or_updated_tool_shed_repositories.append( tool_shed_repository )
# Only append the tool shed repository to the list of created_or_updated_tool_shed_repositories if it is supposed to be installed.
@@ -459,9 +461,10 @@
common_util.parse_repository_dependency_tuple( repository_dependency )
if suc.tool_shed_is_this_tool_shed( toolshed ):
required_repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
- required_repository_metadata = metadata_util.get_repository_metadata_by_repository_id_changeset_revision( trans,
- trans.security.encode_id( required_repository.id ),
- changeset_revision )
+ required_repository_metadata = \
+ metadata_util.get_repository_metadata_by_repository_id_changeset_revision( trans,
+ trans.security.encode_id( required_repository.id ),
+ changeset_revision )
if required_repository_metadata:
# The required_repository_metadata changeset_revision is installable.
required_metadata = required_repository_metadata.metadata
diff -r d96d5309b5c52bb43c410c7a943808294bee12c0 -r c40ecf876d68ed86ac874f9b8c4b4d0fcb9bc69e lib/tool_shed/util/tool_util.py
--- a/lib/tool_shed/util/tool_util.py
+++ b/lib/tool_shed/util/tool_util.py
@@ -108,7 +108,7 @@
for k, v in trans.app.toolbox.tool_panel.items():
if isinstance( v, galaxy.tools.ToolSection ):
options.append( ( v.name, v.id ) )
- select_field = SelectField( name='tool_panel_section', display='radio' )
+ select_field = SelectField( name='tool_panel_section_id', display='radio' )
for option_tup in options:
select_field.add_option( option_tup[ 0 ], option_tup[ 1 ] )
return select_field
@@ -461,29 +461,28 @@
fh.close()
return sample_files, deleted_sample_files
-def get_or_create_tool_section( trans, tool_panel_section_id, new_tool_panel_section=None ):
- tool_panel_section_key = 'section_%s' % str( tool_panel_section_id )
+def get_or_create_tool_section( trans, tool_panel_section_id, new_tool_panel_section_label=None ):
+ if tool_panel_section_id.startswith( 'section_' ):
+ tool_panel_section_key = tool_panel_section_id
+ tool_panel_section_id.lstrip( 'section_' )
+ else:
+ tool_panel_section_key = 'section_%s' % str( tool_panel_section_id )
if tool_panel_section_key in trans.app.toolbox.tool_panel:
# Appending a tool to an existing section in trans.app.toolbox.tool_panel
tool_section = trans.app.toolbox.tool_panel[ tool_panel_section_key ]
log.debug( "Appending to tool panel section: %s" % str( tool_section.name ) )
else:
# Appending a new section to trans.app.toolbox.tool_panel
- try:
- new_tool_panel_section_name = new_tool_panel_section.name
- except:
- new_tool_panel_section_name = new_tool_panel_section
- if new_tool_panel_section_name:
- elem = XmlET.Element( 'section' )
- elem.attrib[ 'name' ] = new_tool_panel_section_name
- elem.attrib[ 'id' ] = tool_panel_section_id
- elem.attrib[ 'version' ] = ''
- tool_section = galaxy.tools.ToolSection( elem )
- trans.app.toolbox.tool_panel[ tool_panel_section_key ] = tool_section
- log.debug( "Loading new tool panel section: %s" % str( tool_section.name ) )
- else:
- log.debug( "Unable to create new tool pane section using received new_tool_panel_section: %s" % str( new_tool_panel_section ))
- return None, None
+ if new_tool_panel_section_label is None:
+ # This might add an ugly section label to the tool panel, but, oh well...
+ new_tool_panel_section_label = tool_panel_section_id
+ elem = XmlET.Element( 'section' )
+ elem.attrib[ 'name' ] = new_tool_panel_section_label
+ elem.attrib[ 'id' ] = tool_panel_section_id
+ elem.attrib[ 'version' ] = ''
+ tool_section = galaxy.tools.ToolSection( elem )
+ trans.app.toolbox.tool_panel[ tool_panel_section_key ] = tool_section
+ log.debug( "Loading new tool panel section: %s" % str( tool_section.name ) )
return tool_panel_section_key, tool_section
def get_tool_path_install_dir( partial_install_dir, shed_tool_conf_dict, tool_dict, config_elems ):
@@ -633,16 +632,20 @@
error = True
return error, message
-def handle_tool_panel_selection( trans, metadata, no_changes_checked, tool_panel_section, new_tool_panel_section ):
- """Handle the selected tool panel location for loading tools included in tool shed repositories when installing or reinstalling them."""
+def handle_tool_panel_selection( trans, metadata, no_changes_checked, tool_panel_section_id, new_tool_panel_section_label ):
+ """
+ Handle the selected tool panel location for loading tools included in tool shed repositories when installing
+ or reinstalling them.
+ """
# Get the location in the tool panel in which each tool was originally loaded.
tool_section = None
tool_panel_section_key = None
if 'tools' in metadata:
# This forces everything to be loaded into the same section (or no section) in the tool panel.
if no_changes_checked:
- # Make sure the no_changes check box overrides the new_tool_panel_section if the user checked the check box and entered something into the field.
- new_tool_panel_section = None
+ # Make sure the no_changes check box overrides the new_tool_panel_section_label if the user checked the check
+ # box and entered something into the field.
+ new_tool_panel_section_label = None
if 'tool_panel_section' in metadata:
tool_panel_dict = metadata[ 'tool_panel_section' ]
if not tool_panel_dict:
@@ -656,24 +659,33 @@
original_section_id = tool_section_dict[ 'id' ]
original_section_name = tool_section_dict[ 'name' ]
if original_section_id:
- tool_panel_section_key, tool_section = get_or_create_tool_section( trans,
- tool_panel_section_id=original_section_id,
- new_tool_panel_section=new_tool_panel_section )
+ tool_panel_section_key, tool_section = \
+ get_or_create_tool_section( trans,
+ tool_panel_section_id=original_section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
else:
# The user elected to change the tool panel section to contain the tools.
tool_panel_section_key, tool_section = handle_tool_panel_section( trans,
- tool_panel_section=tool_panel_section,
- new_tool_panel_section=new_tool_panel_section )
- return tool_section, new_tool_panel_section, tool_panel_section_key
+ tool_panel_section_id=tool_panel_section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
+ return tool_section, tool_panel_section_key
-def handle_tool_panel_section( trans, tool_panel_section=None, new_tool_panel_section=None ):
- if new_tool_panel_section:
- section_id = new_tool_panel_section.lower().replace( ' ', '_' )
- tool_panel_section_key, tool_section = get_or_create_tool_section( trans,
- tool_panel_section_id=section_id,
- new_tool_panel_section=new_tool_panel_section )
- elif tool_panel_section:
- tool_panel_section_key = 'section_%s' % str( tool_panel_section )
+def handle_tool_panel_section( trans, tool_panel_section_id=None, new_tool_panel_section_label=None ):
+ """Return a ToolSection object retrieved from the current in-memory tool_panel."""
+ # If tool_panel_section_id is received, the section exists in the tool panel. In this case, the value of the
+ # received tool_panel_section_id must be the id retrieved from a tool panel config (e.g., tool_conf.xml, which
+ # may have getext). If new_tool_panel_section_label is received, a new section will be added to the tool panel.
+ if new_tool_panel_section_label:
+ section_id = 'section_%s' % str( new_tool_panel_section_label.lower().replace( ' ', '_' ) )
+ tool_panel_section_key, tool_section = \
+ get_or_create_tool_section( trans,
+ tool_panel_section_id=section_id,
+ new_tool_panel_section_label=new_tool_panel_section_label )
+ elif tool_panel_section_id:
+ if tool_panel_section_id.startswith( 'section_' ):
+ tool_panel_section_key = str( tool_panel_section_id )
+ else:
+ tool_panel_section_key = 'section_%s' % str( tool_panel_section_id )
tool_section = trans.app.toolbox.tool_panel[ tool_panel_section_key ]
else:
return None, None
https://bitbucket.org/galaxy/galaxy-central/commits/c5c57ec1af3c/
Changeset: c5c57ec1af3c
User: inithello
Date: 2014-01-14 16:35:04
Summary: Merge stable.
Affected #: 4 files
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: Make inclusion of the changeset revision date in the revision label an optional setting and do not include it on some pages.
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0f50bb21db37/
Changeset: 0f50bb21db37
User: greg
Date: 2014-01-14 16:13:53
Summary: Make inclusion of the changeset revision date in the revision label an optional setting and do not include it on some pages.
Affected #: 7 files
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -1137,7 +1137,7 @@
else:
containers_dict = None
export_repository_dependencies_check_box = None
- revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=True )
return trans.fill_template( "/webapps/tool_shed/repository/export_repository.mako",
changeset_revision=changeset_revision,
containers_dict=containers_dict,
@@ -2198,7 +2198,7 @@
selected_value=changeset_revision,
add_id_to_name=False,
downloadable=False )
- revision_label = suc.get_revision_label( trans, repository, repository.tip( trans.app ) )
+ revision_label = suc.get_revision_label( trans, repository, repository.tip( trans.app ), include_date=False )
repository_metadata = None
metadata = None
is_malicious = False
@@ -2207,16 +2207,18 @@
if changeset_revision != suc.INITIAL_CHANGELOG_HASH:
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
if repository_metadata:
- revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=False )
metadata = repository_metadata.metadata
is_malicious = repository_metadata.malicious
else:
- # There is no repository_metadata defined for the changeset_revision, so see if it was defined in a previous changeset in the changelog.
- previous_changeset_revision = suc.get_previous_metadata_changeset_revision( repository, repo, changeset_revision, downloadable=False )
+ # There is no repository_metadata defined for the changeset_revision, so see if it was defined in a previous
+ # changeset in the changelog.
+ previous_changeset_revision = \
+ suc.get_previous_metadata_changeset_revision( repository, repo, changeset_revision, downloadable=False )
if previous_changeset_revision != suc.INITIAL_CHANGELOG_HASH:
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, id, previous_changeset_revision )
if repository_metadata:
- revision_label = suc.get_revision_label( trans, repository, previous_changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, previous_changeset_revision, include_date=False )
metadata = repository_metadata.metadata
is_malicious = repository_metadata.malicious
if repository_metadata:
@@ -2383,7 +2385,7 @@
repository_metadata_id = None
metadata = None
repository_dependencies = None
- revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=True )
changeset_revision_select_field = grids_util.build_changeset_revision_select_field( trans,
repository,
selected_value=changeset_revision,
@@ -2457,7 +2459,7 @@
changeset_revision,
metadata_only=True )
repository_type_select_field = rt_util.build_repository_type_select_field( trans, repository=repository )
- revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=True )
return trans.fill_template( '/webapps/tool_shed/repository/rate_repository.mako',
repository=repository,
metadata=metadata,
@@ -3019,7 +3021,7 @@
selected_value=changeset_revision,
add_id_to_name=False,
downloadable=False )
- revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=False )
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
if repository_metadata:
metadata = repository_metadata.metadata
@@ -3081,7 +3083,7 @@
tool = None
guid = None
original_tool_data_path = trans.app.config.tool_data_path
- revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=False )
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision )
if repository_metadata:
repository_metadata_id = trans.security.encode_id( repository_metadata.id )
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 lib/tool_shed/grids/admin_grids.py
--- a/lib/tool_shed/grids/admin_grids.py
+++ b/lib/tool_shed/grids/admin_grids.py
@@ -422,7 +422,7 @@
repository = repository_metadata.repository
repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) )
ctx = suc.get_changectx_for_changeset( repo, repository_metadata.changeset_revision )
- return suc.get_revision_label( trans, repository, repository_metadata.changeset_revision )
+ return suc.get_revision_label( trans, repository, repository_metadata.changeset_revision, include_date=True )
class ToolsColumn( grids.TextColumn ):
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 lib/tool_shed/grids/repository_grids.py
--- a/lib/tool_shed/grids/repository_grids.py
+++ b/lib/tool_shed/grids/repository_grids.py
@@ -105,7 +105,7 @@
else:
heads_str = ''
for ctx in heads:
- heads_str += '%s<br/>' % suc.get_revision_label_from_ctx( ctx )
+ heads_str += '%s<br/>' % suc.get_revision_label_from_ctx( ctx, include_date=True )
heads_str.rstrip( '<br/>' )
if multiple_heads:
heads_str += '</font>'
@@ -1160,7 +1160,7 @@
def get_value( self, trans, grid, repository_metadata ):
repository = repository_metadata.repository
changeset_revision = repository_metadata.changeset_revision
- changeset_revision_label = suc.get_revision_label( trans, repository, changeset_revision )
+ changeset_revision_label = suc.get_revision_label( trans, repository, changeset_revision, include_date=True )
return changeset_revision_label
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 lib/tool_shed/grids/repository_review_grids.py
--- a/lib/tool_shed/grids/repository_review_grids.py
+++ b/lib/tool_shed/grids/repository_review_grids.py
@@ -307,7 +307,8 @@
rval += 'edit_review'
else:
rval +='browse_review'
- rval += '?id=%s">%s</a>' % ( encoded_review_id, suc.get_revision_label( trans, review.repository, review.changeset_revision ) )
+ revision_label = suc.get_revision_label( trans, review.repository, review.changeset_revision, include_date=True )
+ rval += '?id=%s">%s</a>' % ( encoded_review_id, revision_label )
return rval
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 lib/tool_shed/util/shed_util_common.py
--- a/lib/tool_shed/util/shed_util_common.py
+++ b/lib/tool_shed/util/shed_util_common.py
@@ -1213,25 +1213,33 @@
reversed_changelog.insert( 0, changeset )
return reversed_changelog
-def get_revision_label( trans, repository, changeset_revision ):
- """Return a string consisting of the human read-able changeset rev and the changeset revision string."""
+def get_revision_label( trans, repository, changeset_revision, include_date=True ):
+ """
+ Return a string consisting of the human read-able changeset rev and the changeset revision string
+ which includes the revision date if the receive include_date is True.
+ """
repo = hg.repository( get_configured_ui(), repository.repo_path( trans.app ) )
ctx = get_changectx_for_changeset( repo, changeset_revision )
if ctx:
- return get_revision_label_from_ctx( ctx )
+ return get_revision_label_from_ctx( ctx, include_date=include_date )
else:
return "-1:%s" % changeset_revision
-def get_revision_label_from_ctx( ctx ):
- return '%s:%s <i><font color="#666666">(%s)</font></i>' % \
- ( str( ctx.rev() ), str( ctx ), str( get_readable_ctx_date( ctx ) ) )
+def get_revision_label_from_ctx( ctx, include_date=True ):
+ if include_date:
+ return '%s:%s <i><font color="#666666">(%s)</font></i>' % \
+ ( str( ctx.rev() ), str( ctx ), str( get_readable_ctx_date( ctx ) ) )
+ return '%s:%s' % ( str( ctx.rev() ), str( ctx ) )
-def get_rev_label_from_changeset_revision( repo, changeset_revision ):
- """Given a changeset revision hash, return two strings, the changeset rev and the changeset revision hash."""
+def get_rev_label_from_changeset_revision( repo, changeset_revision, include_date=True ):
+ """
+ Given a changeset revision hash, return two strings, the changeset rev and the changeset revision hash
+ which includes the revision date if the receive include_date is True.
+ """
ctx = get_changectx_for_changeset( repo, changeset_revision )
if ctx:
rev = '%04d' % ctx.rev()
- label = get_revision_label_from_ctx( ctx )
+ label = get_revision_label_from_ctx( ctx, include_date=include_date )
else:
rev = '-1'
label = "-1:%s" % changeset_revision
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 templates/webapps/tool_shed/repository/manage_repository.mako
--- a/templates/webapps/tool_shed/repository/manage_repository.mako
+++ b/templates/webapps/tool_shed/repository/manage_repository.mako
@@ -125,7 +125,7 @@
from tool_shed.util.shed_util_common import get_revision_label_from_ctx
heads_str = ''
for ctx in heads:
- heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx )
+ heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx, include_date=True )
%>
Contact the administrator of this Tool Shed as soon as possible and let them know that
this repository has the following multiple heads which must be merged.<br/>
diff -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 -r 0f50bb21db37bac8444262d9ee561f4e7f7101b0 templates/webapps/tool_shed/repository/view_repository.mako
--- a/templates/webapps/tool_shed/repository/view_repository.mako
+++ b/templates/webapps/tool_shed/repository/view_repository.mako
@@ -72,7 +72,7 @@
from tool_shed.util.shed_util_common import get_revision_label_from_ctx
heads_str = ''
for ctx in heads:
- heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx )
+ heads_str += '%s<br/>' % get_revision_label_from_ctx( ctx, include_date=True )
%>
Contact the administrator of this Tool Shed as soon as possible and let them know that
this repository has the following multiple heads which must be merged.<br/>
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: Visualizations Registry: default to on
by commits-noreply@bitbucket.org 14 Jan '14
by commits-noreply@bitbucket.org 14 Jan '14
14 Jan '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0d50efaac5d4/
Changeset: 0d50efaac5d4
User: carlfeberhard
Date: 2014-01-14 16:04:20
Summary: Visualizations Registry: default to on
Affected #: 16 files
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 config/plugins/visualizations/scatterplot/config/scatterplot.xml
--- a/config/plugins/visualizations/scatterplot/config/scatterplot.xml
+++ b/config/plugins/visualizations/scatterplot/config/scatterplot.xml
@@ -7,9 +7,15 @@
<test type="isinstance" test_attr="datatype" result_type="datatype">tabular.Tabular</test><to_param param_attr="id">dataset_id</to_param></data_source>
+ <data_source>
+ <model_class>Visualization</model_class>
+ <test test_attr="type">scatterplot</test>
+ <to_param param_attr="id">visualization_id</to_param>
+ </data_source></data_sources><params><param type="dataset" var_name_in_template="hda" required="true">dataset_id</param>
+ <param type="visualization" var_name_in_template="visualization">visualization_id</param></params><template>scatterplot.mako</template></visualization>
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -10,6 +10,7 @@
Allow setting perPage of config
Auto render if given data and/or config
Allow option to auto set width/height based on screen real estate avail.
+ Handle large number of pages better (Known genes hg19)
Use d3.nest to allow grouping, pagination/filtration by group (e.g. chromCol)
Semantic HTML (figure, caption)
Save as visualization, load from visualization
@@ -29,26 +30,28 @@
* configuring which data will be used
* configuring the plot display
*/
-var ScatterplotConfigEditor = BaseView.extend( LoggableMixin ).extend({
+var ScatterplotConfigEditor = Backbone.View.extend( LoggableMixin ).extend({
//TODO: !should be a view on a visualization model
//logger : console,
className : 'scatterplot-control-form',
/** initialize requires a configuration Object containing a dataset Object */
initialize : function( attributes ){
- //console.log( this + '.initialize, attributes:', attributes );
- if( !attributes || !attributes.config || !attributes.dataset ){
- throw new Error( "ScatterplotView requires a configuration and dataset" );
+ if( !this.model ){
+ this.model = new Visualization({ type: 'scatterplot' });
}
- //console.log( 'config:', attributes.config );
+ console.log( this + '.initialize, attributes:', attributes );
+ if( !attributes || !attributes.dataset ){
+ throw new Error( "ScatterplotConfigEditor requires a dataset" );
+ }
this.dataset = attributes.dataset;
- //console.log( 'dataset:', this.dataset );
+ console.log( 'dataset:', this.dataset );
//TODO: ScatterplotView -> ScatterplotDisplay, this.plotView -> this.display
this.plotView = new ScatterplotView({
dataset : attributes.dataset,
- config : attributes.config
+ model : this.model
//TODO: if data
});
},
@@ -58,8 +61,7 @@
//console.log( this + '.render' );
// render the tab controls, areas and loading indicator
- this.$el.append( ScatterplotConfigEditor.templates.mainLayout({
- }));
+ this.$el.append( ScatterplotConfigEditor.templates.mainLayout({}));
// render the tab content
this.$el.find( '#data-control' ).append( this._render_dataControl() );
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
--- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
@@ -1,1 +1,1 @@
-function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){q.redraw(),e(),s=d(),$(".chart-info-box").remove(),$(o.node()).trigger("zoom.scatterplot",[])}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,10]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=4;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",b.height).attr("r",0);t.transition().duration(b.animDuration).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",b.datapointSize),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.Templates=this.Templates||{},this.Templates.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<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 data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</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 data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</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 data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</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 data-config-key="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="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="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="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.Templates.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return 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\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\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="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\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 ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\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<button class="render-button btn btn-primary active">Draw</button>\n'}),this.Templates.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(!a||!a.config||!a.dataset)throw new Error("ScatterplotView requires a configuration and dataset");this.dataset=a.dataset,this.plotView=new ScatterplotView({dataset:a.dataset,config:a.config})},render:function(){return this.$el.append(ScatterplotConfigEditor.templates.mainLayout({})),this.$el.find("#data-control").append(this._render_dataControl()),this._render_chartControls(this.$el.find("#chart-control")),this._render_chartDisplay(),this.$el.find("[title]").tooltip(),this},_render_dataControl:function(){var a=this.dataset,b=_.map(a.metadata_column_types,function(b,c){var d={index:c,type:b,name:"column "+(c+1)};return a.metadata_column_names&&a.metadata_column_names[c]&&(d.name=a.metadata_column_names[c]),d}),c=_.filter(b,function(a){return"int"===a.type||"float"===a.type});2>c&&(c=b);var d=this.$el.find(".tab-pane#data-control");return d.html(ScatterplotConfigEditor.templates.dataControl({allColumns:b,numericColumns:c})),d.find('[name="xColumn"]').val(this.plotView.config.xColumn||c[0].index),d.find('[name="yColumn"]').val(this.plotView.config.yColumn||c[1].index),void 0!==this.plotView.config.idColumn&&(d.find("#include-id-checkbox").prop("checked",!0).trigger("change"),d.find('select[name="idColumn"]').val(this.plotView.config.idColumn)),d},_render_chartControls:function(a){function b(){var a=$(this);a.siblings(".slider-output").text(a.slider("value"))}a.html(ScatterplotConfigEditor.templates.chartControl(this.plotView.config));var c=this,d={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};return a.find(".numeric-slider-input").each(function(){var a=$(this),e=a.attr("data-config-key"),f=_.extend(d[e],{value:c.plotView.config[e],change:b,slide:b});a.find(".slider").slider(f)}),this.dataset.metadata_column_names,a},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");return this.plotView.setElement(a),this.plotView.render(),a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart"},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.updateConfigWithDataSettings(),this.updateConfigWithChartSettings(),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.plotView.fetchData()},updateConfigWithDataSettings:function(){var a=this.$el.find("#data-control"),b={xColumn:Number(a.find('[name="xColumn"]').val()),yColumn:Number(a.find('[name="yColumn"]').val())};return a.find("#include-id-checkbox").prop("checked")&&(b.idColumn=a.find('[name="idColumn"]').val()),_.extend(this.plotView.config,b)},updateConfigWithChartSettings:function(){var a=this.plotView,b=this.$el.find("#chart-control");return["datapointSize","width","height"].forEach(function(c){a.config[c]=b.find('.numeric-slider-input[data-config-key="'+c+'"]').find(".slider").slider("value")}),a.config.x.label=b.find('input[name="X-axis-label"]').val(),a.config.y.label=b.find('input[name="Y-axis-label"]').val(),a.config},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:Templates.editor,dataControl:Templates.datacontrol,chartControl:Templates.chartcontrol};var ScatterplotView=Backbone.View.extend({defaults:{metadata:{dataLines:void 0},pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500},initialize:function(a){this.config=_.extend(_.clone(this.defaults),a.config||{}),this.dataset=a.dataset},updateConfig:function(a){this.config=this.config||{},_.extend(this.config,a)},fetchData:function(){this.showLoadingIndicator("getting data");var a=this;return xhr=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:this.config.pagination.perPage,offset:this.config.pagination.currPage*this.config.pagination.perPage}),xhr.done(function(b){a.renderData(b.data)}),xhr.fail(function(a,b,c){alert("Error loading data:\n"+a.responseText),console.error(a,b,c)}),xhr.always(function(){a.hideLoadingIndicator()}),xhr},render:function(a){return this.$el.addClass("scatterplot-display").html(['<div class="controls clear"></div>','<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message"></span>',"</div>","<svg/>",'<div class="stats-display"></div>'].join("")),this.$el.children().hide(),a&&this.renderData(a),this},showLoadingIndicator:function(a,b){a=a||"",b=b||"fast";var c=this.$el.find(".loading-indicator");a&&c.find(".loading-indicator-message").text(a),c.is(":visible")||(this.toggleStats(!1),c.css({left:this.config.width/2,top:this.config.height/2}).show())},hideLoadingIndicator:function(a){a=a||"fast",this.$el.find(".loading-indicator").hide()},renderData:function(a){this.$el.find(".controls").empty().append(this.renderControls(a)).show(),this.renderPlot(a),this.getStats(a)},renderControls:function(a){var b=this,c=$('<div class="left"></div>'),d=$('<div class="right"></div>');return c.append([this.renderPrevNext(a),this.renderPagination(a)]),d.append([this.renderLineInfo(a),$("<button>Stats</button>").addClass("stats-toggle-btn").click(function(){b.toggleStats()}),$("<button>Redraw</button>").addClass("rerender-btn").click(function(){b.renderPlot(a)})]),[c,d]},renderLineInfo:function(a){var b=this.dataset.metadata_data_lines||"an unknown number of",c=this.config.pagination.currPage*this.config.pagination.perPage,d=c+a.length;return $("<p/>").addClass("scatterplot-data-info").text(["Displaying lines",c+1,"to",d,"of",b,"lines"].join(" "))},renderPrevNext:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;var c=this,d=this.dataset.metadata_data_lines,e=d?Math.ceil(d/this.config.pagination.perPage):void 0,f=b("Prev").click(function(){c.config.pagination.currPage>0&&(c.config.pagination.currPage-=1,c.fetchData())}),g=b("Next").click(function(){(!e||c.config.pagination.currPage<e-1)&&(c.config.pagination.currPage+=1,c.fetchData())}),h=$("<ul/>").addClass("pagination data-prev-next").append([f,g]);return 0===c.config.pagination.currPage&&f.addClass("disabled"),e&&c.config.pagination.currPage===e-1&&g.addClass("disabled"),h},renderPagination:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}function c(){d.config.pagination.currPage=$(this).data("page"),d.fetchData()}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;for(var d=this,e=this.dataset.metadata_data_lines,f=e?Math.ceil(e/this.config.pagination.perPage):void 0,g=$("<ul/>").addClass("pagination data-pages"),h=0;f>h;h+=1){var i=b(h+1).attr("data-page",h).click(c);h===this.config.pagination.currPage&&i.addClass("active"),g.append(i)}return g},renderPlot:function(a){this.toggleStats(!1);var b=this.$el.find("svg");b.off().empty().show(),scatterplot(b.get(0),this.config,a)},getStats:function(a){var b=this;meanWorker=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js"),meanWorker.postMessage({data:a,keys:[this.config.xColumn,this.config.yColumn]}),meanWorker.onerror=function(){meanWorker.terminate()},meanWorker.onmessage=function(a){b.renderStats(a.data)}},renderStats:function(a){var b=this.$el.find(".stats-display"),c=this.config.x.label,d=this.config.y.label,e=$("<table/>").addClass("table").append(["<thead><th></th><th>",c,"</th><th>",d,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));b.empty().append(e)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}});
\ No newline at end of file
+function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){q.redraw(),e(),s=d(),$(".chart-info-box").remove(),$(o.node()).trigger("zoom.scatterplot",[])}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,10]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=4;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",b.height).attr("r",0);t.transition().duration(b.animDuration).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",b.datapointSize),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<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 data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</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 data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</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 data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</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 data-config-key="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="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="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="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return 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\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\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="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\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 ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\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<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),console.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,console.log("dataset:",this.dataset),this.plotView=new ScatterplotView({dataset:a.dataset,model:this.model})},render:function(){return this.$el.append(ScatterplotConfigEditor.templates.mainLayout({})),this.$el.find("#data-control").append(this._render_dataControl()),this._render_chartControls(this.$el.find("#chart-control")),this._render_chartDisplay(),this.$el.find("[title]").tooltip(),this},_render_dataControl:function(){var a=this.dataset,b=_.map(a.metadata_column_types,function(b,c){var d={index:c,type:b,name:"column "+(c+1)};return a.metadata_column_names&&a.metadata_column_names[c]&&(d.name=a.metadata_column_names[c]),d}),c=_.filter(b,function(a){return"int"===a.type||"float"===a.type});2>c&&(c=b);var d=this.$el.find(".tab-pane#data-control");return d.html(ScatterplotConfigEditor.templates.dataControl({allColumns:b,numericColumns:c})),d.find('[name="xColumn"]').val(this.plotView.config.xColumn||c[0].index),d.find('[name="yColumn"]').val(this.plotView.config.yColumn||c[1].index),void 0!==this.plotView.config.idColumn&&(d.find("#include-id-checkbox").prop("checked",!0).trigger("change"),d.find('select[name="idColumn"]').val(this.plotView.config.idColumn)),d},_render_chartControls:function(a){function b(){var a=$(this);a.siblings(".slider-output").text(a.slider("value"))}a.html(ScatterplotConfigEditor.templates.chartControl(this.plotView.config));var c=this,d={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};return a.find(".numeric-slider-input").each(function(){var a=$(this),e=a.attr("data-config-key"),f=_.extend(d[e],{value:c.plotView.config[e],change:b,slide:b});a.find(".slider").slider(f)}),this.dataset.metadata_column_names,a},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");return this.plotView.setElement(a),this.plotView.render(),a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart"},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.updateConfigWithDataSettings(),this.updateConfigWithChartSettings(),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.plotView.fetchData()},updateConfigWithDataSettings:function(){var a=this.$el.find("#data-control"),b={xColumn:Number(a.find('[name="xColumn"]').val()),yColumn:Number(a.find('[name="yColumn"]').val())};return a.find("#include-id-checkbox").prop("checked")&&(b.idColumn=a.find('[name="idColumn"]').val()),_.extend(this.plotView.config,b)},updateConfigWithChartSettings:function(){var a=this.plotView,b=this.$el.find("#chart-control");return["datapointSize","width","height"].forEach(function(c){a.config[c]=b.find('.numeric-slider-input[data-config-key="'+c+'"]').find(".slider").slider("value")}),a.config.x.label=b.find('input[name="X-axis-label"]').val(),a.config.y.label=b.find('input[name="Y-axis-label"]').val(),a.config},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotView=Backbone.View.extend({defaults:{metadata:{dataLines:void 0},pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500},initialize:function(a){this.config=_.extend(_.clone(this.defaults),a.config||{}),this.dataset=a.dataset},updateConfig:function(a){this.config=this.config||{},_.extend(this.config,a)},fetchData:function(){this.showLoadingIndicator("getting data");var a=this;return xhr=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:this.config.pagination.perPage,offset:this.config.pagination.currPage*this.config.pagination.perPage}),xhr.done(function(b){a.renderData(b.data)}),xhr.fail(function(a,b,c){alert("Error loading data:\n"+a.responseText),console.error(a,b,c)}),xhr.always(function(){a.hideLoadingIndicator()}),xhr},render:function(a){return this.$el.addClass("scatterplot-display").html(['<div class="controls clear"></div>','<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message"></span>',"</div>","<svg/>",'<div class="stats-display"></div>'].join("")),this.$el.children().hide(),a&&this.renderData(a),this},showLoadingIndicator:function(a,b){a=a||"",b=b||"fast";var c=this.$el.find(".loading-indicator");a&&c.find(".loading-indicator-message").text(a),c.is(":visible")||(this.toggleStats(!1),c.css({left:this.config.width/2,top:this.config.height/2}).show())},hideLoadingIndicator:function(a){a=a||"fast",this.$el.find(".loading-indicator").hide()},renderData:function(a){this.$el.find(".controls").empty().append(this.renderControls(a)).show(),this.renderPlot(a),this.getStats(a)},renderControls:function(a){var b=this,c=$('<div class="left"></div>'),d=$('<div class="right"></div>');return c.append([this.renderPrevNext(a),this.renderPagination(a)]),d.append([this.renderLineInfo(a),$("<button>Stats</button>").addClass("stats-toggle-btn").click(function(){b.toggleStats()}),$("<button>Redraw</button>").addClass("rerender-btn").click(function(){b.renderPlot(a)})]),[c,d]},renderLineInfo:function(a){var b=this.dataset.metadata_data_lines||"an unknown number of",c=this.config.pagination.currPage*this.config.pagination.perPage,d=c+a.length;return $("<p/>").addClass("scatterplot-data-info").text(["Displaying lines",c+1,"to",d,"of",b,"lines"].join(" "))},renderPrevNext:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;var c=this,d=this.dataset.metadata_data_lines,e=d?Math.ceil(d/this.config.pagination.perPage):void 0,f=b("Prev").click(function(){c.config.pagination.currPage>0&&(c.config.pagination.currPage-=1,c.fetchData())}),g=b("Next").click(function(){(!e||c.config.pagination.currPage<e-1)&&(c.config.pagination.currPage+=1,c.fetchData())}),h=$("<ul/>").addClass("pagination data-prev-next").append([f,g]);return 0===c.config.pagination.currPage&&f.addClass("disabled"),e&&c.config.pagination.currPage===e-1&&g.addClass("disabled"),h},renderPagination:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}function c(){d.config.pagination.currPage=$(this).data("page"),d.fetchData()}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;for(var d=this,e=this.dataset.metadata_data_lines,f=e?Math.ceil(e/this.config.pagination.perPage):void 0,g=$("<ul/>").addClass("pagination data-pages"),h=0;f>h;h+=1){var i=b(h+1).attr("data-page",h).click(c);h===this.config.pagination.currPage&&i.addClass("active"),g.append(i)}return g},renderPlot:function(a){this.toggleStats(!1);var b=this.$el.find("svg");b.off().empty().show(),scatterplot(b.get(0),this.config,a)},getStats:function(a){var b=this;meanWorker=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js"),meanWorker.postMessage({data:a,keys:[this.config.xColumn,this.config.yColumn]}),meanWorker.onerror=function(){meanWorker.terminate()},meanWorker.onmessage=function(a){b.renderStats(a.data)}},renderStats:function(a){var b=this.$el.find(".stats-display"),c=this.config.x.label,d=this.config.y.label,e=$("<table/>").addClass("table").append(["<thead><th></th><th>",c,"</th><th>",d,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));b.empty().append(e)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{metadata:{dataLines:void 0},pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500}}});
\ No newline at end of file
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 config/plugins/visualizations/scatterplot/templates/scatterplot.mako
--- a/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
+++ b/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
@@ -1,3 +1,24 @@
+<%
+ hda_dict = trans.security.encode_dict_ids( hda.to_dict() )
+
+ config = query_args
+ title = "Scatterplot of '" + hda.name + "'"
+ info = hda.info
+
+ visualization = context.get( 'visualization' )
+ if visualization is not None:
+ config = visualization.latest_revision.config
+ config.update( query_args )
+ title = visualization.title
+ info = config.get( 'description', info )
+
+ config[ 'type' ] = 'scatterplot'
+
+ # optionally bootstrap data from dprov
+ ##data = list( hda.datatype.dataset_column_dataprovider( hda, limit=10000 ) )
+%>
+## ----------------------------------------------------------------------------
+
<!DOCTYPE HTML><html><head>
@@ -13,14 +34,15 @@
## ----------------------------------------------------------------------------
<script type="text/javascript" src="/static/scripts/libs/jquery/jquery.js"></script><script type="text/javascript" src="/static/scripts/libs/jquery/jquery.migrate.js"></script>
+<script type="text/javascript" src="/static/scripts/libs/jquery/jquery-ui.js"></script>
+<script type="text/javascript" src="/static/scripts/libs/bootstrap.js"></script><script type="text/javascript" src="/static/scripts/libs/underscore.js"></script><script type="text/javascript" src="/static/scripts/libs/backbone/backbone.js"></script><script type="text/javascript" src="/static/scripts/libs/handlebars.runtime.js"></script><script type="text/javascript" src="/static/scripts/libs/d3.js"></script>
-<script type="text/javascript" src="/static/scripts/libs/bootstrap.js"></script>
-<script type="text/javascript" src="/static/scripts/libs/jquery/jquery-ui.js"></script>
-<script type="text/javascript" src="/static/scripts/utils/LazyDataLoader.js"></script>
+
<script type="text/javascript" src="/static/scripts/mvc/base-mvc.js"></script>
+<script type="text/javascript" src="/static/scripts/mvc/visualization/visualization-model.js"></script><script type="text/javascript" src="/plugins/visualizations/scatterplot/static/scatterplot-edit.js"></script></head>
@@ -30,25 +52,20 @@
%if not embedded:
## dataset info: only show if on own page
<div class="chart-header">
- <h2>Scatterplot of '${hda.name}'</h2>
- <p>${hda.info}</p>
+ <h2>${title}</h2>
+ <p>${info}</p></div><div class="scatterplot-editor"></div><script type="text/javascript">
$(function(){
- <%
- # optionally, bootstrap data from dprov
- data = None
- ##data = list( hda.datatype.dataset_column_dataprovider( hda, limit=10000 ) )
- %>
- var hda = ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ) )};
-
- var editor = new ScatterplotConfigEditor({
- el : $( '.scatterplot-editor' ).attr( 'id', 'scatterplot-editor-hda-' + hda.id ),
- config : ${h.to_json_string( query_args )},
- dataset : ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ) )}
- }).render();
+ var model = new ScatterplotModel( ${h.to_json_string( config )} ),
+ hdaJson = ${h.to_json_string( hda_dict )},
+ editor = new ScatterplotConfigEditor({
+ el : $( '.scatterplot-editor' ).attr( 'id', 'scatterplot-editor-hda-' + hdaJson.id ),
+ model : model,
+ dataset : hdaJson
+ }).render();
window.editor = editor;
// uncomment to auto render for development
//$( '.render-button:visible' ).click();
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -87,9 +87,9 @@
load_genome_index_tools( self.toolbox )
# visualizations registry: associates resources with visualizations, controls how to render
self.visualizations_registry = None
- if self.config.visualizations_plugins_directory:
+ if self.config.visualization_plugins_directory:
self.visualizations_registry = VisualizationsRegistry( self,
- directories_setting=self.config.visualizations_plugins_directory,
+ directories_setting=self.config.visualization_plugins_directory,
template_cache_dir=self.config.template_cache )
# Load security policy.
self.security_agent = self.model.security_agent
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -343,7 +343,7 @@
self.fluent_host = kwargs.get( 'fluent_host', 'localhost' )
self.fluent_port = int( kwargs.get( 'fluent_port', 24224 ) )
# visualization plugin framework
- self.visualizations_plugins_directory = kwargs.get( 'visualizations_plugins_directory', None )
+ self.visualization_plugins_directory = kwargs.get( 'visualization_plugins_directory', None )
@property
def sentry_dsn_public( self ):
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -38,6 +38,61 @@
user_pref for ordering/ex/inclusion of particular visualizations
"""
+# ------------------------------------------------------------------- misc
+#TODO: move to utils?
+def getattr_recursive( item, attr_key, *args ):
+ """
+ Allows dot member notation in attribute name when getting an item's attribute.
+
+ NOTE: also searches dictionaries
+ """
+ #print '\n\t getattr_recursive:', item, attr_key
+ using_default = len( args ) >= 1
+ default = args[0] if using_default else None
+ #print '\t defaults:', using_default, default
+
+ for attr_key in attr_key.split( '.' ):
+ try:
+ #print '\t\t attr:', attr_key, 'item:', item
+ if isinstance( item, dict ):
+ item = item.__getitem__( attr_key )
+ else:
+ item = getattr( item, attr_key )
+
+ except ( KeyError, AttributeError ), err:
+ #print '\t\t error:', err
+ if using_default:
+ #print '\t\t\t default:', default
+ return default
+ raise
+
+ return item
+
+def hasattr_recursive( item, attr_key ):
+ """
+ Allows dot member notation in attribute name when getting an item's attribute.
+
+ NOTE: also searches dictionaries
+ """
+ if '.' in attr_key:
+ attr_key, last_key = attr_key.rsplit( '.', 1 )
+ item = getattr_recursive( item, attr_key, None )
+ if item is None:
+ return False
+ attr_key = last_key
+
+ try:
+ #print '\t\t attr:', attr_key, 'item:', item
+ if isinstance( item, dict ):
+ return item.__contains__( attr_key )
+ else:
+ return hasattr( item, attr_key )
+
+ except ( KeyError, AttributeError ), err:
+ return False
+
+ return True
+
# ------------------------------------------------------------------- the registry
class VisualizationsRegistry( pluginframework.PageServingPluginManager ):
"""
@@ -141,21 +196,26 @@
`visualization_name` if it's applicable to `target_object` or
`None` if it's not.
"""
+ #log.debug( 'VisReg.get_visualization: %s, %s', visualization_name, target_object )
visualization = self.plugins.get( visualization_name, None )
if not visualization:
return None
data_sources = visualization.config[ 'data_sources' ]
for data_source in data_sources:
+ #log.debug( 'data_source: %s', data_source )
# currently a model class is required
model_class = data_source[ 'model_class' ]
+ #log.debug( '\t model_class: %s', model_class )
if not isinstance( target_object, model_class ):
continue
+ #log.debug( '\t passed model_class' )
# tests are optional - default is the above class test
tests = data_source[ 'tests' ]
if tests and not self.is_object_applicable( trans, target_object, tests ):
continue
+ #log.debug( '\t passed tests' )
param_data = data_source[ 'to_params' ]
url = self.get_visualization_url( trans, target_object, visualization_name, param_data )
@@ -199,7 +259,7 @@
#NOTE: tests are OR'd, if any test passes - the visualization can be applied
if test_fn( target_object, test_result ):
- #log.debug( 'test passed' )
+ #log.debug( '\t test passed' )
return True
return False
@@ -212,6 +272,8 @@
#precondition: the target_object should be usable by the visualization (accrd. to data_sources)
# convert params using vis.data_source.to_params
params = self.get_url_params( trans, target_object, param_data )
+ #for param in params:
+ # print param
# we want existing visualizations to work as normal but still be part of the registry (without mod'ing)
# so generate their urls differently
@@ -238,13 +300,20 @@
# one or the other is needed
# assign takes precedence (goes last, overwrites)?
#NOTE this is only one level
- if target_attr and hasattr( target_object, target_attr ):
- params[ to_param_name ] = getattr( target_object, target_attr )
+
+ #print 'target_object:', target_object
+ #print 'target_attr:', target_attr
+
+ if target_attr and hasattr_recursive( target_object, target_attr ):
+ params[ to_param_name ] = getattr_recursive( target_object, target_attr )
+ #print 'params[ %s ]:' %( to_param_name ), params[ to_param_name ]
+
if assign:
params[ to_param_name ] = assign
#NOTE!: don't expose raw ids: encode id, _id
if params:
+#TODO: double encodes if from config
params = trans.security.encode_dict_ids( params )
return params
@@ -407,6 +476,7 @@
# these are the allowed classes to associate visualizations with (as strings)
# any model_class element not in this list will throw a parsing ParsingExcepion
ALLOWED_MODEL_CLASSES = [
+ 'Visualization',
'HistoryDatasetAssociation',
'LibraryDatasetDatasetAssociation'
]
@@ -683,6 +753,9 @@
If param is required and not present, raises a `KeyError`.
"""
+ # first parse any params from any visualizations that were passed
+ query_params = self.get_params_from_visualization_param( trans, controller, param_config_dict, query_params )
+
# parse the modifiers first since they modify the params coming next
#TODO: this is all really for hda_ldda - which we could replace with model polymorphism
params_that_modify_other_params = self.parse_parameter_modifiers(
@@ -761,6 +834,44 @@
# (and adding this code to the xml parser)
return self.parse_parameter( trans, param_config, default )
+ def get_params_from_visualization_param( self, trans, controller, param_config_dict, query_params ):
+ log.debug( 'parse_visualization_params: %s', param_config_dict )
+ log.debug( ' : %s', query_params )
+
+ # first, find the visualization in the parameters if any
+ visualization = None
+ #precondition: assume one visualization
+ for param_name, param_config in param_config_dict.items():
+ if param_config.get( 'type' ) == 'visualization':
+ query_val = query_params.get( param_name )
+ if query_val is None:
+ continue
+
+ log.debug( 'found visualization param: %s, %s', param_name, query_val )
+ visualization = self.parse_parameter( trans, controller, param_config, query_val )
+ if visualization:
+ break
+
+ # if no vis is found, can't get any new params from it: return the original query_params
+ if not visualization:
+ log.debug( 'visualization not found' )
+ return query_params
+ log.debug( 'found visualization: %s', visualization )
+
+ # next, attempt to copy any params from the visualizations config
+ visualization_config = visualization.latest_revision.config
+ log.debug( '\t config: %s', visualization_config )
+ params_from_visualization = {}
+ for param_name, param_config in param_config_dict.items():
+ if param_name in visualization_config:
+ params_from_visualization[ param_name ] = visualization_config[ param_name ]
+ log.debug( 'params_from_visualization: %s', params_from_visualization )
+
+ # layer the query_params over the params from the visualization, returning the combined
+ params_from_visualization.update( query_params )
+ return params_from_visualization
+
+#TODO: make parse_visualization separate
def parse_parameter( self, trans, controller, expected_param_data, query_param,
recurse=True, param_modifiers=None ):
"""
@@ -808,6 +919,7 @@
#TODO: subclass here?
elif param_type == 'visualization':
encoded_visualization_id = query_param
+ log.debug( 'visualization param, id : %s', encoded_visualization_id )
#TODO:?? some fallback if there's no get_X in controller that's passed?
parsed_param = controller.get_visualization( trans, encoded_visualization_id,
check_ownership=False, check_accessible=True )
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/mvc/dataset/hda-edit.js
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -248,7 +248,6 @@
classes : 'dataset-visualize-btn',
faIcon : 'fa-bar-chart-o'
});
- $icon.addClass( 'visualize-icon' ); // needed?
// No need for popup menu because there's a single visualization.
if( _.keys( visualizations ).length === 1 ) {
@@ -259,9 +258,21 @@
} else {
var popup_menu_options = [];
_.each( visualizations, function( linkData ) {
+ linkData.func = function(){
+ if( Galaxy.frame.active ){
+ Galaxy.frame.add({
+ title : "Visualization",
+ type : "url",
+ content : linkData.href
+ });
+ return false;
+ }
+ return true;
+ };
popup_menu_options.push( linkData );
+ return false;
});
- var popup = new PopupMenu( $icon, popup_menu_options );
+ PopupMenu.create( $icon, popup_menu_options );
}
return $icon;
},
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/mvc/ui.js
--- a/static/scripts/mvc/ui.js
+++ b/static/scripts/mvc/ui.js
@@ -359,6 +359,10 @@
return 'PopupMenu';
}
});
+/** shortcut to new for when you don't need to preserve the ref */
+PopupMenu.create = function _create( $button, options ){
+ return new PopupMenu( $button, options );
+};
// -----------------------------------------------------------------------------
// the following class functions are bridges from the original make_popupmenu and make_popup_menus
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/mvc/visualization/scatterplotControlForm.js
--- /dev/null
+++ b/static/scripts/mvc/visualization/scatterplotControlForm.js
@@ -0,0 +1,631 @@
+/* =============================================================================
+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 = Backbone.View.extend( LoggableMixin ).extend({
+ //logger : console,
+ className : 'scatterplot-control-form',
+
+ //NOTE: should include time needed to render
+ dataLoadDelay : 4000,
+ dataLoadSize : 5000,
+
+ 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 );
+
+ // 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
+ 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_config.root + 'static/images/' + 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( '[title]' ).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 = [],
+ 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,
+ // 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
+ 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.html( ScatterplotControlForm.templates.dataControl({
+ allColumns : allColumns,
+ 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 );
+ 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',
+ 'change #first-line-header-checkbox' : 'rerenderDataControl',
+ '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();
+ },
+
+ 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)
+ 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',
+ provider : 'column_with_stats',
+ 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 = ( this.$chartControl.find( '#animate-chart' ).is( ':checked' ) )?
+ ( this.chart.defaults.animDuration ):( 0 );
+
+ 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 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/mvc/visualization/visualization-model.js
--- /dev/null
+++ b/static/scripts/mvc/visualization/visualization-model.js
@@ -0,0 +1,158 @@
+//define([
+//], function(){
+
+//function saveVis( title, config ){
+// var xhr = jQuery.ajax( '/api/visualizations', {
+// type : 'POST',
+// data : {
+// type : 'scatterplot',
+// title : title,
+// config : JSON.stringify( config )
+// }
+// });
+// xhr.fail( function( xhr, status, message ){
+// console.debug( jQuery.makeArray( arguments ) );
+// console.error( 'Error saving visualization:', xhr.responseJSON.error );
+// });
+// return xhr.then( function( saveInfo ){
+// return saveInfo;
+// });
+//}
+
+//==============================================================================
+/** @class Model for a saved Galaxy visualization.
+ *
+ * @augments Backbone.Model
+ * @borrows LoggableMixin#logger as #logger
+ * @borrows LoggableMixin#log as #log
+ * @constructs
+ */
+var Visualization = Backbone.Model.extend( LoggableMixin ).extend(
+/** @lends Visualization.prototype */{
+
+ ///** logger used to record this.log messages, commonly set to console */
+ //// comment this out to suppress log output
+ //logger : console,
+
+ /** default attributes for a model */
+ defaults : {
+ //id : null,
+ //type : null,
+ //title : null,
+ //dbkey : null,
+ //user_id : null,
+ //slug : null,
+ //revisions : [],
+ //latest_revision : null
+
+ //(this is unusual in that visualizations don't have configs, revisions do)
+ //config : {}
+ },
+
+ url : function(){
+ return galaxy_config.root + 'api/visualizations';
+ },
+
+ /** Set up the model, determine if accessible, bind listeners
+ * @see Backbone.Model#initialize
+ */
+ initialize : function( data ){
+ this.log( this + '.initialize', data, this.attributes );
+ this._setUpListeners();
+ },
+
+ /** set up any event listeners
+ */
+ _setUpListeners : function(){
+ },
+
+ // ........................................................................ config
+ setConfig: function( config ){
+ var oldConfig = this.get( 'config' );
+ // extend if already exists (and clone in order to trigger change)
+ if( _.isObject( oldConfig ) ){
+ config = _.extend( _.clone( oldConfig ), config );
+ }
+ this.set( 'config', config );
+ return this;
+ },
+
+ // ........................................................................ common queries
+ // ........................................................................ ajax
+ // ........................................................................ misc
+ /** String representation */
+ toString : function(){
+ var idAndTitle = this.get( 'id' ) || '';
+ if( this.get( 'title' ) ){
+ idAndTitle += ':' + this.get( 'title' );
+ }
+ return 'Visualization(' + idAndTitle + ')';
+ }
+});
+
+
+//==============================================================================
+/** @class Backbone collection of visualization models
+ *
+ * @borrows LoggableMixin#logger as #logger
+ * @borrows LoggableMixin#log as #log
+ * @constructs
+ */
+var VisualizationCollection = Backbone.Collection.extend( LoggableMixin ).extend(
+/** @lends VisualizationCollection.prototype */{
+ model : Visualization,
+
+ ///** logger used to record this.log messages, commonly set to console */
+ //// comment this out to suppress log output
+ //logger : console,
+
+ url : function(){
+ return galaxy_config.root + 'api/visualizations';
+ },
+
+ /** Set up.
+ * @see Backbone.Collection#initialize
+ */
+ initialize : function( models, options ){
+ options = options || {};
+ //this._setUpListeners();
+ },
+
+ //_setUpListeners : function(){
+ //},
+
+ // ........................................................................ common queries
+ // ........................................................................ ajax
+ // ........................................................................ misc
+ set : function( models, options ){
+ // arrrrrrrrrrrrrrrrrg...
+ // override to get a correct/smarter merge when incoming data is partial (e.g. stupid backbone)
+ // w/o this partial models from the server will fill in missing data with model defaults
+ // and overwrite existing data on the client
+ // see Backbone.Collection.set and _prepareModel
+ var collection = this;
+ models = _.map( models, function( model ){
+ var existing = collection.get( model.id );
+ if( !existing ){ return model; }
+
+ // merge the models _BEFORE_ calling the superclass version
+ var merged = existing.toJSON();
+ _.extend( merged, model );
+ return merged;
+ });
+ // now call superclass when the data is filled
+ Backbone.Collection.prototype.set.call( this, models, options );
+ },
+
+ /** String representation. */
+ toString : function(){
+ return ([ 'VisualizationCollection(', [ this.historyId, this.length ].join(), ')' ].join( '' ));
+ }
+});
+
+
+//==============================================================================
+//return {
+// Visualization : Visualization,
+// VisualizationCollection : VisualizationCollection
+//};});
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/mvc/visualizations/scatterplotControlForm.js
--- a/static/scripts/mvc/visualizations/scatterplotControlForm.js
+++ /dev/null
@@ -1,631 +0,0 @@
-/* =============================================================================
-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 = Backbone.View.extend( LoggableMixin ).extend({
- //logger : console,
- className : 'scatterplot-control-form',
-
- //NOTE: should include time needed to render
- dataLoadDelay : 4000,
- dataLoadSize : 5000,
-
- 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 );
-
- // 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
- 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_config.root + 'static/images/' + 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( '[title]' ).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 = [],
- 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,
- // 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
- 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.html( ScatterplotControlForm.templates.dataControl({
- allColumns : allColumns,
- 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 );
- 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',
- 'change #first-line-header-checkbox' : 'rerenderDataControl',
- '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();
- },
-
- 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)
- 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',
- provider : 'column_with_stats',
- 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 = ( this.$chartControl.find( '#animate-chart' ).is( ':checked' ) )?
- ( this.chart.defaults.animDuration ):( 0 );
-
- 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 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/packed/mvc/dataset/hda-edit.js
--- a/static/scripts/packed/mvc/dataset/hda-edit.js
+++ b/static/scripts/packed/mvc/dataset/hda-edit.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var i=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});i.addClass("visualize-icon");if(_.keys(g).length===1){i.attr("title",_.keys(g)[0]);i.attr("href",_.values(g)[0])}else{var j=[];_.each(g,function(k){j.push(k)});var h=new PopupMenu(i,j)}return i},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h,scratchbook:true});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i),scratchbook:true})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m,scratchbook:true})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var h=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(_.keys(g).length===1){h.attr("title",_.keys(g)[0]);h.attr("href",_.values(g)[0])}else{var i=[];_.each(g,function(j){j.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:j.href});return false}return true};i.push(j);return false});PopupMenu.create(h,i)}return h},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h,scratchbook:true});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i),scratchbook:true})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m,scratchbook:true})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 static/scripts/packed/mvc/ui.js
--- a/static/scripts/packed/mvc/ui.js
+++ b/static/scripts/packed/mvc/ui.js
@@ -1,1 +1,1 @@
-var IconButton=Backbone.Model.extend({defaults:{title:"",icon_class:"",on_click:null,menu_options:null,is_menu_button:true,id:null,href:null,target:null,enabled:true,visible:true,tooltip_config:{}}});var IconButtonView=Backbone.View.extend({initialize:function(){this.model.attributes.tooltip_config={placement:"bottom"};this.model.bind("change",this.render,this)},render:function(){this.$el.tooltip("hide");var a=this.template(this.model.toJSON());a.tooltip(this.model.get("tooltip_config"));this.$el.replaceWith(a);this.setElement(a);return this},events:{click:"click"},click:function(a){if(_.isFunction(this.model.get("on_click"))){this.model.get("on_click")(a);return false}return true},template:function(b){var a='title="'+b.title+'" class="icon-button';if(b.is_menu_button){a+=" menu-button"}a+=" "+b.icon_class;if(!b.enabled){a+="_disabled"}a+='"';if(b.id){a+=' id="'+b.id+'"'}a+=' href="'+b.href+'"';if(b.target){a+=' target="'+b.target+'"'}if(!b.visible){a+=' style="display: none;"'}if(b.enabled){a="<a "+a+"/>"}else{a="<span "+a+"/>"}return $(a)}});var IconButtonCollection=Backbone.Collection.extend({model:IconButton});var IconButtonMenuView=Backbone.View.extend({tagName:"div",initialize:function(){this.render()},render:function(){var a=this;this.collection.each(function(d){var b=$("<a/>").attr("href","javascript:void(0)").attr("title",d.attributes.title).addClass("icon-button menu-button").addClass(d.attributes.icon_class).appendTo(a.$el).click(d.attributes.on_click);if(d.attributes.tooltip_config){b.tooltip(d.attributes.tooltip_config)}var c=d.get("options");if(c){make_popupmenu(b,c)}});return this}});var create_icon_buttons_menu=function(b,a){if(!a){a={}}var c=new IconButtonCollection(_.map(b,function(d){return new IconButton(_.extend(d,a))}));return new IconButtonMenuView({collection:c})};var Grid=Backbone.Collection.extend({});var GridView=Backbone.View.extend({});var PopupMenu=Backbone.View.extend({initialize:function(b,a){this.$button=b;if(!this.$button.size()){this.$button=$("<div/>")}this.options=a||[];var c=this;this.$button.click(function(d){$(".popmenu-wrapper").remove();c._renderAndShow(d);return false})},_renderAndShow:function(a){this.render();this.$el.appendTo("body").css(this._getShownPosition(a)).show();this._setUpCloseBehavior()},render:function(){this.$el.addClass("popmenu-wrapper").hide().css({position:"absolute"}).html(this.template(this.$button.attr("id"),this.options));if(this.options.length){var a=this;this.$el.find("li").each(function(c,b){var d=a.options[c];if(d.func){$(this).children("a.popupmenu-option").click(function(e){d.func.call(a,e,d)})}})}return this},template:function(b,a){return['<ul id="',b,'-menu" class="dropdown-menu">',this._templateOptions(a),"</ul>"].join("")},_templateOptions:function(a){if(!a.length){return"<li>(no options)</li>"}return _.map(a,function(d){if(d.divider){return'<li class="divider"></li>'}else{if(d.header){return['<li class="head"><a href="javascript:void(0);">',d.html,"</a></li>"].join("")}}var c=d.href||"javascript:void(0);",e=(d.target)?(' target="'+d.target+'"'):(""),b=(d.checked)?('<span class="fa fa-check"></span>'):("");return['<li><a class="popupmenu-option" href="',c,'"',e,">",b,d.html,"</a></li>"].join("")}).join("")},_getShownPosition:function(b){var c=this.$el.width();var a=b.pageX-c/2;a=Math.min(a,$(document).scrollLeft()+$(window).width()-c-5);a=Math.max(a,$(document).scrollLeft()+5);return{top:b.pageY,left:a}},_setUpCloseBehavior:function(){var c=this;function a(e){$(document).off("click.close_popup");if(window.parent!==window){try{$(window.parent.document).off("click.close_popup")}catch(d){}}else{try{$("iframe#galaxy_main").contents().off("click.close_popup")}catch(d){}}c.remove()}$("html").one("click.close_popup",a);if(window.parent!==window){try{$(window.parent.document).find("html").one("click.close_popup",a)}catch(b){}}else{try{$("iframe#galaxy_main").contents().one("click.close_popup",a)}catch(b){}}},addItem:function(b,a){a=(a>=0)?a:this.options.length;this.options.splice(a,0,b);return this},removeItem:function(a){if(a>=0){this.options.splice(a,1)}return this},findIndexByHtml:function(b){for(var a=0;a<this.options.length;a++){if(_.has(this.options[a],"html")&&(this.options[a].html===b)){return a}}return null},findItemByHtml:function(a){return this.options[(this.findIndexByHtml(a))]},toString:function(){return"PopupMenu"}});PopupMenu.make_popupmenu=function(b,c){var a=[];_.each(c,function(f,d){var e={html:d};if(f===null){e.header=true}else{if(jQuery.type(f)==="function"){e.func=f}}a.push(e)});return new PopupMenu($(b),a)};PopupMenu.convertLinksToOptions=function(c,a){c=$(c);a=a||"a";var b=[];c.find(a).each(function(g,e){var f={},d=$(g);f.html=d.text();if(d.attr("href")){var j=d.attr("href"),k=d.attr("target"),h=d.attr("confirm");f.func=function(){if((h)&&(!confirm(h))){return}switch(k){case"_parent":window.parent.location=j;break;case"_top":window.top.location=j;break;default:window.location=j}}}b.push(f)});return b};PopupMenu.fromExistingDom=function(d,c,a){d=$(d);c=$(c);var b=PopupMenu.convertLinksToOptions(c,a);c.remove();return new PopupMenu(d,b)};PopupMenu.make_popup_menus=function(c,b,d){c=c||document;b=b||"div[popupmenu]";d=d||function(e,f){return"#"+e.attr("popupmenu")};var a=[];$(c).find(b).each(function(){var e=$(this),f=$(c).find(d(e,c));a.push(PopupMenu.fromDom(f,e));f.addClass("popup")});return a};var faIconButton=function(a){a=a||{};a.tooltipConfig=a.tooltipConfig||{placement:"bottom"};a.classes=["icon-btn"].concat(a.classes||[]);if(a.disabled){a.classes.push("disabled")}var b=['<a class="',a.classes.join(" "),'"',((a.title)?(' title="'+a.title+'"'):("")),((!a.disabled&&a.target)?(' target="'+a.target+'"'):("")),' href="',((!a.disabled&&a.href)?(a.href):("javascript:void(0);")),'">','<span class="fa ',a.faIcon,'"></span>',"</a>"].join("");var c=$(b).tooltip(a.tooltipConfig);if(_.isFunction(a.onclick)){c.click(a.onclick)}return c};(function(){function a(j,p){var d=27,m=13,c=$(j),e=true,g={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(q){},minSearchLen:0,escWillClear:true,oninit:function(){}};function i(q){var r=$(this).parent().children("input");r.val("");r.trigger("clear:searchInput");p.onclear()}function o(r,q){$(this).trigger("search:searchInput",q);if(typeof p.onfirstsearch==="function"&&e){e=false;p.onfirstsearch(q)}else{p.onsearch(q)}}function f(){return['<input type="text" name="',p.name,'" placeholder="',p.placeholder,'" ','class="search-query ',p.classes,'" ',"/>"].join("")}function l(){return $(f()).focus(function(q){$(this).select()}).keyup(function(r){if(r.which===d&&p.escWillClear){i.call(this,r)}else{var q=$(this).val();if((r.which===m)||(p.minSearchLen&&q.length>=p.minSearchLen)){o.call(this,r,q)}else{if(!q.length){i.call(this,r)}}}}).val(p.initialVal)}function k(){return $(['<span class="search-clear fa fa-times-circle" ','title="',_l("clear search (esc)"),'"></span>'].join("")).tooltip({placement:"bottom"}).click(function(q){i.call(this,q)})}function n(){return $(['<span class="search-loading fa fa-spinner fa-spin" ','title="',_l("loading..."),'"></span>'].join("")).hide().tooltip({placement:"bottom"})}function h(){c.find(".search-loading").toggle();c.find(".search-clear").toggle()}if(jQuery.type(p)==="string"){if(p==="toggle-loading"){h()}return c}if(jQuery.type(p)==="object"){p=jQuery.extend(true,{},g,p)}return c.addClass("search-input").prepend([l(),k(),n()])}jQuery.fn.extend({searchInput:function b(c){return this.each(function(){return a(this,c)})}})}());(function(){function b(m,l){this.currModeIndex=0;return this.init(m,l)}b.prototype.DATA_KEY="mode-button";b.prototype.defaults={modes:[{mode:"default"}]};b.prototype.init=function f(m,l){l=l||{};this.$element=$(m);this.options=jQuery.extend(true,{},this.defaults,l);var o=this;this.$element.click(function n(p){o.callModeFn();o._incModeIndex();$(this).html(o.options.modes[o.currModeIndex].html)});this.currModeIndex=0;if(this.options.initialMode){this.currModeIndex=this._getModeIndex(this.options.initialMode)}return this};b.prototype._getModeIndex=function j(l){for(var m=0;m<this.options.modes.length;m+=1){if(this.options.modes[m].mode===l){return m}}throw new Error("mode not found: "+l)};b.prototype.getCurrMode=function a(){return this.options.modes[this.currModeIndex]};b.prototype.getMode=function g(l){if(!l){return this.getCurrMode()}return this.options.modes[(this._getModeIndex(l))]};b.prototype.hasMode=function k(l){return !!this.getMode(l)};b.prototype.currentMode=function e(){return this.options.modes[this.currModeIndex]};b.prototype.setMode=function c(m){var l=this.getMode(m);this.$element.html(l.html||null);return this};b.prototype._incModeIndex=function d(){this.currModeIndex+=1;if(this.currModeIndex>=this.options.modes.length){this.currModeIndex=0}return this};b.prototype.callModeFn=function h(l){var m=this.getMode(l).onclick;if(m&&jQuery.type(m==="function")){return m.call(this)}return undefined};jQuery.fn.extend({modeButton:function i(m){var l=jQuery.makeArray(arguments).slice(1);return this.map(function(){var p=$(this),o=p.data("mode-button");if(jQuery.type(m)==="object"){o=new b(p,m);p.data("mode-button",o)}else{if(o&&jQuery.type(m)==="string"){var n=o[m];if(jQuery.type(n)==="function"){return n.apply(o,l)}}else{if(o){return o}}}return this})}})}());function LoadingIndicator(a,c){var b=this;c=jQuery.extend({cover:false},c||{});function d(){var e=['<div class="loading-indicator">','<div class="loading-indicator-text">','<span class="fa fa-spinner fa-spin fa-lg"></span>','<span class="loading-indicator-message">loading...</span>',"</div>","</div>"].join("\n");var g=$(e).hide().css(c.css||{position:"fixed"}),f=g.children(".loading-indicator-text");if(c.cover){g.css({"z-index":2,top:a.css("top"),bottom:a.css("bottom"),left:a.css("left"),right:a.css("right"),opacity:0.5,"background-color":"white","text-align":"center"});f=g.children(".loading-indicator-text").css({"margin-top":"20px"})}else{f=g.children(".loading-indicator-text").css({margin:"12px 0px 0px 10px",opacity:"0.85",color:"grey"});f.children(".loading-indicator-message").css({margin:"0px 8px 0px 0px","font-style":"italic"})}return g}b.show=function(f,e,g){f=f||"loading...";e=e||"fast";b.$indicator=d().insertBefore(a);b.message(f);b.$indicator.fadeIn(e,g);return b};b.message=function(e){b.$indicator.find("i").text(e)};b.hide=function(e,f){e=e||"fast";if(b.$indicator&&b.$indicator.size()){b.$indicator.fadeOut(e,function(){b.$indicator.remove();if(f){f()}})}else{if(f){f()}}return b};return b}function dropDownSelect(b,c){c=c||((!_.isEmpty(b))?(b[0]):(""));var a=$(['<div class="dropdown-select btn-group">','<button type="button" class="btn btn-default">','<span class="dropdown-select-selected">'+c+"</span>","</button>","</div>"].join("\n"));if(b&&b.length>1){a.find("button").addClass("dropdown-toggle").attr("data-toggle","dropdown").append(' <span class="caret"></span>');a.append(['<ul class="dropdown-menu" role="menu">',_.map(b,function(e){return['<li><a href="javascript:void(0)">',e,"</a></li>"].join("")}).join("\n"),"</ul>"].join("\n"))}function d(g){var h=$(this),f=h.parents(".dropdown-select"),e=h.text();f.find(".dropdown-select-selected").text(e);f.trigger("change.dropdown-select",e)}a.find("a").click(d);return a}(function(){function e(k,j){return this.init(k,j)}e.prototype.DATA_KEY="filter-control";e.prototype.init=function g(k,j){j=j||{filters:[]};this.$element=$(k).addClass("filter-control btn-group");this.options=jQuery.extend(true,{},this.defaults,j);this.currFilter=this.options.filters[0];return this.render()};e.prototype.render=function d(){this.$element.empty().append([this._renderKeySelect(),this._renderOpSelect(),this._renderValueInput()]);return this};e.prototype._renderKeySelect=function a(){var j=this;var k=this.options.filters.map(function(l){return l.key});this.$keySelect=dropDownSelect(k,this.currFilter.key).addClass("filter-control-key").on("change.dropdown-select",function(m,l){j.currFilter=_.findWhere(j.options.filters,{key:l});j.render()._triggerChange()});return this.$keySelect};e.prototype._renderOpSelect=function i(){var j=this,k=this.currFilter.ops;this.$opSelect=dropDownSelect(k,k[0]).addClass("filter-control-op").on("change.dropdown-select",function(m,l){j._triggerChange()});return this.$opSelect};e.prototype._renderValueInput=function c(){var j=this;if(this.currFilter.values){this.$valueSelect=dropDownSelect(this.currFilter.values,this.currFilter.values[0]).on("change.dropdown-select",function(l,k){j._triggerChange()})}else{this.$valueSelect=$("<input/>").addClass("form-control").on("change",function(k,l){j._triggerChange()})}this.$valueSelect.addClass("filter-control-value");return this.$valueSelect};e.prototype.val=function b(){var k=this.$element.find(".filter-control-key .dropdown-select-selected").text(),m=this.$element.find(".filter-control-op .dropdown-select-selected").text(),j=this.$element.find(".filter-control-value"),l=(j.hasClass("dropdown-select"))?(j.find(".dropdown-select-selected").text()):(j.val());return{key:k,op:m,value:l}};e.prototype._triggerChange=function h(){this.$element.trigger("change.filter-control",this.val())};jQuery.fn.extend({filterControl:function f(k){var j=jQuery.makeArray(arguments).slice(1);return this.map(function(){var n=$(this),m=n.data(e.prototype.DATA_KEY);if(jQuery.type(k)==="object"){m=new e(n,k);n.data(e.prototype.DATA_KEY,m)}if(m&&jQuery.type(k)==="string"){var l=m[k];if(jQuery.type(l)==="function"){return l.apply(m,j)}}return this})}})}());
\ No newline at end of file
+var IconButton=Backbone.Model.extend({defaults:{title:"",icon_class:"",on_click:null,menu_options:null,is_menu_button:true,id:null,href:null,target:null,enabled:true,visible:true,tooltip_config:{}}});var IconButtonView=Backbone.View.extend({initialize:function(){this.model.attributes.tooltip_config={placement:"bottom"};this.model.bind("change",this.render,this)},render:function(){this.$el.tooltip("hide");var a=this.template(this.model.toJSON());a.tooltip(this.model.get("tooltip_config"));this.$el.replaceWith(a);this.setElement(a);return this},events:{click:"click"},click:function(a){if(_.isFunction(this.model.get("on_click"))){this.model.get("on_click")(a);return false}return true},template:function(b){var a='title="'+b.title+'" class="icon-button';if(b.is_menu_button){a+=" menu-button"}a+=" "+b.icon_class;if(!b.enabled){a+="_disabled"}a+='"';if(b.id){a+=' id="'+b.id+'"'}a+=' href="'+b.href+'"';if(b.target){a+=' target="'+b.target+'"'}if(!b.visible){a+=' style="display: none;"'}if(b.enabled){a="<a "+a+"/>"}else{a="<span "+a+"/>"}return $(a)}});var IconButtonCollection=Backbone.Collection.extend({model:IconButton});var IconButtonMenuView=Backbone.View.extend({tagName:"div",initialize:function(){this.render()},render:function(){var a=this;this.collection.each(function(d){var b=$("<a/>").attr("href","javascript:void(0)").attr("title",d.attributes.title).addClass("icon-button menu-button").addClass(d.attributes.icon_class).appendTo(a.$el).click(d.attributes.on_click);if(d.attributes.tooltip_config){b.tooltip(d.attributes.tooltip_config)}var c=d.get("options");if(c){make_popupmenu(b,c)}});return this}});var create_icon_buttons_menu=function(b,a){if(!a){a={}}var c=new IconButtonCollection(_.map(b,function(d){return new IconButton(_.extend(d,a))}));return new IconButtonMenuView({collection:c})};var Grid=Backbone.Collection.extend({});var GridView=Backbone.View.extend({});var PopupMenu=Backbone.View.extend({initialize:function(b,a){this.$button=b;if(!this.$button.size()){this.$button=$("<div/>")}this.options=a||[];var c=this;this.$button.click(function(d){$(".popmenu-wrapper").remove();c._renderAndShow(d);return false})},_renderAndShow:function(a){this.render();this.$el.appendTo("body").css(this._getShownPosition(a)).show();this._setUpCloseBehavior()},render:function(){this.$el.addClass("popmenu-wrapper").hide().css({position:"absolute"}).html(this.template(this.$button.attr("id"),this.options));if(this.options.length){var a=this;this.$el.find("li").each(function(c,b){var d=a.options[c];if(d.func){$(this).children("a.popupmenu-option").click(function(e){d.func.call(a,e,d)})}})}return this},template:function(b,a){return['<ul id="',b,'-menu" class="dropdown-menu">',this._templateOptions(a),"</ul>"].join("")},_templateOptions:function(a){if(!a.length){return"<li>(no options)</li>"}return _.map(a,function(d){if(d.divider){return'<li class="divider"></li>'}else{if(d.header){return['<li class="head"><a href="javascript:void(0);">',d.html,"</a></li>"].join("")}}var c=d.href||"javascript:void(0);",e=(d.target)?(' target="'+d.target+'"'):(""),b=(d.checked)?('<span class="fa fa-check"></span>'):("");return['<li><a class="popupmenu-option" href="',c,'"',e,">",b,d.html,"</a></li>"].join("")}).join("")},_getShownPosition:function(b){var c=this.$el.width();var a=b.pageX-c/2;a=Math.min(a,$(document).scrollLeft()+$(window).width()-c-5);a=Math.max(a,$(document).scrollLeft()+5);return{top:b.pageY,left:a}},_setUpCloseBehavior:function(){var c=this;function a(e){$(document).off("click.close_popup");if(window.parent!==window){try{$(window.parent.document).off("click.close_popup")}catch(d){}}else{try{$("iframe#galaxy_main").contents().off("click.close_popup")}catch(d){}}c.remove()}$("html").one("click.close_popup",a);if(window.parent!==window){try{$(window.parent.document).find("html").one("click.close_popup",a)}catch(b){}}else{try{$("iframe#galaxy_main").contents().one("click.close_popup",a)}catch(b){}}},addItem:function(b,a){a=(a>=0)?a:this.options.length;this.options.splice(a,0,b);return this},removeItem:function(a){if(a>=0){this.options.splice(a,1)}return this},findIndexByHtml:function(b){for(var a=0;a<this.options.length;a++){if(_.has(this.options[a],"html")&&(this.options[a].html===b)){return a}}return null},findItemByHtml:function(a){return this.options[(this.findIndexByHtml(a))]},toString:function(){return"PopupMenu"}});PopupMenu.create=function _create(b,a){return new PopupMenu(b,a)};PopupMenu.make_popupmenu=function(b,c){var a=[];_.each(c,function(f,d){var e={html:d};if(f===null){e.header=true}else{if(jQuery.type(f)==="function"){e.func=f}}a.push(e)});return new PopupMenu($(b),a)};PopupMenu.convertLinksToOptions=function(c,a){c=$(c);a=a||"a";var b=[];c.find(a).each(function(g,e){var f={},d=$(g);f.html=d.text();if(d.attr("href")){var j=d.attr("href"),k=d.attr("target"),h=d.attr("confirm");f.func=function(){if((h)&&(!confirm(h))){return}switch(k){case"_parent":window.parent.location=j;break;case"_top":window.top.location=j;break;default:window.location=j}}}b.push(f)});return b};PopupMenu.fromExistingDom=function(d,c,a){d=$(d);c=$(c);var b=PopupMenu.convertLinksToOptions(c,a);c.remove();return new PopupMenu(d,b)};PopupMenu.make_popup_menus=function(c,b,d){c=c||document;b=b||"div[popupmenu]";d=d||function(e,f){return"#"+e.attr("popupmenu")};var a=[];$(c).find(b).each(function(){var e=$(this),f=$(c).find(d(e,c));a.push(PopupMenu.fromDom(f,e));f.addClass("popup")});return a};var faIconButton=function(a){a=a||{};a.tooltipConfig=a.tooltipConfig||{placement:"bottom"};a.classes=["icon-btn"].concat(a.classes||[]);if(a.disabled){a.classes.push("disabled")}var b=['<a class="',a.classes.join(" "),'"',((a.title)?(' title="'+a.title+'"'):("")),((!a.disabled&&a.target)?(' target="'+a.target+'"'):("")),' href="',((!a.disabled&&a.href)?(a.href):("javascript:void(0);")),'">','<span class="fa ',a.faIcon,'"></span>',"</a>"].join("");var c=$(b).tooltip(a.tooltipConfig);if(_.isFunction(a.onclick)){c.click(a.onclick)}return c};(function(){function a(j,p){var d=27,m=13,c=$(j),e=true,g={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(q){},minSearchLen:0,escWillClear:true,oninit:function(){}};function i(q){var r=$(this).parent().children("input");r.val("");r.trigger("clear:searchInput");p.onclear()}function o(r,q){$(this).trigger("search:searchInput",q);if(typeof p.onfirstsearch==="function"&&e){e=false;p.onfirstsearch(q)}else{p.onsearch(q)}}function f(){return['<input type="text" name="',p.name,'" placeholder="',p.placeholder,'" ','class="search-query ',p.classes,'" ',"/>"].join("")}function l(){return $(f()).focus(function(q){$(this).select()}).keyup(function(r){if(r.which===d&&p.escWillClear){i.call(this,r)}else{var q=$(this).val();if((r.which===m)||(p.minSearchLen&&q.length>=p.minSearchLen)){o.call(this,r,q)}else{if(!q.length){i.call(this,r)}}}}).val(p.initialVal)}function k(){return $(['<span class="search-clear fa fa-times-circle" ','title="',_l("clear search (esc)"),'"></span>'].join("")).tooltip({placement:"bottom"}).click(function(q){i.call(this,q)})}function n(){return $(['<span class="search-loading fa fa-spinner fa-spin" ','title="',_l("loading..."),'"></span>'].join("")).hide().tooltip({placement:"bottom"})}function h(){c.find(".search-loading").toggle();c.find(".search-clear").toggle()}if(jQuery.type(p)==="string"){if(p==="toggle-loading"){h()}return c}if(jQuery.type(p)==="object"){p=jQuery.extend(true,{},g,p)}return c.addClass("search-input").prepend([l(),k(),n()])}jQuery.fn.extend({searchInput:function b(c){return this.each(function(){return a(this,c)})}})}());(function(){function b(m,l){this.currModeIndex=0;return this.init(m,l)}b.prototype.DATA_KEY="mode-button";b.prototype.defaults={modes:[{mode:"default"}]};b.prototype.init=function f(m,l){l=l||{};this.$element=$(m);this.options=jQuery.extend(true,{},this.defaults,l);var o=this;this.$element.click(function n(p){o.callModeFn();o._incModeIndex();$(this).html(o.options.modes[o.currModeIndex].html)});this.currModeIndex=0;if(this.options.initialMode){this.currModeIndex=this._getModeIndex(this.options.initialMode)}return this};b.prototype._getModeIndex=function j(l){for(var m=0;m<this.options.modes.length;m+=1){if(this.options.modes[m].mode===l){return m}}throw new Error("mode not found: "+l)};b.prototype.getCurrMode=function a(){return this.options.modes[this.currModeIndex]};b.prototype.getMode=function g(l){if(!l){return this.getCurrMode()}return this.options.modes[(this._getModeIndex(l))]};b.prototype.hasMode=function k(l){return !!this.getMode(l)};b.prototype.currentMode=function e(){return this.options.modes[this.currModeIndex]};b.prototype.setMode=function c(m){var l=this.getMode(m);this.$element.html(l.html||null);return this};b.prototype._incModeIndex=function d(){this.currModeIndex+=1;if(this.currModeIndex>=this.options.modes.length){this.currModeIndex=0}return this};b.prototype.callModeFn=function h(l){var m=this.getMode(l).onclick;if(m&&jQuery.type(m==="function")){return m.call(this)}return undefined};jQuery.fn.extend({modeButton:function i(m){var l=jQuery.makeArray(arguments).slice(1);return this.map(function(){var p=$(this),o=p.data("mode-button");if(jQuery.type(m)==="object"){o=new b(p,m);p.data("mode-button",o)}else{if(o&&jQuery.type(m)==="string"){var n=o[m];if(jQuery.type(n)==="function"){return n.apply(o,l)}}else{if(o){return o}}}return this})}})}());function LoadingIndicator(a,c){var b=this;c=jQuery.extend({cover:false},c||{});function d(){var e=['<div class="loading-indicator">','<div class="loading-indicator-text">','<span class="fa fa-spinner fa-spin fa-lg"></span>','<span class="loading-indicator-message">loading...</span>',"</div>","</div>"].join("\n");var g=$(e).hide().css(c.css||{position:"fixed"}),f=g.children(".loading-indicator-text");if(c.cover){g.css({"z-index":2,top:a.css("top"),bottom:a.css("bottom"),left:a.css("left"),right:a.css("right"),opacity:0.5,"background-color":"white","text-align":"center"});f=g.children(".loading-indicator-text").css({"margin-top":"20px"})}else{f=g.children(".loading-indicator-text").css({margin:"12px 0px 0px 10px",opacity:"0.85",color:"grey"});f.children(".loading-indicator-message").css({margin:"0px 8px 0px 0px","font-style":"italic"})}return g}b.show=function(f,e,g){f=f||"loading...";e=e||"fast";b.$indicator=d().insertBefore(a);b.message(f);b.$indicator.fadeIn(e,g);return b};b.message=function(e){b.$indicator.find("i").text(e)};b.hide=function(e,f){e=e||"fast";if(b.$indicator&&b.$indicator.size()){b.$indicator.fadeOut(e,function(){b.$indicator.remove();if(f){f()}})}else{if(f){f()}}return b};return b}function dropDownSelect(b,c){c=c||((!_.isEmpty(b))?(b[0]):(""));var a=$(['<div class="dropdown-select btn-group">','<button type="button" class="btn btn-default">','<span class="dropdown-select-selected">'+c+"</span>","</button>","</div>"].join("\n"));if(b&&b.length>1){a.find("button").addClass("dropdown-toggle").attr("data-toggle","dropdown").append(' <span class="caret"></span>');a.append(['<ul class="dropdown-menu" role="menu">',_.map(b,function(e){return['<li><a href="javascript:void(0)">',e,"</a></li>"].join("")}).join("\n"),"</ul>"].join("\n"))}function d(g){var h=$(this),f=h.parents(".dropdown-select"),e=h.text();f.find(".dropdown-select-selected").text(e);f.trigger("change.dropdown-select",e)}a.find("a").click(d);return a}(function(){function e(k,j){return this.init(k,j)}e.prototype.DATA_KEY="filter-control";e.prototype.init=function g(k,j){j=j||{filters:[]};this.$element=$(k).addClass("filter-control btn-group");this.options=jQuery.extend(true,{},this.defaults,j);this.currFilter=this.options.filters[0];return this.render()};e.prototype.render=function d(){this.$element.empty().append([this._renderKeySelect(),this._renderOpSelect(),this._renderValueInput()]);return this};e.prototype._renderKeySelect=function a(){var j=this;var k=this.options.filters.map(function(l){return l.key});this.$keySelect=dropDownSelect(k,this.currFilter.key).addClass("filter-control-key").on("change.dropdown-select",function(m,l){j.currFilter=_.findWhere(j.options.filters,{key:l});j.render()._triggerChange()});return this.$keySelect};e.prototype._renderOpSelect=function i(){var j=this,k=this.currFilter.ops;this.$opSelect=dropDownSelect(k,k[0]).addClass("filter-control-op").on("change.dropdown-select",function(m,l){j._triggerChange()});return this.$opSelect};e.prototype._renderValueInput=function c(){var j=this;if(this.currFilter.values){this.$valueSelect=dropDownSelect(this.currFilter.values,this.currFilter.values[0]).on("change.dropdown-select",function(l,k){j._triggerChange()})}else{this.$valueSelect=$("<input/>").addClass("form-control").on("change",function(k,l){j._triggerChange()})}this.$valueSelect.addClass("filter-control-value");return this.$valueSelect};e.prototype.val=function b(){var k=this.$element.find(".filter-control-key .dropdown-select-selected").text(),m=this.$element.find(".filter-control-op .dropdown-select-selected").text(),j=this.$element.find(".filter-control-value"),l=(j.hasClass("dropdown-select"))?(j.find(".dropdown-select-selected").text()):(j.val());return{key:k,op:m,value:l}};e.prototype._triggerChange=function h(){this.$element.trigger("change.filter-control",this.val())};jQuery.fn.extend({filterControl:function f(k){var j=jQuery.makeArray(arguments).slice(1);return this.map(function(){var n=$(this),m=n.data(e.prototype.DATA_KEY);if(jQuery.type(k)==="object"){m=new e(n,k);n.data(e.prototype.DATA_KEY,m)}if(m&&jQuery.type(k)==="string"){var l=m[k];if(jQuery.type(l)==="function"){return l.apply(m,j)}}return this})}})}());
\ No newline at end of file
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 templates/webapps/galaxy/visualization/scatterplot.mako
--- a/templates/webapps/galaxy/visualization/scatterplot.mako
+++ b/templates/webapps/galaxy/visualization/scatterplot.mako
@@ -216,7 +216,7 @@
)}
${h.js(
- "mvc/visualizations/scatterplotControlForm",
+ "mvc/visualization/scatterplotControlForm",
)}
<script type="text/javascript">
diff -r 1afdcafa4b28e246efc0c96987c63e469e2255e7 -r 0d50efaac5d481b173f379a75b64a42b1b43b8a4 universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -177,7 +177,7 @@
# Visualizations config directory: where to look for individual visualization plugins.
# The path is relative to the Galaxy root dir. To use an absolute path begin the path
# with '/'.
-#visualizations_plugins_directory = config/plugins/visualizations
+visualization_plugins_directory = config/plugins/visualizations
# Each job is given a unique empty directory as its current working directory.
# This option defines in what parent directory those directories will be
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