Thread View
j
: Next unread message
k
: Previous unread message
j a
: Jump to all threads
j l
: Jump to MailingList overview
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/e86e9e97daf8/
changeset: e86e9e97daf8
user: smcmanus
date: 2012-09-29 06:37:46
summary: This is the migration script for the Job and Task tables' exit_code column.
affected #: 1 file
diff -r f76eaed275e6999fbe0cb575f95647fb168ed37f -r e86e9e97daf8f4b682cfe1fe5c12c8a408fd9cc3 lib/galaxy/model/migrate/versions/0107_add_exit_code_to_job_and_task.py
--- /dev/null
+++ b/lib/galaxy/model/migrate/versions/0107_add_exit_code_to_job_and_task.py
@@ -0,0 +1,70 @@
+"""
+Add the exit_code column to the Job and Task tables.
+"""
+
+from sqlalchemy import *
+from sqlalchemy.orm import *
+from migrate import *
+from migrate.changeset import *
+
+import logging
+log = logging.getLogger( __name__ )
+
+# Need our custom types, but don't import anything else from model
+from galaxy.model.custom_types import *
+
+metadata = MetaData( migrate_engine )
+db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
+
+# There was a bug when only one column was used for both tables,
+# so create separate columns.
+exit_code_job_col = Column( "exit_code", Integer, nullable=True )
+exit_code_task_col = Column( "exit_code", Integer, nullable=True )
+
+def display_migration_details():
+ print ""
+ print "This migration script adds a 'handler' column to the Job table."
+
+def upgrade():
+ print __doc__
+ metadata.reflect()
+
+ # Add the exit_code column to the Job table.
+ try:
+ job_table = Table( "job", metadata, autoload=True )
+ exit_code_job_col.create( job_table )
+ assert exit_code_job_col is job_table.c.exit_code
+ except Exception, e:
+ print str(e)
+ log.error( "Adding column 'exit_code' to job table failed: %s" % str( e ) )
+ return
+
+ # Add the exit_code column to the Task table.
+ try:
+ task_table = Table( "task", metadata, autoload=True )
+ exit_code_task_col.create( task_table )
+ assert exit_code_task_col is task_table.c.exit_code
+ except Exception, e:
+ print str(e)
+ log.error( "Adding column 'exit_code' to task table failed: %s" % str( e ) )
+ return
+
+def downgrade():
+ metadata.reflect()
+
+ # Drop the Job table's exit_code column.
+ try:
+ job_table = Table( "job", metadata, autoload=True )
+ exit_code_col = job_table.c.exit_code
+ exit_code_col.drop()
+ except Exception, e:
+ log.debug( "Dropping 'exit_code' column from job table failed: %s" % ( str( e ) ) )
+
+ # Drop the Job table's exit_code column.
+ try:
+ task_table = Table( "task", metadata, autoload=True )
+ exit_code_col = task_table.c.exit_code
+ exit_code_col.drop()
+ except Exception, e:
+ log.debug( "Dropping 'exit_code' column from task table failed: %s" % ( str( e ) ) )
+
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 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/f76eaed275e6/
changeset: f76eaed275e6
user: smcmanus
date: 2012-09-29 05:26:53
summary: Added exit_code columns for the Job and Task tables, as well as a migration script
affected #: 2 files
diff -r 578c082d9b46689268aa4a0cd4d29327f84d2791 -r f76eaed275e6999fbe0cb575f95647fb168ed37f lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -134,6 +134,7 @@
self.post_job_actions = []
self.imported = False
self.handler = None
+ self.exit_code = 0
# TODO: Add accessors for members defined in SQL Alchemy for the Job table and
# for the mapper defined to the Job table.
@@ -319,6 +320,7 @@
# SM: Using default empty strings avoids None exceptions later on.
self.stdout = ""
self.stderr = ""
+ self.exit_code = 0
self.prepare_input_files_cmd = prepare_files_cmd
def get_param_values( self, app ):
diff -r 578c082d9b46689268aa4a0cd4d29327f84d2791 -r f76eaed275e6999fbe0cb575f95647fb168ed37f lib/galaxy/model/mapping.py
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -435,6 +435,7 @@
Column( "runner_name", String( 255 ) ),
Column( "stdout", TEXT ),
Column( "stderr", TEXT ),
+ Column( "exit_code", Integer, nullable=True ),
Column( "traceback", TEXT ),
Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ),
@@ -529,6 +530,7 @@
Column( "runner_name", String( 255 ) ),
Column( "stdout", TEXT ),
Column( "stderr", TEXT ),
+ Column( "exit_code", Integer, nullable=True ),
Column( "info", TrimmedString ( 255 ) ),
Column( "traceback", TEXT ),
Column( "job_id", Integer, ForeignKey( "job.id" ), index=True, nullable=False ),
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org . You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/578c082d9b46/
changeset: 578c082d9b46
user: james_taylor
date: 2012-09-28 23:55:02
summary: Lookup templates in 'templates/webapps/${webapp.name}/' first before looking in the root directory. This avoids the ned to specify the webapp name in the template path. Still needs to be updated in nearly all templates
affected #: 10 files
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -220,13 +220,23 @@
base.WebApplication.__init__( self )
self.set_transaction_factory( lambda e: self.transaction_chooser( e, galaxy_app, session_cookie ) )
# Mako support
- self.mako_template_lookup = mako.lookup.TemplateLookup(
- directories = [ galaxy_app.config.template_path ] ,
+ self.mako_template_lookup = self.create_mako_template_lookup( galaxy_app, name )
+ # Security helper
+ self.security = galaxy_app.security
+
+ def create_mako_template_lookup( self, galaxy_app, name ):
+ paths = []
+ # First look in webapp specific directory
+ if name is not None:
+ paths.append( os.path.join( galaxy_app.config.template_path, 'webapps', name ) )
+ # Then look in root directory
+ paths.append( galaxy_app.config.template_path )
+ # Create TemplateLookup with a small cache
+ return mako.lookup.TemplateLookup(
+ directories = paths,
module_directory = galaxy_app.config.template_cache,
collection_size = 500,
output_encoding = 'utf-8' )
- # Security helper
- self.security = galaxy_app.security
def handle_controller_exception( self, e, trans, **kwargs ):
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/base/base_panels.mako
--- /dev/null
+++ b/templates/base/base_panels.mako
@@ -0,0 +1,299 @@
+<!DOCTYPE HTML>
+
+<%
+ self.has_left_panel=True
+ self.has_right_panel=True
+ self.message_box_visible=False
+ self.overlay_visible=False
+ self.message_box_class=""
+ self.active_view=None
+ self.body_class=""
+ self.require_javascript=False
+%>
+
+<%def name="init()">
+ ## Override
+</%def>
+
+## Default stylesheets
+<%def name="stylesheets()">
+ ${h.css('base','panel_layout','jquery.rating')}
+ <style type="text/css">
+ body, html {
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ }
+ #center {
+ %if not self.has_left_panel:
+ left: 0 !important;
+ %endif
+ %if not self.has_right_panel:
+ right: 0 !important;
+ %endif
+ }
+ %if self.message_box_visible:
+ #left, #left-border, #center, #right-border, #right
+ {
+ top: 64px;
+ }
+ %endif
+ </style>
+</%def>
+
+## Default javascripts
+<%def name="javascripts()">
+ <!--[if lt IE 7]>
+ ${h.js( 'libs/IE/IE7', 'libs/IE/ie7-recalc' )}
+ <![endif]-->
+ ${h.js(
+ 'libs/jquery/jquery',
+ 'libs/json2',
+ 'libs/bootstrap',
+ 'libs/underscore',
+ 'libs/backbone/backbone',
+ 'libs/backbone/backbone-relational',
+ 'libs/handlebars.runtime',
+ 'mvc/ui',
+ 'galaxy.base'
+ )}
+ <script type="text/javascript">
+ // Set up needed paths.
+ var galaxy_paths = new GalaxyPaths({
+ root_path: '${h.url_for( "/" )}',
+ image_path: '${h.url_for( "/static/images" )}',
+
+ tool_url: '${h.url_for( controller="/api/tools" )}',
+ history_url: '${h.url_for( controller="/api/histories" )}',
+
+ datasets_url: '${h.url_for( controller="/api/datasets" )}',
+ sweepster_url: '${h.url_for( controller="/visualization", action="sweepster" )}',
+ visualization_url: '${h.url_for( controller="/visualization", action="save" )}',
+ });
+ </script>
+</%def>
+
+## Default late-load javascripts
+<%def name="late_javascripts()">
+ ## Scripts can be loaded later since they progressively add features to
+ ## the panels, but do not change layout
+ ${h.js( 'libs/jquery/jquery.event.drag', 'libs/jquery/jquery.event.hover', 'libs/jquery/jquery.form', 'libs/jquery/jquery.rating', 'galaxy.panels' )}
+ <script type="text/javascript">
+
+ ensure_dd_helper();
+
+ %if self.has_left_panel:
+ var lp = new Panel( { panel: $("#left"), center: $("#center"), drag: $("#left > .unified-panel-footer > .drag" ), toggle: $("#left > .unified-panel-footer > .panel-collapse" ) } );
+ force_left_panel = function( x ) { lp.force_panel( x ) };
+ %endif
+
+ %if self.has_right_panel:
+ var rp = new Panel( { panel: $("#right"), center: $("#center"), drag: $("#right > .unified-panel-footer > .drag" ), toggle: $("#right > .unified-panel-footer > .panel-collapse" ), right: true } );
+ window.handle_minwidth_hint = function( x ) { rp.handle_minwidth_hint( x ) };
+ force_right_panel = function( x ) { rp.force_panel( x ) };
+ %endif
+
+ </script>
+ ## Handle AJAX (actually hidden iframe) upload tool
+ <![if !IE]>
+ <script type="text/javascript">
+ var upload_form_error = function( msg ) {
+ if ( ! $("iframe#galaxy_main").contents().find("body").find("div[class='errormessage']").size() ) {
+ $("iframe#galaxy_main").contents().find("body").prepend( '<div class="errormessage" name="upload_error">' + msg + '</div><p/>' );
+ } else {
+ $("iframe#galaxy_main").contents().find("body").find("div[class='errormessage']").text( msg );
+ }
+ }
+ var uploads_in_progress = 0;
+ jQuery( function() {
+ $("iframe#galaxy_main").load( function() {
+ $(this).contents().find("form").each( function() {
+ if ( $(this).find("input[galaxy-ajax-upload]").length > 0 ){
+ $(this).submit( function() {
+ // Only bother using a hidden iframe if there's a file (e.g. big data) upload
+ var file_upload = false;
+ $(this).find("input[galaxy-ajax-upload]").each( function() {
+ if ( $(this).val() != '' ) {
+ file_upload = true;
+ }
+ });
+ if ( ! file_upload ) {
+ return true;
+ }
+ // Make a synchronous request to create the datasets first
+ var async_datasets;
+ var upload_error = false;
+ $.ajax( {
+ async: false,
+ type: "POST",
+ url: "${h.url_for(controller='/tool_runner', action='upload_async_create')}",
+ data: $(this).formSerialize(),
+ dataType: "json",
+ success: function(array_obj, status) {
+ if (array_obj.length > 0) {
+ if (array_obj[0] == 'error') {
+ upload_error = true;
+ upload_form_error(array_obj[1]);
+ } else {
+ async_datasets = array_obj.join();
+ }
+ } else {
+ // ( gvk 1/22/10 ) FIXME: this block is never entered, so there may be a bug somewhere
+ // I've done some debugging like checking to see if array_obj is undefined, but have not
+ // tracked down the behavior that will result in this block being entered. I believe the
+ // intent was to have this block entered if the upload button is clicked on the upload
+ // form but no file was selected.
+ upload_error = true;
+ upload_form_error( 'No data was entered in the upload form. You may choose to upload a file, paste some data directly in the data box, or enter URL(s) to fetch data.' );
+ }
+ }
+ } );
+ if (upload_error == true) {
+ return false;
+ } else {
+ $(this).find("input[name=async_datasets]").val( async_datasets );
+ $(this).append("<input type='hidden' name='ajax_upload' value='true'>");
+ }
+ // iframe submit is required for nginx (otherwise the encoding is wrong)
+ $(this).ajaxSubmit( { iframe: true,
+ complete: function (xhr, stat) {
+ uploads_in_progress--;
+ if (uploads_in_progress == 0) {
+ window.onbeforeunload = null;
+ }
+ }
+ } );
+ uploads_in_progress++;
+ window.onbeforeunload = function() { return "Navigating away from the Galaxy analysis interface will interrupt the file upload(s) currently in progress. Do you really want to do this?"; }
+ if ( $(this).find("input[name='folder_id']").val() != undefined ) {
+ var library_id = $(this).find("input[name='library_id']").val();
+ var show_deleted = $(this).find("input[name='show_deleted']").val();
+ if ( location.pathname.indexOf( 'admin' ) != -1 ) {
+ $("iframe#galaxy_main").attr("src","${h.url_for( controller='library_common', action='browse_library' )}?cntrller=library_admin&id=" + library_id + "&created_ldda_ids=" + async_datasets + "&show_deleted=" + show_deleted);
+ } else {
+ $("iframe#galaxy_main").attr("src","${h.url_for( controller='library_common', action='browse_library' )}?cntrller=library&id=" + library_id + "&created_ldda_ids=" + async_datasets + "&show_deleted=" + show_deleted);
+ }
+ } else {
+ $("iframe#galaxy_main").attr("src","${h.url_for(controller='tool_runner', action='upload_async_message')}");
+ }
+ return false;
+ });
+ }
+ });
+ });
+ });
+ </script>
+ <![endif]>
+</%def>
+
+## Masthead
+<%def name="masthead()">
+ ## Override
+</%def>
+
+<%def name="overlay( title='', content='', visible=False )">
+ <%def name="title()"></%def>
+ <%def name="content()"></%def>
+
+ <%
+ if visible:
+ display = "style='display: block;'"
+ overlay_class = "in"
+ else:
+ display = "style='display: none;'"
+ overlay_class = ""
+ %>
+
+ <div id="overlay" ${display}>
+
+ <div id="overlay-background" class="modal-backdrop fade ${overlay_class}"></div>
+
+ <div id="dialog-box" class="modal dialog-box" border="0" ${display}>
+ <div class="modal-header">
+ <span><h3 class='title'>${title}</h3></span>
+ </div>
+ <div class="modal-body">${content}</div>
+ <div class="modal-footer">
+ <div class="buttons" style="float: right;"></div>
+ <div class="extra_buttons" style=""></div>
+ <div style="clear: both;"></div>
+ </div>
+ </div>
+
+ </div>
+</%def>
+
+## Messagebox
+<%def name="message_box_content()">
+</%def>
+
+## Document
+<html>
+ ${self.init()}
+ <head>
+ <title>${self.title()}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ ## For mobile browsers, don't scale up
+ <meta name = "viewport" content = "maximum-scale=1.0">
+ ## Force IE to standards mode, and prefer Google Chrome Frame if the user has already installed it
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
+ ${self.stylesheets()}
+ ${self.javascripts()}
+ </head>
+
+ <body scroll="no" class="${self.body_class}">
+ %if self.require_javascript:
+ <noscript>
+ <div class="overlay overlay-background">
+ <div class="modal dialog-box" border="0">
+ <div class="modal-header"><h3 class="title">Javascript Required</h3></div>
+ <div class="modal-body">The Galaxy analysis interface requires a browser with Javascript enabled. <br> Please enable Javascript and refresh this page</div>
+ </div>
+ </div>
+ </noscript>
+ %endif
+ <div id="everything" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; min-width: 600px;">
+ ## Background displays first
+ <div id="background"></div>
+ ## Layer iframes over backgrounds
+ <div id="masthead" class="navbar navbar-fixed-top">
+ <div class="masthead-inner navbar-inner">
+ ${self.masthead()}
+ </div>
+ </div>
+ <div id="messagebox" class="panel-${self.message_box_class}-message">
+ %if self.message_box_visible:
+ ${self.message_box_content()}
+ %endif
+ </div>
+ ${self.overlay(visible=self.overlay_visible)}
+ %if self.has_left_panel:
+ <div id="left">
+ ${self.left_panel()}
+ <div class="unified-panel-footer">
+ <div class="panel-collapse"></span></div>
+ <div class="drag"></div>
+ </div>
+ </div>
+ %endif
+ <div id="center">
+ ${self.center_panel()}
+ </div>
+ %if self.has_right_panel:
+ <div id="right">
+ ${self.right_panel()}
+ <div class="unified-panel-footer">
+ <div class="panel-collapse right"></span></div>
+ <div class="drag"></div>
+ </div>
+ </div>
+ %endif
+ </div>
+ ## Allow other body level elements
+ </body>
+ ## Scripts can be loaded later since they progressively add features to
+ ## the panels, but do not change layout
+ ${self.late_javascripts()}
+</html>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/base_panels.mako
--- a/templates/base_panels.mako
+++ /dev/null
@@ -1,299 +0,0 @@
-<!DOCTYPE HTML>
-
-<%
- self.has_left_panel=True
- self.has_right_panel=True
- self.message_box_visible=False
- self.overlay_visible=False
- self.message_box_class=""
- self.active_view=None
- self.body_class=""
- self.require_javascript=False
-%>
-
-<%def name="init()">
- ## Override
-</%def>
-
-## Default stylesheets
-<%def name="stylesheets()">
- ${h.css('base','panel_layout','jquery.rating')}
- <style type="text/css">
- body, html {
- overflow: hidden;
- margin: 0;
- padding: 0;
- width: 100%;
- height: 100%;
- }
- #center {
- %if not self.has_left_panel:
- left: 0 !important;
- %endif
- %if not self.has_right_panel:
- right: 0 !important;
- %endif
- }
- %if self.message_box_visible:
- #left, #left-border, #center, #right-border, #right
- {
- top: 64px;
- }
- %endif
- </style>
-</%def>
-
-## Default javascripts
-<%def name="javascripts()">
- <!--[if lt IE 7]>
- ${h.js( 'libs/IE/IE7', 'libs/IE/ie7-recalc' )}
- <![endif]-->
- ${h.js(
- 'libs/jquery/jquery',
- 'libs/json2',
- 'libs/bootstrap',
- 'libs/underscore',
- 'libs/backbone/backbone',
- 'libs/backbone/backbone-relational',
- 'libs/handlebars.runtime',
- 'mvc/ui',
- 'galaxy.base'
- )}
- <script type="text/javascript">
- // Set up needed paths.
- var galaxy_paths = new GalaxyPaths({
- root_path: '${h.url_for( "/" )}',
- image_path: '${h.url_for( "/static/images" )}',
-
- tool_url: '${h.url_for( controller="/api/tools" )}',
- history_url: '${h.url_for( controller="/api/histories" )}',
-
- datasets_url: '${h.url_for( controller="/api/datasets" )}',
- sweepster_url: '${h.url_for( controller="/visualization", action="sweepster" )}',
- visualization_url: '${h.url_for( controller="/visualization", action="save" )}',
- });
- </script>
-</%def>
-
-## Default late-load javascripts
-<%def name="late_javascripts()">
- ## Scripts can be loaded later since they progressively add features to
- ## the panels, but do not change layout
- ${h.js( 'libs/jquery/jquery.event.drag', 'libs/jquery/jquery.event.hover', 'libs/jquery/jquery.form', 'libs/jquery/jquery.rating', 'galaxy.panels' )}
- <script type="text/javascript">
-
- ensure_dd_helper();
-
- %if self.has_left_panel:
- var lp = new Panel( { panel: $("#left"), center: $("#center"), drag: $("#left > .unified-panel-footer > .drag" ), toggle: $("#left > .unified-panel-footer > .panel-collapse" ) } );
- force_left_panel = function( x ) { lp.force_panel( x ) };
- %endif
-
- %if self.has_right_panel:
- var rp = new Panel( { panel: $("#right"), center: $("#center"), drag: $("#right > .unified-panel-footer > .drag" ), toggle: $("#right > .unified-panel-footer > .panel-collapse" ), right: true } );
- window.handle_minwidth_hint = function( x ) { rp.handle_minwidth_hint( x ) };
- force_right_panel = function( x ) { rp.force_panel( x ) };
- %endif
-
- </script>
- ## Handle AJAX (actually hidden iframe) upload tool
- <![if !IE]>
- <script type="text/javascript">
- var upload_form_error = function( msg ) {
- if ( ! $("iframe#galaxy_main").contents().find("body").find("div[class='errormessage']").size() ) {
- $("iframe#galaxy_main").contents().find("body").prepend( '<div class="errormessage" name="upload_error">' + msg + '</div><p/>' );
- } else {
- $("iframe#galaxy_main").contents().find("body").find("div[class='errormessage']").text( msg );
- }
- }
- var uploads_in_progress = 0;
- jQuery( function() {
- $("iframe#galaxy_main").load( function() {
- $(this).contents().find("form").each( function() {
- if ( $(this).find("input[galaxy-ajax-upload]").length > 0 ){
- $(this).submit( function() {
- // Only bother using a hidden iframe if there's a file (e.g. big data) upload
- var file_upload = false;
- $(this).find("input[galaxy-ajax-upload]").each( function() {
- if ( $(this).val() != '' ) {
- file_upload = true;
- }
- });
- if ( ! file_upload ) {
- return true;
- }
- // Make a synchronous request to create the datasets first
- var async_datasets;
- var upload_error = false;
- $.ajax( {
- async: false,
- type: "POST",
- url: "${h.url_for(controller='/tool_runner', action='upload_async_create')}",
- data: $(this).formSerialize(),
- dataType: "json",
- success: function(array_obj, status) {
- if (array_obj.length > 0) {
- if (array_obj[0] == 'error') {
- upload_error = true;
- upload_form_error(array_obj[1]);
- } else {
- async_datasets = array_obj.join();
- }
- } else {
- // ( gvk 1/22/10 ) FIXME: this block is never entered, so there may be a bug somewhere
- // I've done some debugging like checking to see if array_obj is undefined, but have not
- // tracked down the behavior that will result in this block being entered. I believe the
- // intent was to have this block entered if the upload button is clicked on the upload
- // form but no file was selected.
- upload_error = true;
- upload_form_error( 'No data was entered in the upload form. You may choose to upload a file, paste some data directly in the data box, or enter URL(s) to fetch data.' );
- }
- }
- } );
- if (upload_error == true) {
- return false;
- } else {
- $(this).find("input[name=async_datasets]").val( async_datasets );
- $(this).append("<input type='hidden' name='ajax_upload' value='true'>");
- }
- // iframe submit is required for nginx (otherwise the encoding is wrong)
- $(this).ajaxSubmit( { iframe: true,
- complete: function (xhr, stat) {
- uploads_in_progress--;
- if (uploads_in_progress == 0) {
- window.onbeforeunload = null;
- }
- }
- } );
- uploads_in_progress++;
- window.onbeforeunload = function() { return "Navigating away from the Galaxy analysis interface will interrupt the file upload(s) currently in progress. Do you really want to do this?"; }
- if ( $(this).find("input[name='folder_id']").val() != undefined ) {
- var library_id = $(this).find("input[name='library_id']").val();
- var show_deleted = $(this).find("input[name='show_deleted']").val();
- if ( location.pathname.indexOf( 'admin' ) != -1 ) {
- $("iframe#galaxy_main").attr("src","${h.url_for( controller='library_common', action='browse_library' )}?cntrller=library_admin&id=" + library_id + "&created_ldda_ids=" + async_datasets + "&show_deleted=" + show_deleted);
- } else {
- $("iframe#galaxy_main").attr("src","${h.url_for( controller='library_common', action='browse_library' )}?cntrller=library&id=" + library_id + "&created_ldda_ids=" + async_datasets + "&show_deleted=" + show_deleted);
- }
- } else {
- $("iframe#galaxy_main").attr("src","${h.url_for(controller='tool_runner', action='upload_async_message')}");
- }
- return false;
- });
- }
- });
- });
- });
- </script>
- <![endif]>
-</%def>
-
-## Masthead
-<%def name="masthead()">
- ## Override
-</%def>
-
-<%def name="overlay( title='', content='', visible=False )">
- <%def name="title()"></%def>
- <%def name="content()"></%def>
-
- <%
- if visible:
- display = "style='display: block;'"
- overlay_class = "in"
- else:
- display = "style='display: none;'"
- overlay_class = ""
- %>
-
- <div id="overlay" ${display}>
-
- <div id="overlay-background" class="modal-backdrop fade ${overlay_class}"></div>
-
- <div id="dialog-box" class="modal dialog-box" border="0" ${display}>
- <div class="modal-header">
- <span><h3 class='title'>${title}</h3></span>
- </div>
- <div class="modal-body">${content}</div>
- <div class="modal-footer">
- <div class="buttons" style="float: right;"></div>
- <div class="extra_buttons" style=""></div>
- <div style="clear: both;"></div>
- </div>
- </div>
-
- </div>
-</%def>
-
-## Messagebox
-<%def name="message_box_content()">
-</%def>
-
-## Document
-<html>
- ${self.init()}
- <head>
- <title>${self.title()}</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- ## For mobile browsers, don't scale up
- <meta name = "viewport" content = "maximum-scale=1.0">
- ## Force IE to standards mode, and prefer Google Chrome Frame if the user has already installed it
- <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
- ${self.stylesheets()}
- ${self.javascripts()}
- </head>
-
- <body scroll="no" class="${self.body_class}">
- %if self.require_javascript:
- <noscript>
- <div class="overlay overlay-background">
- <div class="modal dialog-box" border="0">
- <div class="modal-header"><h3 class="title">Javascript Required</h3></div>
- <div class="modal-body">The Galaxy analysis interface requires a browser with Javascript enabled. <br> Please enable Javascript and refresh this page</div>
- </div>
- </div>
- </noscript>
- %endif
- <div id="everything" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; min-width: 600px;">
- ## Background displays first
- <div id="background"></div>
- ## Layer iframes over backgrounds
- <div id="masthead" class="navbar navbar-fixed-top">
- <div class="masthead-inner navbar-inner">
- ${self.masthead()}
- </div>
- </div>
- <div id="messagebox" class="panel-${self.message_box_class}-message">
- %if self.message_box_visible:
- ${self.message_box_content()}
- %endif
- </div>
- ${self.overlay(visible=self.overlay_visible)}
- %if self.has_left_panel:
- <div id="left">
- ${self.left_panel()}
- <div class="unified-panel-footer">
- <div class="panel-collapse"></span></div>
- <div class="drag"></div>
- </div>
- </div>
- %endif
- <div id="center">
- ${self.center_panel()}
- </div>
- %if self.has_right_panel:
- <div id="right">
- ${self.right_panel()}
- <div class="unified-panel-footer">
- <div class="panel-collapse right"></span></div>
- <div class="drag"></div>
- </div>
- </div>
- %endif
- </div>
- ## Allow other body level elements
- </body>
- ## Scripts can be loaded later since they progressively add features to
- ## the panels, but do not change layout
- ${self.late_javascripts()}
-</html>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/user/dbkeys.mako
--- a/templates/user/dbkeys.mako
+++ b/templates/user/dbkeys.mako
@@ -1,7 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- return '/webapps/%s/base_panels.mako' % t.webapp.name
+ return '/base_panels.mako'
else:
return '/base.mako'
%>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/user/login.mako
--- a/templates/user/login.mako
+++ b/templates/user/login.mako
@@ -1,7 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- return '/webapps/%s/base_panels.mako' % context.get('t').webapp.name
+ return '/base_panels.mako'
else:
return '/base.mako'
%>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/user/logout.mako
--- a/templates/user/logout.mako
+++ b/templates/user/logout.mako
@@ -1,8 +1,4 @@
-<%!
- def inherit(context):
- return '/webapps/%s/base_panels.mako' % context.get('t').webapp.name
-%>
-<%inherit file="${inherit(context)}"/>
+<%inherit file="/base_panels.mako"/><%namespace file="/message.mako" import="render_msg" />
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/user/openid_associate.mako
--- a/templates/user/openid_associate.mako
+++ b/templates/user/openid_associate.mako
@@ -1,7 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- return '/webapps/%s/base_panels.mako' % t.webapp.name
+ return '/base_panels.mako'
else:
return '/base.mako'
%>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/webapps/community/base_panels.mako
--- a/templates/webapps/community/base_panels.mako
+++ b/templates/webapps/community/base_panels.mako
@@ -1,4 +1,4 @@
-<%inherit file="/base_panels.mako"/>
+<%inherit file="/base/base_panels.mako"/>
## Default title
<%def name="title()">Galaxy Tool Shed</%def>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/webapps/galaxy/base_panels.mako
--- a/templates/webapps/galaxy/base_panels.mako
+++ b/templates/webapps/galaxy/base_panels.mako
@@ -1,4 +1,4 @@
-<%inherit file="/base_panels.mako"/>
+<%inherit file="/base/base_panels.mako"/>
## Default title
<%def name="title()">Galaxy</%def>
diff -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 -r 578c082d9b46689268aa4a0cd4d29327f84d2791 templates/webapps/reports/base_panels.mako
--- a/templates/webapps/reports/base_panels.mako
+++ b/templates/webapps/reports/base_panels.mako
@@ -1,4 +1,4 @@
-<%inherit file="/base_panels.mako"/>
+<%inherit file="/base/base_panels.mako"/>
## Default title
<%def name="title()">Galaxy Reports</%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.
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/2ca0e99d6d6d/
changeset: 2ca0e99d6d6d
user: james_taylor
date: 2012-09-28 23:31:30
summary: Pull base Admin controller out into its own module, begin removing as much gratuitous passing around of the webapp parameter as possible
affected #: 20 files
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/web/base/controller.py
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -1525,1191 +1525,8 @@
class ControllerUnavailable( Exception ):
pass
-class Admin( object ):
- # Override these
- user_list_grid = None
- role_list_grid = None
- group_list_grid = None
- quota_list_grid = None
- repository_list_grid = None
- tool_version_list_grid = None
- delete_operation = None
- undelete_operation = None
- purge_operation = None
-
- @web.expose
- @web.require_admin
- def index( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- message = kwd.get( 'message', '' )
- status = kwd.get( 'status', 'done' )
- if webapp == 'galaxy':
- installed_repositories = trans.sa_session.query( trans.model.ToolShedRepository ).first()
- installing_repository_ids = get_ids_of_tool_shed_repositories_being_installed( trans, as_string=True )
- return trans.fill_template( '/webapps/galaxy/admin/index.mako',
- webapp=webapp,
- installed_repositories=installed_repositories,
- installing_repository_ids=installing_repository_ids,
- message=message,
- status=status )
- else:
- return trans.fill_template( '/webapps/community/admin/index.mako',
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def center( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- message = kwd.get( 'message', '' )
- status = kwd.get( 'status', 'done' )
- if webapp == 'galaxy':
- return trans.fill_template( '/webapps/galaxy/admin/center.mako',
- message=message,
- status=status )
- else:
- return trans.fill_template( '/webapps/community/admin/center.mako',
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def reload_tool( self, trans, **kwd ):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- toolbox = self.app.toolbox
- if params.get( 'reload_tool_button', False ):
- tool_id = params.tool_id
- message, status = toolbox.reload_tool_by_id( tool_id )
- return trans.fill_template( '/admin/reload_tool.mako',
- toolbox=toolbox,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def tool_versions( self, trans, **kwd ):
- if 'message' not in kwd or not kwd[ 'message' ]:
- kwd[ 'message' ] = 'Tool ids for tools that are currently loaded into the tool panel are highlighted in green (click to display).'
- return self.tool_version_list_grid( trans, **kwd )
- # Galaxy Role Stuff
- @web.expose
- @web.require_admin
- def roles( self, trans, **kwargs ):
- if 'operation' in kwargs:
- operation = kwargs['operation'].lower()
- if operation == "roles":
- return self.role( trans, **kwargs )
- if operation == "create":
- return self.create_role( trans, **kwargs )
- if operation == "delete":
- return self.mark_role_deleted( trans, **kwargs )
- if operation == "undelete":
- return self.undelete_role( trans, **kwargs )
- if operation == "purge":
- return self.purge_role( trans, **kwargs )
- if operation == "manage users and groups":
- return self.manage_users_and_groups_for_role( trans, **kwargs )
- if operation == "rename":
- return self.rename_role( trans, **kwargs )
- # Render the list view
- return self.role_list_grid( trans, **kwargs )
- @web.expose
- @web.require_admin
- def create_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- name = util.restore_text( params.get( 'name', '' ) )
- description = util.restore_text( params.get( 'description', '' ) )
- in_users = util.listify( params.get( 'in_users', [] ) )
- out_users = util.listify( params.get( 'out_users', [] ) )
- in_groups = util.listify( params.get( 'in_groups', [] ) )
- out_groups = util.listify( params.get( 'out_groups', [] ) )
- create_group_for_role = params.get( 'create_group_for_role', '' )
- create_group_for_role_checked = CheckboxField.is_checked( create_group_for_role )
- ok = True
- if params.get( 'create_role_button', False ):
- if not name or not description:
- message = "Enter a valid name and a description."
- status = 'error'
- ok = False
- elif trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==name ).first():
- message = "Role names must be unique and a role with that name already exists, so choose another name."
- status = 'error'
- ok = False
- else:
- # Create the role
- role = trans.app.model.Role( name=name, description=description, type=trans.app.model.Role.types.ADMIN )
- trans.sa_session.add( role )
- # Create the UserRoleAssociations
- for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
- ura = trans.app.model.UserRoleAssociation( user, role )
- trans.sa_session.add( ura )
- # Create the GroupRoleAssociations
- for group in [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in in_groups ]:
- gra = trans.app.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- if create_group_for_role_checked:
- # Create the group
- group = trans.app.model.Group( name=name )
- trans.sa_session.add( group )
- # Associate the group with the role
- gra = trans.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- num_in_groups = len( in_groups ) + 1
- else:
- num_in_groups = len( in_groups )
- trans.sa_session.flush()
- message = "Role '%s' has been created with %d associated users and %d associated groups. " \
- % ( role.name, len( in_users ), num_in_groups )
- if create_group_for_role_checked:
- message += 'One of the groups associated with this role is the newly created group with the same name.'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- if ok:
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- out_users.append( ( user.id, user.email ) )
- for group in trans.sa_session.query( trans.app.model.Group ) \
- .filter( trans.app.model.Group.table.c.deleted==False ) \
- .order_by( trans.app.model.Group.table.c.name ):
- out_groups.append( ( group.id, group.name ) )
- return trans.fill_template( '/admin/dataset_security/role/role_create.mako',
- webapp=webapp,
- name=name,
- description=description,
- in_users=in_users,
- out_users=out_users,
- in_groups=in_groups,
- out_groups=out_groups,
- create_group_for_role_checked=create_group_for_role_checked,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def rename_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- id = params.get( 'id', None )
- if not id:
- message = "No role ids received for renaming"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- role = get_role( trans, id )
- if params.get( 'rename_role_button', False ):
- old_name = role.name
- new_name = util.restore_text( params.name )
- new_description = util.restore_text( params.description )
- if not new_name:
- message = 'Enter a valid name'
- status='error'
- else:
- existing_role = trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==new_name ).first()
- if existing_role and existing_role.id != role.id:
- message = 'A role with that name already exists'
- status = 'error'
- else:
- if not ( role.name == new_name and role.description == new_description ):
- role.name = new_name
- role.description = new_description
- trans.sa_session.add( role )
- trans.sa_session.flush()
- message = "Role '%s' has been renamed to '%s'" % ( old_name, new_name )
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- return trans.fill_template( '/admin/dataset_security/role/role_rename.mako',
- role=role,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def manage_users_and_groups_for_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- id = params.get( 'id', None )
- if not id:
- message = "No role ids received for managing users and groups"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- role = get_role( trans, id )
- if params.get( 'role_members_edit_button', False ):
- in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
- for ura in role.users:
- user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
- if user not in in_users:
- # Delete DefaultUserPermissions for previously associated users that have been removed from the role
- for dup in user.default_permissions:
- if role == dup.role:
- trans.sa_session.delete( dup )
- # Delete DefaultHistoryPermissions for previously associated users that have been removed from the role
- for history in user.histories:
- for dhp in history.default_permissions:
- if role == dhp.role:
- trans.sa_session.delete( dhp )
- trans.sa_session.flush()
- in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
- trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups )
- trans.sa_session.refresh( role )
- message = "Role '%s' has been updated with %d associated users and %d associated groups" % ( role.name, len( in_users ), len( in_groups ) )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status=status ) )
- in_users = []
- out_users = []
- in_groups = []
- out_groups = []
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- if user in [ x.user for x in role.users ]:
- in_users.append( ( user.id, user.email ) )
- else:
- out_users.append( ( user.id, user.email ) )
- for group in trans.sa_session.query( trans.app.model.Group ) \
- .filter( trans.app.model.Group.table.c.deleted==False ) \
- .order_by( trans.app.model.Group.table.c.name ):
- if group in [ x.group for x in role.groups ]:
- in_groups.append( ( group.id, group.name ) )
- else:
- out_groups.append( ( group.id, group.name ) )
- library_dataset_actions = {}
- if webapp == 'galaxy':
- # Build a list of tuples that are LibraryDatasetDatasetAssociationss followed by a list of actions
- # whose DatasetPermissions is associated with the Role
- # [ ( LibraryDatasetDatasetAssociation [ action, action ] ) ]
- for dp in role.dataset_actions:
- for ldda in trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
- .filter( trans.app.model.LibraryDatasetDatasetAssociation.dataset_id==dp.dataset_id ):
- root_found = False
- folder_path = ''
- folder = ldda.library_dataset.folder
- while not root_found:
- folder_path = '%s / %s' % ( folder.name, folder_path )
- if not folder.parent:
- root_found = True
- else:
- folder = folder.parent
- folder_path = '%s %s' % ( folder_path, ldda.name )
- library = trans.sa_session.query( trans.app.model.Library ) \
- .filter( trans.app.model.Library.table.c.root_folder_id == folder.id ) \
- .first()
- if library not in library_dataset_actions:
- library_dataset_actions[ library ] = {}
- try:
- library_dataset_actions[ library ][ folder_path ].append( dp.action )
- except:
- library_dataset_actions[ library ][ folder_path ] = [ dp.action ]
- return trans.fill_template( '/admin/dataset_security/role/role.mako',
- role=role,
- in_users=in_users,
- out_users=out_users,
- in_groups=in_groups,
- out_groups=out_groups,
- library_dataset_actions=library_dataset_actions,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def mark_role_deleted( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No role ids received for deleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- message = "Deleted %d roles: " % len( ids )
- for role_id in ids:
- role = get_role( trans, role_id )
- role.deleted = True
- trans.sa_session.add( role )
- trans.sa_session.flush()
- message += " %s " % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def undelete_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No role ids received for undeleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- count = 0
- undeleted_roles = ""
- for role_id in ids:
- role = get_role( trans, role_id )
- if not role.deleted:
- message = "Role '%s' has not been deleted, so it cannot be undeleted." % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- role.deleted = False
- trans.sa_session.add( role )
- trans.sa_session.flush()
- count += 1
- undeleted_roles += " %s" % role.name
- message = "Undeleted %d roles: %s" % ( count, undeleted_roles )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def purge_role( self, trans, **kwd ):
- # This method should only be called for a Role that has previously been deleted.
- # Purging a deleted Role deletes all of the following from the database:
- # - UserRoleAssociations where role_id == Role.id
- # - DefaultUserPermissions where role_id == Role.id
- # - DefaultHistoryPermissions where role_id == Role.id
- # - GroupRoleAssociations where role_id == Role.id
- # - DatasetPermissionss where role_id == Role.id
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No role ids received for purging"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- ids = util.listify( id )
- message = "Purged %d roles: " % len( ids )
- for role_id in ids:
- role = get_role( trans, role_id )
- if not role.deleted:
- message = "Role '%s' has not been deleted, so it cannot be purged." % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- # Delete UserRoleAssociations
- for ura in role.users:
- user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
- # Delete DefaultUserPermissions for associated users
- for dup in user.default_permissions:
- if role == dup.role:
- trans.sa_session.delete( dup )
- # Delete DefaultHistoryPermissions for associated users
- for history in user.histories:
- for dhp in history.default_permissions:
- if role == dhp.role:
- trans.sa_session.delete( dhp )
- trans.sa_session.delete( ura )
- # Delete GroupRoleAssociations
- for gra in role.groups:
- trans.sa_session.delete( gra )
- # Delete DatasetPermissionss
- for dp in role.dataset_actions:
- trans.sa_session.delete( dp )
- trans.sa_session.flush()
- message += " %s " % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
-
- # Galaxy Group Stuff
- @web.expose
- @web.require_admin
- def groups( self, trans, **kwargs ):
- if 'operation' in kwargs:
- operation = kwargs['operation'].lower()
- if operation == "groups":
- return self.group( trans, **kwargs )
- if operation == "create":
- return self.create_group( trans, **kwargs )
- if operation == "delete":
- return self.mark_group_deleted( trans, **kwargs )
- if operation == "undelete":
- return self.undelete_group( trans, **kwargs )
- if operation == "purge":
- return self.purge_group( trans, **kwargs )
- if operation == "manage users and roles":
- return self.manage_users_and_roles_for_group( trans, **kwargs )
- if operation == "rename":
- return self.rename_group( trans, **kwargs )
- # Render the list view
- return self.group_list_grid( trans, **kwargs )
- @web.expose
- @web.require_admin
- def rename_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- id = params.get( 'id', None )
- if not id:
- message = "No group ids received for renaming"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=message,
- status='error' ) )
- group = get_group( trans, id )
- if params.get( 'rename_group_button', False ):
- old_name = group.name
- new_name = util.restore_text( params.name )
- if not new_name:
- message = 'Enter a valid name'
- status = 'error'
- else:
- existing_group = trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==new_name ).first()
- if existing_group and existing_group.id != group.id:
- message = 'A group with that name already exists'
- status = 'error'
- else:
- if group.name != new_name:
- group.name = new_name
- trans.sa_session.add( group )
- trans.sa_session.flush()
- message = "Group '%s' has been renamed to '%s'" % ( old_name, new_name )
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- return trans.fill_template( '/admin/dataset_security/group/group_rename.mako',
- group=group,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def manage_users_and_roles_for_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- group = get_group( trans, params.id )
- if params.get( 'group_roles_users_edit_button', False ):
- in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.in_roles ) ]
- in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
- trans.app.security_agent.set_entity_group_associations( groups=[ group ], roles=in_roles, users=in_users )
- trans.sa_session.refresh( group )
- message += "Group '%s' has been updated with %d associated roles and %d associated users" % ( group.name, len( in_roles ), len( in_users ) )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status=status ) )
- in_roles = []
- out_roles = []
- in_users = []
- out_users = []
- for role in trans.sa_session.query(trans.app.model.Role ) \
- .filter( trans.app.model.Role.table.c.deleted==False ) \
- .order_by( trans.app.model.Role.table.c.name ):
- if role in [ x.role for x in group.roles ]:
- in_roles.append( ( role.id, role.name ) )
- else:
- out_roles.append( ( role.id, role.name ) )
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- if user in [ x.user for x in group.users ]:
- in_users.append( ( user.id, user.email ) )
- else:
- out_users.append( ( user.id, user.email ) )
- message += 'Group %s is currently associated with %d roles and %d users' % ( group.name, len( in_roles ), len( in_users ) )
- return trans.fill_template( '/admin/dataset_security/group/group.mako',
- group=group,
- in_roles=in_roles,
- out_roles=out_roles,
- in_users=in_users,
- out_users=out_users,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def create_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- name = util.restore_text( params.get( 'name', '' ) )
- in_users = util.listify( params.get( 'in_users', [] ) )
- out_users = util.listify( params.get( 'out_users', [] ) )
- in_roles = util.listify( params.get( 'in_roles', [] ) )
- out_roles = util.listify( params.get( 'out_roles', [] ) )
- create_role_for_group = params.get( 'create_role_for_group', '' )
- create_role_for_group_checked = CheckboxField.is_checked( create_role_for_group )
- ok = True
- if params.get( 'create_group_button', False ):
- if not name:
- message = "Enter a valid name."
- status = 'error'
- ok = False
- elif trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==name ).first():
- message = "Group names must be unique and a group with that name already exists, so choose another name."
- status = 'error'
- ok = False
- else:
- # Create the group
- group = trans.app.model.Group( name=name )
- trans.sa_session.add( group )
- trans.sa_session.flush()
- # Create the UserRoleAssociations
- for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
- uga = trans.app.model.UserGroupAssociation( user, group )
- trans.sa_session.add( uga )
- # Create the GroupRoleAssociations
- for role in [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in in_roles ]:
- gra = trans.app.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- if create_role_for_group_checked:
- # Create the role
- role = trans.app.model.Role( name=name, description='Role for group %s' % name )
- trans.sa_session.add( role )
- # Associate the role with the group
- gra = trans.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- num_in_roles = len( in_roles ) + 1
- else:
- num_in_roles = len( in_roles )
- trans.sa_session.flush()
- message = "Group '%s' has been created with %d associated users and %d associated roles. " \
- % ( group.name, len( in_users ), num_in_roles )
- if create_role_for_group_checked:
- message += 'One of the roles associated with this group is the newly created role with the same name.'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
-
-
- if ok:
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- out_users.append( ( user.id, user.email ) )
- for role in trans.sa_session.query( trans.app.model.Role ) \
- .filter( trans.app.model.Role.table.c.deleted==False ) \
- .order_by( trans.app.model.Role.table.c.name ):
- out_roles.append( ( role.id, role.name ) )
- return trans.fill_template( '/admin/dataset_security/group/group_create.mako',
- webapp=webapp,
- name=name,
- in_users=in_users,
- out_users=out_users,
- in_roles=in_roles,
- out_roles=out_roles,
- create_role_for_group_checked=create_role_for_group_checked,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def mark_group_deleted( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = params.get( 'id', None )
- if not id:
- message = "No group ids received for marking deleted"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- message = "Deleted %d groups: " % len( ids )
- for group_id in ids:
- group = get_group( trans, group_id )
- group.deleted = True
- trans.sa_session.add( group )
- trans.sa_session.flush()
- message += " %s " % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def undelete_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No group ids received for undeleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- count = 0
- undeleted_groups = ""
- for group_id in ids:
- group = get_group( trans, group_id )
- if not group.deleted:
- message = "Group '%s' has not been deleted, so it cannot be undeleted." % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- group.deleted = False
- trans.sa_session.add( group )
- trans.sa_session.flush()
- count += 1
- undeleted_groups += " %s" % group.name
- message = "Undeleted %d groups: %s" % ( count, undeleted_groups )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def purge_group( self, trans, **kwd ):
- # This method should only be called for a Group that has previously been deleted.
- # Purging a deleted Group simply deletes all UserGroupAssociations and GroupRoleAssociations.
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No group ids received for purging"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- ids = util.listify( id )
- message = "Purged %d groups: " % len( ids )
- for group_id in ids:
- group = get_group( trans, group_id )
- if not group.deleted:
- # We should never reach here, but just in case there is a bug somewhere...
- message = "Group '%s' has not been deleted, so it cannot be purged." % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- # Delete UserGroupAssociations
- for uga in group.users:
- trans.sa_session.delete( uga )
- # Delete GroupRoleAssociations
- for gra in group.roles:
- trans.sa_session.delete( gra )
- trans.sa_session.flush()
- message += " %s " % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
-
- # Galaxy User Stuff
- @web.expose
- @web.require_admin
- def create_new_user( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- return trans.response.send_redirect( web.url_for( controller='user',
- action='create',
- cntrller='admin',
- webapp=webapp ) )
- @web.expose
- @web.require_admin
- def reset_user_password( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- user_id = kwd.get( 'id', None )
- if not user_id:
- message = "No users received for resetting passwords."
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=message,
- status='error' ) )
- user_ids = util.listify( user_id )
- if 'reset_user_password_button' in kwd:
- message = ''
- status = ''
- for user_id in user_ids:
- user = get_user( trans, user_id )
- password = kwd.get( 'password', None )
- confirm = kwd.get( 'confirm' , None )
- if len( password ) < 6:
- message = "Use a password of at least 6 characters."
- status = 'error'
- break
- elif password != confirm:
- message = "Passwords do not match."
- status = 'error'
- break
- else:
- user.set_password_cleartext( password )
- trans.sa_session.add( user )
- trans.sa_session.flush()
- if not message and not status:
- message = "Passwords reset for %d %s." % ( len( user_ids ), inflector.cond_plural( len( user_ids ), 'user' ) )
- status = 'done'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status=status ) )
- users = [ get_user( trans, user_id ) for user_id in user_ids ]
- if len( user_ids ) > 1:
- user_id = ','.join( user_ids )
- return trans.fill_template( '/admin/user/reset_password.mako',
- id=user_id,
- users=users,
- password='',
- confirm='',
- webapp=webapp )
- @web.expose
- @web.require_admin
- def mark_user_deleted( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No user ids received for deleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- message = "Deleted %d users: " % len( ids )
- for user_id in ids:
- user = get_user( trans, user_id )
- user.deleted = True
- trans.sa_session.add( user )
- trans.sa_session.flush()
- message += " %s " % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def undelete_user( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No user ids received for undeleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- count = 0
- undeleted_users = ""
- for user_id in ids:
- user = get_user( trans, user_id )
- if not user.deleted:
- message = "User '%s' has not been deleted, so it cannot be undeleted." % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- user.deleted = False
- trans.sa_session.add( user )
- trans.sa_session.flush()
- count += 1
- undeleted_users += " %s" % user.email
- message = "Undeleted %d users: %s" % ( count, undeleted_users )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def purge_user( self, trans, **kwd ):
- # This method should only be called for a User that has previously been deleted.
- # We keep the User in the database ( marked as purged ), and stuff associated
- # with the user's private role in case we want the ability to unpurge the user
- # some time in the future.
- # Purging a deleted User deletes all of the following:
- # - History where user_id = User.id
- # - HistoryDatasetAssociation where history_id = History.id
- # - Dataset where HistoryDatasetAssociation.dataset_id = Dataset.id
- # - UserGroupAssociation where user_id == User.id
- # - UserRoleAssociation where user_id == User.id EXCEPT FOR THE PRIVATE ROLE
- # - UserAddress where user_id == User.id
- # Purging Histories and Datasets must be handled via the cleanup_datasets.py script
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No user ids received for purging"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- ids = util.listify( id )
- message = "Purged %d users: " % len( ids )
- for user_id in ids:
- user = get_user( trans, user_id )
- if not user.deleted:
- # We should never reach here, but just in case there is a bug somewhere...
- message = "User '%s' has not been deleted, so it cannot be purged." % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- private_role = trans.app.security_agent.get_private_user_role( user )
- # Delete History
- for h in user.active_histories:
- trans.sa_session.refresh( h )
- for hda in h.active_datasets:
- # Delete HistoryDatasetAssociation
- d = trans.sa_session.query( trans.app.model.Dataset ).get( hda.dataset_id )
- # Delete Dataset
- if not d.deleted:
- d.deleted = True
- trans.sa_session.add( d )
- hda.deleted = True
- trans.sa_session.add( hda )
- h.deleted = True
- trans.sa_session.add( h )
- # Delete UserGroupAssociations
- for uga in user.groups:
- trans.sa_session.delete( uga )
- # Delete UserRoleAssociations EXCEPT FOR THE PRIVATE ROLE
- for ura in user.roles:
- if ura.role_id != private_role.id:
- trans.sa_session.delete( ura )
- # Delete UserAddresses
- for address in user.addresses:
- trans.sa_session.delete( address )
- # Purge the user
- user.purged = True
- trans.sa_session.add( user )
- trans.sa_session.flush()
- message += "%s " % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def users( self, trans, **kwd ):
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- if operation == "roles":
- return self.user( trans, **kwd )
- elif operation == "reset password":
- return self.reset_user_password( trans, **kwd )
- elif operation == "delete":
- return self.mark_user_deleted( trans, **kwd )
- elif operation == "undelete":
- return self.undelete_user( trans, **kwd )
- elif operation == "purge":
- return self.purge_user( trans, **kwd )
- elif operation == "create":
- return self.create_new_user( trans, **kwd )
- elif operation == "information":
- user_id = kwd.get( 'id', None )
- if not user_id:
- kwd[ 'message' ] = util.sanitize_text( "Invalid user id (%s) received" % str( user_id ) )
- kwd[ 'status' ] = 'error'
- else:
- return trans.response.send_redirect( web.url_for( controller='user',
- action='manage_user_info',
- cntrller='admin',
- **kwd ) )
- elif operation == "manage roles and groups":
- return self.manage_roles_and_groups_for_user( trans, **kwd )
- if trans.app.config.allow_user_deletion:
- if self.delete_operation not in self.user_list_grid.operations:
- self.user_list_grid.operations.append( self.delete_operation )
- if self.undelete_operation not in self.user_list_grid.operations:
- self.user_list_grid.operations.append( self.undelete_operation )
- if self.purge_operation not in self.user_list_grid.operations:
- self.user_list_grid.operations.append( self.purge_operation )
- # Render the list view
- return self.user_list_grid( trans, **kwd )
- @web.expose
- @web.require_admin
- def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
- """Return autocomplete data for user emails"""
- ac_data = ""
- for user in trans.sa_session.query( User ).filter_by( deleted=False ).filter( func.lower( User.email ).like( q.lower() + "%" ) ):
- ac_data = ac_data + user.email + "\n"
- return ac_data
- @web.expose
- @web.require_admin
- def manage_roles_and_groups_for_user( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- user_id = kwd.get( 'id', None )
- message = ''
- status = ''
- if not user_id:
- message += "Invalid user id (%s) received" % str( user_id )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- user = get_user( trans, user_id )
- private_role = trans.app.security_agent.get_private_user_role( user )
- if kwd.get( 'user_roles_groups_edit_button', False ):
- # Make sure the user is not dis-associating himself from his private role
- out_roles = kwd.get( 'out_roles', [] )
- if out_roles:
- out_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( out_roles ) ]
- if private_role in out_roles:
- message += "You cannot eliminate a user's private role association. "
- status = 'error'
- in_roles = kwd.get( 'in_roles', [] )
- if in_roles:
- in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( in_roles ) ]
- out_groups = kwd.get( 'out_groups', [] )
- if out_groups:
- out_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( out_groups ) ]
- in_groups = kwd.get( 'in_groups', [] )
- if in_groups:
- in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( in_groups ) ]
- if in_roles:
- trans.app.security_agent.set_entity_user_associations( users=[ user ], roles=in_roles, groups=in_groups )
- trans.sa_session.refresh( user )
- message += "User '%s' has been updated with %d associated roles and %d associated groups (private roles are not displayed)" % \
- ( user.email, len( in_roles ), len( in_groups ) )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- in_roles = []
- out_roles = []
- in_groups = []
- out_groups = []
- for role in trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.deleted==False ) \
- .order_by( trans.app.model.Role.table.c.name ):
- if role in [ x.role for x in user.roles ]:
- in_roles.append( ( role.id, role.name ) )
- elif role.type != trans.app.model.Role.types.PRIVATE:
- # There is a 1 to 1 mapping between a user and a PRIVATE role, so private roles should
- # not be listed in the roles form fields, except for the currently selected user's private
- # role, which should always be in in_roles. The check above is added as an additional
- # precaution, since for a period of time we were including private roles in the form fields.
- out_roles.append( ( role.id, role.name ) )
- for group in trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.deleted==False ) \
- .order_by( trans.app.model.Group.table.c.name ):
- if group in [ x.group for x in user.groups ]:
- in_groups.append( ( group.id, group.name ) )
- else:
- out_groups.append( ( group.id, group.name ) )
- message += "User '%s' is currently associated with %d roles and is a member of %d groups" % \
- ( user.email, len( in_roles ), len( in_groups ) )
- if not status:
- status = 'done'
- return trans.fill_template( '/admin/user/user.mako',
- user=user,
- in_roles=in_roles,
- out_roles=out_roles,
- in_groups=in_groups,
- out_groups=out_groups,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ):
- if self.app.memdump is None:
- return trans.show_error_message( "Memdump is not enabled (set <code>use_memdump = True</code> in universe_wsgi.ini)" )
- heap = self.app.memdump.get()
- p = util.Params( kwd )
- msg = None
- if p.dump:
- heap = self.app.memdump.get( update = True )
- msg = "Heap dump complete"
- elif p.setref:
- self.app.memdump.setref()
- msg = "Reference point set (dump to see delta from this point)"
- ids = ids.split( ',' )
- sorts = sorts.split( ',' )
- if new_id is not None:
- ids.append( new_id )
- sorts.append( 'None' )
- elif new_sort is not None:
- sorts[-1] = new_sort
- breadcrumb = "<a href='%s' class='breadcrumb'>heap</a>" % web.url_for()
- # new lists so we can assemble breadcrumb links
- new_ids = []
- new_sorts = []
- for id, sort in zip( ids, sorts ):
- new_ids.append( id )
- if id != 'None':
- breadcrumb += "<a href='%s' class='breadcrumb'>[%s]</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), id )
- heap = heap[int(id)]
- new_sorts.append( sort )
- if sort != 'None':
- breadcrumb += "<a href='%s' class='breadcrumb'>.by('%s')</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), sort )
- heap = heap.by( sort )
- ids = ','.join( new_ids )
- sorts = ','.join( new_sorts )
- if p.theone:
- breadcrumb += ".theone"
- heap = heap.theone
- return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg )
-
- @web.expose
- @web.require_admin
- def jobs( self, trans, stop = [], stop_msg = None, cutoff = 180, job_lock = None, ajl_submit = None, **kwd ):
- deleted = []
- msg = None
- status = None
- if self.app.config.job_manager != self.app.config.server_name:
- return trans.show_error_message( 'This Galaxy instance (%s) is not the job manager (%s). If using multiple servers, please directly access the job manager instance to manage jobs.' % (self.app.config.server_name, self.app.config.job_manager) )
- job_ids = util.listify( stop )
- if job_ids and stop_msg in [ None, '' ]:
- msg = 'Please enter an error message to display to the user describing why the job was terminated'
- status = 'error'
- elif job_ids:
- if stop_msg[-1] not in string.punctuation:
- stop_msg += '.'
- for job_id in job_ids:
- trans.app.job_manager.job_stop_queue.put( job_id, error_msg="This job was stopped by an administrator: %s For more information or help" % stop_msg )
- deleted.append( str( job_id ) )
- if deleted:
- msg = 'Queued job'
- if len( deleted ) > 1:
- msg += 's'
- msg += ' for deletion: '
- msg += ', '.join( deleted )
- status = 'done'
- if ajl_submit:
- if job_lock == 'on':
- trans.app.job_manager.job_queue.job_lock = True
- else:
- trans.app.job_manager.job_queue.job_lock = False
- cutoff_time = datetime.utcnow() - timedelta( seconds=int( cutoff ) )
- jobs = trans.sa_session.query( trans.app.model.Job ) \
- .filter( and_( trans.app.model.Job.table.c.update_time < cutoff_time,
- or_( trans.app.model.Job.state == trans.app.model.Job.states.NEW,
- trans.app.model.Job.state == trans.app.model.Job.states.QUEUED,
- trans.app.model.Job.state == trans.app.model.Job.states.RUNNING,
- trans.app.model.Job.state == trans.app.model.Job.states.UPLOAD ) ) ) \
- .order_by( trans.app.model.Job.table.c.update_time.desc() )
- last_updated = {}
- for job in jobs:
- delta = datetime.utcnow() - job.update_time
- if delta > timedelta( minutes=60 ):
- last_updated[job.id] = '%s hours' % int( delta.seconds / 60 / 60 )
- else:
- last_updated[job.id] = '%s minutes' % int( delta.seconds / 60 )
- return trans.fill_template( '/admin/jobs.mako',
- jobs = jobs,
- last_updated = last_updated,
- cutoff = cutoff,
- msg = msg,
- status = status,
- job_lock = trans.app.job_manager.job_queue.job_lock )
-
## ---- Utility methods -------------------------------------------------------
-def get_ids_of_tool_shed_repositories_being_installed( trans, as_string=False ):
- installing_repository_ids = []
- new_status = trans.model.ToolShedRepository.installation_status.NEW
- cloning_status = trans.model.ToolShedRepository.installation_status.CLONING
- setting_tool_versions_status = trans.model.ToolShedRepository.installation_status.SETTING_TOOL_VERSIONS
- installing_dependencies_status = trans.model.ToolShedRepository.installation_status.INSTALLING_TOOL_DEPENDENCIES
- loading_datatypes_status = trans.model.ToolShedRepository.installation_status.LOADING_PROPRIETARY_DATATYPES
- for tool_shed_repository in trans.sa_session.query( trans.model.ToolShedRepository ) \
- .filter( or_( trans.model.ToolShedRepository.status == new_status,
- trans.model.ToolShedRepository.status == cloning_status,
- trans.model.ToolShedRepository.status == setting_tool_versions_status,
- trans.model.ToolShedRepository.status == installing_dependencies_status,
- trans.model.ToolShedRepository.status == loading_datatypes_status ) ):
- installing_repository_ids.append( trans.security.encode_id( tool_shed_repository.id ) )
- if as_string:
- return ','.join( installing_repository_ids )
- return installing_repository_ids
-
-def get_user( trans, user_id ):
- """Get a User from the database by id."""
- user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
- if not user:
- return trans.show_error_message( "User not found for id (%s)" % str( user_id ) )
- return user
-def get_user_by_username( trans, username ):
- """Get a user from the database by username"""
- # TODO: Add exception handling here.
- return trans.sa_session.query( trans.model.User ) \
- .filter( trans.model.User.table.c.username == username ) \
- .one()
-def get_role( trans, id ):
- """Get a Role from the database by id."""
- # Load user from database
- id = trans.security.decode_id( id )
- role = trans.sa_session.query( trans.model.Role ).get( id )
- if not role:
- return trans.show_error_message( "Role not found for id (%s)" % str( id ) )
- return role
-def get_group( trans, id ):
- """Get a Group from the database by id."""
- # Load user from database
- id = trans.security.decode_id( id )
- group = trans.sa_session.query( trans.model.Group ).get( id )
- if not group:
- return trans.show_error_message( "Group not found for id (%s)" % str( id ) )
- return group
-def get_quota( trans, id ):
- """Get a Quota from the database by id."""
- # Load user from database
- id = trans.security.decode_id( id )
- quota = trans.sa_session.query( trans.model.Quota ).get( id )
- return quota
-def get_webapp( trans, **kwd ):
- """Get the value of the webapp, can be one of 'community', 'galaxy', 'reports', 'demo_sequencer'."""
- if 'webapp' in kwd:
- return kwd[ 'webapp' ]
- if 'webapp' in trans.environ:
- return trans.environ[ 'webapp' ]
- # The default is galaxy.
- return 'galaxy'
def sort_by_attr( seq, attr ):
"""
Sort the sequence of objects by object's attribute
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/web/base/controllers/admin.py
--- /dev/null
+++ b/lib/galaxy/web/base/controllers/admin.py
@@ -0,0 +1,1114 @@
+from datetime import date, datetime, timedelta
+
+from galaxy import config, tools, web, util
+from galaxy.model.orm import *
+
+class Admin( object ):
+ # Override these
+ user_list_grid = None
+ role_list_grid = None
+ group_list_grid = None
+ quota_list_grid = None
+ repository_list_grid = None
+ tool_version_list_grid = None
+ delete_operation = None
+ undelete_operation = None
+ purge_operation = None
+
+ @web.expose
+ @web.require_admin
+ def index( self, trans, **kwd ):
+ message = kwd.get( 'message', '' )
+ status = kwd.get( 'status', 'done' )
+ if trans.webapp.name == 'galaxy':
+ installed_repositories = trans.sa_session.query( trans.model.ToolShedRepository ).first()
+ installing_repository_ids = get_ids_of_tool_shed_repositories_being_installed( trans, as_string=True )
+ return trans.fill_template( '/webapps/galaxy/admin/index.mako',
+ installed_repositories=installed_repositories,
+ installing_repository_ids=installing_repository_ids,
+ message=message,
+ status=status )
+ else:
+ return trans.fill_template( '/webapps/community/admin/index.mako',
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def center( self, trans, **kwd ):
+ message = kwd.get( 'message', '' )
+ status = kwd.get( 'status', 'done' )
+ if trans.webapp.name == 'galaxy':
+ return trans.fill_template( '/webapps/galaxy/admin/center.mako',
+ message=message,
+ status=status )
+ else:
+ return trans.fill_template( '/webapps/community/admin/center.mako',
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def reload_tool( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ toolbox = self.app.toolbox
+ if params.get( 'reload_tool_button', False ):
+ tool_id = params.tool_id
+ message, status = toolbox.reload_tool_by_id( tool_id )
+ return trans.fill_template( '/admin/reload_tool.mako',
+ toolbox=toolbox,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def tool_versions( self, trans, **kwd ):
+ if 'message' not in kwd or not kwd[ 'message' ]:
+ kwd[ 'message' ] = 'Tool ids for tools that are currently loaded into the tool panel are highlighted in green (click to display).'
+ return self.tool_version_list_grid( trans, **kwd )
+ # Galaxy Role Stuff
+ @web.expose
+ @web.require_admin
+ def roles( self, trans, **kwargs ):
+ if 'operation' in kwargs:
+ operation = kwargs['operation'].lower()
+ if operation == "roles":
+ return self.role( trans, **kwargs )
+ if operation == "create":
+ return self.create_role( trans, **kwargs )
+ if operation == "delete":
+ return self.mark_role_deleted( trans, **kwargs )
+ if operation == "undelete":
+ return self.undelete_role( trans, **kwargs )
+ if operation == "purge":
+ return self.purge_role( trans, **kwargs )
+ if operation == "manage users and groups":
+ return self.manage_users_and_groups_for_role( trans, **kwargs )
+ if operation == "rename":
+ return self.rename_role( trans, **kwargs )
+ # Render the list view
+ return self.role_list_grid( trans, **kwargs )
+ @web.expose
+ @web.require_admin
+ def create_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ name = util.restore_text( params.get( 'name', '' ) )
+ description = util.restore_text( params.get( 'description', '' ) )
+ in_users = util.listify( params.get( 'in_users', [] ) )
+ out_users = util.listify( params.get( 'out_users', [] ) )
+ in_groups = util.listify( params.get( 'in_groups', [] ) )
+ out_groups = util.listify( params.get( 'out_groups', [] ) )
+ create_group_for_role = params.get( 'create_group_for_role', '' )
+ create_group_for_role_checked = CheckboxField.is_checked( create_group_for_role )
+ ok = True
+ if params.get( 'create_role_button', False ):
+ if not name or not description:
+ message = "Enter a valid name and a description."
+ status = 'error'
+ ok = False
+ elif trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==name ).first():
+ message = "Role names must be unique and a role with that name already exists, so choose another name."
+ status = 'error'
+ ok = False
+ else:
+ # Create the role
+ role = trans.app.model.Role( name=name, description=description, type=trans.app.model.Role.types.ADMIN )
+ trans.sa_session.add( role )
+ # Create the UserRoleAssociations
+ for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
+ ura = trans.app.model.UserRoleAssociation( user, role )
+ trans.sa_session.add( ura )
+ # Create the GroupRoleAssociations
+ for group in [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in in_groups ]:
+ gra = trans.app.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ if create_group_for_role_checked:
+ # Create the group
+ group = trans.app.model.Group( name=name )
+ trans.sa_session.add( group )
+ # Associate the group with the role
+ gra = trans.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ num_in_groups = len( in_groups ) + 1
+ else:
+ num_in_groups = len( in_groups )
+ trans.sa_session.flush()
+ message = "Role '%s' has been created with %d associated users and %d associated groups. " \
+ % ( role.name, len( in_users ), num_in_groups )
+ if create_group_for_role_checked:
+ message += 'One of the groups associated with this role is the newly created group with the same name.'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ if ok:
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ out_users.append( ( user.id, user.email ) )
+ for group in trans.sa_session.query( trans.app.model.Group ) \
+ .filter( trans.app.model.Group.table.c.deleted==False ) \
+ .order_by( trans.app.model.Group.table.c.name ):
+ out_groups.append( ( group.id, group.name ) )
+ return trans.fill_template( '/admin/dataset_security/role/role_create.mako',
+ name=name,
+ description=description,
+ in_users=in_users,
+ out_users=out_users,
+ in_groups=in_groups,
+ out_groups=out_groups,
+ create_group_for_role_checked=create_group_for_role_checked,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def rename_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No role ids received for renaming"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ role = get_role( trans, id )
+ if params.get( 'rename_role_button', False ):
+ old_name = role.name
+ new_name = util.restore_text( params.name )
+ new_description = util.restore_text( params.description )
+ if not new_name:
+ message = 'Enter a valid name'
+ status='error'
+ else:
+ existing_role = trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==new_name ).first()
+ if existing_role and existing_role.id != role.id:
+ message = 'A role with that name already exists'
+ status = 'error'
+ else:
+ if not ( role.name == new_name and role.description == new_description ):
+ role.name = new_name
+ role.description = new_description
+ trans.sa_session.add( role )
+ trans.sa_session.flush()
+ message = "Role '%s' has been renamed to '%s'" % ( old_name, new_name )
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ return trans.fill_template( '/admin/dataset_security/role/role_rename.mako',
+ role=role,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def manage_users_and_groups_for_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No role ids received for managing users and groups"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ role = get_role( trans, id )
+ if params.get( 'role_members_edit_button', False ):
+ in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
+ for ura in role.users:
+ user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
+ if user not in in_users:
+ # Delete DefaultUserPermissions for previously associated users that have been removed from the role
+ for dup in user.default_permissions:
+ if role == dup.role:
+ trans.sa_session.delete( dup )
+ # Delete DefaultHistoryPermissions for previously associated users that have been removed from the role
+ for history in user.histories:
+ for dhp in history.default_permissions:
+ if role == dhp.role:
+ trans.sa_session.delete( dhp )
+ trans.sa_session.flush()
+ in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
+ trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups )
+ trans.sa_session.refresh( role )
+ message = "Role '%s' has been updated with %d associated users and %d associated groups" % ( role.name, len( in_users ), len( in_groups ) )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status=status ) )
+ in_users = []
+ out_users = []
+ in_groups = []
+ out_groups = []
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ if user in [ x.user for x in role.users ]:
+ in_users.append( ( user.id, user.email ) )
+ else:
+ out_users.append( ( user.id, user.email ) )
+ for group in trans.sa_session.query( trans.app.model.Group ) \
+ .filter( trans.app.model.Group.table.c.deleted==False ) \
+ .order_by( trans.app.model.Group.table.c.name ):
+ if group in [ x.group for x in role.groups ]:
+ in_groups.append( ( group.id, group.name ) )
+ else:
+ out_groups.append( ( group.id, group.name ) )
+ library_dataset_actions = {}
+ if trans.webapp.name == 'galaxy':
+ # Build a list of tuples that are LibraryDatasetDatasetAssociationss followed by a list of actions
+ # whose DatasetPermissions is associated with the Role
+ # [ ( LibraryDatasetDatasetAssociation [ action, action ] ) ]
+ for dp in role.dataset_actions:
+ for ldda in trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
+ .filter( trans.app.model.LibraryDatasetDatasetAssociation.dataset_id==dp.dataset_id ):
+ root_found = False
+ folder_path = ''
+ folder = ldda.library_dataset.folder
+ while not root_found:
+ folder_path = '%s / %s' % ( folder.name, folder_path )
+ if not folder.parent:
+ root_found = True
+ else:
+ folder = folder.parent
+ folder_path = '%s %s' % ( folder_path, ldda.name )
+ library = trans.sa_session.query( trans.app.model.Library ) \
+ .filter( trans.app.model.Library.table.c.root_folder_id == folder.id ) \
+ .first()
+ if library not in library_dataset_actions:
+ library_dataset_actions[ library ] = {}
+ try:
+ library_dataset_actions[ library ][ folder_path ].append( dp.action )
+ except:
+ library_dataset_actions[ library ][ folder_path ] = [ dp.action ]
+ return trans.fill_template( '/admin/dataset_security/role/role.mako',
+ role=role,
+ in_users=in_users,
+ out_users=out_users,
+ in_groups=in_groups,
+ out_groups=out_groups,
+ library_dataset_actions=library_dataset_actions,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def mark_role_deleted( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No role ids received for deleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Deleted %d roles: " % len( ids )
+ for role_id in ids:
+ role = get_role( trans, role_id )
+ role.deleted = True
+ trans.sa_session.add( role )
+ trans.sa_session.flush()
+ message += " %s " % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def undelete_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No role ids received for undeleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ count = 0
+ undeleted_roles = ""
+ for role_id in ids:
+ role = get_role( trans, role_id )
+ if not role.deleted:
+ message = "Role '%s' has not been deleted, so it cannot be undeleted." % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ role.deleted = False
+ trans.sa_session.add( role )
+ trans.sa_session.flush()
+ count += 1
+ undeleted_roles += " %s" % role.name
+ message = "Undeleted %d roles: %s" % ( count, undeleted_roles )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def purge_role( self, trans, **kwd ):
+ # This method should only be called for a Role that has previously been deleted.
+ # Purging a deleted Role deletes all of the following from the database:
+ # - UserRoleAssociations where role_id == Role.id
+ # - DefaultUserPermissions where role_id == Role.id
+ # - DefaultHistoryPermissions where role_id == Role.id
+ # - GroupRoleAssociations where role_id == Role.id
+ # - DatasetPermissionss where role_id == Role.id
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No role ids received for purging"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d roles: " % len( ids )
+ for role_id in ids:
+ role = get_role( trans, role_id )
+ if not role.deleted:
+ message = "Role '%s' has not been deleted, so it cannot be purged." % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ # Delete UserRoleAssociations
+ for ura in role.users:
+ user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
+ # Delete DefaultUserPermissions for associated users
+ for dup in user.default_permissions:
+ if role == dup.role:
+ trans.sa_session.delete( dup )
+ # Delete DefaultHistoryPermissions for associated users
+ for history in user.histories:
+ for dhp in history.default_permissions:
+ if role == dhp.role:
+ trans.sa_session.delete( dhp )
+ trans.sa_session.delete( ura )
+ # Delete GroupRoleAssociations
+ for gra in role.groups:
+ trans.sa_session.delete( gra )
+ # Delete DatasetPermissionss
+ for dp in role.dataset_actions:
+ trans.sa_session.delete( dp )
+ trans.sa_session.flush()
+ message += " %s " % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+
+ # Galaxy Group Stuff
+ @web.expose
+ @web.require_admin
+ def groups( self, trans, **kwargs ):
+ if 'operation' in kwargs:
+ operation = kwargs['operation'].lower()
+ if operation == "groups":
+ return self.group( trans, **kwargs )
+ if operation == "create":
+ return self.create_group( trans, **kwargs )
+ if operation == "delete":
+ return self.mark_group_deleted( trans, **kwargs )
+ if operation == "undelete":
+ return self.undelete_group( trans, **kwargs )
+ if operation == "purge":
+ return self.purge_group( trans, **kwargs )
+ if operation == "manage users and roles":
+ return self.manage_users_and_roles_for_group( trans, **kwargs )
+ if operation == "rename":
+ return self.rename_group( trans, **kwargs )
+ # Render the list view
+ return self.group_list_grid( trans, **kwargs )
+ @web.expose
+ @web.require_admin
+ def rename_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No group ids received for renaming"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=message,
+ status='error' ) )
+ group = get_group( trans, id )
+ if params.get( 'rename_group_button', False ):
+ old_name = group.name
+ new_name = util.restore_text( params.name )
+ if not new_name:
+ message = 'Enter a valid name'
+ status = 'error'
+ else:
+ existing_group = trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==new_name ).first()
+ if existing_group and existing_group.id != group.id:
+ message = 'A group with that name already exists'
+ status = 'error'
+ else:
+ if group.name != new_name:
+ group.name = new_name
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ message = "Group '%s' has been renamed to '%s'" % ( old_name, new_name )
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ return trans.fill_template( '/admin/dataset_security/group/group_rename.mako',
+ group=group,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def manage_users_and_roles_for_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ group = get_group( trans, params.id )
+ if params.get( 'group_roles_users_edit_button', False ):
+ in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.in_roles ) ]
+ in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
+ trans.app.security_agent.set_entity_group_associations( groups=[ group ], roles=in_roles, users=in_users )
+ trans.sa_session.refresh( group )
+ message += "Group '%s' has been updated with %d associated roles and %d associated users" % ( group.name, len( in_roles ), len( in_users ) )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status=status ) )
+ in_roles = []
+ out_roles = []
+ in_users = []
+ out_users = []
+ for role in trans.sa_session.query(trans.app.model.Role ) \
+ .filter( trans.app.model.Role.table.c.deleted==False ) \
+ .order_by( trans.app.model.Role.table.c.name ):
+ if role in [ x.role for x in group.roles ]:
+ in_roles.append( ( role.id, role.name ) )
+ else:
+ out_roles.append( ( role.id, role.name ) )
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ if user in [ x.user for x in group.users ]:
+ in_users.append( ( user.id, user.email ) )
+ else:
+ out_users.append( ( user.id, user.email ) )
+ message += 'Group %s is currently associated with %d roles and %d users' % ( group.name, len( in_roles ), len( in_users ) )
+ return trans.fill_template( '/admin/dataset_security/group/group.mako',
+ group=group,
+ in_roles=in_roles,
+ out_roles=out_roles,
+ in_users=in_users,
+ out_users=out_users,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def create_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ name = util.restore_text( params.get( 'name', '' ) )
+ in_users = util.listify( params.get( 'in_users', [] ) )
+ out_users = util.listify( params.get( 'out_users', [] ) )
+ in_roles = util.listify( params.get( 'in_roles', [] ) )
+ out_roles = util.listify( params.get( 'out_roles', [] ) )
+ create_role_for_group = params.get( 'create_role_for_group', '' )
+ create_role_for_group_checked = CheckboxField.is_checked( create_role_for_group )
+ ok = True
+ if params.get( 'create_group_button', False ):
+ if not name:
+ message = "Enter a valid name."
+ status = 'error'
+ ok = False
+ elif trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==name ).first():
+ message = "Group names must be unique and a group with that name already exists, so choose another name."
+ status = 'error'
+ ok = False
+ else:
+ # Create the group
+ group = trans.app.model.Group( name=name )
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ # Create the UserRoleAssociations
+ for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
+ uga = trans.app.model.UserGroupAssociation( user, group )
+ trans.sa_session.add( uga )
+ # Create the GroupRoleAssociations
+ for role in [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in in_roles ]:
+ gra = trans.app.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ if create_role_for_group_checked:
+ # Create the role
+ role = trans.app.model.Role( name=name, description='Role for group %s' % name )
+ trans.sa_session.add( role )
+ # Associate the role with the group
+ gra = trans.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ num_in_roles = len( in_roles ) + 1
+ else:
+ num_in_roles = len( in_roles )
+ trans.sa_session.flush()
+ message = "Group '%s' has been created with %d associated users and %d associated roles. " \
+ % ( group.name, len( in_users ), num_in_roles )
+ if create_role_for_group_checked:
+ message += 'One of the roles associated with this group is the newly created role with the same name.'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+
+
+ if ok:
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ out_users.append( ( user.id, user.email ) )
+ for role in trans.sa_session.query( trans.app.model.Role ) \
+ .filter( trans.app.model.Role.table.c.deleted==False ) \
+ .order_by( trans.app.model.Role.table.c.name ):
+ out_roles.append( ( role.id, role.name ) )
+ return trans.fill_template( '/admin/dataset_security/group/group_create.mako',
+ name=name,
+ in_users=in_users,
+ out_users=out_users,
+ in_roles=in_roles,
+ out_roles=out_roles,
+ create_role_for_group_checked=create_role_for_group_checked,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def mark_group_deleted( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No group ids received for marking deleted"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Deleted %d groups: " % len( ids )
+ for group_id in ids:
+ group = get_group( trans, group_id )
+ group.deleted = True
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ message += " %s " % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def undelete_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No group ids received for undeleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ count = 0
+ undeleted_groups = ""
+ for group_id in ids:
+ group = get_group( trans, group_id )
+ if not group.deleted:
+ message = "Group '%s' has not been deleted, so it cannot be undeleted." % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ group.deleted = False
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ count += 1
+ undeleted_groups += " %s" % group.name
+ message = "Undeleted %d groups: %s" % ( count, undeleted_groups )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def purge_group( self, trans, **kwd ):
+ # This method should only be called for a Group that has previously been deleted.
+ # Purging a deleted Group simply deletes all UserGroupAssociations and GroupRoleAssociations.
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No group ids received for purging"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d groups: " % len( ids )
+ for group_id in ids:
+ group = get_group( trans, group_id )
+ if not group.deleted:
+ # We should never reach here, but just in case there is a bug somewhere...
+ message = "Group '%s' has not been deleted, so it cannot be purged." % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ # Delete UserGroupAssociations
+ for uga in group.users:
+ trans.sa_session.delete( uga )
+ # Delete GroupRoleAssociations
+ for gra in group.roles:
+ trans.sa_session.delete( gra )
+ trans.sa_session.flush()
+ message += " %s " % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+
+ # Galaxy User Stuff
+ @web.expose
+ @web.require_admin
+ def create_new_user( self, trans, **kwd ):
+ return trans.response.send_redirect( web.url_for( controller='user',
+ action='create',
+ cntrller='admin' ) )
+ @web.expose
+ @web.require_admin
+ def reset_user_password( self, trans, **kwd ):
+ user_id = kwd.get( 'id', None )
+ if not user_id:
+ message = "No users received for resetting passwords."
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=message,
+ status='error' ) )
+ user_ids = util.listify( user_id )
+ if 'reset_user_password_button' in kwd:
+ message = ''
+ status = ''
+ for user_id in user_ids:
+ user = get_user( trans, user_id )
+ password = kwd.get( 'password', None )
+ confirm = kwd.get( 'confirm' , None )
+ if len( password ) < 6:
+ message = "Use a password of at least 6 characters."
+ status = 'error'
+ break
+ elif password != confirm:
+ message = "Passwords do not match."
+ status = 'error'
+ break
+ else:
+ user.set_password_cleartext( password )
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ if not message and not status:
+ message = "Passwords reset for %d %s." % ( len( user_ids ), inflector.cond_plural( len( user_ids ), 'user' ) )
+ status = 'done'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status=status ) )
+ users = [ get_user( trans, user_id ) for user_id in user_ids ]
+ if len( user_ids ) > 1:
+ user_id = ','.join( user_ids )
+ return trans.fill_template( '/admin/user/reset_password.mako',
+ id=user_id,
+ users=users,
+ password='',
+ confirm='' )
+ @web.expose
+ @web.require_admin
+ def mark_user_deleted( self, trans, **kwd ):
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No user ids received for deleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Deleted %d users: " % len( ids )
+ for user_id in ids:
+ user = get_user( trans, user_id )
+ user.deleted = True
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ message += " %s " % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def undelete_user( self, trans, **kwd ):
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No user ids received for undeleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ count = 0
+ undeleted_users = ""
+ for user_id in ids:
+ user = get_user( trans, user_id )
+ if not user.deleted:
+ message = "User '%s' has not been deleted, so it cannot be undeleted." % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ user.deleted = False
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ count += 1
+ undeleted_users += " %s" % user.email
+ message = "Undeleted %d users: %s" % ( count, undeleted_users )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def purge_user( self, trans, **kwd ):
+ # This method should only be called for a User that has previously been deleted.
+ # We keep the User in the database ( marked as purged ), and stuff associated
+ # with the user's private role in case we want the ability to unpurge the user
+ # some time in the future.
+ # Purging a deleted User deletes all of the following:
+ # - History where user_id = User.id
+ # - HistoryDatasetAssociation where history_id = History.id
+ # - Dataset where HistoryDatasetAssociation.dataset_id = Dataset.id
+ # - UserGroupAssociation where user_id == User.id
+ # - UserRoleAssociation where user_id == User.id EXCEPT FOR THE PRIVATE ROLE
+ # - UserAddress where user_id == User.id
+ # Purging Histories and Datasets must be handled via the cleanup_datasets.py script
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No user ids received for purging"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d users: " % len( ids )
+ for user_id in ids:
+ user = get_user( trans, user_id )
+ if not user.deleted:
+ # We should never reach here, but just in case there is a bug somewhere...
+ message = "User '%s' has not been deleted, so it cannot be purged." % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ private_role = trans.app.security_agent.get_private_user_role( user )
+ # Delete History
+ for h in user.active_histories:
+ trans.sa_session.refresh( h )
+ for hda in h.active_datasets:
+ # Delete HistoryDatasetAssociation
+ d = trans.sa_session.query( trans.app.model.Dataset ).get( hda.dataset_id )
+ # Delete Dataset
+ if not d.deleted:
+ d.deleted = True
+ trans.sa_session.add( d )
+ hda.deleted = True
+ trans.sa_session.add( hda )
+ h.deleted = True
+ trans.sa_session.add( h )
+ # Delete UserGroupAssociations
+ for uga in user.groups:
+ trans.sa_session.delete( uga )
+ # Delete UserRoleAssociations EXCEPT FOR THE PRIVATE ROLE
+ for ura in user.roles:
+ if ura.role_id != private_role.id:
+ trans.sa_session.delete( ura )
+ # Delete UserAddresses
+ for address in user.addresses:
+ trans.sa_session.delete( address )
+ # Purge the user
+ user.purged = True
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ message += "%s " % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def users( self, trans, **kwd ):
+ if 'operation' in kwd:
+ operation = kwd['operation'].lower()
+ if operation == "roles":
+ return self.user( trans, **kwd )
+ elif operation == "reset password":
+ return self.reset_user_password( trans, **kwd )
+ elif operation == "delete":
+ return self.mark_user_deleted( trans, **kwd )
+ elif operation == "undelete":
+ return self.undelete_user( trans, **kwd )
+ elif operation == "purge":
+ return self.purge_user( trans, **kwd )
+ elif operation == "create":
+ return self.create_new_user( trans, **kwd )
+ elif operation == "information":
+ user_id = kwd.get( 'id', None )
+ if not user_id:
+ kwd[ 'message' ] = util.sanitize_text( "Invalid user id (%s) received" % str( user_id ) )
+ kwd[ 'status' ] = 'error'
+ else:
+ return trans.response.send_redirect( web.url_for( controller='user',
+ action='manage_user_info',
+ cntrller='admin',
+ **kwd ) )
+ elif operation == "manage roles and groups":
+ return self.manage_roles_and_groups_for_user( trans, **kwd )
+ if trans.app.config.allow_user_deletion:
+ if self.delete_operation not in self.user_list_grid.operations:
+ self.user_list_grid.operations.append( self.delete_operation )
+ if self.undelete_operation not in self.user_list_grid.operations:
+ self.user_list_grid.operations.append( self.undelete_operation )
+ if self.purge_operation not in self.user_list_grid.operations:
+ self.user_list_grid.operations.append( self.purge_operation )
+ # Render the list view
+ return self.user_list_grid( trans, **kwd )
+ @web.expose
+ @web.require_admin
+ def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
+ """Return autocomplete data for user emails"""
+ ac_data = ""
+ for user in trans.sa_session.query( User ).filter_by( deleted=False ).filter( func.lower( User.email ).like( q.lower() + "%" ) ):
+ ac_data = ac_data + user.email + "\n"
+ return ac_data
+ @web.expose
+ @web.require_admin
+ def manage_roles_and_groups_for_user( self, trans, **kwd ):
+ user_id = kwd.get( 'id', None )
+ message = ''
+ status = ''
+ if not user_id:
+ message += "Invalid user id (%s) received" % str( user_id )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ user = get_user( trans, user_id )
+ private_role = trans.app.security_agent.get_private_user_role( user )
+ if kwd.get( 'user_roles_groups_edit_button', False ):
+ # Make sure the user is not dis-associating himself from his private role
+ out_roles = kwd.get( 'out_roles', [] )
+ if out_roles:
+ out_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( out_roles ) ]
+ if private_role in out_roles:
+ message += "You cannot eliminate a user's private role association. "
+ status = 'error'
+ in_roles = kwd.get( 'in_roles', [] )
+ if in_roles:
+ in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( in_roles ) ]
+ out_groups = kwd.get( 'out_groups', [] )
+ if out_groups:
+ out_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( out_groups ) ]
+ in_groups = kwd.get( 'in_groups', [] )
+ if in_groups:
+ in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( in_groups ) ]
+ if in_roles:
+ trans.app.security_agent.set_entity_user_associations( users=[ user ], roles=in_roles, groups=in_groups )
+ trans.sa_session.refresh( user )
+ message += "User '%s' has been updated with %d associated roles and %d associated groups (private roles are not displayed)" % \
+ ( user.email, len( in_roles ), len( in_groups ) )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ in_roles = []
+ out_roles = []
+ in_groups = []
+ out_groups = []
+ for role in trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.deleted==False ) \
+ .order_by( trans.app.model.Role.table.c.name ):
+ if role in [ x.role for x in user.roles ]:
+ in_roles.append( ( role.id, role.name ) )
+ elif role.type != trans.app.model.Role.types.PRIVATE:
+ # There is a 1 to 1 mapping between a user and a PRIVATE role, so private roles should
+ # not be listed in the roles form fields, except for the currently selected user's private
+ # role, which should always be in in_roles. The check above is added as an additional
+ # precaution, since for a period of time we were including private roles in the form fields.
+ out_roles.append( ( role.id, role.name ) )
+ for group in trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.deleted==False ) \
+ .order_by( trans.app.model.Group.table.c.name ):
+ if group in [ x.group for x in user.groups ]:
+ in_groups.append( ( group.id, group.name ) )
+ else:
+ out_groups.append( ( group.id, group.name ) )
+ message += "User '%s' is currently associated with %d roles and is a member of %d groups" % \
+ ( user.email, len( in_roles ), len( in_groups ) )
+ if not status:
+ status = 'done'
+ return trans.fill_template( '/admin/user/user.mako',
+ user=user,
+ in_roles=in_roles,
+ out_roles=out_roles,
+ in_groups=in_groups,
+ out_groups=out_groups,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ):
+ if self.app.memdump is None:
+ return trans.show_error_message( "Memdump is not enabled (set <code>use_memdump = True</code> in universe_wsgi.ini)" )
+ heap = self.app.memdump.get()
+ p = util.Params( kwd )
+ msg = None
+ if p.dump:
+ heap = self.app.memdump.get( update = True )
+ msg = "Heap dump complete"
+ elif p.setref:
+ self.app.memdump.setref()
+ msg = "Reference point set (dump to see delta from this point)"
+ ids = ids.split( ',' )
+ sorts = sorts.split( ',' )
+ if new_id is not None:
+ ids.append( new_id )
+ sorts.append( 'None' )
+ elif new_sort is not None:
+ sorts[-1] = new_sort
+ breadcrumb = "<a href='%s' class='breadcrumb'>heap</a>" % web.url_for()
+ # new lists so we can assemble breadcrumb links
+ new_ids = []
+ new_sorts = []
+ for id, sort in zip( ids, sorts ):
+ new_ids.append( id )
+ if id != 'None':
+ breadcrumb += "<a href='%s' class='breadcrumb'>[%s]</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), id )
+ heap = heap[int(id)]
+ new_sorts.append( sort )
+ if sort != 'None':
+ breadcrumb += "<a href='%s' class='breadcrumb'>.by('%s')</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), sort )
+ heap = heap.by( sort )
+ ids = ','.join( new_ids )
+ sorts = ','.join( new_sorts )
+ if p.theone:
+ breadcrumb += ".theone"
+ heap = heap.theone
+ return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg )
+
+ @web.expose
+ @web.require_admin
+ def jobs( self, trans, stop = [], stop_msg = None, cutoff = 180, job_lock = None, ajl_submit = None, **kwd ):
+ deleted = []
+ msg = None
+ status = None
+ if self.app.config.job_manager != self.app.config.server_name:
+ return trans.show_error_message( 'This Galaxy instance (%s) is not the job manager (%s). If using multiple servers, please directly access the job manager instance to manage jobs.' % (self.app.config.server_name, self.app.config.job_manager) )
+ job_ids = util.listify( stop )
+ if job_ids and stop_msg in [ None, '' ]:
+ msg = 'Please enter an error message to display to the user describing why the job was terminated'
+ status = 'error'
+ elif job_ids:
+ if stop_msg[-1] not in string.punctuation:
+ stop_msg += '.'
+ for job_id in job_ids:
+ trans.app.job_manager.job_stop_queue.put( job_id, error_msg="This job was stopped by an administrator: %s For more information or help" % stop_msg )
+ deleted.append( str( job_id ) )
+ if deleted:
+ msg = 'Queued job'
+ if len( deleted ) > 1:
+ msg += 's'
+ msg += ' for deletion: '
+ msg += ', '.join( deleted )
+ status = 'done'
+ if ajl_submit:
+ if job_lock == 'on':
+ trans.app.job_manager.job_queue.job_lock = True
+ else:
+ trans.app.job_manager.job_queue.job_lock = False
+ cutoff_time = datetime.utcnow() - timedelta( seconds=int( cutoff ) )
+ jobs = trans.sa_session.query( trans.app.model.Job ) \
+ .filter( and_( trans.app.model.Job.table.c.update_time < cutoff_time,
+ or_( trans.app.model.Job.state == trans.app.model.Job.states.NEW,
+ trans.app.model.Job.state == trans.app.model.Job.states.QUEUED,
+ trans.app.model.Job.state == trans.app.model.Job.states.RUNNING,
+ trans.app.model.Job.state == trans.app.model.Job.states.UPLOAD ) ) ) \
+ .order_by( trans.app.model.Job.table.c.update_time.desc() )
+ last_updated = {}
+ for job in jobs:
+ delta = datetime.utcnow() - job.update_time
+ if delta > timedelta( minutes=60 ):
+ last_updated[job.id] = '%s hours' % int( delta.seconds / 60 / 60 )
+ else:
+ last_updated[job.id] = '%s minutes' % int( delta.seconds / 60 )
+ return trans.fill_template( '/admin/jobs.mako',
+ jobs = jobs,
+ last_updated = last_updated,
+ cutoff = cutoff,
+ msg = msg,
+ status = status,
+ job_lock = trans.app.job_manager.job_queue.job_lock )
+
+## ---- Utility methods -------------------------------------------------------
+
+def get_ids_of_tool_shed_repositories_being_installed( trans, as_string=False ):
+ installing_repository_ids = []
+ new_status = trans.model.ToolShedRepository.installation_status.NEW
+ cloning_status = trans.model.ToolShedRepository.installation_status.CLONING
+ setting_tool_versions_status = trans.model.ToolShedRepository.installation_status.SETTING_TOOL_VERSIONS
+ installing_dependencies_status = trans.model.ToolShedRepository.installation_status.INSTALLING_TOOL_DEPENDENCIES
+ loading_datatypes_status = trans.model.ToolShedRepository.installation_status.LOADING_PROPRIETARY_DATATYPES
+ for tool_shed_repository in trans.sa_session.query( trans.model.ToolShedRepository ) \
+ .filter( or_( trans.model.ToolShedRepository.status == new_status,
+ trans.model.ToolShedRepository.status == cloning_status,
+ trans.model.ToolShedRepository.status == setting_tool_versions_status,
+ trans.model.ToolShedRepository.status == installing_dependencies_status,
+ trans.model.ToolShedRepository.status == loading_datatypes_status ) ):
+ installing_repository_ids.append( trans.security.encode_id( tool_shed_repository.id ) )
+ if as_string:
+ return ','.join( installing_repository_ids )
+ return installing_repository_ids
+
+def get_user( trans, user_id ):
+ """Get a User from the database by id."""
+ user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
+ if not user:
+ return trans.show_error_message( "User not found for id (%s)" % str( user_id ) )
+ return user
+def get_user_by_username( trans, username ):
+ """Get a user from the database by username"""
+ # TODO: Add exception handling here.
+ return trans.sa_session.query( trans.model.User ) \
+ .filter( trans.model.User.table.c.username == username ) \
+ .one()
+def get_role( trans, id ):
+ """Get a Role from the database by id."""
+ # Load user from database
+ id = trans.security.decode_id( id )
+ role = trans.sa_session.query( trans.model.Role ).get( id )
+ if not role:
+ return trans.show_error_message( "Role not found for id (%s)" % str( id ) )
+ return role
+def get_group( trans, id ):
+ """Get a Group from the database by id."""
+ # Load user from database
+ id = trans.security.decode_id( id )
+ group = trans.sa_session.query( trans.model.Group ).get( id )
+ if not group:
+ return trans.show_error_message( "Group not found for id (%s)" % str( id ) )
+ return group
+def get_quota( trans, id ):
+ """Get a Quota from the database by id."""
+ # Load user from database
+ id = trans.security.decode_id( id )
+ quota = trans.sa_session.query( trans.model.Quota ).get( id )
+ return quota
\ No newline at end of file
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -607,7 +607,7 @@
Update the session cookie to match the current session.
"""
self.set_cookie( self.security.encode_guid( self.galaxy_session.session_key ), name=name, path=self.app.config.cookie_path )
- def handle_user_login( self, user, webapp ):
+ def handle_user_login( self, user ):
"""
Login a new user (possibly newly created)
- create a new session
@@ -621,7 +621,7 @@
prev_galaxy_session.is_valid = False
# Define a new current_session
self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
- if webapp == 'galaxy':
+ if self.webapp.name == 'galaxy':
cookie_name = 'galaxysession'
# Associated the current user's last accessed history (if exists) with their new session
history = None
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/web/framework/helpers/grids.py
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -53,7 +53,8 @@
def __call__( self, trans, **kwargs ):
# Get basics.
- webapp = get_webapp( trans, **kwargs )
+ # FIXME: pretty sure this is only here to pass along, can likely be eliminated
+ webapp = trans.webapp.name
status = kwargs.get( 'status', None )
message = kwargs.get( 'message', None )
# Build a base filter and sort key that is the combination of the saved state and defaults.
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/webapps/community/controllers/admin.py
--- a/lib/galaxy/webapps/community/controllers/admin.py
+++ b/lib/galaxy/webapps/community/controllers/admin.py
@@ -1,4 +1,5 @@
from galaxy.web.base.controller import *
+from galaxy.web.base.controllers.admin import Admin
from galaxy.webapps.community import model
from galaxy.model.orm import *
from galaxy.web.framework.helpers import time_ago, iff, grids
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/webapps/galaxy/api/quotas.py
--- a/lib/galaxy/webapps/galaxy/api/quotas.py
+++ b/lib/galaxy/webapps/galaxy/api/quotas.py
@@ -2,7 +2,8 @@
API operations on Quota objects.
"""
import logging
-from galaxy.web.base.controller import BaseAPIController, Admin, UsesQuotaMixin, url_for
+from galaxy.web.base.controller import BaseAPIController, UsesQuotaMixin, url_for
+from galaxy.web.base.controllers.admin import Admin
from galaxy import web, util
from elementtree.ElementTree import XML
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/webapps/galaxy/controllers/admin.py
--- a/lib/galaxy/webapps/galaxy/controllers/admin.py
+++ b/lib/galaxy/webapps/galaxy/controllers/admin.py
@@ -1,4 +1,5 @@
from galaxy.web.base.controller import *
+from galaxy.web.base.controllers.admin import Admin
from galaxy import model
from galaxy.model.orm import *
from galaxy.web.framework.helpers import time_ago, iff, grids
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c lib/galaxy/webapps/galaxy/controllers/user.py
--- a/lib/galaxy/webapps/galaxy/controllers/user.py
+++ b/lib/galaxy/webapps/galaxy/controllers/user.py
@@ -14,7 +14,7 @@
from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password, transform_publicname
from galaxy.util.json import from_json_string, to_json_string
from galaxy.web import url_for
-from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin, get_webapp
+from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin
from galaxy.web.form_builder import CheckboxField, build_select_field
from galaxy.web.framework.helpers import time_ago, grids
@@ -49,10 +49,10 @@
installed_len_files = None
@web.expose
- def index( self, trans, cntrller, webapp='galaxy', **kwd ):
- return trans.fill_template( '/user/index.mako', cntrller=cntrller, webapp=webapp )
+ def index( self, trans, cntrller, **kwd ):
+ return trans.fill_template( '/user/index.mako', cntrller=cntrller )
@web.expose
- def openid_auth( self, trans, webapp='galaxy', **kwd ):
+ def openid_auth( self, trans, **kwd ):
'''Handles user request to access an OpenID provider'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -102,7 +102,7 @@
message=message,
status='error' ) )
@web.expose
- def openid_process( self, trans, webapp='galaxy', **kwd ):
+ def openid_process( self, trans, **kwd ):
'''Handle's response from OpenID Providers'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -178,7 +178,7 @@
message=message,
status=status ) )
elif user_openid.user:
- trans.handle_user_login( user_openid.user, webapp )
+ trans.handle_user_login( user_openid.user )
trans.log_event( "User logged in via OpenID: %s" % display_identifier )
openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info )
if not redirect:
@@ -222,7 +222,7 @@
message=message,
status=status ) )
@web.expose
- def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
+ def openid_associate( self, trans, cntrller='user', **kwd ):
'''Associates a user with an OpenID log in'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -241,7 +241,7 @@
elif is_admin:
return trans.show_error_message( 'Associating OpenIDs with accounts cannot be done by administrators.' )
if kwd.get( 'login_button', False ):
- message, status, user, success = self.__validate_login( trans, webapp, **kwd )
+ message, status, user, success = self.__validate_login( trans, **kwd )
if success:
openid_objs = []
for openid in openids:
@@ -285,7 +285,7 @@
error = 'User registration is disabled. Please contact your Galaxy administrator for an account.'
else:
# Check email and password validity
- error = self.__validate( trans, params, email, password, confirm, username, webapp )
+ error = self.__validate( trans, params, email, password, confirm, username )
if not error:
# all the values are valid
message, status, user, success = self.__register( trans,
@@ -330,7 +330,7 @@
else:
message = error
status = 'error'
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
user_type_form_definition = self.__get_user_type_form_definition( trans, user=user, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
@@ -342,7 +342,6 @@
user_type_form_definition = None
widgets = []
return trans.fill_template( '/user/openid_associate.mako',
- webapp=webapp,
cntrller=cntrller,
email=email,
password='',
@@ -362,7 +361,7 @@
openids=openids )
@web.expose
@web.require_login( 'manage OpenIDs' )
- def openid_disassociate( self, trans, webapp='galaxy', **kwd ):
+ def openid_disassociate( self, trans, **kwd ):
'''Disassociates a user with an OpenID'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -404,7 +403,7 @@
@web.expose
@web.require_login( 'manage OpenIDs' )
- def openid_manage( self, trans, webapp='galaxy', **kwd ):
+ def openid_manage( self, trans, **kwd ):
'''Manage OpenIDs for user'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -421,7 +420,7 @@
return self.user_openid_grid( trans, **kwd )
@web.expose
- def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **kwd ):
+ def login( self, trans, redirect_url='', refresh_frames=[], **kwd ):
'''Handle Galaxy Log in'''
redirect = kwd.get( 'redirect', trans.request.referer ).strip()
use_panels = util.string_as_bool( kwd.get( 'use_panels', False ) )
@@ -430,16 +429,13 @@
header = ''
user = None
email = kwd.get( 'email', '' )
- #Sanitize webapp login here, once, since it can be reflected to the user in messages/etc.
- #Only text is valid.
- webapp = util.sanitize_text(webapp)
if kwd.get( 'login_button', False ):
- if webapp == 'galaxy' and not refresh_frames:
+ if trans.webapp.name == 'galaxy' and not refresh_frames:
if trans.app.config.require_login:
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
refresh_frames = [ 'masthead', 'history' ]
- message, status, user, success = self.__validate_login( trans, webapp, **kwd )
+ message, status, user, success = self.__validate_login( trans, **kwd )
if success and redirect and not redirect.startswith( trans.request.base + url_for( controller='user', action='logout' ) ):
redirect_url = redirect
elif success:
@@ -447,18 +443,17 @@
if not user and trans.app.config.require_login:
if trans.app.config.allow_user_creation:
create_account_str = " If you don't already have an account, <a href='%s'>you may create one</a>." % \
- web.url_for( action='create', cntrller='user', webapp=webapp )
- if webapp == 'galaxy':
+ web.url_for( action='create', cntrller='user' )
+ if trans.webapp.name == 'galaxy':
header = require_login_template % ( "Galaxy instance", create_account_str )
else:
header = require_login_template % ( "Galaxy tool shed", create_account_str )
else:
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
header = require_login_template % ( "Galaxy instance", "" )
else:
header = require_login_template % ( "Galaxy tool shed", "" )
return trans.fill_template( '/user/login.mako',
- webapp=webapp,
email=email,
header=header,
use_panels=use_panels,
@@ -469,7 +464,7 @@
status=status,
openid_providers=trans.app.openid_providers,
active_view="user" )
- def __validate_login( self, trans, webapp='galaxy', **kwd ):
+ def __validate_login( self, trans, **kwd ):
message = kwd.get( 'message', '' )
status = kwd.get( 'status', 'done' )
email = kwd.get( 'email', '' )
@@ -490,8 +485,8 @@
message = "Invalid password"
status = 'error'
else:
- trans.handle_user_login( user, webapp )
- if webapp == 'galaxy':
+ trans.handle_user_login( user )
+ if trans.webapp.name == 'galaxy':
trans.log_event( "User logged in" )
message = 'You are now logged in as %s.<br>You can <a target="_top" href="%s">go back to the page you were visiting</a> or <a target="_top" href="%s">go to the home page</a>.' % \
( user.email, redirect, url_for( '/' ) )
@@ -501,8 +496,8 @@
return ( message, status, user, success )
@web.expose
- def logout( self, trans, webapp='galaxy', logout_all=False ):
- if webapp == 'galaxy':
+ def logout( self, trans, logout_all=False ):
+ if trans.webapp.name == 'galaxy':
if trans.app.config.require_login:
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
@@ -515,7 +510,6 @@
message = 'You have been logged out.<br>You can log in again, <a target="_top" href="%s">go back to the page you were visiting</a> or <a target="_top" href="%s">go to the home page</a>.' % \
( trans.request.referer, url_for( '/' ) )
return trans.fill_template( '/user/logout.mako',
- webapp=webapp,
refresh_frames=refresh_frames,
message=message,
status='done',
@@ -526,7 +520,6 @@
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
- webapp = get_webapp( trans, **kwd )
use_panels = util.string_as_bool( kwd.get( 'use_panels', True ) )
email = util.restore_text( params.get( 'email', '' ) )
# Do not sanitize passwords, so take from kwd
@@ -543,7 +536,7 @@
status = 'error'
else:
if not refresh_frames:
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
if trans.app.config.require_login:
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
@@ -553,19 +546,19 @@
# Create the user, save all the user info and login to Galaxy
if params.get( 'create_user_button', False ):
# Check email and password validity
- message = self.__validate( trans, params, email, password, confirm, username, webapp )
+ message = self.__validate( trans, params, email, password, confirm, username )
if not message:
# All the values are valid
message, status, user, success = self.__register( trans,
cntrller,
subscribe_checked,
**kwd )
- if webapp == 'community':
+ if trans.webapp.name == 'community':
redirect_url = url_for( '/' )
if success and not is_admin:
# The handle_user_login() method has a call to the history_set_default_permissions() method
# (needed when logging in with a history), user needs to have default permissions set before logging in
- trans.handle_user_login( user, webapp )
+ trans.handle_user_login( user )
trans.log_event( "User created a new account" )
trans.log_event( "User logged in" )
if success and is_admin:
@@ -577,7 +570,7 @@
status=status ) )
else:
status = 'error'
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
user_type_form_definition = self.__get_user_type_form_definition( trans, user=None, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
@@ -596,7 +589,6 @@
user_type_fd_id_select_field=user_type_fd_id_select_field,
user_type_form_definition=user_type_form_definition,
widgets=widgets,
- webapp=webapp,
use_panels=use_panels,
redirect=redirect,
redirect_url=redirect_url,
@@ -608,7 +600,6 @@
email = util.restore_text( kwd.get( 'email', '' ) )
password = kwd.get( 'password', '' )
username = util.restore_text( kwd.get( 'username', '' ) )
- webapp = get_webapp( trans, **kwd )
status = kwd.get( 'status', 'done' )
is_admin = cntrller == 'admin' and trans.user_is_admin()
user = trans.app.model.User( email=email )
@@ -618,7 +609,7 @@
trans.sa_session.flush()
trans.app.security_agent.create_private_user_role( user )
error = ''
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
# We set default user permissions, before we log in and set the default history permissions
trans.app.security_agent.user_set_default_permissions( user,
default_access_private=trans.app.config.new_user_dataset_access_role_default_private )
@@ -654,7 +645,7 @@
if not error and not is_admin:
# The handle_user_login() method has a call to the history_set_default_permissions() method
# (needed when logging in with a history), user needs to have default permissions set before logging in
- trans.handle_user_login( user, webapp )
+ trans.handle_user_login( user )
trans.log_event( "User created a new account" )
trans.log_event( "User logged in" )
elif not error:
@@ -706,14 +697,13 @@
user = trans.user
if not user:
raise AssertionError, "The user id (%s) is not valid" % str( user_id )
- webapp = get_webapp( trans, **kwd )
email = util.restore_text( params.get( 'email', user.email ) )
username = util.restore_text( params.get( 'username', '' ) )
if not username:
username = user.username
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
user_type_form_definition = self.__get_user_type_form_definition( trans, user=user, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
@@ -742,7 +732,6 @@
widgets=widgets,
addresses=addresses,
show_filter=show_filter,
- webapp=webapp,
message=message,
status=status )
else:
@@ -751,7 +740,6 @@
user=user,
email=email,
username=username,
- webapp=webapp,
message=message,
status=status )
@@ -761,7 +749,6 @@
def edit_username( self, trans, cntrller, **kwd ):
params = util.Params( kwd )
is_admin = cntrller == 'admin' and trans.user_is_admin()
- webapp = get_webapp( trans, **kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
user_id = params.get( 'user_id', None )
@@ -784,14 +771,12 @@
cntrller=cntrller,
user=user,
username=user.username,
- webapp=webapp,
message=message,
status=status )
@web.expose
def edit_info( self, trans, cntrller, **kwd ):
params = util.Params( kwd )
is_admin = cntrller == 'admin' and trans.user_is_admin()
- webapp = get_webapp( trans, **kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
user_id = params.get( 'user_id', None )
@@ -885,7 +870,7 @@
trans.sa_session.add( user )
trans.sa_session.flush()
message = "The user information has been updated with the changes."
- if user and webapp == 'galaxy' and is_admin:
+ if user and trans.webapp.name == 'galaxy' and is_admin:
kwd[ 'user_id' ] = trans.security.encode_id( user.id )
kwd[ 'id' ] = user_id
if message:
@@ -897,7 +882,7 @@
cntrller=cntrller,
**kwd ) )
@web.expose
- def reset_password( self, trans, email=None, webapp='galaxy', **kwd ):
+ def reset_password( self, trans, email=None, **kwd ):
if trans.app.config.smtp_server is None:
return trans.show_error_message( "Mail is not configured for this Galaxy instance. Please contact an administrator." )
message = util.restore_text( kwd.get( 'message', '' ) )
@@ -941,12 +926,11 @@
elif email is None:
email = ""
return trans.fill_template( '/user/reset_password.mako',
- webapp=webapp,
message=message,
status=status )
- def __validate( self, trans, params, email, password, confirm, username, webapp ):
+ def __validate( self, trans, params, email, password, confirm, username ):
# If coming from the community webapp, we'll require a public user name
- if webapp == 'community' and not username:
+ if trans.webapp.name == 'community' and not username:
return "A public user name is required"
message = validate_email( trans, email )
if not message:
@@ -954,7 +938,7 @@
if not message and username:
message = validate_publicname( trans, username )
if not message:
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
if self.get_all_forms( trans,
filter=dict( deleted=False ),
form_type=trans.app.model.FormDefinition.types.USER_INFO ):
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/dbkeys.mako
--- a/templates/user/dbkeys.mako
+++ b/templates/user/dbkeys.mako
@@ -1,11 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % t.webapp.name
else:
return '/base.mako'
%>
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/index.mako
--- a/templates/user/index.mako
+++ b/templates/user/index.mako
@@ -9,27 +9,27 @@
<h2>${_('User preferences')}</h2><p>You are currently logged in as ${trans.user.email}.</p><ul>
- %if webapp == 'galaxy':
- <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller, webapp=webapp )}">${_('Manage your information')}</a></li>
- <li><a href="${h.url_for( controller='user', action='set_default_permissions', cntrller=cntrller, webapp=webapp )}">${_('Change default permissions')}</a> for new histories</li>
- <li><a href="${h.url_for( controller='user', action='api_keys', cntrller=cntrller, webapp=webapp )}">${_('Manage your API keys')}</a></li>
+ %if t.webapp.name == 'galaxy':
+ <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller )}">${_('Manage your information')}</a></li>
+ <li><a href="${h.url_for( controller='user', action='set_default_permissions', cntrller=cntrller )}">${_('Change default permissions')}</a> for new histories</li>
+ <li><a href="${h.url_for( controller='user', action='api_keys', cntrller=cntrller )}">${_('Manage your API keys')}</a></li>
%if trans.app.config.enable_openid:
- <li><a href="${h.url_for( controller='user', action='openid_manage', cntrller=cntrller, webapp=webapp )}">${_('Manage OpenIDs')}</a> linked to your account</li>
+ <li><a href="${h.url_for( controller='user', action='openid_manage', cntrller=cntrller )}">${_('Manage OpenIDs')}</a> linked to your account</li>
%endif
%if trans.app.config.use_remote_user:
%if trans.app.config.remote_user_logout_href:
<li><a href="${trans.app.config.remote_user_logout_href}" target="_top">${_('Logout')}</a></li>
%endif
%else:
- <li><a href="${h.url_for( controller='user', action='logout', webapp=webapp, logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
+ <li><a href="${h.url_for( controller='user', action='logout', logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
%endif
%else:
- <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller, webapp=webapp )}">${_('Manage your information')}</a></li>
- <li><a href="${h.url_for( controller='repository', action='manage_email_alerts', cntrller=cntrller, webapp=webapp )}">${_('Manage your email alerts')}</a></li>
- <li><a href="${h.url_for( controller='user', action='logout', webapp=webapp, logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
+ <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller )}">${_('Manage your information')}</a></li>
+ <li><a href="${h.url_for( controller='repository', action='manage_email_alerts', cntrller=cntrller )}">${_('Manage your email alerts')}</a></li>
+ <li><a href="${h.url_for( controller='user', action='logout', logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
%endif
</ul>
- %if webapp == 'galaxy':
+ %if t.webapp.name == 'galaxy':
<p>
You are using <strong>${trans.user.get_disk_usage( nice_size=True )}</strong> of disk space in this Galaxy instance.
%if trans.app.config.enable_quotas:
@@ -43,7 +43,7 @@
<p>${n_('You are currently not logged in.')}</p>
%endif
<ul>
- <li><a href="${h.url_for( action='login', webapp=webapp )}">${_('Login')}</li>
- <li><a href="${h.url_for( action='create', cntrller='user', webapp=webapp )}">${_('Register')}</a></li>
+ <li><a href="${h.url_for( action='login' )}">${_('Login')}</li>
+ <li><a href="${h.url_for( action='create', cntrller='user' )}">${_('Register')}</a></li></ul>
%endif
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/info.mako
--- a/templates/user/info.mako
+++ b/templates/user/info.mako
@@ -7,13 +7,12 @@
%if not is_admin:
<ul class="manage-table-actions"><li>
- <a class="action-button" href="${h.url_for( controller='user', action='index', cntrller=cntrller, webapp=webapp )}">User preferences</a>
+ <a class="action-button" href="${h.url_for( controller='user', action='index', cntrller=cntrller )}">User preferences</a></li></ul>
%endif
<div class="toolForm"><form name="login_info" id="login_info" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
- <input type="hidden" name="webapp" value="${webapp}" size="40"/><div class="toolFormTitle">Login Information</div><div class="form-row"><label>Email address:</label>
@@ -21,7 +20,7 @@
</div><div class="form-row"><label>Public name:</label>
- %if webapp == 'community':
+ %if t.webapp.name == 'community':
%if user.active_repositories:
<input type="hidden" name="username" value="${username}"/>
${username}
@@ -54,7 +53,6 @@
<p></p><div class="toolForm"><form name="change_password" id="change_password" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
- <input type="hidden" name="webapp" value="${webapp}" size="40"/><div class="toolFormTitle">Change Password</div>
%if not is_admin:
<div class="form-row">
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/login.mako
--- a/templates/user/login.mako
+++ b/templates/user/login.mako
@@ -1,11 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % context.get('t').webapp.name
else:
return '/base.mako'
%>
@@ -82,14 +78,13 @@
<div class="form-row"><label>Email address:</label><input type="text" name="email" value="${email | h}" size="40"/>
- <input type="hidden" name="webapp" value="${webapp | h}" size="40"/><input type="hidden" name="redirect" value="${redirect | h}" size="40"/></div><div class="form-row"><label>Password:</label><input type="password" name="password" value="" size="40"/><div class="toolParamHelp" style="clear: both;">
- <a href="${h.url_for( controller='user', action='reset_password', webapp=webapp, use_panels=use_panels )}">Forgot password? Reset here</a>
+ <a href="${h.url_for( controller='user', action='reset_password', use_panels=use_panels )}">Forgot password? Reset here</a></div></div><div class="form-row">
@@ -107,7 +102,6 @@
<div class="form-row"><label>OpenID URL:</label><input type="text" name="openid_url" size="60" style="background-image:url('${h.url_for( '/static/images/openid-16x16.gif' )}' ); background-repeat: no-repeat; padding-right: 20px; background-position: 99% 50%;"/>
- <input type="hidden" name="webapp" value="${webapp | h}" size="40"/><input type="hidden" name="redirect" value="${redirect | h}" size="40"/></div><div class="form-row">
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/logout.mako
--- a/templates/user/logout.mako
+++ b/templates/user/logout.mako
@@ -1,10 +1,6 @@
<%!
def inherit(context):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % context.get('t').webapp.name
%><%inherit file="${inherit(context)}"/><%namespace file="/message.mako" import="render_msg" />
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/openid_associate.mako
--- a/templates/user/openid_associate.mako
+++ b/templates/user/openid_associate.mako
@@ -1,11 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % t.webapp.name
else:
return '/base.mako'
%>
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/register.mako
--- a/templates/user/register.mako
+++ b/templates/user/register.mako
@@ -43,7 +43,6 @@
<div class="form-row"><label>Email address:</label><input type="text" name="email" value="${email | h}" size="40"/>
- <input type="hidden" name="webapp" value="${webapp | h}" size="40"/><input type="hidden" name="redirect" value="${redirect | h}" size="40"/></div><div class="form-row">
@@ -57,7 +56,7 @@
<div class="form-row"><label>Public name:</label><input type="text" name="username" size="40" value="${username |h}"/>
- %if webapp == 'galaxy':
+ %if t.webapp.name == 'galaxy':
<div class="toolParamHelp" style="clear: both;">
Your public name is an identifier that will be used to generate addresses for information
you share publicly. Public names must be at least four characters in length and contain only lower-case
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/reset_password.mako
--- a/templates/user/reset_password.mako
+++ b/templates/user/reset_password.mako
@@ -11,7 +11,6 @@
<div class="form-row"><label>Email:</label><input type="text" name="email" value="" size="40"/>
- <input type="hidden" name="webapp" value="${webapp}" size="40"/></div><div style="clear: both"></div><div class="form-row">
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/user/username.mako
--- a/templates/user/username.mako
+++ b/templates/user/username.mako
@@ -5,7 +5,6 @@
<h2>Manage Public Name</h2><div class="toolForm"><form name="username" id="username" action="${h.url_for( controller='user', action='edit_username', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
- <input type="hidden" name="webapp" value="${webapp}" size="40"/><div class="toolFormTitle">Login Information</div><div class="form-row"><label>Public name:</label>
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/webapps/galaxy/admin/index.mako
--- a/templates/webapps/galaxy/admin/index.mako
+++ b/templates/webapps/galaxy/admin/index.mako
@@ -42,11 +42,11 @@
<div class="toolSectionTitle">Security</div><div class="toolSectionBody"><div class="toolSectionBg">
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='users', webapp=webapp )}" target="galaxy_main">Manage users</a></div>
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='groups', webapp=webapp )}" target="galaxy_main">Manage groups</a></div>
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='roles', webapp=webapp )}" target="galaxy_main">Manage roles</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='users' )}" target="galaxy_main">Manage users</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='groups' )}" target="galaxy_main">Manage groups</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='roles' )}" target="galaxy_main">Manage roles</a></div>
%if trans.app.config.allow_user_impersonation:
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='impersonate', webapp=webapp )}" target="galaxy_main">Impersonate a user</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='impersonate' )}" target="galaxy_main">Impersonate a user</a></div>
%endif
</div></div>
@@ -54,7 +54,7 @@
<div class="toolSectionTitle">Data</div><div class="toolSectionBody"><div class="toolSectionBg">
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='quotas', webapp=webapp )}" target="galaxy_main">Manage quotas</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='quotas' )}" target="galaxy_main">Manage quotas</a></div><div class="toolTitle"><a href="${h.url_for( controller='library_admin', action='browse_libraries' )}" target="galaxy_main">Manage data libraries</a></div>
%if trans.app.config.enable_beta_job_managers:
<div class="toolTitle"><a href="${h.url_for( controller='data_admin', action='manage_data' )}" target="galaxy_main">Manage local data</a></div>
@@ -111,6 +111,6 @@
</%def><%def name="center_panel()">
- <% center_url = h.url_for( controller='admin', action='center', webapp='galaxy', message=message, status=status ) %>
+ <% center_url = h.url_for( controller='admin', action='center', message=message, status=status ) %><iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"></iframe></%def>
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r 2ca0e99d6d6d940af0712e9c56b9f238fd59635c templates/webapps/galaxy/base_panels.mako
--- a/templates/webapps/galaxy/base_panels.mako
+++ b/templates/webapps/galaxy/base_panels.mako
@@ -136,7 +136,7 @@
# Menu for user who is not logged in.
menu_options = [ [ _("Login"), h.url_for( controller='/user', action='login' ), "galaxy_main" ] ]
if app.config.allow_user_creation:
- menu_options.append( [ _("Register"), h.url_for( controller='/user', action='create', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _("Register"), h.url_for( controller='/user', action='create', cntrller='user' ), "galaxy_main" ] )
extra_class = "loggedout-only"
visible = ( trans.user == None )
tab( "user", _("User"), None, visible=visible, menu_options=menu_options )
@@ -151,20 +151,20 @@
if app.config.remote_user_logout_href:
menu_options.append( [ _('Logout'), app.config.remote_user_logout_href, "_top" ] )
else:
- menu_options.append( [ _('Preferences'), h.url_for( controller='/user', action='index', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _('Preferences'), h.url_for( controller='/user', action='index', cntrller='user' ), "galaxy_main" ] )
menu_options.append( [ 'Custom Builds', h.url_for( controller='/user', action='dbkeys' ), "galaxy_main" ] )
if app.config.require_login:
- logout_url = h.url_for( controller='/root', action='index', m_c='user', m_a='logout', webapp='galaxy' )
+ logout_url = h.url_for( controller='/root', action='index', m_c='user', m_a='logout' )
else:
- logout_url = h.url_for( controller='/user', action='logout', webapp='galaxy' )
+ logout_url = h.url_for( controller='/user', action='logout' )
menu_options.append( [ 'Logout', logout_url, "_top" ] )
menu_options.append( None )
menu_options.append( [ _('Saved Histories'), h.url_for( controller='/history', action='list' ), "galaxy_main" ] )
menu_options.append( [ _('Saved Datasets'), h.url_for( controller='/dataset', action='list' ), "galaxy_main" ] )
menu_options.append( [ _('Saved Pages'), h.url_for( controller='/page', action='list' ), "_top" ] )
- menu_options.append( [ _('API Keys'), h.url_for( controller='/user', action='api_keys', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _('API Keys'), h.url_for( controller='/user', action='api_keys', cntrller='user' ), "galaxy_main" ] )
if app.config.use_remote_user:
- menu_options.append( [ _('Public Name'), h.url_for( controller='/user', action='edit_username', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _('Public Name'), h.url_for( controller='/user', action='edit_username', cntrller='user' ), "galaxy_main" ] )
extra_class = "loggedin-only"
visible = ( trans.user != None )
https://bitbucket.org/galaxy/galaxy-central/changeset/5b977db5fc50/
changeset: 5b977db5fc50
user: james_taylor
date: 2012-09-28 23:37:31
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 20 files
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/web/base/controller.py
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -1525,1191 +1525,8 @@
class ControllerUnavailable( Exception ):
pass
-class Admin( object ):
- # Override these
- user_list_grid = None
- role_list_grid = None
- group_list_grid = None
- quota_list_grid = None
- repository_list_grid = None
- tool_version_list_grid = None
- delete_operation = None
- undelete_operation = None
- purge_operation = None
-
- @web.expose
- @web.require_admin
- def index( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- message = kwd.get( 'message', '' )
- status = kwd.get( 'status', 'done' )
- if webapp == 'galaxy':
- installed_repositories = trans.sa_session.query( trans.model.ToolShedRepository ).first()
- installing_repository_ids = get_ids_of_tool_shed_repositories_being_installed( trans, as_string=True )
- return trans.fill_template( '/webapps/galaxy/admin/index.mako',
- webapp=webapp,
- installed_repositories=installed_repositories,
- installing_repository_ids=installing_repository_ids,
- message=message,
- status=status )
- else:
- return trans.fill_template( '/webapps/community/admin/index.mako',
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def center( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- message = kwd.get( 'message', '' )
- status = kwd.get( 'status', 'done' )
- if webapp == 'galaxy':
- return trans.fill_template( '/webapps/galaxy/admin/center.mako',
- message=message,
- status=status )
- else:
- return trans.fill_template( '/webapps/community/admin/center.mako',
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def reload_tool( self, trans, **kwd ):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- toolbox = self.app.toolbox
- if params.get( 'reload_tool_button', False ):
- tool_id = params.tool_id
- message, status = toolbox.reload_tool_by_id( tool_id )
- return trans.fill_template( '/admin/reload_tool.mako',
- toolbox=toolbox,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def tool_versions( self, trans, **kwd ):
- if 'message' not in kwd or not kwd[ 'message' ]:
- kwd[ 'message' ] = 'Tool ids for tools that are currently loaded into the tool panel are highlighted in green (click to display).'
- return self.tool_version_list_grid( trans, **kwd )
- # Galaxy Role Stuff
- @web.expose
- @web.require_admin
- def roles( self, trans, **kwargs ):
- if 'operation' in kwargs:
- operation = kwargs['operation'].lower()
- if operation == "roles":
- return self.role( trans, **kwargs )
- if operation == "create":
- return self.create_role( trans, **kwargs )
- if operation == "delete":
- return self.mark_role_deleted( trans, **kwargs )
- if operation == "undelete":
- return self.undelete_role( trans, **kwargs )
- if operation == "purge":
- return self.purge_role( trans, **kwargs )
- if operation == "manage users and groups":
- return self.manage_users_and_groups_for_role( trans, **kwargs )
- if operation == "rename":
- return self.rename_role( trans, **kwargs )
- # Render the list view
- return self.role_list_grid( trans, **kwargs )
- @web.expose
- @web.require_admin
- def create_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- name = util.restore_text( params.get( 'name', '' ) )
- description = util.restore_text( params.get( 'description', '' ) )
- in_users = util.listify( params.get( 'in_users', [] ) )
- out_users = util.listify( params.get( 'out_users', [] ) )
- in_groups = util.listify( params.get( 'in_groups', [] ) )
- out_groups = util.listify( params.get( 'out_groups', [] ) )
- create_group_for_role = params.get( 'create_group_for_role', '' )
- create_group_for_role_checked = CheckboxField.is_checked( create_group_for_role )
- ok = True
- if params.get( 'create_role_button', False ):
- if not name or not description:
- message = "Enter a valid name and a description."
- status = 'error'
- ok = False
- elif trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==name ).first():
- message = "Role names must be unique and a role with that name already exists, so choose another name."
- status = 'error'
- ok = False
- else:
- # Create the role
- role = trans.app.model.Role( name=name, description=description, type=trans.app.model.Role.types.ADMIN )
- trans.sa_session.add( role )
- # Create the UserRoleAssociations
- for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
- ura = trans.app.model.UserRoleAssociation( user, role )
- trans.sa_session.add( ura )
- # Create the GroupRoleAssociations
- for group in [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in in_groups ]:
- gra = trans.app.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- if create_group_for_role_checked:
- # Create the group
- group = trans.app.model.Group( name=name )
- trans.sa_session.add( group )
- # Associate the group with the role
- gra = trans.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- num_in_groups = len( in_groups ) + 1
- else:
- num_in_groups = len( in_groups )
- trans.sa_session.flush()
- message = "Role '%s' has been created with %d associated users and %d associated groups. " \
- % ( role.name, len( in_users ), num_in_groups )
- if create_group_for_role_checked:
- message += 'One of the groups associated with this role is the newly created group with the same name.'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- if ok:
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- out_users.append( ( user.id, user.email ) )
- for group in trans.sa_session.query( trans.app.model.Group ) \
- .filter( trans.app.model.Group.table.c.deleted==False ) \
- .order_by( trans.app.model.Group.table.c.name ):
- out_groups.append( ( group.id, group.name ) )
- return trans.fill_template( '/admin/dataset_security/role/role_create.mako',
- webapp=webapp,
- name=name,
- description=description,
- in_users=in_users,
- out_users=out_users,
- in_groups=in_groups,
- out_groups=out_groups,
- create_group_for_role_checked=create_group_for_role_checked,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def rename_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- id = params.get( 'id', None )
- if not id:
- message = "No role ids received for renaming"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- role = get_role( trans, id )
- if params.get( 'rename_role_button', False ):
- old_name = role.name
- new_name = util.restore_text( params.name )
- new_description = util.restore_text( params.description )
- if not new_name:
- message = 'Enter a valid name'
- status='error'
- else:
- existing_role = trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==new_name ).first()
- if existing_role and existing_role.id != role.id:
- message = 'A role with that name already exists'
- status = 'error'
- else:
- if not ( role.name == new_name and role.description == new_description ):
- role.name = new_name
- role.description = new_description
- trans.sa_session.add( role )
- trans.sa_session.flush()
- message = "Role '%s' has been renamed to '%s'" % ( old_name, new_name )
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- return trans.fill_template( '/admin/dataset_security/role/role_rename.mako',
- role=role,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def manage_users_and_groups_for_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- id = params.get( 'id', None )
- if not id:
- message = "No role ids received for managing users and groups"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- role = get_role( trans, id )
- if params.get( 'role_members_edit_button', False ):
- in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
- for ura in role.users:
- user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
- if user not in in_users:
- # Delete DefaultUserPermissions for previously associated users that have been removed from the role
- for dup in user.default_permissions:
- if role == dup.role:
- trans.sa_session.delete( dup )
- # Delete DefaultHistoryPermissions for previously associated users that have been removed from the role
- for history in user.histories:
- for dhp in history.default_permissions:
- if role == dhp.role:
- trans.sa_session.delete( dhp )
- trans.sa_session.flush()
- in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
- trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups )
- trans.sa_session.refresh( role )
- message = "Role '%s' has been updated with %d associated users and %d associated groups" % ( role.name, len( in_users ), len( in_groups ) )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status=status ) )
- in_users = []
- out_users = []
- in_groups = []
- out_groups = []
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- if user in [ x.user for x in role.users ]:
- in_users.append( ( user.id, user.email ) )
- else:
- out_users.append( ( user.id, user.email ) )
- for group in trans.sa_session.query( trans.app.model.Group ) \
- .filter( trans.app.model.Group.table.c.deleted==False ) \
- .order_by( trans.app.model.Group.table.c.name ):
- if group in [ x.group for x in role.groups ]:
- in_groups.append( ( group.id, group.name ) )
- else:
- out_groups.append( ( group.id, group.name ) )
- library_dataset_actions = {}
- if webapp == 'galaxy':
- # Build a list of tuples that are LibraryDatasetDatasetAssociationss followed by a list of actions
- # whose DatasetPermissions is associated with the Role
- # [ ( LibraryDatasetDatasetAssociation [ action, action ] ) ]
- for dp in role.dataset_actions:
- for ldda in trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
- .filter( trans.app.model.LibraryDatasetDatasetAssociation.dataset_id==dp.dataset_id ):
- root_found = False
- folder_path = ''
- folder = ldda.library_dataset.folder
- while not root_found:
- folder_path = '%s / %s' % ( folder.name, folder_path )
- if not folder.parent:
- root_found = True
- else:
- folder = folder.parent
- folder_path = '%s %s' % ( folder_path, ldda.name )
- library = trans.sa_session.query( trans.app.model.Library ) \
- .filter( trans.app.model.Library.table.c.root_folder_id == folder.id ) \
- .first()
- if library not in library_dataset_actions:
- library_dataset_actions[ library ] = {}
- try:
- library_dataset_actions[ library ][ folder_path ].append( dp.action )
- except:
- library_dataset_actions[ library ][ folder_path ] = [ dp.action ]
- return trans.fill_template( '/admin/dataset_security/role/role.mako',
- role=role,
- in_users=in_users,
- out_users=out_users,
- in_groups=in_groups,
- out_groups=out_groups,
- library_dataset_actions=library_dataset_actions,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def mark_role_deleted( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No role ids received for deleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- message = "Deleted %d roles: " % len( ids )
- for role_id in ids:
- role = get_role( trans, role_id )
- role.deleted = True
- trans.sa_session.add( role )
- trans.sa_session.flush()
- message += " %s " % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def undelete_role( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No role ids received for undeleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- count = 0
- undeleted_roles = ""
- for role_id in ids:
- role = get_role( trans, role_id )
- if not role.deleted:
- message = "Role '%s' has not been deleted, so it cannot be undeleted." % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- role.deleted = False
- trans.sa_session.add( role )
- trans.sa_session.flush()
- count += 1
- undeleted_roles += " %s" % role.name
- message = "Undeleted %d roles: %s" % ( count, undeleted_roles )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def purge_role( self, trans, **kwd ):
- # This method should only be called for a Role that has previously been deleted.
- # Purging a deleted Role deletes all of the following from the database:
- # - UserRoleAssociations where role_id == Role.id
- # - DefaultUserPermissions where role_id == Role.id
- # - DefaultHistoryPermissions where role_id == Role.id
- # - GroupRoleAssociations where role_id == Role.id
- # - DatasetPermissionss where role_id == Role.id
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No role ids received for purging"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- ids = util.listify( id )
- message = "Purged %d roles: " % len( ids )
- for role_id in ids:
- role = get_role( trans, role_id )
- if not role.deleted:
- message = "Role '%s' has not been deleted, so it cannot be purged." % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- # Delete UserRoleAssociations
- for ura in role.users:
- user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
- # Delete DefaultUserPermissions for associated users
- for dup in user.default_permissions:
- if role == dup.role:
- trans.sa_session.delete( dup )
- # Delete DefaultHistoryPermissions for associated users
- for history in user.histories:
- for dhp in history.default_permissions:
- if role == dhp.role:
- trans.sa_session.delete( dhp )
- trans.sa_session.delete( ura )
- # Delete GroupRoleAssociations
- for gra in role.groups:
- trans.sa_session.delete( gra )
- # Delete DatasetPermissionss
- for dp in role.dataset_actions:
- trans.sa_session.delete( dp )
- trans.sa_session.flush()
- message += " %s " % role.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='roles',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
-
- # Galaxy Group Stuff
- @web.expose
- @web.require_admin
- def groups( self, trans, **kwargs ):
- if 'operation' in kwargs:
- operation = kwargs['operation'].lower()
- if operation == "groups":
- return self.group( trans, **kwargs )
- if operation == "create":
- return self.create_group( trans, **kwargs )
- if operation == "delete":
- return self.mark_group_deleted( trans, **kwargs )
- if operation == "undelete":
- return self.undelete_group( trans, **kwargs )
- if operation == "purge":
- return self.purge_group( trans, **kwargs )
- if operation == "manage users and roles":
- return self.manage_users_and_roles_for_group( trans, **kwargs )
- if operation == "rename":
- return self.rename_group( trans, **kwargs )
- # Render the list view
- return self.group_list_grid( trans, **kwargs )
- @web.expose
- @web.require_admin
- def rename_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- id = params.get( 'id', None )
- if not id:
- message = "No group ids received for renaming"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=message,
- status='error' ) )
- group = get_group( trans, id )
- if params.get( 'rename_group_button', False ):
- old_name = group.name
- new_name = util.restore_text( params.name )
- if not new_name:
- message = 'Enter a valid name'
- status = 'error'
- else:
- existing_group = trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==new_name ).first()
- if existing_group and existing_group.id != group.id:
- message = 'A group with that name already exists'
- status = 'error'
- else:
- if group.name != new_name:
- group.name = new_name
- trans.sa_session.add( group )
- trans.sa_session.flush()
- message = "Group '%s' has been renamed to '%s'" % ( old_name, new_name )
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- return trans.fill_template( '/admin/dataset_security/group/group_rename.mako',
- group=group,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def manage_users_and_roles_for_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- group = get_group( trans, params.id )
- if params.get( 'group_roles_users_edit_button', False ):
- in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.in_roles ) ]
- in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
- trans.app.security_agent.set_entity_group_associations( groups=[ group ], roles=in_roles, users=in_users )
- trans.sa_session.refresh( group )
- message += "Group '%s' has been updated with %d associated roles and %d associated users" % ( group.name, len( in_roles ), len( in_users ) )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status=status ) )
- in_roles = []
- out_roles = []
- in_users = []
- out_users = []
- for role in trans.sa_session.query(trans.app.model.Role ) \
- .filter( trans.app.model.Role.table.c.deleted==False ) \
- .order_by( trans.app.model.Role.table.c.name ):
- if role in [ x.role for x in group.roles ]:
- in_roles.append( ( role.id, role.name ) )
- else:
- out_roles.append( ( role.id, role.name ) )
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- if user in [ x.user for x in group.users ]:
- in_users.append( ( user.id, user.email ) )
- else:
- out_users.append( ( user.id, user.email ) )
- message += 'Group %s is currently associated with %d roles and %d users' % ( group.name, len( in_roles ), len( in_users ) )
- return trans.fill_template( '/admin/dataset_security/group/group.mako',
- group=group,
- in_roles=in_roles,
- out_roles=out_roles,
- in_users=in_users,
- out_users=out_users,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def create_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- name = util.restore_text( params.get( 'name', '' ) )
- in_users = util.listify( params.get( 'in_users', [] ) )
- out_users = util.listify( params.get( 'out_users', [] ) )
- in_roles = util.listify( params.get( 'in_roles', [] ) )
- out_roles = util.listify( params.get( 'out_roles', [] ) )
- create_role_for_group = params.get( 'create_role_for_group', '' )
- create_role_for_group_checked = CheckboxField.is_checked( create_role_for_group )
- ok = True
- if params.get( 'create_group_button', False ):
- if not name:
- message = "Enter a valid name."
- status = 'error'
- ok = False
- elif trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==name ).first():
- message = "Group names must be unique and a group with that name already exists, so choose another name."
- status = 'error'
- ok = False
- else:
- # Create the group
- group = trans.app.model.Group( name=name )
- trans.sa_session.add( group )
- trans.sa_session.flush()
- # Create the UserRoleAssociations
- for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
- uga = trans.app.model.UserGroupAssociation( user, group )
- trans.sa_session.add( uga )
- # Create the GroupRoleAssociations
- for role in [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in in_roles ]:
- gra = trans.app.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- if create_role_for_group_checked:
- # Create the role
- role = trans.app.model.Role( name=name, description='Role for group %s' % name )
- trans.sa_session.add( role )
- # Associate the role with the group
- gra = trans.model.GroupRoleAssociation( group, role )
- trans.sa_session.add( gra )
- num_in_roles = len( in_roles ) + 1
- else:
- num_in_roles = len( in_roles )
- trans.sa_session.flush()
- message = "Group '%s' has been created with %d associated users and %d associated roles. " \
- % ( group.name, len( in_users ), num_in_roles )
- if create_role_for_group_checked:
- message += 'One of the roles associated with this group is the newly created role with the same name.'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
-
-
- if ok:
- for user in trans.sa_session.query( trans.app.model.User ) \
- .filter( trans.app.model.User.table.c.deleted==False ) \
- .order_by( trans.app.model.User.table.c.email ):
- out_users.append( ( user.id, user.email ) )
- for role in trans.sa_session.query( trans.app.model.Role ) \
- .filter( trans.app.model.Role.table.c.deleted==False ) \
- .order_by( trans.app.model.Role.table.c.name ):
- out_roles.append( ( role.id, role.name ) )
- return trans.fill_template( '/admin/dataset_security/group/group_create.mako',
- webapp=webapp,
- name=name,
- in_users=in_users,
- out_users=out_users,
- in_roles=in_roles,
- out_roles=out_roles,
- create_role_for_group_checked=create_role_for_group_checked,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def mark_group_deleted( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = params.get( 'id', None )
- if not id:
- message = "No group ids received for marking deleted"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- message = "Deleted %d groups: " % len( ids )
- for group_id in ids:
- group = get_group( trans, group_id )
- group.deleted = True
- trans.sa_session.add( group )
- trans.sa_session.flush()
- message += " %s " % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def undelete_group( self, trans, **kwd ):
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No group ids received for undeleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- count = 0
- undeleted_groups = ""
- for group_id in ids:
- group = get_group( trans, group_id )
- if not group.deleted:
- message = "Group '%s' has not been deleted, so it cannot be undeleted." % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- group.deleted = False
- trans.sa_session.add( group )
- trans.sa_session.flush()
- count += 1
- undeleted_groups += " %s" % group.name
- message = "Undeleted %d groups: %s" % ( count, undeleted_groups )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def purge_group( self, trans, **kwd ):
- # This method should only be called for a Group that has previously been deleted.
- # Purging a deleted Group simply deletes all UserGroupAssociations and GroupRoleAssociations.
- params = util.Params( kwd )
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No group ids received for purging"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- ids = util.listify( id )
- message = "Purged %d groups: " % len( ids )
- for group_id in ids:
- group = get_group( trans, group_id )
- if not group.deleted:
- # We should never reach here, but just in case there is a bug somewhere...
- message = "Group '%s' has not been deleted, so it cannot be purged." % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- # Delete UserGroupAssociations
- for uga in group.users:
- trans.sa_session.delete( uga )
- # Delete GroupRoleAssociations
- for gra in group.roles:
- trans.sa_session.delete( gra )
- trans.sa_session.flush()
- message += " %s " % group.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='groups',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
-
- # Galaxy User Stuff
- @web.expose
- @web.require_admin
- def create_new_user( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- return trans.response.send_redirect( web.url_for( controller='user',
- action='create',
- cntrller='admin',
- webapp=webapp ) )
- @web.expose
- @web.require_admin
- def reset_user_password( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- user_id = kwd.get( 'id', None )
- if not user_id:
- message = "No users received for resetting passwords."
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=message,
- status='error' ) )
- user_ids = util.listify( user_id )
- if 'reset_user_password_button' in kwd:
- message = ''
- status = ''
- for user_id in user_ids:
- user = get_user( trans, user_id )
- password = kwd.get( 'password', None )
- confirm = kwd.get( 'confirm' , None )
- if len( password ) < 6:
- message = "Use a password of at least 6 characters."
- status = 'error'
- break
- elif password != confirm:
- message = "Passwords do not match."
- status = 'error'
- break
- else:
- user.set_password_cleartext( password )
- trans.sa_session.add( user )
- trans.sa_session.flush()
- if not message and not status:
- message = "Passwords reset for %d %s." % ( len( user_ids ), inflector.cond_plural( len( user_ids ), 'user' ) )
- status = 'done'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status=status ) )
- users = [ get_user( trans, user_id ) for user_id in user_ids ]
- if len( user_ids ) > 1:
- user_id = ','.join( user_ids )
- return trans.fill_template( '/admin/user/reset_password.mako',
- id=user_id,
- users=users,
- password='',
- confirm='',
- webapp=webapp )
- @web.expose
- @web.require_admin
- def mark_user_deleted( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No user ids received for deleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- message = "Deleted %d users: " % len( ids )
- for user_id in ids:
- user = get_user( trans, user_id )
- user.deleted = True
- trans.sa_session.add( user )
- trans.sa_session.flush()
- message += " %s " % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def undelete_user( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No user ids received for undeleting"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=message,
- status='error' ) )
- ids = util.listify( id )
- count = 0
- undeleted_users = ""
- for user_id in ids:
- user = get_user( trans, user_id )
- if not user.deleted:
- message = "User '%s' has not been deleted, so it cannot be undeleted." % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- user.deleted = False
- trans.sa_session.add( user )
- trans.sa_session.flush()
- count += 1
- undeleted_users += " %s" % user.email
- message = "Undeleted %d users: %s" % ( count, undeleted_users )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def purge_user( self, trans, **kwd ):
- # This method should only be called for a User that has previously been deleted.
- # We keep the User in the database ( marked as purged ), and stuff associated
- # with the user's private role in case we want the ability to unpurge the user
- # some time in the future.
- # Purging a deleted User deletes all of the following:
- # - History where user_id = User.id
- # - HistoryDatasetAssociation where history_id = History.id
- # - Dataset where HistoryDatasetAssociation.dataset_id = Dataset.id
- # - UserGroupAssociation where user_id == User.id
- # - UserRoleAssociation where user_id == User.id EXCEPT FOR THE PRIVATE ROLE
- # - UserAddress where user_id == User.id
- # Purging Histories and Datasets must be handled via the cleanup_datasets.py script
- webapp = get_webapp( trans, **kwd )
- id = kwd.get( 'id', None )
- if not id:
- message = "No user ids received for purging"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- ids = util.listify( id )
- message = "Purged %d users: " % len( ids )
- for user_id in ids:
- user = get_user( trans, user_id )
- if not user.deleted:
- # We should never reach here, but just in case there is a bug somewhere...
- message = "User '%s' has not been deleted, so it cannot be purged." % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- private_role = trans.app.security_agent.get_private_user_role( user )
- # Delete History
- for h in user.active_histories:
- trans.sa_session.refresh( h )
- for hda in h.active_datasets:
- # Delete HistoryDatasetAssociation
- d = trans.sa_session.query( trans.app.model.Dataset ).get( hda.dataset_id )
- # Delete Dataset
- if not d.deleted:
- d.deleted = True
- trans.sa_session.add( d )
- hda.deleted = True
- trans.sa_session.add( hda )
- h.deleted = True
- trans.sa_session.add( h )
- # Delete UserGroupAssociations
- for uga in user.groups:
- trans.sa_session.delete( uga )
- # Delete UserRoleAssociations EXCEPT FOR THE PRIVATE ROLE
- for ura in user.roles:
- if ura.role_id != private_role.id:
- trans.sa_session.delete( ura )
- # Delete UserAddresses
- for address in user.addresses:
- trans.sa_session.delete( address )
- # Purge the user
- user.purged = True
- trans.sa_session.add( user )
- trans.sa_session.flush()
- message += "%s " % user.email
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- @web.expose
- @web.require_admin
- def users( self, trans, **kwd ):
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- if operation == "roles":
- return self.user( trans, **kwd )
- elif operation == "reset password":
- return self.reset_user_password( trans, **kwd )
- elif operation == "delete":
- return self.mark_user_deleted( trans, **kwd )
- elif operation == "undelete":
- return self.undelete_user( trans, **kwd )
- elif operation == "purge":
- return self.purge_user( trans, **kwd )
- elif operation == "create":
- return self.create_new_user( trans, **kwd )
- elif operation == "information":
- user_id = kwd.get( 'id', None )
- if not user_id:
- kwd[ 'message' ] = util.sanitize_text( "Invalid user id (%s) received" % str( user_id ) )
- kwd[ 'status' ] = 'error'
- else:
- return trans.response.send_redirect( web.url_for( controller='user',
- action='manage_user_info',
- cntrller='admin',
- **kwd ) )
- elif operation == "manage roles and groups":
- return self.manage_roles_and_groups_for_user( trans, **kwd )
- if trans.app.config.allow_user_deletion:
- if self.delete_operation not in self.user_list_grid.operations:
- self.user_list_grid.operations.append( self.delete_operation )
- if self.undelete_operation not in self.user_list_grid.operations:
- self.user_list_grid.operations.append( self.undelete_operation )
- if self.purge_operation not in self.user_list_grid.operations:
- self.user_list_grid.operations.append( self.purge_operation )
- # Render the list view
- return self.user_list_grid( trans, **kwd )
- @web.expose
- @web.require_admin
- def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
- """Return autocomplete data for user emails"""
- ac_data = ""
- for user in trans.sa_session.query( User ).filter_by( deleted=False ).filter( func.lower( User.email ).like( q.lower() + "%" ) ):
- ac_data = ac_data + user.email + "\n"
- return ac_data
- @web.expose
- @web.require_admin
- def manage_roles_and_groups_for_user( self, trans, **kwd ):
- webapp = get_webapp( trans, **kwd )
- user_id = kwd.get( 'id', None )
- message = ''
- status = ''
- if not user_id:
- message += "Invalid user id (%s) received" % str( user_id )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='error' ) )
- user = get_user( trans, user_id )
- private_role = trans.app.security_agent.get_private_user_role( user )
- if kwd.get( 'user_roles_groups_edit_button', False ):
- # Make sure the user is not dis-associating himself from his private role
- out_roles = kwd.get( 'out_roles', [] )
- if out_roles:
- out_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( out_roles ) ]
- if private_role in out_roles:
- message += "You cannot eliminate a user's private role association. "
- status = 'error'
- in_roles = kwd.get( 'in_roles', [] )
- if in_roles:
- in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( in_roles ) ]
- out_groups = kwd.get( 'out_groups', [] )
- if out_groups:
- out_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( out_groups ) ]
- in_groups = kwd.get( 'in_groups', [] )
- if in_groups:
- in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( in_groups ) ]
- if in_roles:
- trans.app.security_agent.set_entity_user_associations( users=[ user ], roles=in_roles, groups=in_groups )
- trans.sa_session.refresh( user )
- message += "User '%s' has been updated with %d associated roles and %d associated groups (private roles are not displayed)" % \
- ( user.email, len( in_roles ), len( in_groups ) )
- trans.response.send_redirect( web.url_for( controller='admin',
- action='users',
- webapp=webapp,
- message=util.sanitize_text( message ),
- status='done' ) )
- in_roles = []
- out_roles = []
- in_groups = []
- out_groups = []
- for role in trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.deleted==False ) \
- .order_by( trans.app.model.Role.table.c.name ):
- if role in [ x.role for x in user.roles ]:
- in_roles.append( ( role.id, role.name ) )
- elif role.type != trans.app.model.Role.types.PRIVATE:
- # There is a 1 to 1 mapping between a user and a PRIVATE role, so private roles should
- # not be listed in the roles form fields, except for the currently selected user's private
- # role, which should always be in in_roles. The check above is added as an additional
- # precaution, since for a period of time we were including private roles in the form fields.
- out_roles.append( ( role.id, role.name ) )
- for group in trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.deleted==False ) \
- .order_by( trans.app.model.Group.table.c.name ):
- if group in [ x.group for x in user.groups ]:
- in_groups.append( ( group.id, group.name ) )
- else:
- out_groups.append( ( group.id, group.name ) )
- message += "User '%s' is currently associated with %d roles and is a member of %d groups" % \
- ( user.email, len( in_roles ), len( in_groups ) )
- if not status:
- status = 'done'
- return trans.fill_template( '/admin/user/user.mako',
- user=user,
- in_roles=in_roles,
- out_roles=out_roles,
- in_groups=in_groups,
- out_groups=out_groups,
- webapp=webapp,
- message=message,
- status=status )
- @web.expose
- @web.require_admin
- def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ):
- if self.app.memdump is None:
- return trans.show_error_message( "Memdump is not enabled (set <code>use_memdump = True</code> in universe_wsgi.ini)" )
- heap = self.app.memdump.get()
- p = util.Params( kwd )
- msg = None
- if p.dump:
- heap = self.app.memdump.get( update = True )
- msg = "Heap dump complete"
- elif p.setref:
- self.app.memdump.setref()
- msg = "Reference point set (dump to see delta from this point)"
- ids = ids.split( ',' )
- sorts = sorts.split( ',' )
- if new_id is not None:
- ids.append( new_id )
- sorts.append( 'None' )
- elif new_sort is not None:
- sorts[-1] = new_sort
- breadcrumb = "<a href='%s' class='breadcrumb'>heap</a>" % web.url_for()
- # new lists so we can assemble breadcrumb links
- new_ids = []
- new_sorts = []
- for id, sort in zip( ids, sorts ):
- new_ids.append( id )
- if id != 'None':
- breadcrumb += "<a href='%s' class='breadcrumb'>[%s]</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), id )
- heap = heap[int(id)]
- new_sorts.append( sort )
- if sort != 'None':
- breadcrumb += "<a href='%s' class='breadcrumb'>.by('%s')</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), sort )
- heap = heap.by( sort )
- ids = ','.join( new_ids )
- sorts = ','.join( new_sorts )
- if p.theone:
- breadcrumb += ".theone"
- heap = heap.theone
- return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg )
-
- @web.expose
- @web.require_admin
- def jobs( self, trans, stop = [], stop_msg = None, cutoff = 180, job_lock = None, ajl_submit = None, **kwd ):
- deleted = []
- msg = None
- status = None
- if self.app.config.job_manager != self.app.config.server_name:
- return trans.show_error_message( 'This Galaxy instance (%s) is not the job manager (%s). If using multiple servers, please directly access the job manager instance to manage jobs.' % (self.app.config.server_name, self.app.config.job_manager) )
- job_ids = util.listify( stop )
- if job_ids and stop_msg in [ None, '' ]:
- msg = 'Please enter an error message to display to the user describing why the job was terminated'
- status = 'error'
- elif job_ids:
- if stop_msg[-1] not in string.punctuation:
- stop_msg += '.'
- for job_id in job_ids:
- trans.app.job_manager.job_stop_queue.put( job_id, error_msg="This job was stopped by an administrator: %s For more information or help" % stop_msg )
- deleted.append( str( job_id ) )
- if deleted:
- msg = 'Queued job'
- if len( deleted ) > 1:
- msg += 's'
- msg += ' for deletion: '
- msg += ', '.join( deleted )
- status = 'done'
- if ajl_submit:
- if job_lock == 'on':
- trans.app.job_manager.job_queue.job_lock = True
- else:
- trans.app.job_manager.job_queue.job_lock = False
- cutoff_time = datetime.utcnow() - timedelta( seconds=int( cutoff ) )
- jobs = trans.sa_session.query( trans.app.model.Job ) \
- .filter( and_( trans.app.model.Job.table.c.update_time < cutoff_time,
- or_( trans.app.model.Job.state == trans.app.model.Job.states.NEW,
- trans.app.model.Job.state == trans.app.model.Job.states.QUEUED,
- trans.app.model.Job.state == trans.app.model.Job.states.RUNNING,
- trans.app.model.Job.state == trans.app.model.Job.states.UPLOAD ) ) ) \
- .order_by( trans.app.model.Job.table.c.update_time.desc() )
- last_updated = {}
- for job in jobs:
- delta = datetime.utcnow() - job.update_time
- if delta > timedelta( minutes=60 ):
- last_updated[job.id] = '%s hours' % int( delta.seconds / 60 / 60 )
- else:
- last_updated[job.id] = '%s minutes' % int( delta.seconds / 60 )
- return trans.fill_template( '/admin/jobs.mako',
- jobs = jobs,
- last_updated = last_updated,
- cutoff = cutoff,
- msg = msg,
- status = status,
- job_lock = trans.app.job_manager.job_queue.job_lock )
-
## ---- Utility methods -------------------------------------------------------
-def get_ids_of_tool_shed_repositories_being_installed( trans, as_string=False ):
- installing_repository_ids = []
- new_status = trans.model.ToolShedRepository.installation_status.NEW
- cloning_status = trans.model.ToolShedRepository.installation_status.CLONING
- setting_tool_versions_status = trans.model.ToolShedRepository.installation_status.SETTING_TOOL_VERSIONS
- installing_dependencies_status = trans.model.ToolShedRepository.installation_status.INSTALLING_TOOL_DEPENDENCIES
- loading_datatypes_status = trans.model.ToolShedRepository.installation_status.LOADING_PROPRIETARY_DATATYPES
- for tool_shed_repository in trans.sa_session.query( trans.model.ToolShedRepository ) \
- .filter( or_( trans.model.ToolShedRepository.status == new_status,
- trans.model.ToolShedRepository.status == cloning_status,
- trans.model.ToolShedRepository.status == setting_tool_versions_status,
- trans.model.ToolShedRepository.status == installing_dependencies_status,
- trans.model.ToolShedRepository.status == loading_datatypes_status ) ):
- installing_repository_ids.append( trans.security.encode_id( tool_shed_repository.id ) )
- if as_string:
- return ','.join( installing_repository_ids )
- return installing_repository_ids
-
-def get_user( trans, user_id ):
- """Get a User from the database by id."""
- user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
- if not user:
- return trans.show_error_message( "User not found for id (%s)" % str( user_id ) )
- return user
-def get_user_by_username( trans, username ):
- """Get a user from the database by username"""
- # TODO: Add exception handling here.
- return trans.sa_session.query( trans.model.User ) \
- .filter( trans.model.User.table.c.username == username ) \
- .one()
-def get_role( trans, id ):
- """Get a Role from the database by id."""
- # Load user from database
- id = trans.security.decode_id( id )
- role = trans.sa_session.query( trans.model.Role ).get( id )
- if not role:
- return trans.show_error_message( "Role not found for id (%s)" % str( id ) )
- return role
-def get_group( trans, id ):
- """Get a Group from the database by id."""
- # Load user from database
- id = trans.security.decode_id( id )
- group = trans.sa_session.query( trans.model.Group ).get( id )
- if not group:
- return trans.show_error_message( "Group not found for id (%s)" % str( id ) )
- return group
-def get_quota( trans, id ):
- """Get a Quota from the database by id."""
- # Load user from database
- id = trans.security.decode_id( id )
- quota = trans.sa_session.query( trans.model.Quota ).get( id )
- return quota
-def get_webapp( trans, **kwd ):
- """Get the value of the webapp, can be one of 'community', 'galaxy', 'reports', 'demo_sequencer'."""
- if 'webapp' in kwd:
- return kwd[ 'webapp' ]
- if 'webapp' in trans.environ:
- return trans.environ[ 'webapp' ]
- # The default is galaxy.
- return 'galaxy'
def sort_by_attr( seq, attr ):
"""
Sort the sequence of objects by object's attribute
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/web/base/controllers/admin.py
--- /dev/null
+++ b/lib/galaxy/web/base/controllers/admin.py
@@ -0,0 +1,1114 @@
+from datetime import date, datetime, timedelta
+
+from galaxy import config, tools, web, util
+from galaxy.model.orm import *
+
+class Admin( object ):
+ # Override these
+ user_list_grid = None
+ role_list_grid = None
+ group_list_grid = None
+ quota_list_grid = None
+ repository_list_grid = None
+ tool_version_list_grid = None
+ delete_operation = None
+ undelete_operation = None
+ purge_operation = None
+
+ @web.expose
+ @web.require_admin
+ def index( self, trans, **kwd ):
+ message = kwd.get( 'message', '' )
+ status = kwd.get( 'status', 'done' )
+ if trans.webapp.name == 'galaxy':
+ installed_repositories = trans.sa_session.query( trans.model.ToolShedRepository ).first()
+ installing_repository_ids = get_ids_of_tool_shed_repositories_being_installed( trans, as_string=True )
+ return trans.fill_template( '/webapps/galaxy/admin/index.mako',
+ installed_repositories=installed_repositories,
+ installing_repository_ids=installing_repository_ids,
+ message=message,
+ status=status )
+ else:
+ return trans.fill_template( '/webapps/community/admin/index.mako',
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def center( self, trans, **kwd ):
+ message = kwd.get( 'message', '' )
+ status = kwd.get( 'status', 'done' )
+ if trans.webapp.name == 'galaxy':
+ return trans.fill_template( '/webapps/galaxy/admin/center.mako',
+ message=message,
+ status=status )
+ else:
+ return trans.fill_template( '/webapps/community/admin/center.mako',
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def reload_tool( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ toolbox = self.app.toolbox
+ if params.get( 'reload_tool_button', False ):
+ tool_id = params.tool_id
+ message, status = toolbox.reload_tool_by_id( tool_id )
+ return trans.fill_template( '/admin/reload_tool.mako',
+ toolbox=toolbox,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def tool_versions( self, trans, **kwd ):
+ if 'message' not in kwd or not kwd[ 'message' ]:
+ kwd[ 'message' ] = 'Tool ids for tools that are currently loaded into the tool panel are highlighted in green (click to display).'
+ return self.tool_version_list_grid( trans, **kwd )
+ # Galaxy Role Stuff
+ @web.expose
+ @web.require_admin
+ def roles( self, trans, **kwargs ):
+ if 'operation' in kwargs:
+ operation = kwargs['operation'].lower()
+ if operation == "roles":
+ return self.role( trans, **kwargs )
+ if operation == "create":
+ return self.create_role( trans, **kwargs )
+ if operation == "delete":
+ return self.mark_role_deleted( trans, **kwargs )
+ if operation == "undelete":
+ return self.undelete_role( trans, **kwargs )
+ if operation == "purge":
+ return self.purge_role( trans, **kwargs )
+ if operation == "manage users and groups":
+ return self.manage_users_and_groups_for_role( trans, **kwargs )
+ if operation == "rename":
+ return self.rename_role( trans, **kwargs )
+ # Render the list view
+ return self.role_list_grid( trans, **kwargs )
+ @web.expose
+ @web.require_admin
+ def create_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ name = util.restore_text( params.get( 'name', '' ) )
+ description = util.restore_text( params.get( 'description', '' ) )
+ in_users = util.listify( params.get( 'in_users', [] ) )
+ out_users = util.listify( params.get( 'out_users', [] ) )
+ in_groups = util.listify( params.get( 'in_groups', [] ) )
+ out_groups = util.listify( params.get( 'out_groups', [] ) )
+ create_group_for_role = params.get( 'create_group_for_role', '' )
+ create_group_for_role_checked = CheckboxField.is_checked( create_group_for_role )
+ ok = True
+ if params.get( 'create_role_button', False ):
+ if not name or not description:
+ message = "Enter a valid name and a description."
+ status = 'error'
+ ok = False
+ elif trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==name ).first():
+ message = "Role names must be unique and a role with that name already exists, so choose another name."
+ status = 'error'
+ ok = False
+ else:
+ # Create the role
+ role = trans.app.model.Role( name=name, description=description, type=trans.app.model.Role.types.ADMIN )
+ trans.sa_session.add( role )
+ # Create the UserRoleAssociations
+ for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
+ ura = trans.app.model.UserRoleAssociation( user, role )
+ trans.sa_session.add( ura )
+ # Create the GroupRoleAssociations
+ for group in [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in in_groups ]:
+ gra = trans.app.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ if create_group_for_role_checked:
+ # Create the group
+ group = trans.app.model.Group( name=name )
+ trans.sa_session.add( group )
+ # Associate the group with the role
+ gra = trans.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ num_in_groups = len( in_groups ) + 1
+ else:
+ num_in_groups = len( in_groups )
+ trans.sa_session.flush()
+ message = "Role '%s' has been created with %d associated users and %d associated groups. " \
+ % ( role.name, len( in_users ), num_in_groups )
+ if create_group_for_role_checked:
+ message += 'One of the groups associated with this role is the newly created group with the same name.'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ if ok:
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ out_users.append( ( user.id, user.email ) )
+ for group in trans.sa_session.query( trans.app.model.Group ) \
+ .filter( trans.app.model.Group.table.c.deleted==False ) \
+ .order_by( trans.app.model.Group.table.c.name ):
+ out_groups.append( ( group.id, group.name ) )
+ return trans.fill_template( '/admin/dataset_security/role/role_create.mako',
+ name=name,
+ description=description,
+ in_users=in_users,
+ out_users=out_users,
+ in_groups=in_groups,
+ out_groups=out_groups,
+ create_group_for_role_checked=create_group_for_role_checked,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def rename_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No role ids received for renaming"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ role = get_role( trans, id )
+ if params.get( 'rename_role_button', False ):
+ old_name = role.name
+ new_name = util.restore_text( params.name )
+ new_description = util.restore_text( params.description )
+ if not new_name:
+ message = 'Enter a valid name'
+ status='error'
+ else:
+ existing_role = trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.name==new_name ).first()
+ if existing_role and existing_role.id != role.id:
+ message = 'A role with that name already exists'
+ status = 'error'
+ else:
+ if not ( role.name == new_name and role.description == new_description ):
+ role.name = new_name
+ role.description = new_description
+ trans.sa_session.add( role )
+ trans.sa_session.flush()
+ message = "Role '%s' has been renamed to '%s'" % ( old_name, new_name )
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ return trans.fill_template( '/admin/dataset_security/role/role_rename.mako',
+ role=role,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def manage_users_and_groups_for_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No role ids received for managing users and groups"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ role = get_role( trans, id )
+ if params.get( 'role_members_edit_button', False ):
+ in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
+ for ura in role.users:
+ user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
+ if user not in in_users:
+ # Delete DefaultUserPermissions for previously associated users that have been removed from the role
+ for dup in user.default_permissions:
+ if role == dup.role:
+ trans.sa_session.delete( dup )
+ # Delete DefaultHistoryPermissions for previously associated users that have been removed from the role
+ for history in user.histories:
+ for dhp in history.default_permissions:
+ if role == dhp.role:
+ trans.sa_session.delete( dhp )
+ trans.sa_session.flush()
+ in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
+ trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups )
+ trans.sa_session.refresh( role )
+ message = "Role '%s' has been updated with %d associated users and %d associated groups" % ( role.name, len( in_users ), len( in_groups ) )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status=status ) )
+ in_users = []
+ out_users = []
+ in_groups = []
+ out_groups = []
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ if user in [ x.user for x in role.users ]:
+ in_users.append( ( user.id, user.email ) )
+ else:
+ out_users.append( ( user.id, user.email ) )
+ for group in trans.sa_session.query( trans.app.model.Group ) \
+ .filter( trans.app.model.Group.table.c.deleted==False ) \
+ .order_by( trans.app.model.Group.table.c.name ):
+ if group in [ x.group for x in role.groups ]:
+ in_groups.append( ( group.id, group.name ) )
+ else:
+ out_groups.append( ( group.id, group.name ) )
+ library_dataset_actions = {}
+ if trans.webapp.name == 'galaxy':
+ # Build a list of tuples that are LibraryDatasetDatasetAssociationss followed by a list of actions
+ # whose DatasetPermissions is associated with the Role
+ # [ ( LibraryDatasetDatasetAssociation [ action, action ] ) ]
+ for dp in role.dataset_actions:
+ for ldda in trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
+ .filter( trans.app.model.LibraryDatasetDatasetAssociation.dataset_id==dp.dataset_id ):
+ root_found = False
+ folder_path = ''
+ folder = ldda.library_dataset.folder
+ while not root_found:
+ folder_path = '%s / %s' % ( folder.name, folder_path )
+ if not folder.parent:
+ root_found = True
+ else:
+ folder = folder.parent
+ folder_path = '%s %s' % ( folder_path, ldda.name )
+ library = trans.sa_session.query( trans.app.model.Library ) \
+ .filter( trans.app.model.Library.table.c.root_folder_id == folder.id ) \
+ .first()
+ if library not in library_dataset_actions:
+ library_dataset_actions[ library ] = {}
+ try:
+ library_dataset_actions[ library ][ folder_path ].append( dp.action )
+ except:
+ library_dataset_actions[ library ][ folder_path ] = [ dp.action ]
+ return trans.fill_template( '/admin/dataset_security/role/role.mako',
+ role=role,
+ in_users=in_users,
+ out_users=out_users,
+ in_groups=in_groups,
+ out_groups=out_groups,
+ library_dataset_actions=library_dataset_actions,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def mark_role_deleted( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No role ids received for deleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Deleted %d roles: " % len( ids )
+ for role_id in ids:
+ role = get_role( trans, role_id )
+ role.deleted = True
+ trans.sa_session.add( role )
+ trans.sa_session.flush()
+ message += " %s " % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def undelete_role( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No role ids received for undeleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ count = 0
+ undeleted_roles = ""
+ for role_id in ids:
+ role = get_role( trans, role_id )
+ if not role.deleted:
+ message = "Role '%s' has not been deleted, so it cannot be undeleted." % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ role.deleted = False
+ trans.sa_session.add( role )
+ trans.sa_session.flush()
+ count += 1
+ undeleted_roles += " %s" % role.name
+ message = "Undeleted %d roles: %s" % ( count, undeleted_roles )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def purge_role( self, trans, **kwd ):
+ # This method should only be called for a Role that has previously been deleted.
+ # Purging a deleted Role deletes all of the following from the database:
+ # - UserRoleAssociations where role_id == Role.id
+ # - DefaultUserPermissions where role_id == Role.id
+ # - DefaultHistoryPermissions where role_id == Role.id
+ # - GroupRoleAssociations where role_id == Role.id
+ # - DatasetPermissionss where role_id == Role.id
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No role ids received for purging"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d roles: " % len( ids )
+ for role_id in ids:
+ role = get_role( trans, role_id )
+ if not role.deleted:
+ message = "Role '%s' has not been deleted, so it cannot be purged." % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ # Delete UserRoleAssociations
+ for ura in role.users:
+ user = trans.sa_session.query( trans.app.model.User ).get( ura.user_id )
+ # Delete DefaultUserPermissions for associated users
+ for dup in user.default_permissions:
+ if role == dup.role:
+ trans.sa_session.delete( dup )
+ # Delete DefaultHistoryPermissions for associated users
+ for history in user.histories:
+ for dhp in history.default_permissions:
+ if role == dhp.role:
+ trans.sa_session.delete( dhp )
+ trans.sa_session.delete( ura )
+ # Delete GroupRoleAssociations
+ for gra in role.groups:
+ trans.sa_session.delete( gra )
+ # Delete DatasetPermissionss
+ for dp in role.dataset_actions:
+ trans.sa_session.delete( dp )
+ trans.sa_session.flush()
+ message += " %s " % role.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='roles',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+
+ # Galaxy Group Stuff
+ @web.expose
+ @web.require_admin
+ def groups( self, trans, **kwargs ):
+ if 'operation' in kwargs:
+ operation = kwargs['operation'].lower()
+ if operation == "groups":
+ return self.group( trans, **kwargs )
+ if operation == "create":
+ return self.create_group( trans, **kwargs )
+ if operation == "delete":
+ return self.mark_group_deleted( trans, **kwargs )
+ if operation == "undelete":
+ return self.undelete_group( trans, **kwargs )
+ if operation == "purge":
+ return self.purge_group( trans, **kwargs )
+ if operation == "manage users and roles":
+ return self.manage_users_and_roles_for_group( trans, **kwargs )
+ if operation == "rename":
+ return self.rename_group( trans, **kwargs )
+ # Render the list view
+ return self.group_list_grid( trans, **kwargs )
+ @web.expose
+ @web.require_admin
+ def rename_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No group ids received for renaming"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=message,
+ status='error' ) )
+ group = get_group( trans, id )
+ if params.get( 'rename_group_button', False ):
+ old_name = group.name
+ new_name = util.restore_text( params.name )
+ if not new_name:
+ message = 'Enter a valid name'
+ status = 'error'
+ else:
+ existing_group = trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==new_name ).first()
+ if existing_group and existing_group.id != group.id:
+ message = 'A group with that name already exists'
+ status = 'error'
+ else:
+ if group.name != new_name:
+ group.name = new_name
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ message = "Group '%s' has been renamed to '%s'" % ( old_name, new_name )
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ return trans.fill_template( '/admin/dataset_security/group/group_rename.mako',
+ group=group,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def manage_users_and_roles_for_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ group = get_group( trans, params.id )
+ if params.get( 'group_roles_users_edit_button', False ):
+ in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.in_roles ) ]
+ in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
+ trans.app.security_agent.set_entity_group_associations( groups=[ group ], roles=in_roles, users=in_users )
+ trans.sa_session.refresh( group )
+ message += "Group '%s' has been updated with %d associated roles and %d associated users" % ( group.name, len( in_roles ), len( in_users ) )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status=status ) )
+ in_roles = []
+ out_roles = []
+ in_users = []
+ out_users = []
+ for role in trans.sa_session.query(trans.app.model.Role ) \
+ .filter( trans.app.model.Role.table.c.deleted==False ) \
+ .order_by( trans.app.model.Role.table.c.name ):
+ if role in [ x.role for x in group.roles ]:
+ in_roles.append( ( role.id, role.name ) )
+ else:
+ out_roles.append( ( role.id, role.name ) )
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ if user in [ x.user for x in group.users ]:
+ in_users.append( ( user.id, user.email ) )
+ else:
+ out_users.append( ( user.id, user.email ) )
+ message += 'Group %s is currently associated with %d roles and %d users' % ( group.name, len( in_roles ), len( in_users ) )
+ return trans.fill_template( '/admin/dataset_security/group/group.mako',
+ group=group,
+ in_roles=in_roles,
+ out_roles=out_roles,
+ in_users=in_users,
+ out_users=out_users,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def create_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ name = util.restore_text( params.get( 'name', '' ) )
+ in_users = util.listify( params.get( 'in_users', [] ) )
+ out_users = util.listify( params.get( 'out_users', [] ) )
+ in_roles = util.listify( params.get( 'in_roles', [] ) )
+ out_roles = util.listify( params.get( 'out_roles', [] ) )
+ create_role_for_group = params.get( 'create_role_for_group', '' )
+ create_role_for_group_checked = CheckboxField.is_checked( create_role_for_group )
+ ok = True
+ if params.get( 'create_group_button', False ):
+ if not name:
+ message = "Enter a valid name."
+ status = 'error'
+ ok = False
+ elif trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.name==name ).first():
+ message = "Group names must be unique and a group with that name already exists, so choose another name."
+ status = 'error'
+ ok = False
+ else:
+ # Create the group
+ group = trans.app.model.Group( name=name )
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ # Create the UserRoleAssociations
+ for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]:
+ uga = trans.app.model.UserGroupAssociation( user, group )
+ trans.sa_session.add( uga )
+ # Create the GroupRoleAssociations
+ for role in [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in in_roles ]:
+ gra = trans.app.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ if create_role_for_group_checked:
+ # Create the role
+ role = trans.app.model.Role( name=name, description='Role for group %s' % name )
+ trans.sa_session.add( role )
+ # Associate the role with the group
+ gra = trans.model.GroupRoleAssociation( group, role )
+ trans.sa_session.add( gra )
+ num_in_roles = len( in_roles ) + 1
+ else:
+ num_in_roles = len( in_roles )
+ trans.sa_session.flush()
+ message = "Group '%s' has been created with %d associated users and %d associated roles. " \
+ % ( group.name, len( in_users ), num_in_roles )
+ if create_role_for_group_checked:
+ message += 'One of the roles associated with this group is the newly created role with the same name.'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+
+
+ if ok:
+ for user in trans.sa_session.query( trans.app.model.User ) \
+ .filter( trans.app.model.User.table.c.deleted==False ) \
+ .order_by( trans.app.model.User.table.c.email ):
+ out_users.append( ( user.id, user.email ) )
+ for role in trans.sa_session.query( trans.app.model.Role ) \
+ .filter( trans.app.model.Role.table.c.deleted==False ) \
+ .order_by( trans.app.model.Role.table.c.name ):
+ out_roles.append( ( role.id, role.name ) )
+ return trans.fill_template( '/admin/dataset_security/group/group_create.mako',
+ name=name,
+ in_users=in_users,
+ out_users=out_users,
+ in_roles=in_roles,
+ out_roles=out_roles,
+ create_role_for_group_checked=create_role_for_group_checked,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def mark_group_deleted( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = params.get( 'id', None )
+ if not id:
+ message = "No group ids received for marking deleted"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Deleted %d groups: " % len( ids )
+ for group_id in ids:
+ group = get_group( trans, group_id )
+ group.deleted = True
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ message += " %s " % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def undelete_group( self, trans, **kwd ):
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No group ids received for undeleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ count = 0
+ undeleted_groups = ""
+ for group_id in ids:
+ group = get_group( trans, group_id )
+ if not group.deleted:
+ message = "Group '%s' has not been deleted, so it cannot be undeleted." % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ group.deleted = False
+ trans.sa_session.add( group )
+ trans.sa_session.flush()
+ count += 1
+ undeleted_groups += " %s" % group.name
+ message = "Undeleted %d groups: %s" % ( count, undeleted_groups )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def purge_group( self, trans, **kwd ):
+ # This method should only be called for a Group that has previously been deleted.
+ # Purging a deleted Group simply deletes all UserGroupAssociations and GroupRoleAssociations.
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No group ids received for purging"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d groups: " % len( ids )
+ for group_id in ids:
+ group = get_group( trans, group_id )
+ if not group.deleted:
+ # We should never reach here, but just in case there is a bug somewhere...
+ message = "Group '%s' has not been deleted, so it cannot be purged." % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ # Delete UserGroupAssociations
+ for uga in group.users:
+ trans.sa_session.delete( uga )
+ # Delete GroupRoleAssociations
+ for gra in group.roles:
+ trans.sa_session.delete( gra )
+ trans.sa_session.flush()
+ message += " %s " % group.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='groups',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+
+ # Galaxy User Stuff
+ @web.expose
+ @web.require_admin
+ def create_new_user( self, trans, **kwd ):
+ return trans.response.send_redirect( web.url_for( controller='user',
+ action='create',
+ cntrller='admin' ) )
+ @web.expose
+ @web.require_admin
+ def reset_user_password( self, trans, **kwd ):
+ user_id = kwd.get( 'id', None )
+ if not user_id:
+ message = "No users received for resetting passwords."
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=message,
+ status='error' ) )
+ user_ids = util.listify( user_id )
+ if 'reset_user_password_button' in kwd:
+ message = ''
+ status = ''
+ for user_id in user_ids:
+ user = get_user( trans, user_id )
+ password = kwd.get( 'password', None )
+ confirm = kwd.get( 'confirm' , None )
+ if len( password ) < 6:
+ message = "Use a password of at least 6 characters."
+ status = 'error'
+ break
+ elif password != confirm:
+ message = "Passwords do not match."
+ status = 'error'
+ break
+ else:
+ user.set_password_cleartext( password )
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ if not message and not status:
+ message = "Passwords reset for %d %s." % ( len( user_ids ), inflector.cond_plural( len( user_ids ), 'user' ) )
+ status = 'done'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status=status ) )
+ users = [ get_user( trans, user_id ) for user_id in user_ids ]
+ if len( user_ids ) > 1:
+ user_id = ','.join( user_ids )
+ return trans.fill_template( '/admin/user/reset_password.mako',
+ id=user_id,
+ users=users,
+ password='',
+ confirm='' )
+ @web.expose
+ @web.require_admin
+ def mark_user_deleted( self, trans, **kwd ):
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No user ids received for deleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Deleted %d users: " % len( ids )
+ for user_id in ids:
+ user = get_user( trans, user_id )
+ user.deleted = True
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ message += " %s " % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def undelete_user( self, trans, **kwd ):
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No user ids received for undeleting"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=message,
+ status='error' ) )
+ ids = util.listify( id )
+ count = 0
+ undeleted_users = ""
+ for user_id in ids:
+ user = get_user( trans, user_id )
+ if not user.deleted:
+ message = "User '%s' has not been deleted, so it cannot be undeleted." % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ user.deleted = False
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ count += 1
+ undeleted_users += " %s" % user.email
+ message = "Undeleted %d users: %s" % ( count, undeleted_users )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def purge_user( self, trans, **kwd ):
+ # This method should only be called for a User that has previously been deleted.
+ # We keep the User in the database ( marked as purged ), and stuff associated
+ # with the user's private role in case we want the ability to unpurge the user
+ # some time in the future.
+ # Purging a deleted User deletes all of the following:
+ # - History where user_id = User.id
+ # - HistoryDatasetAssociation where history_id = History.id
+ # - Dataset where HistoryDatasetAssociation.dataset_id = Dataset.id
+ # - UserGroupAssociation where user_id == User.id
+ # - UserRoleAssociation where user_id == User.id EXCEPT FOR THE PRIVATE ROLE
+ # - UserAddress where user_id == User.id
+ # Purging Histories and Datasets must be handled via the cleanup_datasets.py script
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No user ids received for purging"
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d users: " % len( ids )
+ for user_id in ids:
+ user = get_user( trans, user_id )
+ if not user.deleted:
+ # We should never reach here, but just in case there is a bug somewhere...
+ message = "User '%s' has not been deleted, so it cannot be purged." % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ private_role = trans.app.security_agent.get_private_user_role( user )
+ # Delete History
+ for h in user.active_histories:
+ trans.sa_session.refresh( h )
+ for hda in h.active_datasets:
+ # Delete HistoryDatasetAssociation
+ d = trans.sa_session.query( trans.app.model.Dataset ).get( hda.dataset_id )
+ # Delete Dataset
+ if not d.deleted:
+ d.deleted = True
+ trans.sa_session.add( d )
+ hda.deleted = True
+ trans.sa_session.add( hda )
+ h.deleted = True
+ trans.sa_session.add( h )
+ # Delete UserGroupAssociations
+ for uga in user.groups:
+ trans.sa_session.delete( uga )
+ # Delete UserRoleAssociations EXCEPT FOR THE PRIVATE ROLE
+ for ura in user.roles:
+ if ura.role_id != private_role.id:
+ trans.sa_session.delete( ura )
+ # Delete UserAddresses
+ for address in user.addresses:
+ trans.sa_session.delete( address )
+ # Purge the user
+ user.purged = True
+ trans.sa_session.add( user )
+ trans.sa_session.flush()
+ message += "%s " % user.email
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ @web.expose
+ @web.require_admin
+ def users( self, trans, **kwd ):
+ if 'operation' in kwd:
+ operation = kwd['operation'].lower()
+ if operation == "roles":
+ return self.user( trans, **kwd )
+ elif operation == "reset password":
+ return self.reset_user_password( trans, **kwd )
+ elif operation == "delete":
+ return self.mark_user_deleted( trans, **kwd )
+ elif operation == "undelete":
+ return self.undelete_user( trans, **kwd )
+ elif operation == "purge":
+ return self.purge_user( trans, **kwd )
+ elif operation == "create":
+ return self.create_new_user( trans, **kwd )
+ elif operation == "information":
+ user_id = kwd.get( 'id', None )
+ if not user_id:
+ kwd[ 'message' ] = util.sanitize_text( "Invalid user id (%s) received" % str( user_id ) )
+ kwd[ 'status' ] = 'error'
+ else:
+ return trans.response.send_redirect( web.url_for( controller='user',
+ action='manage_user_info',
+ cntrller='admin',
+ **kwd ) )
+ elif operation == "manage roles and groups":
+ return self.manage_roles_and_groups_for_user( trans, **kwd )
+ if trans.app.config.allow_user_deletion:
+ if self.delete_operation not in self.user_list_grid.operations:
+ self.user_list_grid.operations.append( self.delete_operation )
+ if self.undelete_operation not in self.user_list_grid.operations:
+ self.user_list_grid.operations.append( self.undelete_operation )
+ if self.purge_operation not in self.user_list_grid.operations:
+ self.user_list_grid.operations.append( self.purge_operation )
+ # Render the list view
+ return self.user_list_grid( trans, **kwd )
+ @web.expose
+ @web.require_admin
+ def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
+ """Return autocomplete data for user emails"""
+ ac_data = ""
+ for user in trans.sa_session.query( User ).filter_by( deleted=False ).filter( func.lower( User.email ).like( q.lower() + "%" ) ):
+ ac_data = ac_data + user.email + "\n"
+ return ac_data
+ @web.expose
+ @web.require_admin
+ def manage_roles_and_groups_for_user( self, trans, **kwd ):
+ user_id = kwd.get( 'id', None )
+ message = ''
+ status = ''
+ if not user_id:
+ message += "Invalid user id (%s) received" % str( user_id )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ user = get_user( trans, user_id )
+ private_role = trans.app.security_agent.get_private_user_role( user )
+ if kwd.get( 'user_roles_groups_edit_button', False ):
+ # Make sure the user is not dis-associating himself from his private role
+ out_roles = kwd.get( 'out_roles', [] )
+ if out_roles:
+ out_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( out_roles ) ]
+ if private_role in out_roles:
+ message += "You cannot eliminate a user's private role association. "
+ status = 'error'
+ in_roles = kwd.get( 'in_roles', [] )
+ if in_roles:
+ in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( in_roles ) ]
+ out_groups = kwd.get( 'out_groups', [] )
+ if out_groups:
+ out_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( out_groups ) ]
+ in_groups = kwd.get( 'in_groups', [] )
+ if in_groups:
+ in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( in_groups ) ]
+ if in_roles:
+ trans.app.security_agent.set_entity_user_associations( users=[ user ], roles=in_roles, groups=in_groups )
+ trans.sa_session.refresh( user )
+ message += "User '%s' has been updated with %d associated roles and %d associated groups (private roles are not displayed)" % \
+ ( user.email, len( in_roles ), len( in_groups ) )
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='users',
+ message=util.sanitize_text( message ),
+ status='done' ) )
+ in_roles = []
+ out_roles = []
+ in_groups = []
+ out_groups = []
+ for role in trans.sa_session.query( trans.app.model.Role ).filter( trans.app.model.Role.table.c.deleted==False ) \
+ .order_by( trans.app.model.Role.table.c.name ):
+ if role in [ x.role for x in user.roles ]:
+ in_roles.append( ( role.id, role.name ) )
+ elif role.type != trans.app.model.Role.types.PRIVATE:
+ # There is a 1 to 1 mapping between a user and a PRIVATE role, so private roles should
+ # not be listed in the roles form fields, except for the currently selected user's private
+ # role, which should always be in in_roles. The check above is added as an additional
+ # precaution, since for a period of time we were including private roles in the form fields.
+ out_roles.append( ( role.id, role.name ) )
+ for group in trans.sa_session.query( trans.app.model.Group ).filter( trans.app.model.Group.table.c.deleted==False ) \
+ .order_by( trans.app.model.Group.table.c.name ):
+ if group in [ x.group for x in user.groups ]:
+ in_groups.append( ( group.id, group.name ) )
+ else:
+ out_groups.append( ( group.id, group.name ) )
+ message += "User '%s' is currently associated with %d roles and is a member of %d groups" % \
+ ( user.email, len( in_roles ), len( in_groups ) )
+ if not status:
+ status = 'done'
+ return trans.fill_template( '/admin/user/user.mako',
+ user=user,
+ in_roles=in_roles,
+ out_roles=out_roles,
+ in_groups=in_groups,
+ out_groups=out_groups,
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
+ def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ):
+ if self.app.memdump is None:
+ return trans.show_error_message( "Memdump is not enabled (set <code>use_memdump = True</code> in universe_wsgi.ini)" )
+ heap = self.app.memdump.get()
+ p = util.Params( kwd )
+ msg = None
+ if p.dump:
+ heap = self.app.memdump.get( update = True )
+ msg = "Heap dump complete"
+ elif p.setref:
+ self.app.memdump.setref()
+ msg = "Reference point set (dump to see delta from this point)"
+ ids = ids.split( ',' )
+ sorts = sorts.split( ',' )
+ if new_id is not None:
+ ids.append( new_id )
+ sorts.append( 'None' )
+ elif new_sort is not None:
+ sorts[-1] = new_sort
+ breadcrumb = "<a href='%s' class='breadcrumb'>heap</a>" % web.url_for()
+ # new lists so we can assemble breadcrumb links
+ new_ids = []
+ new_sorts = []
+ for id, sort in zip( ids, sorts ):
+ new_ids.append( id )
+ if id != 'None':
+ breadcrumb += "<a href='%s' class='breadcrumb'>[%s]</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), id )
+ heap = heap[int(id)]
+ new_sorts.append( sort )
+ if sort != 'None':
+ breadcrumb += "<a href='%s' class='breadcrumb'>.by('%s')</a>" % ( web.url_for( ids=','.join( new_ids ), sorts=','.join( new_sorts ) ), sort )
+ heap = heap.by( sort )
+ ids = ','.join( new_ids )
+ sorts = ','.join( new_sorts )
+ if p.theone:
+ breadcrumb += ".theone"
+ heap = heap.theone
+ return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg )
+
+ @web.expose
+ @web.require_admin
+ def jobs( self, trans, stop = [], stop_msg = None, cutoff = 180, job_lock = None, ajl_submit = None, **kwd ):
+ deleted = []
+ msg = None
+ status = None
+ if self.app.config.job_manager != self.app.config.server_name:
+ return trans.show_error_message( 'This Galaxy instance (%s) is not the job manager (%s). If using multiple servers, please directly access the job manager instance to manage jobs.' % (self.app.config.server_name, self.app.config.job_manager) )
+ job_ids = util.listify( stop )
+ if job_ids and stop_msg in [ None, '' ]:
+ msg = 'Please enter an error message to display to the user describing why the job was terminated'
+ status = 'error'
+ elif job_ids:
+ if stop_msg[-1] not in string.punctuation:
+ stop_msg += '.'
+ for job_id in job_ids:
+ trans.app.job_manager.job_stop_queue.put( job_id, error_msg="This job was stopped by an administrator: %s For more information or help" % stop_msg )
+ deleted.append( str( job_id ) )
+ if deleted:
+ msg = 'Queued job'
+ if len( deleted ) > 1:
+ msg += 's'
+ msg += ' for deletion: '
+ msg += ', '.join( deleted )
+ status = 'done'
+ if ajl_submit:
+ if job_lock == 'on':
+ trans.app.job_manager.job_queue.job_lock = True
+ else:
+ trans.app.job_manager.job_queue.job_lock = False
+ cutoff_time = datetime.utcnow() - timedelta( seconds=int( cutoff ) )
+ jobs = trans.sa_session.query( trans.app.model.Job ) \
+ .filter( and_( trans.app.model.Job.table.c.update_time < cutoff_time,
+ or_( trans.app.model.Job.state == trans.app.model.Job.states.NEW,
+ trans.app.model.Job.state == trans.app.model.Job.states.QUEUED,
+ trans.app.model.Job.state == trans.app.model.Job.states.RUNNING,
+ trans.app.model.Job.state == trans.app.model.Job.states.UPLOAD ) ) ) \
+ .order_by( trans.app.model.Job.table.c.update_time.desc() )
+ last_updated = {}
+ for job in jobs:
+ delta = datetime.utcnow() - job.update_time
+ if delta > timedelta( minutes=60 ):
+ last_updated[job.id] = '%s hours' % int( delta.seconds / 60 / 60 )
+ else:
+ last_updated[job.id] = '%s minutes' % int( delta.seconds / 60 )
+ return trans.fill_template( '/admin/jobs.mako',
+ jobs = jobs,
+ last_updated = last_updated,
+ cutoff = cutoff,
+ msg = msg,
+ status = status,
+ job_lock = trans.app.job_manager.job_queue.job_lock )
+
+## ---- Utility methods -------------------------------------------------------
+
+def get_ids_of_tool_shed_repositories_being_installed( trans, as_string=False ):
+ installing_repository_ids = []
+ new_status = trans.model.ToolShedRepository.installation_status.NEW
+ cloning_status = trans.model.ToolShedRepository.installation_status.CLONING
+ setting_tool_versions_status = trans.model.ToolShedRepository.installation_status.SETTING_TOOL_VERSIONS
+ installing_dependencies_status = trans.model.ToolShedRepository.installation_status.INSTALLING_TOOL_DEPENDENCIES
+ loading_datatypes_status = trans.model.ToolShedRepository.installation_status.LOADING_PROPRIETARY_DATATYPES
+ for tool_shed_repository in trans.sa_session.query( trans.model.ToolShedRepository ) \
+ .filter( or_( trans.model.ToolShedRepository.status == new_status,
+ trans.model.ToolShedRepository.status == cloning_status,
+ trans.model.ToolShedRepository.status == setting_tool_versions_status,
+ trans.model.ToolShedRepository.status == installing_dependencies_status,
+ trans.model.ToolShedRepository.status == loading_datatypes_status ) ):
+ installing_repository_ids.append( trans.security.encode_id( tool_shed_repository.id ) )
+ if as_string:
+ return ','.join( installing_repository_ids )
+ return installing_repository_ids
+
+def get_user( trans, user_id ):
+ """Get a User from the database by id."""
+ user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
+ if not user:
+ return trans.show_error_message( "User not found for id (%s)" % str( user_id ) )
+ return user
+def get_user_by_username( trans, username ):
+ """Get a user from the database by username"""
+ # TODO: Add exception handling here.
+ return trans.sa_session.query( trans.model.User ) \
+ .filter( trans.model.User.table.c.username == username ) \
+ .one()
+def get_role( trans, id ):
+ """Get a Role from the database by id."""
+ # Load user from database
+ id = trans.security.decode_id( id )
+ role = trans.sa_session.query( trans.model.Role ).get( id )
+ if not role:
+ return trans.show_error_message( "Role not found for id (%s)" % str( id ) )
+ return role
+def get_group( trans, id ):
+ """Get a Group from the database by id."""
+ # Load user from database
+ id = trans.security.decode_id( id )
+ group = trans.sa_session.query( trans.model.Group ).get( id )
+ if not group:
+ return trans.show_error_message( "Group not found for id (%s)" % str( id ) )
+ return group
+def get_quota( trans, id ):
+ """Get a Quota from the database by id."""
+ # Load user from database
+ id = trans.security.decode_id( id )
+ quota = trans.sa_session.query( trans.model.Quota ).get( id )
+ return quota
\ No newline at end of file
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -607,7 +607,7 @@
Update the session cookie to match the current session.
"""
self.set_cookie( self.security.encode_guid( self.galaxy_session.session_key ), name=name, path=self.app.config.cookie_path )
- def handle_user_login( self, user, webapp ):
+ def handle_user_login( self, user ):
"""
Login a new user (possibly newly created)
- create a new session
@@ -621,7 +621,7 @@
prev_galaxy_session.is_valid = False
# Define a new current_session
self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
- if webapp == 'galaxy':
+ if self.webapp.name == 'galaxy':
cookie_name = 'galaxysession'
# Associated the current user's last accessed history (if exists) with their new session
history = None
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/web/framework/helpers/grids.py
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -53,7 +53,8 @@
def __call__( self, trans, **kwargs ):
# Get basics.
- webapp = get_webapp( trans, **kwargs )
+ # FIXME: pretty sure this is only here to pass along, can likely be eliminated
+ webapp = trans.webapp.name
status = kwargs.get( 'status', None )
message = kwargs.get( 'message', None )
# Build a base filter and sort key that is the combination of the saved state and defaults.
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/webapps/community/controllers/admin.py
--- a/lib/galaxy/webapps/community/controllers/admin.py
+++ b/lib/galaxy/webapps/community/controllers/admin.py
@@ -1,4 +1,5 @@
from galaxy.web.base.controller import *
+from galaxy.web.base.controllers.admin import Admin
from galaxy.webapps.community import model
from galaxy.model.orm import *
from galaxy.web.framework.helpers import time_ago, iff, grids
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/webapps/galaxy/api/quotas.py
--- a/lib/galaxy/webapps/galaxy/api/quotas.py
+++ b/lib/galaxy/webapps/galaxy/api/quotas.py
@@ -2,7 +2,8 @@
API operations on Quota objects.
"""
import logging
-from galaxy.web.base.controller import BaseAPIController, Admin, UsesQuotaMixin, url_for
+from galaxy.web.base.controller import BaseAPIController, UsesQuotaMixin, url_for
+from galaxy.web.base.controllers.admin import Admin
from galaxy import web, util
from elementtree.ElementTree import XML
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/webapps/galaxy/controllers/admin.py
--- a/lib/galaxy/webapps/galaxy/controllers/admin.py
+++ b/lib/galaxy/webapps/galaxy/controllers/admin.py
@@ -1,4 +1,5 @@
from galaxy.web.base.controller import *
+from galaxy.web.base.controllers.admin import Admin
from galaxy import model
from galaxy.model.orm import *
from galaxy.web.framework.helpers import time_ago, iff, grids
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 lib/galaxy/webapps/galaxy/controllers/user.py
--- a/lib/galaxy/webapps/galaxy/controllers/user.py
+++ b/lib/galaxy/webapps/galaxy/controllers/user.py
@@ -14,7 +14,7 @@
from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password, transform_publicname
from galaxy.util.json import from_json_string, to_json_string
from galaxy.web import url_for
-from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin, get_webapp
+from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin
from galaxy.web.form_builder import CheckboxField, build_select_field
from galaxy.web.framework.helpers import time_ago, grids
@@ -49,10 +49,10 @@
installed_len_files = None
@web.expose
- def index( self, trans, cntrller, webapp='galaxy', **kwd ):
- return trans.fill_template( '/user/index.mako', cntrller=cntrller, webapp=webapp )
+ def index( self, trans, cntrller, **kwd ):
+ return trans.fill_template( '/user/index.mako', cntrller=cntrller )
@web.expose
- def openid_auth( self, trans, webapp='galaxy', **kwd ):
+ def openid_auth( self, trans, **kwd ):
'''Handles user request to access an OpenID provider'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -102,7 +102,7 @@
message=message,
status='error' ) )
@web.expose
- def openid_process( self, trans, webapp='galaxy', **kwd ):
+ def openid_process( self, trans, **kwd ):
'''Handle's response from OpenID Providers'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -178,7 +178,7 @@
message=message,
status=status ) )
elif user_openid.user:
- trans.handle_user_login( user_openid.user, webapp )
+ trans.handle_user_login( user_openid.user )
trans.log_event( "User logged in via OpenID: %s" % display_identifier )
openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info )
if not redirect:
@@ -222,7 +222,7 @@
message=message,
status=status ) )
@web.expose
- def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
+ def openid_associate( self, trans, cntrller='user', **kwd ):
'''Associates a user with an OpenID log in'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -241,7 +241,7 @@
elif is_admin:
return trans.show_error_message( 'Associating OpenIDs with accounts cannot be done by administrators.' )
if kwd.get( 'login_button', False ):
- message, status, user, success = self.__validate_login( trans, webapp, **kwd )
+ message, status, user, success = self.__validate_login( trans, **kwd )
if success:
openid_objs = []
for openid in openids:
@@ -285,7 +285,7 @@
error = 'User registration is disabled. Please contact your Galaxy administrator for an account.'
else:
# Check email and password validity
- error = self.__validate( trans, params, email, password, confirm, username, webapp )
+ error = self.__validate( trans, params, email, password, confirm, username )
if not error:
# all the values are valid
message, status, user, success = self.__register( trans,
@@ -330,7 +330,7 @@
else:
message = error
status = 'error'
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
user_type_form_definition = self.__get_user_type_form_definition( trans, user=user, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
@@ -342,7 +342,6 @@
user_type_form_definition = None
widgets = []
return trans.fill_template( '/user/openid_associate.mako',
- webapp=webapp,
cntrller=cntrller,
email=email,
password='',
@@ -362,7 +361,7 @@
openids=openids )
@web.expose
@web.require_login( 'manage OpenIDs' )
- def openid_disassociate( self, trans, webapp='galaxy', **kwd ):
+ def openid_disassociate( self, trans, **kwd ):
'''Disassociates a user with an OpenID'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -404,7 +403,7 @@
@web.expose
@web.require_login( 'manage OpenIDs' )
- def openid_manage( self, trans, webapp='galaxy', **kwd ):
+ def openid_manage( self, trans, **kwd ):
'''Manage OpenIDs for user'''
if not trans.app.config.enable_openid:
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
@@ -421,7 +420,7 @@
return self.user_openid_grid( trans, **kwd )
@web.expose
- def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **kwd ):
+ def login( self, trans, redirect_url='', refresh_frames=[], **kwd ):
'''Handle Galaxy Log in'''
redirect = kwd.get( 'redirect', trans.request.referer ).strip()
use_panels = util.string_as_bool( kwd.get( 'use_panels', False ) )
@@ -430,16 +429,13 @@
header = ''
user = None
email = kwd.get( 'email', '' )
- #Sanitize webapp login here, once, since it can be reflected to the user in messages/etc.
- #Only text is valid.
- webapp = util.sanitize_text(webapp)
if kwd.get( 'login_button', False ):
- if webapp == 'galaxy' and not refresh_frames:
+ if trans.webapp.name == 'galaxy' and not refresh_frames:
if trans.app.config.require_login:
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
refresh_frames = [ 'masthead', 'history' ]
- message, status, user, success = self.__validate_login( trans, webapp, **kwd )
+ message, status, user, success = self.__validate_login( trans, **kwd )
if success and redirect and not redirect.startswith( trans.request.base + url_for( controller='user', action='logout' ) ):
redirect_url = redirect
elif success:
@@ -447,18 +443,17 @@
if not user and trans.app.config.require_login:
if trans.app.config.allow_user_creation:
create_account_str = " If you don't already have an account, <a href='%s'>you may create one</a>." % \
- web.url_for( action='create', cntrller='user', webapp=webapp )
- if webapp == 'galaxy':
+ web.url_for( action='create', cntrller='user' )
+ if trans.webapp.name == 'galaxy':
header = require_login_template % ( "Galaxy instance", create_account_str )
else:
header = require_login_template % ( "Galaxy tool shed", create_account_str )
else:
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
header = require_login_template % ( "Galaxy instance", "" )
else:
header = require_login_template % ( "Galaxy tool shed", "" )
return trans.fill_template( '/user/login.mako',
- webapp=webapp,
email=email,
header=header,
use_panels=use_panels,
@@ -469,7 +464,7 @@
status=status,
openid_providers=trans.app.openid_providers,
active_view="user" )
- def __validate_login( self, trans, webapp='galaxy', **kwd ):
+ def __validate_login( self, trans, **kwd ):
message = kwd.get( 'message', '' )
status = kwd.get( 'status', 'done' )
email = kwd.get( 'email', '' )
@@ -490,8 +485,8 @@
message = "Invalid password"
status = 'error'
else:
- trans.handle_user_login( user, webapp )
- if webapp == 'galaxy':
+ trans.handle_user_login( user )
+ if trans.webapp.name == 'galaxy':
trans.log_event( "User logged in" )
message = 'You are now logged in as %s.<br>You can <a target="_top" href="%s">go back to the page you were visiting</a> or <a target="_top" href="%s">go to the home page</a>.' % \
( user.email, redirect, url_for( '/' ) )
@@ -501,8 +496,8 @@
return ( message, status, user, success )
@web.expose
- def logout( self, trans, webapp='galaxy', logout_all=False ):
- if webapp == 'galaxy':
+ def logout( self, trans, logout_all=False ):
+ if trans.webapp.name == 'galaxy':
if trans.app.config.require_login:
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
@@ -515,7 +510,6 @@
message = 'You have been logged out.<br>You can log in again, <a target="_top" href="%s">go back to the page you were visiting</a> or <a target="_top" href="%s">go to the home page</a>.' % \
( trans.request.referer, url_for( '/' ) )
return trans.fill_template( '/user/logout.mako',
- webapp=webapp,
refresh_frames=refresh_frames,
message=message,
status='done',
@@ -526,7 +520,6 @@
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
- webapp = get_webapp( trans, **kwd )
use_panels = util.string_as_bool( kwd.get( 'use_panels', True ) )
email = util.restore_text( params.get( 'email', '' ) )
# Do not sanitize passwords, so take from kwd
@@ -543,7 +536,7 @@
status = 'error'
else:
if not refresh_frames:
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
if trans.app.config.require_login:
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
@@ -553,19 +546,19 @@
# Create the user, save all the user info and login to Galaxy
if params.get( 'create_user_button', False ):
# Check email and password validity
- message = self.__validate( trans, params, email, password, confirm, username, webapp )
+ message = self.__validate( trans, params, email, password, confirm, username )
if not message:
# All the values are valid
message, status, user, success = self.__register( trans,
cntrller,
subscribe_checked,
**kwd )
- if webapp == 'community':
+ if trans.webapp.name == 'community':
redirect_url = url_for( '/' )
if success and not is_admin:
# The handle_user_login() method has a call to the history_set_default_permissions() method
# (needed when logging in with a history), user needs to have default permissions set before logging in
- trans.handle_user_login( user, webapp )
+ trans.handle_user_login( user )
trans.log_event( "User created a new account" )
trans.log_event( "User logged in" )
if success and is_admin:
@@ -577,7 +570,7 @@
status=status ) )
else:
status = 'error'
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
user_type_form_definition = self.__get_user_type_form_definition( trans, user=None, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
@@ -596,7 +589,6 @@
user_type_fd_id_select_field=user_type_fd_id_select_field,
user_type_form_definition=user_type_form_definition,
widgets=widgets,
- webapp=webapp,
use_panels=use_panels,
redirect=redirect,
redirect_url=redirect_url,
@@ -608,7 +600,6 @@
email = util.restore_text( kwd.get( 'email', '' ) )
password = kwd.get( 'password', '' )
username = util.restore_text( kwd.get( 'username', '' ) )
- webapp = get_webapp( trans, **kwd )
status = kwd.get( 'status', 'done' )
is_admin = cntrller == 'admin' and trans.user_is_admin()
user = trans.app.model.User( email=email )
@@ -618,7 +609,7 @@
trans.sa_session.flush()
trans.app.security_agent.create_private_user_role( user )
error = ''
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
# We set default user permissions, before we log in and set the default history permissions
trans.app.security_agent.user_set_default_permissions( user,
default_access_private=trans.app.config.new_user_dataset_access_role_default_private )
@@ -654,7 +645,7 @@
if not error and not is_admin:
# The handle_user_login() method has a call to the history_set_default_permissions() method
# (needed when logging in with a history), user needs to have default permissions set before logging in
- trans.handle_user_login( user, webapp )
+ trans.handle_user_login( user )
trans.log_event( "User created a new account" )
trans.log_event( "User logged in" )
elif not error:
@@ -706,14 +697,13 @@
user = trans.user
if not user:
raise AssertionError, "The user id (%s) is not valid" % str( user_id )
- webapp = get_webapp( trans, **kwd )
email = util.restore_text( params.get( 'email', user.email ) )
username = util.restore_text( params.get( 'username', '' ) )
if not username:
username = user.username
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
user_type_form_definition = self.__get_user_type_form_definition( trans, user=user, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
@@ -742,7 +732,6 @@
widgets=widgets,
addresses=addresses,
show_filter=show_filter,
- webapp=webapp,
message=message,
status=status )
else:
@@ -751,7 +740,6 @@
user=user,
email=email,
username=username,
- webapp=webapp,
message=message,
status=status )
@@ -761,7 +749,6 @@
def edit_username( self, trans, cntrller, **kwd ):
params = util.Params( kwd )
is_admin = cntrller == 'admin' and trans.user_is_admin()
- webapp = get_webapp( trans, **kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
user_id = params.get( 'user_id', None )
@@ -784,14 +771,12 @@
cntrller=cntrller,
user=user,
username=user.username,
- webapp=webapp,
message=message,
status=status )
@web.expose
def edit_info( self, trans, cntrller, **kwd ):
params = util.Params( kwd )
is_admin = cntrller == 'admin' and trans.user_is_admin()
- webapp = get_webapp( trans, **kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
user_id = params.get( 'user_id', None )
@@ -885,7 +870,7 @@
trans.sa_session.add( user )
trans.sa_session.flush()
message = "The user information has been updated with the changes."
- if user and webapp == 'galaxy' and is_admin:
+ if user and trans.webapp.name == 'galaxy' and is_admin:
kwd[ 'user_id' ] = trans.security.encode_id( user.id )
kwd[ 'id' ] = user_id
if message:
@@ -897,7 +882,7 @@
cntrller=cntrller,
**kwd ) )
@web.expose
- def reset_password( self, trans, email=None, webapp='galaxy', **kwd ):
+ def reset_password( self, trans, email=None, **kwd ):
if trans.app.config.smtp_server is None:
return trans.show_error_message( "Mail is not configured for this Galaxy instance. Please contact an administrator." )
message = util.restore_text( kwd.get( 'message', '' ) )
@@ -941,12 +926,11 @@
elif email is None:
email = ""
return trans.fill_template( '/user/reset_password.mako',
- webapp=webapp,
message=message,
status=status )
- def __validate( self, trans, params, email, password, confirm, username, webapp ):
+ def __validate( self, trans, params, email, password, confirm, username ):
# If coming from the community webapp, we'll require a public user name
- if webapp == 'community' and not username:
+ if trans.webapp.name == 'community' and not username:
return "A public user name is required"
message = validate_email( trans, email )
if not message:
@@ -954,7 +938,7 @@
if not message and username:
message = validate_publicname( trans, username )
if not message:
- if webapp == 'galaxy':
+ if trans.webapp.name == 'galaxy':
if self.get_all_forms( trans,
filter=dict( deleted=False ),
form_type=trans.app.model.FormDefinition.types.USER_INFO ):
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/dbkeys.mako
--- a/templates/user/dbkeys.mako
+++ b/templates/user/dbkeys.mako
@@ -1,11 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % t.webapp.name
else:
return '/base.mako'
%>
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/index.mako
--- a/templates/user/index.mako
+++ b/templates/user/index.mako
@@ -9,27 +9,27 @@
<h2>${_('User preferences')}</h2><p>You are currently logged in as ${trans.user.email}.</p><ul>
- %if webapp == 'galaxy':
- <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller, webapp=webapp )}">${_('Manage your information')}</a></li>
- <li><a href="${h.url_for( controller='user', action='set_default_permissions', cntrller=cntrller, webapp=webapp )}">${_('Change default permissions')}</a> for new histories</li>
- <li><a href="${h.url_for( controller='user', action='api_keys', cntrller=cntrller, webapp=webapp )}">${_('Manage your API keys')}</a></li>
+ %if t.webapp.name == 'galaxy':
+ <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller )}">${_('Manage your information')}</a></li>
+ <li><a href="${h.url_for( controller='user', action='set_default_permissions', cntrller=cntrller )}">${_('Change default permissions')}</a> for new histories</li>
+ <li><a href="${h.url_for( controller='user', action='api_keys', cntrller=cntrller )}">${_('Manage your API keys')}</a></li>
%if trans.app.config.enable_openid:
- <li><a href="${h.url_for( controller='user', action='openid_manage', cntrller=cntrller, webapp=webapp )}">${_('Manage OpenIDs')}</a> linked to your account</li>
+ <li><a href="${h.url_for( controller='user', action='openid_manage', cntrller=cntrller )}">${_('Manage OpenIDs')}</a> linked to your account</li>
%endif
%if trans.app.config.use_remote_user:
%if trans.app.config.remote_user_logout_href:
<li><a href="${trans.app.config.remote_user_logout_href}" target="_top">${_('Logout')}</a></li>
%endif
%else:
- <li><a href="${h.url_for( controller='user', action='logout', webapp=webapp, logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
+ <li><a href="${h.url_for( controller='user', action='logout', logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
%endif
%else:
- <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller, webapp=webapp )}">${_('Manage your information')}</a></li>
- <li><a href="${h.url_for( controller='repository', action='manage_email_alerts', cntrller=cntrller, webapp=webapp )}">${_('Manage your email alerts')}</a></li>
- <li><a href="${h.url_for( controller='user', action='logout', webapp=webapp, logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
+ <li><a href="${h.url_for( controller='user', action='manage_user_info', cntrller=cntrller )}">${_('Manage your information')}</a></li>
+ <li><a href="${h.url_for( controller='repository', action='manage_email_alerts', cntrller=cntrller )}">${_('Manage your email alerts')}</a></li>
+ <li><a href="${h.url_for( controller='user', action='logout', logout_all=True )}" target="_top">${_('Logout')}</a> ${_('of all user sessions')}</li>
%endif
</ul>
- %if webapp == 'galaxy':
+ %if t.webapp.name == 'galaxy':
<p>
You are using <strong>${trans.user.get_disk_usage( nice_size=True )}</strong> of disk space in this Galaxy instance.
%if trans.app.config.enable_quotas:
@@ -43,7 +43,7 @@
<p>${n_('You are currently not logged in.')}</p>
%endif
<ul>
- <li><a href="${h.url_for( action='login', webapp=webapp )}">${_('Login')}</li>
- <li><a href="${h.url_for( action='create', cntrller='user', webapp=webapp )}">${_('Register')}</a></li>
+ <li><a href="${h.url_for( action='login' )}">${_('Login')}</li>
+ <li><a href="${h.url_for( action='create', cntrller='user' )}">${_('Register')}</a></li></ul>
%endif
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/info.mako
--- a/templates/user/info.mako
+++ b/templates/user/info.mako
@@ -7,13 +7,12 @@
%if not is_admin:
<ul class="manage-table-actions"><li>
- <a class="action-button" href="${h.url_for( controller='user', action='index', cntrller=cntrller, webapp=webapp )}">User preferences</a>
+ <a class="action-button" href="${h.url_for( controller='user', action='index', cntrller=cntrller )}">User preferences</a></li></ul>
%endif
<div class="toolForm"><form name="login_info" id="login_info" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
- <input type="hidden" name="webapp" value="${webapp}" size="40"/><div class="toolFormTitle">Login Information</div><div class="form-row"><label>Email address:</label>
@@ -21,7 +20,7 @@
</div><div class="form-row"><label>Public name:</label>
- %if webapp == 'community':
+ %if t.webapp.name == 'community':
%if user.active_repositories:
<input type="hidden" name="username" value="${username}"/>
${username}
@@ -54,7 +53,6 @@
<p></p><div class="toolForm"><form name="change_password" id="change_password" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
- <input type="hidden" name="webapp" value="${webapp}" size="40"/><div class="toolFormTitle">Change Password</div>
%if not is_admin:
<div class="form-row">
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/login.mako
--- a/templates/user/login.mako
+++ b/templates/user/login.mako
@@ -1,11 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % context.get('t').webapp.name
else:
return '/base.mako'
%>
@@ -82,14 +78,13 @@
<div class="form-row"><label>Email address:</label><input type="text" name="email" value="${email | h}" size="40"/>
- <input type="hidden" name="webapp" value="${webapp | h}" size="40"/><input type="hidden" name="redirect" value="${redirect | h}" size="40"/></div><div class="form-row"><label>Password:</label><input type="password" name="password" value="" size="40"/><div class="toolParamHelp" style="clear: both;">
- <a href="${h.url_for( controller='user', action='reset_password', webapp=webapp, use_panels=use_panels )}">Forgot password? Reset here</a>
+ <a href="${h.url_for( controller='user', action='reset_password', use_panels=use_panels )}">Forgot password? Reset here</a></div></div><div class="form-row">
@@ -107,7 +102,6 @@
<div class="form-row"><label>OpenID URL:</label><input type="text" name="openid_url" size="60" style="background-image:url('${h.url_for( '/static/images/openid-16x16.gif' )}' ); background-repeat: no-repeat; padding-right: 20px; background-position: 99% 50%;"/>
- <input type="hidden" name="webapp" value="${webapp | h}" size="40"/><input type="hidden" name="redirect" value="${redirect | h}" size="40"/></div><div class="form-row">
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/logout.mako
--- a/templates/user/logout.mako
+++ b/templates/user/logout.mako
@@ -1,10 +1,6 @@
<%!
def inherit(context):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % context.get('t').webapp.name
%><%inherit file="${inherit(context)}"/><%namespace file="/message.mako" import="render_msg" />
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/openid_associate.mako
--- a/templates/user/openid_associate.mako
+++ b/templates/user/openid_associate.mako
@@ -1,11 +1,7 @@
<%!
def inherit(context):
if context.get('use_panels'):
- if context.get('webapp'):
- webapp = context.get('webapp')
- else:
- webapp = 'galaxy'
- return '/webapps/%s/base_panels.mako' % webapp
+ return '/webapps/%s/base_panels.mako' % t.webapp.name
else:
return '/base.mako'
%>
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/register.mako
--- a/templates/user/register.mako
+++ b/templates/user/register.mako
@@ -43,7 +43,6 @@
<div class="form-row"><label>Email address:</label><input type="text" name="email" value="${email | h}" size="40"/>
- <input type="hidden" name="webapp" value="${webapp | h}" size="40"/><input type="hidden" name="redirect" value="${redirect | h}" size="40"/></div><div class="form-row">
@@ -57,7 +56,7 @@
<div class="form-row"><label>Public name:</label><input type="text" name="username" size="40" value="${username |h}"/>
- %if webapp == 'galaxy':
+ %if t.webapp.name == 'galaxy':
<div class="toolParamHelp" style="clear: both;">
Your public name is an identifier that will be used to generate addresses for information
you share publicly. Public names must be at least four characters in length and contain only lower-case
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/reset_password.mako
--- a/templates/user/reset_password.mako
+++ b/templates/user/reset_password.mako
@@ -11,7 +11,6 @@
<div class="form-row"><label>Email:</label><input type="text" name="email" value="" size="40"/>
- <input type="hidden" name="webapp" value="${webapp}" size="40"/></div><div style="clear: both"></div><div class="form-row">
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/user/username.mako
--- a/templates/user/username.mako
+++ b/templates/user/username.mako
@@ -5,7 +5,6 @@
<h2>Manage Public Name</h2><div class="toolForm"><form name="username" id="username" action="${h.url_for( controller='user', action='edit_username', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
- <input type="hidden" name="webapp" value="${webapp}" size="40"/><div class="toolFormTitle">Login Information</div><div class="form-row"><label>Public name:</label>
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/webapps/galaxy/admin/index.mako
--- a/templates/webapps/galaxy/admin/index.mako
+++ b/templates/webapps/galaxy/admin/index.mako
@@ -42,11 +42,11 @@
<div class="toolSectionTitle">Security</div><div class="toolSectionBody"><div class="toolSectionBg">
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='users', webapp=webapp )}" target="galaxy_main">Manage users</a></div>
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='groups', webapp=webapp )}" target="galaxy_main">Manage groups</a></div>
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='roles', webapp=webapp )}" target="galaxy_main">Manage roles</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='users' )}" target="galaxy_main">Manage users</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='groups' )}" target="galaxy_main">Manage groups</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='roles' )}" target="galaxy_main">Manage roles</a></div>
%if trans.app.config.allow_user_impersonation:
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='impersonate', webapp=webapp )}" target="galaxy_main">Impersonate a user</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='impersonate' )}" target="galaxy_main">Impersonate a user</a></div>
%endif
</div></div>
@@ -54,7 +54,7 @@
<div class="toolSectionTitle">Data</div><div class="toolSectionBody"><div class="toolSectionBg">
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='quotas', webapp=webapp )}" target="galaxy_main">Manage quotas</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='quotas' )}" target="galaxy_main">Manage quotas</a></div><div class="toolTitle"><a href="${h.url_for( controller='library_admin', action='browse_libraries' )}" target="galaxy_main">Manage data libraries</a></div>
%if trans.app.config.enable_beta_job_managers:
<div class="toolTitle"><a href="${h.url_for( controller='data_admin', action='manage_data' )}" target="galaxy_main">Manage local data</a></div>
@@ -111,6 +111,6 @@
</%def><%def name="center_panel()">
- <% center_url = h.url_for( controller='admin', action='center', webapp='galaxy', message=message, status=status ) %>
+ <% center_url = h.url_for( controller='admin', action='center', message=message, status=status ) %><iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"></iframe></%def>
diff -r ee2feed164a9efc9201409b3e6191e2707ec3e18 -r 5b977db5fc50b50caa5309838f37a77010f1e2b7 templates/webapps/galaxy/base_panels.mako
--- a/templates/webapps/galaxy/base_panels.mako
+++ b/templates/webapps/galaxy/base_panels.mako
@@ -136,7 +136,7 @@
# Menu for user who is not logged in.
menu_options = [ [ _("Login"), h.url_for( controller='/user', action='login' ), "galaxy_main" ] ]
if app.config.allow_user_creation:
- menu_options.append( [ _("Register"), h.url_for( controller='/user', action='create', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _("Register"), h.url_for( controller='/user', action='create', cntrller='user' ), "galaxy_main" ] )
extra_class = "loggedout-only"
visible = ( trans.user == None )
tab( "user", _("User"), None, visible=visible, menu_options=menu_options )
@@ -151,20 +151,20 @@
if app.config.remote_user_logout_href:
menu_options.append( [ _('Logout'), app.config.remote_user_logout_href, "_top" ] )
else:
- menu_options.append( [ _('Preferences'), h.url_for( controller='/user', action='index', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _('Preferences'), h.url_for( controller='/user', action='index', cntrller='user' ), "galaxy_main" ] )
menu_options.append( [ 'Custom Builds', h.url_for( controller='/user', action='dbkeys' ), "galaxy_main" ] )
if app.config.require_login:
- logout_url = h.url_for( controller='/root', action='index', m_c='user', m_a='logout', webapp='galaxy' )
+ logout_url = h.url_for( controller='/root', action='index', m_c='user', m_a='logout' )
else:
- logout_url = h.url_for( controller='/user', action='logout', webapp='galaxy' )
+ logout_url = h.url_for( controller='/user', action='logout' )
menu_options.append( [ 'Logout', logout_url, "_top" ] )
menu_options.append( None )
menu_options.append( [ _('Saved Histories'), h.url_for( controller='/history', action='list' ), "galaxy_main" ] )
menu_options.append( [ _('Saved Datasets'), h.url_for( controller='/dataset', action='list' ), "galaxy_main" ] )
menu_options.append( [ _('Saved Pages'), h.url_for( controller='/page', action='list' ), "_top" ] )
- menu_options.append( [ _('API Keys'), h.url_for( controller='/user', action='api_keys', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _('API Keys'), h.url_for( controller='/user', action='api_keys', cntrller='user' ), "galaxy_main" ] )
if app.config.use_remote_user:
- menu_options.append( [ _('Public Name'), h.url_for( controller='/user', action='edit_username', cntrller='user', webapp='galaxy' ), "galaxy_main" ] )
+ menu_options.append( [ _('Public Name'), h.url_for( controller='/user', action='edit_username', cntrller='user' ), "galaxy_main" ] )
extra_class = "loggedin-only"
visible = ( trans.user != None )
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 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/ee2feed164a9/
changeset: ee2feed164a9
user: natefoo
date: 2012-09-28 22:11:38
summary: pgcleanup/set_user_disk_usage: set usage to 0, not null, when the user has no active data.
affected #: 2 files
diff -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 -r ee2feed164a9efc9201409b3e6191e2707ec3e18 scripts/cleanup_datasets/pgcleanup.py
--- a/scripts/cleanup_datasets/pgcleanup.py
+++ b/scripts/cleanup_datasets/pgcleanup.py
@@ -261,7 +261,7 @@
# TODO: h.purged = false should be unnecessary once all hdas in purged histories are purged.
sql = """
UPDATE galaxy_user
- SET disk_usage = (SELECT SUM(total_size)
+ SET disk_usage = (SELECT COALESCE(SUM(total_size), 0)
FROM ( SELECT d.total_size
FROM history_dataset_association hda
JOIN history h ON h.id = hda.history_id
diff -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 -r ee2feed164a9efc9201409b3e6191e2707ec3e18 scripts/set_user_disk_usage.py
--- a/scripts/set_user_disk_usage.py
+++ b/scripts/set_user_disk_usage.py
@@ -30,6 +30,9 @@
import galaxy.config
from galaxy.objectstore import build_object_store_from_config
+ # lazy
+ globals()['nice_size'] = __import__( 'galaxy.util', globals(), locals(), ( 'nice_size', ) ).nice_size
+
config_parser = ConfigParser( dict( here = os.getcwd(),
database_connection = 'sqlite:///database/universe.sqlite?isolation_level=IMMEDIATE' ) )
config_parser.read( os.path.basename( options.config ) )
@@ -48,7 +51,7 @@
def pgcalc( sa_session, id ):
sql = """
UPDATE galaxy_user
- SET disk_usage = (SELECT SUM(total_size)
+ SET disk_usage = (SELECT COALESCE(SUM(total_size), 0)
FROM ( SELECT d.total_size
FROM history_dataset_association hda
JOIN history h ON h.id = hda.history_id
@@ -72,7 +75,7 @@
def quotacheck( sa_session, users, engine ):
sa_session.refresh( user )
current = user.get_disk_usage()
- print user.username, '<' + user.email + '> old usage:', str( current ) + ',',
+ print user.username, '<' + user.email + '>:',
if engine != 'postgres':
new = user.calculate_disk_usage()
sa_session.refresh( user )
@@ -83,10 +86,14 @@
else:
new = pgcalc( sa_session, user.id )
# yes, still a small race condition between here and the flush
- if new == current:
- print 'no change'
+ print 'old usage:', nice_size( current ), 'change:',
+ if new in ( current, None ):
+ print 'none'
else:
- print 'new usage:', new
+ op = '-'
+ if new > current:
+ op = '+'
+ print '%s%s' % ( op, nice_size( new ) )
if not options.dryrun and engine != 'postgres':
user.set_disk_usage( new )
sa_session.add( user )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org . You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/cfd26c91f6c3/
changeset: cfd26c91f6c3
user: inithello
date: 2012-09-28 21:35:44
summary: Migrate BWA to the tool shed.
affected #: 6 files
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 lib/galaxy/tool_shed/migrate/versions/0005_tools.py
--- /dev/null
+++ b/lib/galaxy/tool_shed/migrate/versions/0005_tools.py
@@ -0,0 +1,15 @@
+"""
+The tools "Map with BWA for Illumina" and "Map with BWA for SOLiD" have
+been eliminated from the distribution. The tools are now available
+in the repository named bwa_wrappers from the main Galaxy tool shed at
+http://toolshed.g2.bx.psu.edu, and will be installed into your local
+Galaxy instance at the location discussed above by running the following
+command.
+"""
+
+import sys
+
+def upgrade():
+ print __doc__
+def downgrade():
+ pass
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 scripts/migrate_tools/0005_tools.sh
--- /dev/null
+++ b/scripts/migrate_tools/0005_tools.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cd `dirname $0`/../..
+python ./scripts/migrate_tools/migrate_tools.py 0005_tools.xml $@
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 scripts/migrate_tools/0005_tools.xml
--- /dev/null
+++ b/scripts/migrate_tools/0005_tools.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<toolshed name="toolshed.g2.bx.psu.edu ">
+ <repository name="bwa_wrappers" description="Galaxy wrappers for the BWA short read aligner." changeset_revision="ffa8aaa14f7c">
+ <tool id="bwa_wrapper" version="1.2.3" file="bwa_wrapper.xml"/>
+ <tool id="bwa_color_wrapper" version="1.0.2" file="bwa_color_wrapper.xml"/>
+ </repository>
+</toolshed>
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 tool-data/bwa_index.loc.sample
--- a/tool-data/bwa_index.loc.sample
+++ /dev/null
@@ -1,38 +0,0 @@
-#This is a sample file distributed with Galaxy that enables tools
-#to use a directory of BWA indexed sequences data files. You will need
-#to create these data files and then create a bwa_index.loc file
-#similar to this one (store it in this directory) that points to
-#the directories in which those files are stored. The bwa_index.loc
-#file has this format (longer white space characters are TAB characters):
-#
-#<unique_build_id><dbkey><display_name><file_path>
-#
-#So, for example, if you had phiX indexed stored in
-#/depot/data2/galaxy/phiX/base/,
-#then the bwa_index.loc entry would look like this:
-#
-#phiX174 phiX phiX Pretty /depot/data2/galaxy/phiX/base/phiX.fa
-#
-#and your /depot/data2/galaxy/phiX/base/ directory
-#would contain phiX.fa.* files:
-#
-#-rw-r--r-- 1 james universe 830134 2005-09-13 10:12 phiX.fa.amb
-#-rw-r--r-- 1 james universe 527388 2005-09-13 10:12 phiX.fa.ann
-#-rw-r--r-- 1 james universe 269808 2005-09-13 10:12 phiX.fa.bwt
-#...etc...
-#
-#Your bwa_index.loc file should include an entry per line for each
-#index set you have stored. The "file" in the path does not actually
-#exist, but it is the prefix for the actual index files. For example:
-#
-#phiX174 phiX phiX174 /depot/data2/galaxy/phiX/base/phiX.fa
-#hg18canon hg18 hg18 Canonical /depot/data2/galaxy/hg18/base/hg18canon.fa
-#hg18full hg18 hg18 Full /depot/data2/galaxy/hg18/base/hg18full.fa
-#/orig/path/hg19.fa hg19 hg19 /depot/data2/galaxy/hg19/base/hg19.fa
-#...etc...
-#
-#Note that for backwards compatibility with workflows, the unique ID of
-#an entry must be the path that was in the original loc file, because that
-#is the value stored in the workflow for that parameter. That is why the
-#hg19 entry above looks odd. New genomes can be better-looking.
-#
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 tool-data/bwa_index_color.loc.sample
--- a/tool-data/bwa_index_color.loc.sample
+++ /dev/null
@@ -1,38 +0,0 @@
-#This is a sample file distributed with Galaxy that enables tools
-#to use a directory of BWA indexed sequences data files. You will need
-#to create these data files and then create a bwa_index_color.loc file
-#similar to this one (store it in this directory) that points to
-#the directories in which those files are stored. The bwa_index_color.loc
-#file has this format (longer white space characters are TAB characters):
-#
-#<unique_build_id><dbkey><display_name><file_path>
-#
-#So, for example, if you had phiX indexed stored in
-#/depot/data2/galaxy/phiX/color/,
-#then the bwa_index.loc entry would look like this:
-#
-#phiX174 phiX phiX Pretty /depot/data2/galaxy/phiX/color/phiX.fa
-#
-#and your /depot/data2/galaxy/phiX/color/ directory
-#would contain phiX.fa.* files:
-#
-#-rw-r--r-- 1 james universe 830134 2005-09-13 10:12 phiX.fa.amb
-#-rw-r--r-- 1 james universe 527388 2005-09-13 10:12 phiX.fa.ann
-#-rw-r--r-- 1 james universe 269808 2005-09-13 10:12 phiX.fa.bwt
-#...etc...
-#
-#Your bwa_index_color.loc file should include an entry per line for each
-#index set you have stored. The "file" in the path does not actually
-#exist, but it is the prefix for the actual index files. For example:
-#
-#phiX174 phiX phiX174 /depot/data2/galaxy/phiX/color/phiX.fa
-#hg18canon hg18 hg18 Canonical /depot/data2/galaxy/hg18/color/hg18canon.fa
-#hg18full hg18 hg18 Full /depot/data2/galaxy/hg18/color/hg18full.fa
-#/orig/path/hg19.fa hg19 hg19 /depot/data2/galaxy/hg19/color/hg19.fa
-#...etc...
-#
-#Note that for backwards compatibility with workflows, the unique ID of
-#an entry must be the path that was in the original loc file, because that
-#is the value stored in the workflow for that parameter. That is why the
-#hg19 entry above looks odd. New genomes can be better-looking.
-#
diff -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c -r cfd26c91f6c3bb8b6365e7835263e11fc3013004 tool_conf.xml.sample
--- a/tool_conf.xml.sample
+++ b/tool_conf.xml.sample
@@ -330,8 +330,6 @@
<tool file="sr_mapping/bowtie_wrapper.xml" /><tool file="sr_mapping/bowtie2_wrapper.xml" /><tool file="sr_mapping/bowtie_color_wrapper.xml" />
- <tool file="sr_mapping/bwa_wrapper.xml" />
- <tool file="sr_mapping/bwa_color_wrapper.xml" /><tool file="sr_mapping/bfast_wrapper.xml" /><tool file="metag_tools/megablast_wrapper.xml" /><tool file="metag_tools/megablast_xml_parser.xml" />
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org . You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/203e3e159243/
changeset: 203e3e159243
user: james_taylor
date: 2012-09-28 21:33:08
summary: Fix for main app rename in toolshed
affected #: 1 file
diff -r a37d82881a493b1ebf5e43d692c93b69bea44527 -r 203e3e1592439dcb8c0eb890545ed38a66f08f2c lib/galaxy/webapps/community/controllers/user.py
--- a/lib/galaxy/webapps/community/controllers/user.py
+++ b/lib/galaxy/webapps/community/controllers/user.py
@@ -1,1 +1,1 @@
-from galaxy.webapps.main.controllers.user import *
\ No newline at end of file
+from galaxy.webapps.galaxy.controllers.user import *
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.
10 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/changeset/8dbde0261fda/
changeset: 8dbde0261fda
user: james_taylor
date: 2012-09-24 19:09:50
summary: Moving main webapp out of framework directory
affected #: 103 files
Diff too large to display.
https://bitbucket.org/galaxy/galaxy-central/changeset/14878d0d6389/
changeset: 14878d0d6389
user: james_taylor
date: 2012-09-24 19:27:30
summary: Extracting add ui/api controllers methods up to the framework level
affected #: 2 files
diff -r 8dbde0261fda8d5b955527c76043e9efc4a7aec6 -r 14878d0d63898409799c929460366e2ef46fd505 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -6,6 +6,12 @@
import os, sys, time, socket, random, string
import inspect
+
+from galaxy.web.base.controller import BaseUIController
+from galaxy.web.base.controller import BaseAPIController
+from galaxy.web.base.controller import ControllerUnavailable
+
+
pkg_resources.require( "Cheetah" )
from Cheetah.Template import Template
import base
@@ -212,6 +218,7 @@
return FormBuilder( *args, **kwargs )
class WebApplication( base.WebApplication ):
+
def __init__( self, galaxy_app, session_cookie='galaxysession' ):
base.WebApplication.__init__( self )
self.set_transaction_factory( lambda e: self.transaction_chooser( e, galaxy_app, session_cookie ) )
@@ -223,19 +230,70 @@
output_encoding = 'utf-8' )
# Security helper
self.security = galaxy_app.security
+
def handle_controller_exception( self, e, trans, **kwargs ):
+
if isinstance( e, MessageException ):
return trans.show_message( e.err_msg, e.type )
def make_body_iterable( self, trans, body ):
+
if isinstance( body, FormBuilder ):
body = trans.show_form( body )
return base.WebApplication.make_body_iterable( self, trans, body )
+
def transaction_chooser( self, environ, galaxy_app, session_cookie ):
if 'is_api_request' in environ:
return GalaxyWebAPITransaction( environ, galaxy_app, self, session_cookie )
else:
return GalaxyWebUITransaction( environ, galaxy_app, self, session_cookie )
+ def add_ui_controllers( self, app, package_name ):
+ """
+ Search for UI controllers in `package_name` and add
+ them to the webapp.
+ """
+ package = __import__( package_name )
+ controller_dir = package.__path__[0]
+ for fname in os.listdir( controller_dir ):
+ if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
+ name = fname[:-3]
+ module_name = package_name + "." + name
+ try:
+ module = __import__( module_name )
+ except ControllerUnavailable, exc:
+ log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
+ continue
+ for comp in module_name.split( "." )[1:]:
+ module = getattr( module, comp )
+ # Look for a controller inside the modules
+ for key in dir( module ):
+ T = getattr( module, key )
+ if inspect.isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
+ self.add_ui_controller( name, T( app ) )
+
+ def add_api_controllers( self, app, package_name ):
+ """
+ Search for UI controllers in `package_name` and add
+ them to the webapp.
+ """
+ package = __import__( package_name )
+ controller_dir = package.__path__[0]
+ for fname in os.listdir( controller_dir ):
+ if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
+ name = fname[:-3]
+ module_name = package_name + "." + name
+ try:
+ module = __import__( module_name )
+ except ControllerUnavailable, exc:
+ log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
+ continue
+ for comp in module_name.split( "." )[1:]:
+ module = getattr( module, comp )
+ for key in dir( module ):
+ T = getattr( module, key )
+ if inspect.isclass( T ) and T is not BaseAPIController and issubclass( T, BaseAPIController ):
+ self.add_api_controller( name, T( app ) )
+
class GalaxyWebTransaction( base.DefaultWebTransaction ):
"""
Encapsulates web transaction specific state for the Galaxy application
diff -r 8dbde0261fda8d5b955527c76043e9efc4a7aec6 -r 14878d0d63898409799c929460366e2ef46fd505 lib/galaxy/webapps/main/buildapp.py
--- a/lib/galaxy/webapps/main/buildapp.py
+++ b/lib/galaxy/webapps/main/buildapp.py
@@ -22,53 +22,6 @@
import galaxy.datatypes.registry
import galaxy.web.framework
-def add_ui_controllers( webapp, app ):
- """
- Search for controllers in the 'galaxy.web.controllers' module and add
- them to the webapp.
- """
- from galaxy.web.base.controller import BaseUIController
- from galaxy.web.base.controller import ControllerUnavailable
- import galaxy.web.controllers
- controller_dir = galaxy.web.controllers.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = "galaxy.web.controllers." + name
- try:
- module = __import__( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- for comp in module_name.split( "." )[1:]:
- module = getattr( module, comp )
- # Look for a controller inside the modules
- for key in dir( module ):
- T = getattr( module, key )
- if isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
- webapp.add_ui_controller( name, T( app ) )
-
-def add_api_controllers( webapp, app ):
- from galaxy.web.base.controller import BaseAPIController
- from galaxy.web.base.controller import ControllerUnavailable
- import galaxy.web.api
- controller_dir = galaxy.web.api.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = "galaxy.web.api." + name
- try:
- module = __import__( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- for comp in module_name.split( "." )[1:]:
- module = getattr( module, comp )
- for key in dir( module ):
- T = getattr( module, key )
- if isclass( T ) and T is not BaseAPIController and issubclass( T, BaseAPIController ):
- webapp.add_api_controller( name, T( app ) )
-
def app_factory( global_conf, **kwargs ):
"""
Return a wsgi application serving the root object
@@ -87,7 +40,7 @@
atexit.register( app.shutdown )
# Create the universe WSGI application
webapp = galaxy.web.framework.WebApplication( app, session_cookie='galaxysession' )
- add_ui_controllers( webapp, app )
+ webapp.add_ui_controllers( 'galaxy.webapps.main.controllers', app )
# Force /history to go to /root/history -- needed since the tests assume this
webapp.add_route( '/history', controller='root', action='history' )
# These two routes handle our simple needs at the moment
@@ -105,7 +58,7 @@
webapp.add_route( '/u/:username/v/:slug', controller='visualization', action='display_by_username_and_slug' )
# Add the web API
- add_api_controllers( webapp, app )
+ webapp.add_api_controllers( 'galaxy.webapps.main.api', app )
webapp.api_mapper.resource( 'content',
'contents',
controller='library_contents',
https://bitbucket.org/galaxy/galaxy-central/changeset/20e1f9fb8da9/
changeset: 20e1f9fb8da9
user: james_taylor
date: 2012-09-24 20:34:25
summary: Cleaning up controller imports
affected #: 13 files
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/util/backports/__init__.py
--- /dev/null
+++ b/lib/galaxy/util/backports/__init__.py
@@ -0,0 +1,3 @@
+"""
+Modules for providing backward compatibility with future versions of Python
+"""
\ No newline at end of file
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/util/backports/importlib/__init__.py
--- /dev/null
+++ b/lib/galaxy/util/backports/importlib/__init__.py
@@ -0,0 +1,42 @@
+"""Backport of importlib.import_module from 3.x."""
+# While not critical (and in no way guaranteed!), it would be nice to keep this
+# code compatible with Python 2.3.
+import sys
+
+def _resolve_name(name, package, level):
+ """Return the absolute name of the module to be imported."""
+ if not hasattr(package, 'rindex'):
+ raise ValueError("'package' not set to a string")
+ dot = len(package)
+ for x in xrange(level, 1, -1):
+ try:
+ dot = package.rindex('.', 0, dot)
+ except ValueError:
+ raise ValueError("attempted relative import beyond top-level "
+ "package")
+ return "%s.%s" % (package[:dot], name)
+
+
+def import_module(name, package=None):
+ """Import a module.
+
+ The 'package' argument is required when performing a relative import. It
+ specifies the package to use as the anchor point from which to resolve the
+ relative import to an absolute import.
+
+ """
+ if name.startswith('.'):
+ if not package:
+ raise TypeError("relative imports require the 'package' argument")
+ level = 0
+ for character in name:
+ if character != '.':
+ break
+ level += 1
+ name = _resolve_name(name[level:], package, level)
+ __import__(name)
+ return sys.modules[name]
+
+ ## Note: this was copied from
+ ## http://svn.python.org/projects/python/trunk/Lib/importlib/__init__.py
+ ## on 24 September 2012
\ No newline at end of file
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/web/buildapp.py
--- /dev/null
+++ b/lib/galaxy/web/buildapp.py
@@ -0,0 +1,3 @@
+"""For backward compatibility only, pulls app_factor from galaxy.webapps.main"""
+
+from galaxy.webapps.main.buildapp import app_factory
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -7,11 +7,6 @@
import os, sys, time, socket, random, string
import inspect
-from galaxy.web.base.controller import BaseUIController
-from galaxy.web.base.controller import BaseAPIController
-from galaxy.web.base.controller import ControllerUnavailable
-
-
pkg_resources.require( "Cheetah" )
from Cheetah.Template import Template
import base
@@ -20,6 +15,7 @@
from galaxy import util
from galaxy.exceptions import MessageException
from galaxy.util.json import to_json_string, from_json_string
+from galaxy.util.backports.importlib import import_module
pkg_resources.require( "simplejson" )
import simplejson
@@ -247,48 +243,50 @@
else:
return GalaxyWebUITransaction( environ, galaxy_app, self, session_cookie )
- def add_ui_controllers( self, app, package_name ):
+ def add_ui_controllers( self, package_name, app ):
"""
Search for UI controllers in `package_name` and add
them to the webapp.
"""
- package = __import__( package_name )
+ from galaxy.web.base.controller import BaseUIController
+ from galaxy.web.base.controller import ControllerUnavailable
+ package = import_module( package_name )
+ controller_dir = package.__path__[0]
+ print ">>>", controller_dir, package.__path__
+ for fname in os.listdir( controller_dir ):
+ if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
+ name = fname[:-3]
+ module_name = package_name + "." + name
+ print package_name, name, module_name
+ try:
+ module = import_module( module_name )
+ except ControllerUnavailable, exc:
+ log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
+ continue
+ # Look for a controller inside the modules
+ for key in dir( module ):
+ T = getattr( module, key )
+ if inspect.isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
+ self.add_ui_controller( name, T( app ) )
+
+ def add_api_controllers( self, package_name, app ):
+ """
+ Search for UI controllers in `package_name` and add
+ them to the webapp.
+ """
+ from galaxy.web.base.controller import BaseAPIController
+ from galaxy.web.base.controller import ControllerUnavailable
+ package = import_module( package_name )
controller_dir = package.__path__[0]
for fname in os.listdir( controller_dir ):
if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
name = fname[:-3]
module_name = package_name + "." + name
try:
- module = __import__( module_name )
+ module = import_module( module_name )
except ControllerUnavailable, exc:
log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
continue
- for comp in module_name.split( "." )[1:]:
- module = getattr( module, comp )
- # Look for a controller inside the modules
- for key in dir( module ):
- T = getattr( module, key )
- if inspect.isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ):
- self.add_ui_controller( name, T( app ) )
-
- def add_api_controllers( self, app, package_name ):
- """
- Search for UI controllers in `package_name` and add
- them to the webapp.
- """
- package = __import__( package_name )
- controller_dir = package.__path__[0]
- for fname in os.listdir( controller_dir ):
- if not( fname.startswith( "_" ) ) and fname.endswith( ".py" ):
- name = fname[:-3]
- module_name = package_name + "." + name
- try:
- module = __import__( module_name )
- except ControllerUnavailable, exc:
- log.debug("%s could not be loaded: %s" % (module_name, str(exc)))
- continue
- for comp in module_name.split( "." )[1:]:
- module = getattr( module, comp )
for key in dir( module ):
T = getattr( module, key )
if inspect.isclass( T ) and T is not BaseAPIController and issubclass( T, BaseAPIController ):
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/api/workflows.py
--- a/lib/galaxy/webapps/main/api/workflows.py
+++ b/lib/galaxy/webapps/main/api/workflows.py
@@ -1,3 +1,5 @@
+from __future__ import absolute_import
+
"""
API operations for Workflows
"""
@@ -11,7 +13,8 @@
from galaxy.workflow.modules import module_factory
from galaxy.jobs.actions.post import ActionBox
from galaxy.model.item_attrs import UsesAnnotations
-from galaxy.web.controllers.workflow import attach_ordered_steps
+
+from ..controllers.workflow import attach_ordered_steps
log = logging.getLogger(__name__)
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/buildapp.py
--- a/lib/galaxy/webapps/main/buildapp.py
+++ b/lib/galaxy/webapps/main/buildapp.py
@@ -6,8 +6,6 @@
import os, os.path
import sys, warnings
-from inspect import isclass
-
from paste.request import parse_formvars
from paste.util import import_string
from paste import httpexceptions
@@ -209,7 +207,7 @@
log.debug( "Enabling 'error' middleware" )
# Transaction logging (apache access.log style)
if asbool( conf.get( 'use_translogger', True ) ):
- from framework.middleware.translogger import TransLogger
+ from galaxy.web.framework.middleware.translogger import TransLogger
app = TransLogger( app )
log.debug( "Enabling 'trans logger' middleware" )
# Config middleware just stores the paste config along with the request,
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/controllers/admin_toolshed.py
--- a/lib/galaxy/webapps/main/controllers/admin_toolshed.py
+++ b/lib/galaxy/webapps/main/controllers/admin_toolshed.py
@@ -1,5 +1,5 @@
import urllib2, tempfile
-from galaxy.web.controllers.admin import *
+from admin import *
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.shed_util import *
from galaxy.tool_shed.encoding_util import *
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/controllers/external_service.py
--- a/lib/galaxy/webapps/main/controllers/external_service.py
+++ b/lib/galaxy/webapps/main/controllers/external_service.py
@@ -1,9 +1,11 @@
+from __future__ import absolute_import
+
from galaxy.web.base.controller import *
from galaxy.web.framework.helpers import time_ago, iff, grids
from galaxy.model.orm import *
from galaxy import model, util
from galaxy.web.form_builder import *
-from galaxy.web.controllers.requests_common import invalid_id_redirect
+from .requests_common import invalid_id_redirect
import logging, os
log = logging.getLogger( __name__ )
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/controllers/request_type.py
--- a/lib/galaxy/webapps/main/controllers/request_type.py
+++ b/lib/galaxy/webapps/main/controllers/request_type.py
@@ -1,9 +1,11 @@
+from __future__ import absolute_import
+
from galaxy.web.base.controller import *
from galaxy.web.framework.helpers import time_ago, iff, grids
from galaxy.model.orm import *
from galaxy import model, util
from galaxy.web.form_builder import *
-from galaxy.web.controllers.requests_common import invalid_id_redirect
+from .requests_common import invalid_id_redirect
import logging, os
log = logging.getLogger( __name__ )
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/controllers/requests.py
--- a/lib/galaxy/webapps/main/controllers/requests.py
+++ b/lib/galaxy/webapps/main/controllers/requests.py
@@ -1,8 +1,10 @@
+from __future__ import absolute_import
+
from galaxy.web.base.controller import *
from galaxy.web.framework.helpers import grids
from galaxy.model.orm import *
from galaxy.web.form_builder import *
-from galaxy.web.controllers.requests_common import RequestsGrid
+from .requests_common import RequestsGrid
import logging
log = logging.getLogger( __name__ )
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/controllers/requests_admin.py
--- a/lib/galaxy/webapps/main/controllers/requests_admin.py
+++ b/lib/galaxy/webapps/main/controllers/requests_admin.py
@@ -1,9 +1,11 @@
+from __future__ import absolute_import
+
from galaxy.web.base.controller import *
from galaxy.web.framework.helpers import time_ago, iff, grids
from galaxy.model.orm import *
from galaxy import model, util
from galaxy.web.form_builder import *
-from galaxy.web.controllers.requests_common import RequestsGrid, invalid_id_redirect
+from .requests_common import RequestsGrid, invalid_id_redirect
from amqplib import client_0_8 as amqp
import logging, os, pexpect, ConfigParser
diff -r 14878d0d63898409799c929460366e2ef46fd505 -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd lib/galaxy/webapps/main/controllers/visualization.py
--- a/lib/galaxy/webapps/main/controllers/visualization.py
+++ b/lib/galaxy/webapps/main/controllers/visualization.py
@@ -1,13 +1,16 @@
+from __future__ import absolute_import
+
from galaxy import model
from galaxy.model.item_attrs import *
from galaxy.web.base.controller import *
from galaxy.web.framework.helpers import time_ago, grids, iff
from galaxy.util.sanitize_html import sanitize_html
-from galaxy.web.controllers.library import LibraryListGrid
from galaxy.visualization.genomes import decode_dbkey
from galaxy.visualization.genome.visual_analytics import get_dataset_job
from galaxy.visualization.data_providers.basic import ColumnDataProvider
+from .library import LibraryListGrid
+
#
# -- Grids --
#
https://bitbucket.org/galaxy/galaxy-central/changeset/08dbb6b88aeb/
changeset: 08dbb6b88aeb
user: james_taylor
date: 2012-09-28 21:10:28
summary: Automated merge with ssh://bitbucket.org/james_taylor/galaxy-webapp-refactoring
affected #: 107 files
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1278,6 +1278,18 @@
was converted successfully.
"""
+ # FIXME: copied from controller.py
+ messages = Bunch(
+ PENDING = "pending",
+ NO_DATA = "no data",
+ NO_CHROMOSOME = "no chromosome",
+ NO_CONVERTER = "no converter",
+ NO_TOOL = "no tool",
+ DATA = "data",
+ ERROR = "error",
+ OK = "ok"
+ )
+
# Get converted dataset; this will start the conversion if necessary.
try:
converted_dataset = self.get_converted_dataset( trans, target_type )
@@ -1785,6 +1797,7 @@
# we'll return the next available info_association in the inheritable hierarchy.
# True is also returned if the info_association was inherited, and False if not.
# This enables us to eliminate displaying any contents of the inherited template.
+ # SM: Accessing self.info_association can cause a query to be emitted
if self.info_association:
return self.info_association[0], inherited
if restrict:
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/security/__init__.py
--- a/lib/galaxy/security/__init__.py
+++ b/lib/galaxy/security/__init__.py
@@ -225,13 +225,16 @@
# If role.type is neither private nor sharing, it's ok to display
return True
return role.type != self.model.Role.types.PRIVATE and role.type != self.model.Role.types.SHARING
+
def allow_action( self, roles, action, item ):
"""
Method for checking a permission for the current user ( based on roles ) to perform a
specific action on an item, which must be one of:
Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
"""
+ # SM: Note that calling get_item_actions will emit a query.
item_actions = self.get_item_actions( action, item )
+
if not item_actions:
return action.model == 'restrict'
ret_val = False
@@ -249,8 +252,189 @@
ret_val = True
break
return ret_val
- def can_access_dataset( self, roles, dataset ):
- return self.dataset_is_public( dataset ) or self.allow_action( roles, self.permitted_actions.DATASET_ACCESS, dataset )
+
+
+ def get_actions_for_items( self, trans, action, permission_items ):
+ # TODO: Rename this; it's a replacement for get_item_actions, but it
+ # doesn't represent what it's really confusing.
+ # TODO: Make this work for other classes besides lib_datasets.
+ # That should be as easy as checking the type and writing a query for each;
+ # we're avoiding using the SQLAlchemy backrefs because they can cause lots
+ # of queries to be generated.
+ #
+ # Originally, get_item_actions did:
+ # return [ permission for permission in item.actions if permission.action == action.action ]
+ # The "item" can be just about anything with permissions, and referencing
+ # item.actions causes the item's permissions to be retrieved.
+ # This method will retrieve all permissions for all "items" and only
+ # return the permissions associated with that given action.
+ # We initialize the permissions list to be empty; we will return an
+ # empty list by default.
+ #
+ # If the dataset id has no corresponding action in its permissions,
+ # then the returned permissions will not carry an entry for the dataset.
+ ret_permissions = {}
+ if ( len( permission_items ) > 0 ):
+ if ( isinstance( permission_items[0], trans.model.LibraryDataset ) ):
+ ids = [ item.library_dataset_id for item in permission_items ]
+ permissions = trans.sa_session.query( trans.model.LibraryDatasetPermissions ) \
+ .filter( and_( trans.model.LibraryDatasetPermissions.library_dataset_id.in_( ids ),
+ trans.model.LibraryDatasetPermissions.action == action ) ) \
+ .all()
+
+ # Massage the return data. We will return a list of permissions
+ # for each library dataset. So we initialize the return list to
+ # have an empty list for each dataset. Then each permission is
+ # appended to the right lib dataset.
+ # TODO: Consider eliminating the initialization and just return
+ # empty values for each library dataset id.
+ for item in permission_items:
+ ret_permissions[ item.library_dataset_id ] = []
+ for permission in permissions:
+ ret_permissions[ permission.library_dataset_id ].append( permission )
+
+ # Test that we get the same response from get_item_actions each item:
+ test_code = False
+ if test_code:
+ try:
+ log.debug( "get_actions_for_items: Test start" )
+ for item in permission_items:
+ base_result = self.get_item_actions( action, item )
+ new_result = ret_permissions[ item.library_dataset_id ]
+ # For now, just test against LibraryDatasetIds; other classes
+ # are not tested yet.
+ if len( base_result ) == len( new_result ):
+ common_result = set(base_result).intersection( new_result )
+ if len( common_result ) == len( base_result ):
+ log.debug( "Match on permissions for id %d" %
+ item.library_dataset_id )
+ # TODO: Fix this failure message:
+ else:
+ log.debug( "Error: dataset %d; originally: %s; now: %s"
+ % ( item.library_dataset_id,
+ base_result, new_result ) )
+ else:
+ log.debug( "Error: dataset %d: had %d entries, now %d entries"
+ % ( item.library_dataset_id, len( base_result ),
+ len( new_result ) ) )
+ log.debug( "get_actions_for_items: Test end" )
+ except Exception as e:
+ log.debug( "Exception in test code: %s" % e )
+
+ return ret_permissions
+
+
+ def allow_action_on_libitems( self, trans, user_roles, action, items ):
+ """
+ This should be the equivalent of allow_action defined on multiple items.
+ It is meant to specifically replace allow_action for multiple
+ LibraryDatasets, but it could be reproduced or modified for
+ allow_action's permitted classes - Dataset, Library, LibraryFolder, and
+ LDDAs.
+ """
+ all_items_actions = self.get_actions_for_items( trans, action, items )
+
+ ret_allow_action = {}
+ # Change item to lib_dataset or vice-versa.
+ for item in items:
+ if all_items_actions.has_key( item.id ):
+ item_actions = all_items_actions[ item.id ]
+
+ # For access, all of the dataset's
+ if self.permitted_actions.DATASET_ACCESS == action:
+ ret_allow_action[ item.id ] = True
+ for item_action in item_actions:
+ if item_action.role not in user_roles:
+ ret_allow_action[ item.id ] = False
+ break
+
+ # Else look for just one dataset role to be in the list of
+ # acceptable user roles:
+ else:
+ ret_allow_action[ item.id ] = False
+ for item_action in item_actions:
+ if item_action.role in user_roles:
+ ret_allow_action[ item.id ] = True
+ break
+
+ else:
+ if 'restrict' == action.model:
+ ret_allow_action[ item.id ] = True
+ else:
+ ret_allow_action[ item.id ] = False
+
+ # Test it: the result for each dataset should match the result for
+ # allow_action:
+ test_code = False
+ if test_code:
+ log.debug( "allow_action_for_items: test start" )
+ for item in items:
+ orig_value = self.allow_action( user_roles, action, item )
+ if orig_value == ret_allow_action[ item.id ]:
+ log.debug( "Item %d: success" % item.id )
+ else:
+ log.debug( "Item %d: fail: original: %s; new: %s"
+ % ( item.id, orig_value, ret_allow_action[ item.id ] ) )
+ log.debug( "allow_action_for_items: test end" )
+ return ret_allow_action
+
+
+ # DELETEME: SM: DO NOT TOUCH! This actually works.
+ def dataset_access_mapping( self, trans, user_roles, datasets ):
+ '''
+ For the given list of datasets, return a mapping of the datasets' ids
+ to whether they can be accessed by the user or not. The datasets input
+ is expected to be a simple list of Dataset objects.
+ '''
+ datasets_public_map = self.datasets_are_public( trans, datasets )
+ datasets_allow_action_map = self.allow_action_on_libitems( trans, user_roles, self.permitted_actions.DATASET_ACCESS, datasets )
+ can_access = {}
+ for dataset in datasets:
+ can_access[ dataset.id ] = datasets_public_map[ dataset.id ] or datasets_allow_action_map[ dataset.id ]
+ return can_access
+
+ def dataset_permission_map_for_access( self, trans, user_roles, libitems ):
+ '''
+ For a given list of library items (e.g., Datasets), return a map of the
+ datasets' ids to whether they can have permission to use that action
+ (e.g., "access" or "modify") on the dataset. The libitems input is
+ expected to be a simple list of library items, such as Datasets or
+ LibraryDatasets.
+ NB: This is currently only usable for Datasets; it was intended to
+ be used for any library item.
+ '''
+ # Map the library items to whether they are publicly accessible or not.
+ # Then determine what actions are allowed on the item (in case it's not
+ # public). Finally, the item is accessible if it's publicly available
+ # or the right permissions are enabled.
+ # TODO: This only works for Datasets; other code is using X_is_public,
+ # so this will have to be rewritten to support other items.
+ libitems_public_map = self.datasets_are_public( trans, libitems )
+ libitems_allow_action_map = self.allow_action_on_libitems(
+ trans, user_roles, self.permitted_actions.DATASET_ACCESS, libitems )
+ can_access = {}
+ for libitem in libitems:
+ can_access[ libitem.id ] = libitems_public_map[ libitem.id ] or libitems_allow_action_map[ libitem.id ]
+ return can_access
+
+ def item_permission_map_for_modify( self, trans, user_roles, libitems ):
+ return self.allow_action_on_libitems(
+ trans, user_roles, self.permitted_actions.LIBRARY_MODIFY, libitems )
+
+ def item_permission_map_for_manage( self, trans, user_roles, libitems ):
+ return self.allow_action_on_libitems(
+ trans, user_roles, self.permitted_actions.LIBRARY_MANAGE, libitems )
+
+ def item_permission_map_for_add( self, trans, user_roles, libitems ):
+ return self.allow_action_on_libitems(
+ trans, user_roles, self.permitted_actions.LIBRARY_ADD, libitems )
+
+ def can_access_dataset( self, user_roles, dataset ):
+ # SM: dataset_is_public will access dataset.actions, which is a
+ # backref that causes a query to be made to DatasetPermissions
+ retval = self.dataset_is_public( dataset ) or self.allow_action( user_roles, self.permitted_actions.DATASET_ACCESS, dataset )
+ return retval
+
def can_manage_dataset( self, roles, dataset ):
return self.allow_action( roles, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset )
def can_access_library( self, roles, library ):
@@ -317,9 +501,14 @@
return self.allow_action( roles, self.permitted_actions.LIBRARY_MODIFY, item )
def can_manage_library_item( self, roles, item ):
return self.allow_action( roles, self.permitted_actions.LIBRARY_MANAGE, item )
+
def get_item_actions( self, action, item ):
# item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
+ # SM: Accessing item.actions emits a query to Library_Dataset_Permissions
+ # if the item is a LibraryDataset:
+ # TODO: Pass in the item's actions - the item isn't needed
return [ permission for permission in item.actions if permission.action == action.action ]
+
def guess_derived_permissions_for_datasets( self, datasets=[] ):
"""Returns a dict of { action : [ role, role, ... ] } for the output dataset based upon provided datasets"""
perms = {}
@@ -667,10 +856,55 @@
dataset = library_dataset.library_dataset_dataset_association.dataset
if not dataset.purged and not self.dataset_is_public( dataset ):
self.make_dataset_public( dataset )
+
def dataset_is_public( self, dataset ):
# A dataset is considered public if there are no "access" actions associated with it. Any
# other actions ( 'manage permissions', 'edit metadata' ) are irrelevant.
+ # SM: Accessing dataset.actions will cause a query to be emitted.
return self.permitted_actions.DATASET_ACCESS.action not in [ a.action for a in dataset.actions ]
+
+ def datasets_are_public( self, trans, datasets ):
+ '''
+ Given a transaction object and a list of Datasets, return
+ a mapping from Dataset ids to whether the Dataset is public
+ or not. All Dataset ids should be returned in the mapping's keys.
+ '''
+ # We go the other way around from dataset_is_public: we start with
+ # all datasets being marked as public. If there is an access action
+ # associated with the dataset, then we mark it as nonpublic:
+ datasets_public = {}
+ dataset_ids = [ dataset.id for dataset in datasets ]
+ for dataset_id in dataset_ids:
+ datasets_public[ dataset_id ] = True
+
+ # Now get all datasets which have DATASET_ACCESS actions:
+ access_data_perms = trans.sa_session.query( trans.app.model.DatasetPermissions ) \
+ .filter( and_( trans.app.model.DatasetPermissions.dataset_id in dataset_ids,
+ trans.app.model.DatasetPermissions.action == self.permitted_actions.DATASET_ACCESS.action ) ) \
+ .all()
+
+ # Every dataset returned has "access" privileges associated with it,
+ # so it's not public.
+ for permission in access_data_perms:
+ datasets_public[ permission.dataset_id ] = False
+
+ # Test code: Check if the results match up with the original:
+ test_code = False
+ if test_code:
+ log.debug( "datasets_are_public test: check datasets_are_public matches dataset_is_public:" )
+ test_success = True
+ for dataset in datasets:
+ orig_is_public = self.dataset_is_public( dataset )
+ if orig_is_public == datasets_public[ dataset.id ]:
+ log.debug( "\tMatch for dataset %d" % dataset.id )
+ else:
+ success = False
+ log.error( "\tERROR: Did not match: single is_public: %s; multiple is_public: %s"
+ % ( single_is_public, datasets_public[ dataset.id ] ) )
+ log.debug( "datasets_are_public: test succeeded? %s" % test_success )
+ return datasets_public
+
+
def make_dataset_public( self, dataset ):
# A dataset is considered public if there are no "access" actions associated with it. Any
# other actions ( 'manage permissions', 'edit metadata' ) are irrelevant.
@@ -707,7 +941,7 @@
# Ensure that roles being associated with DATASET_ACCESS are a subset of the legitimate roles
# derived from the roles associated with the access permission on item if it's not public. This
# will keep ill-legitimate roles from being associated with the DATASET_ACCESS permission on the
- # dataset (i.e., in the case where item is a library, if Role1 is associated with LIBRARY_ACCESS,
+ # dataset (i.e., in the case where item is .a library, if Role1 is associated with LIBRARY_ACCESS,
# then only those users that have Role1 should be associated with DATASET_ACCESS.
legitimate_roles = self.get_legitimate_roles( trans, item, cntrller )
ill_legitimate_roles = []
@@ -944,12 +1178,21 @@
if self.can_add_library_item( roles, folder ):
return True, ''
action = self.permitted_actions.DATASET_ACCESS
+
+ # SM: TODO: This is for timing debug. Delete it later.
+ from datetime import datetime, timedelta
+ query_start = datetime.now()
lddas = self.sa_session.query( self.model.LibraryDatasetDatasetAssociation ) \
.join( "library_dataset" ) \
.filter( self.model.LibraryDataset.folder == folder ) \
.join( "dataset" ) \
.options( eagerload_all( "dataset.actions" ) ) \
.all()
+ query_end = datetime.now()
+ query_delta = query_end - query_start
+ #log.debug( "Check folder contents: join query time: %d.%.6d sec" %
+ # ( query_delta.seconds, query_delta.microseconds ) )
+
for ldda in lddas:
ldda_access_permissions = self.get_item_actions( action, ldda.dataset )
if not ldda_access_permissions:
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/tool_shed/install_manager.py
--- a/lib/galaxy/tool_shed/install_manager.py
+++ b/lib/galaxy/tool_shed/install_manager.py
@@ -133,6 +133,7 @@
for k, v in tool_panel_dict_for_tool_config.items():
tool_panel_dict_for_display[ k ] = v
metadata_dict, invalid_file_tups = generate_metadata_for_changeset_revision( app=self.app,
+ repository=tool_shed_repository,
repository_clone_url=repository_clone_url,
relative_install_dir=relative_install_dir,
repository_files_dir=None,
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -159,6 +159,8 @@
else:
panel_dict = panel_component
already_loaded = False
+ loaded_version_key = None
+ lineage_id = None
for lineage_id in tool.lineage_ids:
if lineage_id in self.tools_by_id:
loaded_version_key = 'tool_%s' % lineage_id
@@ -176,7 +178,13 @@
if not inserted:
# If the tool is not defined in integrated_tool_panel.xml, append it to the tool panel.
panel_dict[ key ] = tool
- log.debug( "Loaded tool id: %s, version: %s." % ( tool.id, tool.version ) )
+ log.debug( "Loaded tool id: %s, version: %s into tool panel." % ( tool.id, tool.version ) )
+ elif tool.lineage_ids.index( tool_id ) > tool.lineage_ids.index( lineage_id ):
+ key = 'tool_%s' % tool.id
+ index = panel_dict.keys().index( loaded_version_key )
+ del panel_dict[ loaded_version_key ]
+ panel_dict.insert( index, key, tool )
+ log.debug( "Loaded tool id: %s, version: %s into tool panel." % ( tool.id, tool.version ) )
def load_tool_panel( self ):
for key, val in self.integrated_tool_panel.items():
if key.startswith( 'tool_' ):
@@ -255,70 +263,68 @@
os.write( fd, '<?xml version="1.0"?>\n' )
os.write( fd, '<toolbox>\n' )
for key, item in self.integrated_tool_panel.items():
- if key.startswith( 'tool_' ):
- if item:
+ if item:
+ if key.startswith( 'tool_' ):
os.write( fd, ' <tool id="%s" />\n' % item.id )
- elif key.startswith( 'workflow_' ):
- if item:
+ elif key.startswith( 'workflow_' ):
os.write( fd, ' <workflow id="%s" />\n' % item.id )
- elif key.startswith( 'label_' ):
- label_id = item.id or ''
- label_text = item.text or ''
- label_version = item.version or ''
- os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
- elif key.startswith( 'section_' ):
- section_id = item.id or ''
- section_name = item.name or ''
- section_version = item.version or ''
- os.write( fd, ' <section id="%s" name="%s" version="%s">\n' % ( section_id, section_name, section_version ) )
- for section_key, section_item in item.elems.items():
- if section_key.startswith( 'tool_' ):
- if section_item:
- os.write( fd, ' <tool id="%s" />\n' % section_item.id )
- elif section_key.startswith( 'workflow_' ):
- if section_item:
- os.write( fd, ' <workflow id="%s" />\n' % section_item.id )
- elif section_key.startswith( 'label_' ):
- if section_item:
- label_id = section_item.id or ''
- label_text = section_item.text or ''
- label_version = section_item.version or ''
- os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
- os.write( fd, ' </section>\n' )
+ elif key.startswith( 'label_' ):
+ label_id = item.id or ''
+ label_text = item.text or ''
+ label_version = item.version or ''
+ os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
+ elif key.startswith( 'section_' ):
+ section_id = item.id or ''
+ section_name = item.name or ''
+ section_version = item.version or ''
+ os.write( fd, ' <section id="%s" name="%s" version="%s">\n' % ( section_id, section_name, section_version ) )
+ for section_key, section_item in item.elems.items():
+ if section_key.startswith( 'tool_' ):
+ if section_item:
+ os.write( fd, ' <tool id="%s" />\n' % section_item.id )
+ elif section_key.startswith( 'workflow_' ):
+ if section_item:
+ os.write( fd, ' <workflow id="%s" />\n' % section_item.id )
+ elif section_key.startswith( 'label_' ):
+ if section_item:
+ label_id = section_item.id or ''
+ label_text = section_item.text or ''
+ label_version = section_item.version or ''
+ os.write( fd, ' <label id="%s" text="%s" version="%s" />\n' % ( label_id, label_text, label_version ) )
+ os.write( fd, ' </section>\n' )
os.write( fd, '</toolbox>\n' )
os.close( fd )
shutil.move( filename, os.path.abspath( self.integrated_tool_panel_config ) )
os.chmod( self.integrated_tool_panel_config, 0644 )
def get_tool( self, tool_id, tool_version=None, get_all_versions=False ):
"""Attempt to locate a tool in the tool box."""
- if tool_id in self.tools_by_id:
- tool = self.tools_by_id[ tool_id ]
- if tool_version and tool.version == tool_version:
- if get_all_versions:
- return [ tool ]
- else:
- return tool
- else:
- if get_all_versions:
- return [ tool ]
- else:
- return tool
+ if tool_id in self.tools_by_id and not get_all_versions:
+ #tool_id exactly matches an available tool by id (which is 'old' tool_id or guid)
+ return self.tools_by_id[ tool_id ]
+ #exact tool id match not found, or all versions requested, search for other options, e.g. migrated tools or different versions
+ rval = []
tv = self.__get_tool_version( tool_id )
if tv:
tool_version_ids = tv.get_version_ids( self.app )
- if get_all_versions:
- available_tool_versions = []
- for tool_version_id in tool_version_ids:
- if tool_version_id in self.tools_by_id:
- available_tool_versions.append( self.tools_by_id[ tool_version_id ] )
- return available_tool_versions
for tool_version_id in tool_version_ids:
if tool_version_id in self.tools_by_id:
- tool = self.tools_by_id[ tool_version_id ]
- if tool_version and tool.version == tool_version:
- return tool
- else:
- return tool
+ rval.append( self.tools_by_id[ tool_version_id ] )
+ if not rval:
+ #still no tool, do a deeper search and try to match by old ids
+ for tool in self.tools_by_id.itervalues():
+ if tool.old_id == tool_id:
+ rval.append( tool )
+ if rval:
+ if get_all_versions:
+ return rval
+ else:
+ if tool_version:
+ #return first tool with matching version
+ for tool in rval:
+ if tool.version == tool_version:
+ return tool
+ #No tool matches by version, simply return the first available tool found
+ return rval[0]
return None
def get_loaded_tools_by_lineage( self, tool_id ):
"""Get all loaded tools associated by lineage to the tool whose id is tool_id."""
@@ -381,7 +387,6 @@
tool.repository_owner = repository_owner
tool.installed_changeset_revision = installed_changeset_revision
tool.guid = guid
- tool.old_id = elem.find( "id" ).text
tool.version = elem.find( "version" ).text
# Make sure the tool has a tool_version.
if not self.__get_tool_version( tool.id ):
@@ -906,8 +911,9 @@
if not self.name:
raise Exception, "Missing tool 'name'"
# Get the UNIQUE id for the tool
+ self.old_id = root.get( "id" )
if guid is None:
- self.id = root.get( "id" )
+ self.id = self.old_id
else:
self.id = guid
if not self.id:
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/util/shed_util.py
--- a/lib/galaxy/util/shed_util.py
+++ b/lib/galaxy/util/shed_util.py
@@ -31,8 +31,8 @@
'&' : '&',
'\'' : ''' }
MAX_CONTENT_SIZE = 32768
+NOT_TOOL_CONFIGS = [ 'datatypes_conf.xml', 'tool_dependencies.xml' ]
VALID_CHARS = set( string.letters + string.digits + "'\"-=_.()/+*^,:?!#[]%\\$@;{}" )
-NOT_TOOL_CONFIGS = [ 'datatypes_conf.xml', 'tool_dependencies.xml' ]
class ShedCounter( object ):
def __init__( self, model ):
@@ -602,7 +602,7 @@
else:
tool_dependencies_dict[ 'set_environment' ] = [ requirements_dict ]
return tool_dependencies_dict
-def generate_metadata_for_changeset_revision( app, repository_clone_url, relative_install_dir=None, repository_files_dir=None,
+def generate_metadata_for_changeset_revision( app, repository, repository_clone_url, relative_install_dir=None, repository_files_dir=None,
resetting_all_metadata_on_repository=False, webapp='galaxy' ):
"""
Generate metadata for a repository using it's files on disk. To generate metadata for changeset revisions older than the repository tip,
@@ -610,6 +610,7 @@
disk files, so the value of repository_files_dir will not always be repository.repo_path (it could be an absolute path to a temporary directory
containing a clone). If it is an absolute path, the value of relative_install_dir must contain repository.repo_path.
"""
+ readme_file_names = get_readme_file_names( repository.name )
metadata_dict = {}
invalid_file_tups = []
invalid_tool_configs = []
@@ -653,14 +654,24 @@
tool_data_path=app.config.tool_data_path,
tool_data_table_config_path=app.config.tool_data_table_config_path,
persist=False )
- # Find all tool configs and exported workflows and add them to the repository's metadata.
for root, dirs, files in os.walk( files_dir ):
if root.find( '.hg' ) < 0 and root.find( 'hgrc' ) < 0:
if '.hg' in dirs:
dirs.remove( '.hg' )
for name in files:
- # Find all tool configs.
- if name not in NOT_TOOL_CONFIGS and name.endswith( '.xml' ):
+ # See if we have a READ_ME file.
+ if name.lower() in readme_file_names:
+ if resetting_all_metadata_on_repository:
+ full_path_to_readme = os.path.join( root, name )
+ stripped_path_to_readme = full_path_to_readme.replace( work_dir, '' )
+ if stripped_path_to_readme.startswith( '/' ):
+ stripped_path_to_readme = stripped_path_to_readme[ 1: ]
+ relative_path_to_readme = os.path.join( relative_install_dir, stripped_path_to_readme )
+ else:
+ relative_path_to_readme = os.path.join( root, name )
+ metadata_dict[ 'readme' ] = relative_path_to_readme
+ # See if we have a tool config.
+ elif name not in NOT_TOOL_CONFIGS and name.endswith( '.xml' ):
full_path = os.path.abspath( os.path.join( root, name ) )
if os.path.getsize( full_path ) > 0:
if not ( check_binary( full_path ) or check_image( full_path ) or check_gzip( full_path )[ 0 ]
@@ -699,7 +710,7 @@
else:
for tup in invalid_files_and_errors_tups:
invalid_file_tups.append( tup )
- # Find all exported workflows
+ # Find all exported workflows.
elif name.endswith( '.ga' ):
relative_path = os.path.join( root, name )
if os.path.getsize( os.path.abspath( relative_path ) ) > 0:
@@ -1195,6 +1206,25 @@
if contents:
contents.sort()
return contents
+def get_repository_metadata_by_changeset_revision( trans, id, changeset_revision ):
+ """Get metadata for a specified repository change set from the database"""
+ # Make sure there are no duplicate records, and return the single unique record for the changeset_revision. Duplicate records were somehow
+ # created in the past. The cause of this issue has been resolved, but we'll leave this method as is for a while longer to ensure all duplicate
+ # records are removed.
+ all_metadata_records = trans.sa_session.query( trans.model.RepositoryMetadata ) \
+ .filter( and_( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ),
+ trans.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
+ .order_by( trans.model.RepositoryMetadata.table.c.update_time.desc() ) \
+ .all()
+ if len( all_metadata_records ) > 1:
+ # Delete all recrds older than the last one updated.
+ for repository_metadata in all_metadata_records[ 1: ]:
+ trans.sa_session.delete( repository_metadata )
+ trans.sa_session.flush()
+ return all_metadata_records[ 0 ]
+ elif all_metadata_records:
+ return all_metadata_records[ 0 ]
+ return None
def get_repository_owner( cleaned_repository_url ):
items = cleaned_repository_url.split( 'repos' )
repo_path = items[ 1 ]
@@ -1405,6 +1435,13 @@
return shed_url
# The tool shed from which the repository was originally installed must no longer be configured in tool_sheds_conf.xml.
return None
+def get_readme_file_names( repository_name ):
+ readme_files = [ 'readme', 'read_me', 'install' ]
+ valid_filenames = [ r for r in readme_files ]
+ for r in readme_files:
+ valid_filenames.append( '%s.txt' % r )
+ valid_filenames.append( '%s.txt' % repository_name )
+ return valid_filenames
def handle_missing_data_table_entry( app, relative_install_dir, tool_path, repository_tools_tups ):
"""
Inspect each tool to see if any have input parameters that are dynamically generated select lists that require entries in the
@@ -1469,7 +1506,7 @@
error, message = handle_sample_tool_data_table_conf_file( trans.app, tool_data_table_config )
tool, valid, message2 = load_tool_from_config( trans.app, tool_config_filepath )
message = concat_messages( message, message2 )
- return tool, valid, message
+ return tool, valid, message, sample_files
def handle_sample_files_and_load_tool_from_tmp_config( trans, repo, changeset_revision, tool_config_filename, work_dir ):
tool = None
message = ''
@@ -1490,7 +1527,7 @@
if manifest_ctx and ctx_file:
tool, message2 = load_tool_from_tmp_config( trans, repo, manifest_ctx, ctx_file, work_dir )
message = concat_messages( message, message2 )
- return tool, message
+ return tool, message, sample_files
def handle_sample_tool_data_table_conf_file( app, filename, persist=False ):
"""
Parse the incoming filename and add new entries to the in-memory app.tool_data_tables dictionary. If persist is True (should only occur)
@@ -1907,6 +1944,19 @@
elif c not in [ '\r' ]:
translated.append( '' )
return ''.join( translated )
+def translate_string( raw_text, to_html=True ):
+ if raw_text:
+ if to_html:
+ if len( raw_text ) <= MAX_CONTENT_SIZE:
+ translated_string = to_html_str( raw_text )
+ else:
+ large_str = '\nFile contents truncated because file size is larger than maximum viewing size of %s\n' % util.nice_size( MAX_CONTENT_SIZE )
+ translated_string = to_html_str( '%s%s' % ( raw_text[ 0:MAX_CONTENT_SIZE ], large_str ) )
+ else:
+ raise Exception( "String translation currently only supports text to HTML." )
+ else:
+ translated_string = ''
+ return translated_string
def update_repository( repo, ctx_rev=None ):
"""
Update the cloned repository to changeset_revision. It is critical that the installed repository is updated to the desired
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/visualization/data_providers/basic.py
--- a/lib/galaxy/visualization/data_providers/basic.py
+++ b/lib/galaxy/visualization/data_providers/basic.py
@@ -109,10 +109,10 @@
""" Cast value based on type. """
if type == 'int':
try: val = int( val )
- except: pass
+ except: return None
elif type == 'float':
try: val = float( val )
- except: pass
+ except: return None
return val
f = open( self.original_dataset.file_name )
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/visualization/data_providers/genome.py
--- a/lib/galaxy/visualization/data_providers/genome.py
+++ b/lib/galaxy/visualization/data_providers/genome.py
@@ -113,7 +113,7 @@
class GenomeDataProvider( BaseDataProvider ):
""" Base class for genome data providers. """
- data_type = None
+ dataset_type = None
"""
Mapping from column name to payload data; this mapping is used to create
@@ -179,12 +179,21 @@
"""
Returns data for complete genome.
"""
- dataset_summary = []
+ genome_data = []
for chrom_info in chroms_info[ 'chrom_info' ]:
- summary = self.get_data( chrom_info[ 'chrom' ], 0, chrom_info[ 'len' ], **kwargs )
- dataset_summary.append( summary )
+ chrom = chrom_info[ 'chrom' ]
+ chrom_len = chrom_info[ 'len' ]
+ chrom_data = self.get_data( chrom, 0, chrom_len, **kwargs )
+ if chrom_data:
+ chrom_data[ 'region' ] = "%s:%i-%i" % ( chrom, 0, chrom_len )
+ chrom_data[ 'dataset_type' ] = self.dataset_type
+ genome_data.append( chrom_data )
- return dataset_summary
+ return {
+ 'data': genome_data,
+ 'dataset_type': self.dataset_type
+ }
+
def get_filters( self ):
"""
@@ -316,7 +325,7 @@
class TabixDataProvider( FilterableMixin, GenomeDataProvider ):
- data_type = 'tabix'
+ dataset_type = 'tabix'
"""
Tabix index data provider for the Galaxy track browser.
@@ -358,7 +367,7 @@
#
class IntervalDataProvider( GenomeDataProvider ):
- data_type = 'interval_index'
+ dataset_type = 'interval_index'
"""
Processes interval data from native format to payload format.
@@ -444,7 +453,7 @@
Payload format: [ uid (offset), start, end, name, strand, thick_start, thick_end, blocks ]
"""
- data_type = 'interval_index'
+ dataset_type = 'interval_index'
def get_iterator( self, chrom, start, end ):
raise Exception( "Unimplemented Method" )
@@ -541,7 +550,7 @@
for large datasets.
"""
- data_type = 'interval_index'
+ dataset_type = 'interval_index'
def get_iterator( self, chrom=None, start=None, end=None ):
# Read first line in order to match chrom naming format.
@@ -581,7 +590,7 @@
col_name_data_attr_mapping = { 'Qual' : { 'index': 6 , 'name' : 'Qual' } }
- data_type = 'bai'
+ dataset_type = 'bai'
def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ):
"""
@@ -687,7 +696,7 @@
for large datasets.
"""
- data_type = 'tabix'
+ dataset_type = 'tabix'
def get_iterator( self, chrom, start, end ):
# Read first line in order to match chrom naming format.
@@ -721,7 +730,7 @@
Summary tree data provider for the Galaxy track browser.
"""
- data_type = 'summary_tree'
+ dataset_type = 'summary_tree'
CACHE = LRUCache( 20 ) # Store 20 recently accessed indices for performance
@@ -729,7 +738,7 @@
st = summary_tree_from_file( self.converted_dataset.file_name )
return st.chrom_blocks.keys()
- def get_data( self, chrom, start, end, level=None, resolution=None, detail_cutoff=None, draw_cutoff=None ):
+ def get_data( self, chrom, start, end, level=None, resolution=None, detail_cutoff=None, draw_cutoff=None, **kwargs ):
"""
Returns summary tree data for a given genomic region.
"""
@@ -765,7 +774,14 @@
if results == "detail" or results == "draw":
return results
else:
- return results, stats[ level ][ "max" ], stats[ level ]["avg" ], stats[ level ][ "delta" ]
+ return {
+ 'dataset_type': self.dataset_type,
+ 'data': results,
+ 'max': stats[ level ][ "max" ],
+ 'avg': stats[ level ][ "avg" ],
+ 'delta': stats[ level ][ "delta" ],
+ 'level': level
+ }
def has_data( self, chrom ):
"""
@@ -788,7 +804,7 @@
is reported in 1-based, closed format, i.e. SAM/BAM format.
"""
- data_type = 'bai'
+ dataset_type = 'bai'
def get_filters( self ):
"""
@@ -970,7 +986,7 @@
class SamDataProvider( BamDataProvider ):
- data_type = 'bai'
+ dataset_type = 'bai'
def __init__( self, converted_dataset=None, original_dataset=None, dependencies=None ):
""" Create SamDataProvider. """
@@ -987,7 +1003,7 @@
BBI data provider for the Galaxy track browser.
"""
- data_type = 'bigwig'
+ dataset_type = 'bigwig'
def valid_chroms( self ):
# No way to return this info as of now
@@ -1000,6 +1016,9 @@
return all_dat is not None
def get_data( self, chrom, start, end, start_val=0, max_vals=None, num_samples=1000, **kwargs ):
+ start = int( start )
+ end = int( end )
+
# Bigwig can be a standalone bigwig file, in which case we use
# original_dataset, or coming from wig->bigwig conversion in
# which we use converted_dataset
@@ -1096,7 +1115,10 @@
# Cleanup and return.
f.close()
- return { 'data': result }
+ return {
+ 'data': result,
+ 'dataset_type': self.dataset_type
+ }
class BigBedDataProvider( BBIDataProvider ):
def _get_dataset( self ):
@@ -1122,7 +1144,7 @@
"""
col_name_data_attr_mapping = { 4 : { 'index': 4 , 'name' : 'Score' } }
- data_type = 'interval_index'
+ dataset_type = 'interval_index'
def write_data_to_file( self, regions, filename ):
source = open( self.original_dataset.file_name )
@@ -1201,7 +1223,7 @@
for large datasets.
"""
- data_type = 'interval_index'
+ dataset_type = 'interval_index'
def get_iterator( self, chrom, start, end ):
"""
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/community/controllers/common.py
--- a/lib/galaxy/webapps/community/controllers/common.py
+++ b/lib/galaxy/webapps/community/controllers/common.py
@@ -5,11 +5,11 @@
from galaxy.tools import *
from galaxy.util.json import from_json_string, to_json_string
from galaxy.util.hash_util import *
-from galaxy.util.shed_util import check_tool_input_params, clone_repository, copy_sample_file, generate_metadata_for_changeset_revision
+from galaxy.util.shed_util import check_tool_input_params, clone_repository, concat_messages, copy_sample_file, generate_metadata_for_changeset_revision
from galaxy.util.shed_util import get_changectx_for_changeset, get_config_from_disk, get_configured_ui, get_file_context_from_ctx, get_named_tmpfile_from_ctx
-from galaxy.util.shed_util import handle_sample_files_and_load_tool_from_disk, handle_sample_files_and_load_tool_from_tmp_config
-from galaxy.util.shed_util import handle_sample_tool_data_table_conf_file, INITIAL_CHANGELOG_HASH, load_tool_from_config, reset_tool_data_tables
-from galaxy.util.shed_util import reversed_upper_bounded_changelog, strip_path
+from galaxy.util.shed_util import get_repository_metadata_by_changeset_revision, handle_sample_files_and_load_tool_from_disk
+from galaxy.util.shed_util import handle_sample_files_and_load_tool_from_tmp_config, handle_sample_tool_data_table_conf_file, INITIAL_CHANGELOG_HASH
+from galaxy.util.shed_util import load_tool_from_config, reset_tool_data_tables, reversed_upper_bounded_changelog, strip_path
from galaxy.web.base.controller import *
from galaxy.webapps.community import model
from galaxy.model.orm import *
@@ -473,25 +473,6 @@
.filter( and_( trans.model.Repository.table.c.name == name,
trans.model.Repository.table.c.user_id == user.id ) ) \
.first()
-def get_repository_metadata_by_changeset_revision( trans, id, changeset_revision ):
- """Get metadata for a specified repository change set from the database"""
- # Make sure there are no duplicate records, and return the single unique record for the changeset_revision. Duplicate records were somehow
- # created in the past. The cause of this issue has been resolved, but we'll leave this method as is for a while longer to ensure all duplicate
- # records are removed.
- all_metadata_records = trans.sa_session.query( trans.model.RepositoryMetadata ) \
- .filter( and_( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ),
- trans.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
- .order_by( trans.model.RepositoryMetadata.table.c.update_time.desc() ) \
- .all()
- if len( all_metadata_records ) > 1:
- # Delete all recrds older than the last one updated.
- for repository_metadata in all_metadata_records[ 1: ]:
- trans.sa_session.delete( repository_metadata )
- trans.sa_session.flush()
- return all_metadata_records[ 0 ]
- elif all_metadata_records:
- return all_metadata_records[ 0 ]
- return None
def get_repository_metadata_by_id( trans, id ):
"""Get repository metadata from the database"""
return trans.sa_session.query( trans.model.RepositoryMetadata ).get( trans.security.decode_id( id ) )
@@ -620,7 +601,7 @@
can_use_disk_file = can_use_tool_config_disk_file( trans, repository, repo, tool_config_filepath, changeset_revision )
if can_use_disk_file:
trans.app.config.tool_data_path = work_dir
- tool, valid, message = handle_sample_files_and_load_tool_from_disk( trans, repo_files_dir, tool_config_filepath, work_dir )
+ tool, valid, message, sample_files = handle_sample_files_and_load_tool_from_disk( trans, repo_files_dir, tool_config_filepath, work_dir )
if tool is not None:
invalid_files_and_errors_tups = check_tool_input_params( trans.app,
repo_files_dir,
@@ -637,7 +618,7 @@
message = concat_messages( message, message2 )
status = 'error'
else:
- tool, message = handle_sample_files_and_load_tool_from_tmp_config( trans, repo, changeset_revision, tool_config_filename, work_dir )
+ tool, message, sample_files = handle_sample_files_and_load_tool_from_tmp_config( trans, repo, changeset_revision, tool_config_filename, work_dir )
try:
shutil.rmtree( work_dir )
except:
@@ -762,6 +743,7 @@
if cloned_ok:
log.debug( "Generating metadata for changset revision: %s", str( ctx.rev() ) )
current_metadata_dict, invalid_file_tups = generate_metadata_for_changeset_revision( app=trans.app,
+ repository=repository,
repository_clone_url=repository_clone_url,
relative_install_dir=repo_dir,
repository_files_dir=work_dir,
@@ -836,6 +818,7 @@
repo_dir = repository.repo_path
repo = hg.repository( get_configured_ui(), repo_dir )
metadata_dict, invalid_file_tups = generate_metadata_for_changeset_revision( app=trans.app,
+ repository=repository,
repository_clone_url=repository_clone_url,
relative_install_dir=repo_dir,
repository_files_dir=None,
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/community/controllers/repository.py
--- a/lib/galaxy/webapps/community/controllers/repository.py
+++ b/lib/galaxy/webapps/community/controllers/repository.py
@@ -10,9 +10,10 @@
from galaxy.util.json import from_json_string, to_json_string
from galaxy.model.orm import *
from galaxy.util.shed_util import create_repo_info_dict, get_changectx_for_changeset, get_configured_ui, get_repository_file_contents
-from galaxy.util.shed_util import handle_sample_files_and_load_tool_from_disk, handle_sample_files_and_load_tool_from_tmp_config, INITIAL_CHANGELOG_HASH
-from galaxy.util.shed_util import load_tool_from_config, NOT_TOOL_CONFIGS, open_repository_files_folder, reversed_lower_upper_bounded_changelog
-from galaxy.util.shed_util import reversed_upper_bounded_changelog, strip_path, to_html_escaped, update_repository, url_join
+from galaxy.util.shed_util import get_repository_metadata_by_changeset_revision, handle_sample_files_and_load_tool_from_disk
+from galaxy.util.shed_util import handle_sample_files_and_load_tool_from_tmp_config, INITIAL_CHANGELOG_HASH, load_tool_from_config, NOT_TOOL_CONFIGS
+from galaxy.util.shed_util import open_repository_files_folder, reversed_lower_upper_bounded_changelog, reversed_upper_bounded_changelog, strip_path
+from galaxy.util.shed_util import to_html_escaped, translate_string, update_repository, url_join
from galaxy.tool_shed.encoding_util import *
from common import *
@@ -23,7 +24,6 @@
log = logging.getLogger( __name__ )
VALID_REPOSITORYNAME_RE = re.compile( "^[a-z0-9\_]+$" )
-README_FILES = [ 'readme', 'read_me', 'install' ]
class CategoryListGrid( grids.Grid ):
class NameColumn( grids.TextColumn ):
@@ -613,8 +613,10 @@
# Update repository files for browsing.
update_repository( repo )
is_malicious = changeset_is_malicious( trans, id, repository.tip )
+ metadata = self.get_metadata( trans, id, repository.tip )
return trans.fill_template( '/webapps/community/repository/browse_repository.mako',
repository=repository,
+ metadata=metadata,
commit_message=commit_message,
is_malicious=is_malicious,
webapp=webapp,
@@ -828,9 +830,11 @@
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
repository = get_repository( trans, id )
+ metadata = self.get_metadata( trans, id, repository.tip )
if trans.user and trans.user.email:
return trans.fill_template( "/webapps/community/repository/contact_owner.mako",
repository=repository,
+ metadata=metadata,
message=message,
status=status )
else:
@@ -939,9 +943,11 @@
repository, tool, message = load_tool_from_changeset_revision( trans, repository_id, changeset_revision, tool_config )
tool_state = self.__new_state( trans )
is_malicious = changeset_is_malicious( trans, repository_id, repository.tip )
+ metadata = self.get_metadata( trans, repository_id, changeset_revision )
try:
return trans.fill_template( "/webapps/community/repository/tool_form.mako",
repository=repository,
+ metadata=metadata,
changeset_revision=changeset_revision,
tool=tool,
tool_state=tool_state,
@@ -1181,6 +1187,11 @@
trans.response.headers['Pragma'] = 'no-cache'
trans.response.headers['Expires'] = '0'
return get_repository_file_contents( file_path )
+ def get_metadata( self, trans, repository_id, changeset_revision ):
+ repository_metadata = get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision )
+ if repository_metadata and repository_metadata.metadata:
+ return repository_metadata.metadata
+ return None
@web.json
def get_repository_information( self, trans, repository_ids, changeset_revisions, **kwd ):
"""
@@ -1208,23 +1219,18 @@
return dict( includes_tools=includes_tools, includes_tool_dependencies=includes_tool_dependencies, repo_info_dicts=repo_info_dicts )
@web.expose
def get_readme( self, trans, **kwd ):
- """If the received changeset_revision includes a file named readme (case ignored), return it's contents."""
+ """If the received changeset_revision includes a readme file, return it's contents."""
repository_name = kwd[ 'name' ]
repository_owner = kwd[ 'owner' ]
changeset_revision = kwd[ 'changeset_revision' ]
- valid_filenames = [ r for r in README_FILES ]
- for r in README_FILES:
- valid_filenames.append( '%s.txt' % r )
- valid_filenames.append( '%s.txt' % repository_name )
repository = get_repository_by_name_and_owner( trans, repository_name, repository_owner )
- repo_dir = repository.repo_path
- for root, dirs, files in os.walk( repo_dir ):
- for name in files:
- if name.lower() in valid_filenames:
- f = open( os.path.join( root, name ), 'r' )
- text = f.read()
- f.close()
- return str( text )
+ repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), changeset_revision )
+ metadata = repository_metadata.metadata
+ if metadata and 'readme' in metadata:
+ f = open( metadata[ 'readme' ], 'r' )
+ text = f.read()
+ f.close()
+ return str( text )
return ''
@web.expose
def get_tool_dependencies( self, trans, **kwd ):
@@ -1829,8 +1835,10 @@
display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
rra = self.get_user_item_rating( trans.sa_session, trans.user, repository, webapp_model=trans.model )
is_malicious = changeset_is_malicious( trans, id, repository.tip )
+ metadata = self.get_metadata( trans, id, repository.tip )
return trans.fill_template( '/webapps/community/repository/rate_repository.mako',
repository=repository,
+ metadata=metadata,
avg_rating=avg_rating,
display_reviews=display_reviews,
num_ratings=num_ratings,
@@ -2156,8 +2164,10 @@
# Make sure we'll view latest changeset first.
changesets.insert( 0, change_dict )
is_malicious = changeset_is_malicious( trans, id, repository.tip )
+ metadata = self.get_metadata( trans, id, repository.tip )
return trans.fill_template( '/webapps/community/repository/view_changelog.mako',
repository=repository,
+ metadata=metadata,
changesets=changesets,
is_malicious=is_malicious,
message=message,
@@ -2185,8 +2195,10 @@
for diff in patch.diff( repo, node1=ctx_parent.node(), node2=ctx.node() ):
diffs.append( to_html_escaped( diff ) )
is_malicious = changeset_is_malicious( trans, id, repository.tip )
+ metadata = self.get_metadata( trans, id, ctx_str )
return trans.fill_template( '/webapps/community/repository/view_changeset.mako',
repository=repository,
+ metadata=metadata,
ctx=ctx,
anchors=anchors,
modified=modified,
@@ -2201,6 +2213,33 @@
message=message,
status=status )
@web.expose
+ def view_readme( self, trans, id, changeset_revision, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ cntrller = params.get( 'cntrller', 'repository' )
+ webapp = params.get( 'webapp', 'community' )
+ repository = get_repository( trans, id )
+ repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), changeset_revision )
+ metadata = repository_metadata.metadata
+ if metadata and 'readme' in metadata:
+ f = open( metadata[ 'readme' ], 'r' )
+ raw_text = f.read()
+ f.close()
+ readme_text = translate_string( raw_text, to_html=True )
+ else:
+ readme_text = ''
+ is_malicious = changeset_is_malicious( trans, id, changeset_revision )
+ return trans.fill_template( '/webapps/community/common/view_readme.mako',
+ cntrller=cntrller,
+ repository=repository,
+ changeset_revision=changeset_revision,
+ readme_text=readme_text,
+ is_malicious=is_malicious,
+ webapp=webapp,
+ message=message,
+ status=status )
+ @web.expose
def view_repository( self, trans, id, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
@@ -2301,16 +2340,25 @@
can_use_disk_file = can_use_tool_config_disk_file( trans, repository, repo, full_path_to_tool_config, changeset_revision )
if can_use_disk_file:
trans.app.config.tool_data_path = work_dir
- tool, valid, message = handle_sample_files_and_load_tool_from_disk( trans, repo_files_dir, full_path_to_tool_config, work_dir )
+ tool, valid, message, sample_files = handle_sample_files_and_load_tool_from_disk( trans,
+ repo_files_dir,
+ full_path_to_tool_config,
+ work_dir )
if message:
status = 'error'
else:
- tool, message = handle_sample_files_and_load_tool_from_tmp_config( trans, repo, changeset_revision, tool_config_filename, work_dir )
+ tool, message, sample_files = handle_sample_files_and_load_tool_from_tmp_config( trans,
+ repo,
+ changeset_revision,
+ tool_config_filename,
+ work_dir )
if message:
status = 'error'
break
if guid:
tool_lineage = self.get_versions_of_tool( trans, repository, repository_metadata, guid )
+ else:
+ metadata = None
is_malicious = changeset_is_malicious( trans, repository_id, repository.tip )
changeset_revision_select_field = build_changeset_revision_select_field( trans,
repository,
@@ -2320,6 +2368,7 @@
trans.app.config.tool_data_path = original_tool_data_path
return trans.fill_template( "/webapps/community/repository/view_tool_metadata.mako",
repository=repository,
+ metadata=metadata,
tool=tool,
tool_metadata_dict=tool_metadata_dict,
tool_lineage=tool_lineage,
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/community/controllers/workflow.py
--- a/lib/galaxy/webapps/community/controllers/workflow.py
+++ b/lib/galaxy/webapps/community/controllers/workflow.py
@@ -151,6 +151,7 @@
changeset_revision=repository_metadata.changeset_revision,
repository_metadata_id=repository_metadata_id,
workflow_name=workflow_name,
+ metadata=repository_metadata,
webapp=webapp,
message=message,
status=status )
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/main/api/datasets.py
--- a/lib/galaxy/webapps/main/api/datasets.py
+++ b/lib/galaxy/webapps/main/api/datasets.py
@@ -139,12 +139,11 @@
if mode == "Coverage":
# Get summary using minimal cutoffs.
indexer = data_provider_registry.get_data_provider( trans, original_dataset=dataset, source='index' )
- summary = indexer.get_data( chrom, low, high, resolution=kwargs[ 'resolution' ], detail_cutoff=0, draw_cutoff=0 )
+ summary = indexer.get_data( chrom, low, high, detail_cutoff=0, draw_cutoff=0, **kwargs )
if summary == "detail":
# Use maximum level of detail--2--to get summary data no matter the resolution.
summary = indexer.get_data( chrom, low, high, resolution=kwargs[ 'resolution' ], level=2, detail_cutoff=0, draw_cutoff=0 )
- frequencies, max_v, avg_v, delta = summary
- return { 'dataset_type': indexer.data_type, 'data': frequencies, 'max': max_v, 'avg': avg_v, 'delta': delta }
+ return summary
if 'index' in data_sources and data_sources['index']['name'] == "summary_tree" and mode == "Auto":
# Only check for summary_tree if it's Auto mode (which is the default)
@@ -153,14 +152,13 @@
indexer = data_provider_registry.get_data_provider( trans, original_dataset=dataset, source='index' )
summary = indexer.get_data( chrom, low, high, resolution=kwargs[ 'resolution' ] )
if summary is None:
- return { 'dataset_type': indexer.data_type, 'data': None }
+ return { 'dataset_type': indexer.dataset_type, 'data': None }
if summary == "draw":
kwargs["no_detail"] = True # meh
extra_info = "no_detail"
elif summary != "detail":
- frequencies, max_v, avg_v, delta = summary
- return { 'dataset_type': indexer.data_type, 'data': frequencies, 'max': max_v, 'avg': avg_v, 'delta': delta }
+ return summary
# Get data provider.
data_provider = data_provider_registry.get_data_provider( trans, original_dataset=dataset, source='data' )
@@ -171,7 +169,7 @@
# Get and return data from data_provider.
result = data_provider.get_data( chrom, int( low ), int( high ), int( start_val ), int( max_vals ), **kwargs )
- result.update( { 'dataset_type': data_provider.data_type, 'extra_info': extra_info } )
+ result.update( { 'dataset_type': data_provider.dataset_type, 'extra_info': extra_info } )
return result
def _raw_data( self, trans, dataset, **kwargs ):
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/main/controllers/admin_toolshed.py
--- a/lib/galaxy/webapps/main/controllers/admin_toolshed.py
+++ b/lib/galaxy/webapps/main/controllers/admin_toolshed.py
@@ -342,8 +342,10 @@
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
repository = get_repository( trans, kwd[ 'id' ] )
+ has_readme = repository.metadata and 'readme' in repository.metadata
return trans.fill_template( '/admin/tool_shed_repository/browse_repository.mako',
repository=repository,
+ has_readme=has_readme,
message=message,
status=status )
@web.expose
@@ -388,8 +390,11 @@
message += "Choose <b>Uninstall this tool dependency</b> from the <b>Repository Actions</b> menu, correct problems "
message += "if necessary, and try installing the dependency again."
status = "error"
+ tool_shed_repository = tool_dependency.tool_shed_repository
+ has_readme = tool_shed_repository.metadata and 'readme' in tool_shed_repository.metadata
return trans.fill_template( '/admin/tool_shed_repository/browse_tool_dependency.mako',
- repository=tool_dependency.tool_shed_repository,
+ repository=tool_shed_repository,
+ has_readme=has_readme,
tool_dependency=tool_dependency,
in_error_state=in_error_state,
can_uninstall=can_uninstall,
@@ -487,9 +492,11 @@
message=message,
status=status ) )
remove_from_disk_check_box = CheckboxField( 'remove_from_disk', checked=remove_from_disk_checked )
+ has_readme = tool_shed_repository.metadata and 'readme' in tool_shed_repository.metadata
return trans.fill_template( '/admin/tool_shed_repository/deactivate_or_uninstall_repository.mako',
repository=tool_shed_repository,
remove_from_disk_check_box=remove_from_disk_check_box,
+ has_readme=has_readme,
message=message,
status=status )
@web.expose
@@ -716,6 +723,7 @@
when an admin is installing a new repository or reinstalling an uninstalled repository.
"""
metadata_dict, invalid_file_tups = generate_metadata_for_changeset_revision( app=trans.app,
+ repository=tool_shed_repository,
repository_clone_url=repository_clone_url,
relative_install_dir=relative_install_dir,
repository_files_dir=None,
@@ -837,8 +845,10 @@
trans.sa_session.add( repository )
trans.sa_session.flush()
message = "Repository metadata has been reset."
+ has_readme = repository.metadata and 'readme' in repository.metadata
return trans.fill_template( '/admin/tool_shed_repository/manage_repository.mako',
repository=repository,
+ has_readme=has_readme,
in_error_state=in_error_state,
can_install=can_install,
description=description,
@@ -914,14 +924,14 @@
tool_shed_repository = tool_dependency.tool_shed_repository
self.tool_dependency_grid.title = "Tool shed repository '%s' tool dependencies" % tool_shed_repository.name
self.tool_dependency_grid.global_actions = \
- [ grids.GridAction( label='Browse repository',
+ [ grids.GridAction( label='Manage repository',
+ url_args=dict( controller='admin_toolshed',
+ action='manage_repository',
+ id=trans.security.encode_id( tool_shed_repository.id ) ) ),
+ grids.GridAction( label='Browse repository',
url_args=dict( controller='admin_toolshed',
action='browse_repository',
id=trans.security.encode_id( tool_shed_repository.id ) ) ),
- grids.GridAction( label='Manage repository',
- url_args=dict( controller='admin_toolshed',
- action='manage_repository',
- id=trans.security.encode_id( tool_shed_repository.id ) ) ),
grids.GridAction( label='Get repository updates',
url_args=dict( controller='admin_toolshed',
action='check_for_updates',
@@ -934,6 +944,12 @@
url_args=dict( controller='admin_toolshed',
action='deactivate_or_uninstall_repository',
id=trans.security.encode_id( tool_shed_repository.id ) ) ) ]
+ if tool_shed_repository.metadata and 'readme' in tool_shed_repository.metadata:
+ view_readme_action = grids.GridAction( label='View README',
+ url_args=dict( controller='admin_toolshed',
+ action='view_readme',
+ id=trans.security.encode_id( tool_shed_repository.id ) ) )
+ self.tool_dependency_grid.global_actions.insert( 1, view_readme_action )
if 'operation' in kwd:
operation = kwd[ 'operation' ].lower()
if not tool_dependency_ids:
@@ -1010,9 +1026,8 @@
message += '<b><toolbox></b> tag that includes a <b>tool_path</b> attribute value which is a directory relative to the Galaxy installation '
message += 'directory in order to automatically install tools from a Galaxy tool shed (e.g., the file name <b>shed_tool_conf.xml</b> whose '
message += '<b><toolbox></b> tag is <b><toolbox tool_path="../shed_tools"></b>).<p/>See the '
- message += '<a href="http://wiki.g2.bx.psu.edu/Tool%20Shed#Automatic_installation_of_Galaxy_tool… " '
- message += 'target="_blank">Automatic installation of Galaxy tool shed repository tools into a local Galaxy instance</a> section of the '
- message += '<a href="http://wiki.g2.bx.psu.edu/Tool%20Shed " target="_blank">Galaxy tool shed wiki</a> for all of the details.'
+ message += '<a href="http://wiki.g2.bx.psu.edu/InstallingRepositoriesToGalaxy " target="_blank">Installation of Galaxy tool shed repository tools '
+ message += 'into a local Galaxy instance</a> section of the Galaxy tool shed wiki for all of the details.'
return trans.show_error_message( message )
message = kwd.get( 'message', '' )
status = kwd.get( 'status', 'done' )
@@ -1182,13 +1197,7 @@
response = urllib2.urlopen( url )
raw_text = response.read()
response.close()
- readme_text = ''
- for i, line in enumerate( raw_text ):
- readme_text = '%s%s' % ( readme_text, to_html_str( line ) )
- if len( readme_text ) > MAX_CONTENT_SIZE:
- large_str = '\nFile contents truncated because file size is larger than maximum viewing size of %s\n' % util.nice_size( MAX_CONTENT_SIZE )
- readme_text = '%s%s' % ( readme_text, to_html_str( large_str ) )
- break
+ readme_text = translate_string( raw_text, to_html=True )
else:
readme_text = ''
if trans.app.config.tool_dependency_dir is None:
@@ -1419,8 +1428,19 @@
status = 'warning'
includes_tool_dependencies = 'tool_dependencies' in metadata
install_tool_dependencies_check_box = CheckboxField( 'install_tool_dependencies', checked=True )
+ if metadata and 'readme' in metadata:
+ url = url_join( tool_shed_url,
+ 'repository/get_readme?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
+ ( repository.name, repository.owner, repository.installed_changeset_revision ) )
+ response = urllib2.urlopen( url )
+ raw_text = response.read()
+ response.close()
+ readme_text = translate_string( raw_text, to_html=True )
+ else:
+ readme_text = ''
return trans.fill_template( '/admin/tool_shed_repository/reselect_tool_panel_section.mako',
repository=repository,
+ readme_text=readme_text,
no_changes_check_box=no_changes_check_box,
original_section_name=original_section_name,
install_tool_dependencies_check_box=install_tool_dependencies_check_box,
@@ -1528,6 +1548,8 @@
for tool_dependency_id in tool_dependency_ids:
tool_dependency = get_tool_dependency( trans, tool_dependency_id )
tool_dependencies.append( tool_dependency )
+ tool_shed_repository = tool_dependencies[ 0 ].tool_shed_repository
+ has_readme = tool_shed_repository.metadata and 'readme' in tool_shed_repository.metadata
if kwd.get( 'uninstall_tool_dependencies_button', False ):
errors = False
# Filter tool dependencies to only those that are installed.
@@ -1546,7 +1568,6 @@
status = 'error'
else:
message = "These tool dependencies have been uninstalled: %s" % ','.join( td.name for td in tool_dependencies_for_uninstallation )
- tool_shed_repository = tool_dependencies[ 0 ].tool_shed_repository
td_ids = [ trans.security.encode_id( td.id ) for td in tool_shed_repository.tool_dependencies ]
return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
action='manage_tool_dependencies',
@@ -1554,6 +1575,9 @@
status=status,
message=message ) )
return trans.fill_template( '/admin/tool_shed_repository/uninstall_tool_dependencies.mako',
+ repository=tool_shed_repository,
+ has_readme=has_readme,
+ tool_dependency_ids=tool_dependency_ids,
tool_dependencies=tool_dependencies,
message=message,
status=status )
@@ -1617,27 +1641,55 @@
status=status ) )
@web.expose
@web.require_admin
+ def view_readme( self, trans, id, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ cntrller = params.get( 'cntrller', 'admin_toolshed' )
+ status = params.get( 'status', 'done' )
+ repository = get_repository( trans, id )
+ metadata = repository.metadata
+ if metadata and 'readme' in metadata:
+ f = open( metadata[ 'readme' ], 'r' )
+ raw_text = f.read()
+ f.close()
+ readme_text = translate_string( raw_text, to_html=True )
+ else:
+ readme_text = ''
+ is_malicious = False
+ return trans.fill_template( '/webapps/community/common/view_readme.mako',
+ cntrller=cntrller,
+ repository=repository,
+ changeset_revision=repository.changeset_revision,
+ readme_text=readme_text,
+ is_malicious=is_malicious,
+ webapp='galaxy',
+ message=message,
+ status=status )
+ @web.expose
+ @web.require_admin
def view_tool_metadata( self, trans, repository_id, tool_id, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
webapp = get_webapp( trans, **kwd )
repository = get_repository( trans, repository_id )
- metadata = {}
+ repository_metadata = repository.metadata
+ tool_metadata = {}
tool_lineage = []
tool = None
- if 'tools' in repository.metadata:
- for tool_metadata_dict in repository.metadata[ 'tools' ]:
+ if 'tools' in repository_metadata:
+ for tool_metadata_dict in repository_metadata[ 'tools' ]:
if tool_metadata_dict[ 'id' ] == tool_id:
- metadata = tool_metadata_dict
- tool = trans.app.toolbox.load_tool( os.path.abspath( metadata[ 'tool_config' ] ), guid=metadata[ 'guid' ] )
+ tool_metadata = tool_metadata_dict
+ tool = trans.app.toolbox.load_tool( os.path.abspath( tool_metadata[ 'tool_config' ] ), guid=metadata[ 'guid' ] )
if tool:
tool_lineage = self.get_versions_of_tool( trans.app, tool.id )
break
return trans.fill_template( "/admin/tool_shed_repository/view_tool_metadata.mako",
repository=repository,
+ repository_metadata=repository_metadata,
tool=tool,
- metadata=metadata,
+ tool_metadata=tool_metadata,
tool_lineage=tool_lineage,
message=message,
status=status )
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/main/controllers/library_common.py
--- a/lib/galaxy/webapps/main/controllers/library_common.py
+++ b/lib/galaxy/webapps/main/controllers/library_common.py
@@ -92,6 +92,7 @@
#"force_history_refresh": force_history_refresh
}
return rval
+
@web.expose
def browse_library( self, trans, cntrller, **kwd ):
params = util.Params( kwd )
@@ -129,6 +130,7 @@
status = "info"
comptypes = get_comptypes( trans )
try:
+ # SM: TODO: Add configuration variable asap.
return trans.fill_template( '/library/common/browse_library.mako',
cntrller=cntrller,
use_panels=use_panels,
@@ -144,6 +146,7 @@
message = 'Error attempting to display contents of library (%s): %s.' % ( str( library.name ), str( e ) )
status = 'error'
default_action = params.get( 'default_action', None )
+
return trans.response.send_redirect( web.url_for( use_panels=use_panels,
controller=cntrller,
action='browse_libraries',
@@ -2534,16 +2537,57 @@
.options( eagerload_all( "actions" ) ) \
.order_by( trans.app.model.LibraryFolder.table.c.name ) \
.all()
+
+def map_library_datasets_to_lddas( trans, lib_datasets ):
+ '''
+ Given a list of LibraryDatasets, return a map from the LibraryDatasets
+ to their LDDAs. If an LDDA does not exist for a LibraryDataset, then
+ there will be no entry in the return hash.
+ '''
+ # Get a list of the LibraryDatasets' ids so that we can pass it along to
+ # a query to retrieve the LDDAs. This eliminates querying for each
+ # LibraryDataset.
+ lib_dataset_ids = [ x.library_dataset_dataset_association_id for x in lib_datasets ]
+ lddas = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
+ .filter( trans.app.model.LibraryDatasetDatasetAssociation.id.in_( lib_dataset_ids ) ) \
+ .all()
+
+ # Map the LibraryDataset to the returned LDDAs:
+ ret_lddas = {}
+ for ldda in lddas:
+ ret_lddas[ldda.library_dataset_id] = ldda
+ return ret_lddas
+
+def datasets_for_lddas( trans, lddas ):
+ '''
+ Given a list of LDDAs, return a list of Datasets for them.
+ '''
+ dataset_ids = [ x.dataset_id for x in lddas ]
+ datasets = trans.sa_session.query( trans.app.model.Dataset ) \
+ .filter( trans.app.model.Dataset.id.in_( dataset_ids ) ) \
+ .all()
+ return datasets
+
def active_folders_and_library_datasets( trans, folder ):
+ # SM: TODO: Eliminate timing code
+ from datetime import datetime, timedelta
+ query_start = datetime.now()
folders = active_folders( trans, folder )
library_datasets = trans.sa_session.query( trans.model.LibraryDataset ) \
.filter( and_( trans.model.LibraryDataset.table.c.deleted == False,
trans.model.LibraryDataset.table.c.folder_id == folder.id ) ) \
.order_by( trans.model.LibraryDataset.table.c._name ) \
.all()
+ query_end = datetime.now()
+ query_delta = query_end - query_start
+ #log.debug( "active_folders_and_library_datasets: %d.%.6d" %
+ # ( query_delta.seconds, query_delta.microseconds ) )
return folders, library_datasets
+
def activatable_folders_and_library_datasets( trans, folder ):
folders = activatable_folders( trans, folder )
+ from datetime import datetime, timedelta
+ query_start = datetime.now()
library_datasets = trans.sa_session.query( trans.model.LibraryDataset ) \
.filter( trans.model.LibraryDataset.table.c.folder_id == folder.id ) \
.join( ( trans.model.LibraryDatasetDatasetAssociation.table,
@@ -2553,6 +2597,10 @@
.filter( trans.model.Dataset.table.c.deleted == False ) \
.order_by( trans.model.LibraryDataset.table.c._name ) \
.all()
+ query_end = datetime.now()
+ query_delta = query_end - query_start
+ log.debug( "activatable_folders_and_library_datasets: %d.%.6d" %
+ ( query_delta.seconds, query_delta.microseconds ) )
return folders, library_datasets
def branch_deleted( folder ):
# Return True if a folder belongs to a branch that has been deleted
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/main/controllers/tool_runner.py
--- a/lib/galaxy/webapps/main/controllers/tool_runner.py
+++ b/lib/galaxy/webapps/main/controllers/tool_runner.py
@@ -43,6 +43,7 @@
toolbox = self.get_toolbox()
tool_version_select_field = None
tools = []
+ tool = None
# Backwards compatibility for datasource tools that have default tool_id configured, but which are now using only GALAXY_URL.
tool_ids = util.listify( tool_id )
for tool_id in tool_ids:
@@ -50,27 +51,21 @@
tools = toolbox.get_loaded_tools_by_lineage( tool_id )
else:
tools = toolbox.get_tool( tool_id, tool_version=tool_version, get_all_versions=True )
- if len( tools ) > 1:
- tool_version_select_field = self.build_tool_version_select_field( tools, tool_id, set_selected )
- for tool in tools:
- if tool.id == tool_id:
- break
- else:
- tool = tools[ 0 ]
- else:
- tool = tools[ 0 ]
- break
+ if tools:
+ tool = toolbox.get_tool( tool_id, tool_version=tool_version, get_all_versions=False )
+ if len( tools ) > 1:
+ tool_version_select_field = self.build_tool_version_select_field( tools, tool.id, set_selected )
+ break
return tool_version_select_field, tools, tool
@web.expose
def index(self, trans, tool_id=None, from_noframe=None, **kwd):
# No tool id passed, redirect to main page
if tool_id is None:
return trans.response.send_redirect( url_for( "/static/welcome.html" ) )
- set_selected = 'refresh' in kwd
tool_version_select_field, tools, tool = self.__get_tool_components( tool_id,
tool_version=None,
- get_loaded_tools_by_lineage=True,
- set_selected=set_selected )
+ get_loaded_tools_by_lineage=False,
+ set_selected=True )
# No tool matching the tool id, display an error (shouldn't happen)
if not tool:
log.error( "index called with tool id '%s' but no such tool exists", tool_id )
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/main/controllers/user.py
--- a/lib/galaxy/webapps/main/controllers/user.py
+++ b/lib/galaxy/webapps/main/controllers/user.py
@@ -1,16 +1,22 @@
"""
Contains the user interface in the Universe class
"""
+
+import glob
+import logging
+import os
+import socket
+import string
+import random
+from galaxy import web
+from galaxy import util, model
+from galaxy.model.orm import and_
+from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password, transform_publicname
+from galaxy.util.json import from_json_string, to_json_string
+from galaxy.web import url_for
+from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin, get_webapp
+from galaxy.web.form_builder import CheckboxField, build_select_field
from galaxy.web.framework.helpers import time_ago, grids
-from galaxy.web.base.controller import *
-from galaxy.model.orm import *
-from galaxy import util, model
-import logging, os, string, re, socket, glob
-from random import choice
-from galaxy.web.form_builder import *
-from galaxy.util.json import from_json_string, to_json_string
-from galaxy.web.framework.helpers import iff
-from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password, transform_publicname
log = logging.getLogger( __name__ )
@@ -395,6 +401,7 @@
use_panels=use_panels,
message=message,
status=status ) )
+
@web.expose
@web.require_login( 'manage OpenIDs' )
def openid_manage( self, trans, webapp='galaxy', **kwd ):
@@ -409,10 +416,10 @@
action='openid_disassociate',
use_panels=use_panels,
id=kwd['id'] ) )
-
kwd['redirect'] = kwd.get( 'redirect', url_for( controller='user', action='openid_manage', use_panels=True ) ).strip()
kwd['openid_providers'] = trans.app.openid_providers
return self.user_openid_grid( trans, **kwd )
+
@web.expose
def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **kwd ):
'''Handle Galaxy Log in'''
@@ -423,6 +430,9 @@
header = ''
user = None
email = kwd.get( 'email', '' )
+ #Sanitize webapp login here, once, since it can be reflected to the user in messages/etc.
+ #Only text is valid.
+ webapp = util.sanitize_text(webapp)
if kwd.get( 'login_button', False ):
if webapp == 'galaxy' and not refresh_frames:
if trans.app.config.require_login:
@@ -489,6 +499,7 @@
message += ' <a target="_top" href="%s">Click here</a> to continue to the home page.' % web.url_for( '/static/welcome.html' )
success = True
return ( message, status, user, success )
+
@web.expose
def logout( self, trans, webapp='galaxy', logout_all=False ):
if webapp == 'galaxy':
@@ -509,6 +520,7 @@
message=message,
status='done',
active_view="user" )
+
@web.expose
def create( self, trans, cntrller='user', redirect_url='', refresh_frames=[], **kwd ):
params = util.Params( kwd )
@@ -579,8 +591,6 @@
return trans.fill_template( '/user/register.mako',
cntrller=cntrller,
email=email,
- password=password,
- confirm=confirm,
username=transform_publicname( trans, username ),
subscribe_checked=subscribe_checked,
user_type_fd_id_select_field=user_type_fd_id_select_field,
@@ -593,6 +603,7 @@
refresh_frames=refresh_frames,
message=message,
status=status )
+
def __register( self, trans, cntrller, subscribe_checked, **kwd ):
email = util.restore_text( kwd.get( 'email', '' ) )
password = kwd.get( 'password', '' )
@@ -683,6 +694,7 @@
else:
widgets = user_type_form_definition.get_widgets( None, contents={}, **kwd )
return widgets
+
@web.expose
def manage_user_info( self, trans, cntrller, **kwd ):
'''Manage a user's login, password, public username, type, addresses, etc.'''
@@ -696,11 +708,6 @@
raise AssertionError, "The user id (%s) is not valid" % str( user_id )
webapp = get_webapp( trans, **kwd )
email = util.restore_text( params.get( 'email', user.email ) )
- # Do not sanitize passwords, so take from kwd
- # instead of params ( which were sanitized )
- current = kwd.get( 'current', '' )
- password = kwd.get( 'password', '' )
- confirm = kwd.get( 'confirm', '' )
username = util.restore_text( params.get( 'username', '' ) )
if not username:
username = user.username
@@ -710,7 +717,7 @@
user_type_form_definition = self.__get_user_type_form_definition( trans, user=user, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
- user_type_fd_id = trans.security.encode_id( user_type_form_definition.id )
+ user_type_fd_id = trans.security.encode_id( user_type_form_definition.id )
user_type_fd_id_select_field = self.__build_user_type_fd_id_select_field( trans, selected_value=user_type_fd_id )
widgets = self.__get_widgets( trans, user_type_form_definition, user=user, **kwd )
# user's addresses
@@ -728,14 +735,11 @@
cntrller=cntrller,
user=user,
email=email,
- current=current,
- password=password,
- confirm=confirm,
username=username,
user_type_fd_id_select_field=user_type_fd_id_select_field,
user_info_forms=user_info_forms,
user_type_form_definition=user_type_form_definition,
- widgets=widgets,
+ widgets=widgets,
addresses=addresses,
show_filter=show_filter,
webapp=webapp,
@@ -746,13 +750,11 @@
cntrller=cntrller,
user=user,
email=email,
- current=current,
- password=password,
- confirm=confirm,
username=username,
webapp=webapp,
message=message,
status=status )
+
# For REMOTE_USER, we need the ability to just edit the username
@web.expose
@web.require_login( "to manage the public name" )
@@ -911,7 +913,7 @@
chars = string.letters + string.digits
new_pass = ""
for i in range(15):
- new_pass = new_pass + choice(chars)
+ new_pass = new_pass + random.choice(chars)
host = trans.request.host.split(':')[0]
if host == 'localhost':
host = socket.getfqdn()
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/webapps/main/controllers/visualization.py
--- a/lib/galaxy/webapps/main/controllers/visualization.py
+++ b/lib/galaxy/webapps/main/controllers/visualization.py
@@ -763,7 +763,7 @@
original_dataset=dataset,
source='index' )
# HACK: pass in additional params, which are only used for summary tree data, not BBI data.
- track[ 'genome_wide_data' ] = { 'data': data_provider.get_genome_data( chroms_info, level=4, detail_cutoff=0, draw_cutoff=0 ) }
+ track[ 'preloaded_data' ] = data_provider.get_genome_data( chroms_info, level=4, detail_cutoff=0, draw_cutoff=0 )
return trans.fill_template( 'visualization/circster.mako', viz_config=viz_config, genome=genome )
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 lib/galaxy/workflow/modules.py
--- a/lib/galaxy/workflow/modules.py
+++ b/lib/galaxy/workflow/modules.py
@@ -199,13 +199,9 @@
# TODO: If workflows are ever enhanced to use tool version
# in addition to tool id, enhance the selection process here
# to retrieve the correct version of the tool.
- tool_version = Class.__get_tool_version( trans, tool_id )
- if tool_version:
- tool_version_ids = tool_version.get_version_ids( trans.app )
- for tool_version_id in tool_version_ids:
- if tool_version_id in trans.app.toolbox.tools_by_id:
- tool_id = tool_version_id
- break
+ tool = trans.app.toolbox.get_tool( tool_id )
+ if tool:
+ tool_id = tool.id
if ( trans.app.toolbox and tool_id in trans.app.toolbox.tools_by_id ):
module = Class( trans, tool_id )
module.state = DefaultToolState()
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/june_2007_style/base.less
--- a/static/june_2007_style/base.less
+++ b/static/june_2007_style/base.less
@@ -763,8 +763,16 @@
#search-clear-btn {
position: absolute;
- right: 4px;
- top: 8px;
+ right: 5px;
+ top: 9px;
+ display: block;
+ font-size: 1.4em;
+ text-decoration: none;
+ color: #888;
+ .ficon();
+ &:before {
+ content: "\f057";
+ }
}
// Messages
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/june_2007_style/blue/base.css
--- a/static/june_2007_style/blue/base.css
+++ b/static/june_2007_style/blue/base.css
@@ -715,7 +715,7 @@
.search-query{display:inline-block;padding:4px;font-size:12px;line-height:16px;color:#555555;border:1px solid #999999;padding-left:14px !important;padding-right:14px !important;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;max-width:auto;}
.search-query:focus{border-color:rgba(24, 132, 218, 0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);outline:0;outline:thin dotted \9;}
.search-spinner{position:absolute;display:none;right:5px;top:9px;}
-#search-clear-btn{position:absolute;right:4px;top:8px;}
+#search-clear-btn{position:absolute;right:5px;top:9px;display:block;font-size:1.4em;text-decoration:none;color:#888;font-family:FontAwesome;font-weight:normal;font-style:normal;display:inline-block;}#search-clear-btn:before{content:"\f057";}
.errormessagelarge,.warningmessagelarge,.donemessagelarge,.infomessagelarge{padding:8px 35px 8px 14px;margin-bottom:16px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#ffffcc;border:1px solid #ffdd33;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#666600;min-height:36px;padding-left:52px;background-image:url(error_large.png);background-repeat:no-repeat;background-position:10px 10px;}
.errormessagelarge{background-color:#ffcccc;border-color:#ff3355;color:#660000;padding-left:52px;}
.warningmessagelarge{background-image:url(warn_large.png);border-color:#aaaa66;background-color:#ffffcc;}
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/galaxy.base.js
--- a/static/scripts/galaxy.base.js
+++ b/static/scripts/galaxy.base.js
@@ -65,15 +65,19 @@
});
};
+/**
+ * Sets up popupmenu rendering and binds options functions to the appropriate links
+ */
function make_popupmenu(button_element, initial_options) {
-
/* Use the $.data feature to store options with the link element.
This allows options to be changed at a later time
*/
var element_menu_exists = (button_element.data("menu_options"));
button_element.data("menu_options", initial_options);
+
// If element already has menu, nothing else to do since HTML and actions are already set.
if (element_menu_exists) { return; }
+
button_element.bind("click.show_popup", function(e) {
// Close existing visible menus
$(".popmenu-wrapper").remove();
@@ -93,16 +97,17 @@
menu_element.append( $("<li></li>").addClass( "head" ).append( $("<a href='#'></a>").html(k) ) );
}
});
- var wrapper = $( "<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>" ).append( menu_element ).appendTo( "body" );
+ var wrapper = $( "<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>" )
+ .append( menu_element ).appendTo( "body" );
var x = e.pageX - wrapper.width() / 2 ;
x = Math.min( x, $(document).scrollLeft() + $(window).width() - $(wrapper).width() - 5 );
x = Math.max( x, $(document).scrollLeft() + 5 );
- wrapper.css( {
+ wrapper.css({
top: e.pageY,
left: x
- } );
+ });
}, 10);
setTimeout( function() {
@@ -127,26 +132,53 @@
}
-function make_popup_menus() {
- jQuery( "div[popupmenu]" ).each( function() {
+/**
+ * Convert two seperate (often adjacent) divs into galaxy popupmenu
+ * - div 1 contains a number of anchors which become the menu options
+ * - div 1 should have a 'popupmenu' attribute
+ * - this popupmenu attribute contains the id of div 2
+ * - div 2 becomes the 'face' of the popupmenu
+ *
+ * NOTE: make_popup_menus finds and operates on all divs with a popupmenu attr (no need to point it at something)
+ * but (since that selector searches the dom on the page), you can send a parent in
+ * NOTE: make_popup_menus, and make_popupmenu are horrible names
+ */
+function make_popup_menus( parent ) {
+ // find all popupmenu menu divs (divs that contains anchors to be converted to menu options)
+ // either in the parent or the document if no parent passed
+ parent = parent || document;
+ $( parent ).find( "div[popupmenu]" ).each( function() {
var options = {};
var menu = $(this);
+
+ // find each anchor in the menu, convert them into an options map: { a.text : click_function }
menu.find( "a" ).each( function() {
var link = $(this),
- link_dom = link.get(0);
- var confirmtext = link_dom.getAttribute( "confirm" ),
+ // why do we need the DOM (mixed with jq)?
+ link_dom = link.get(0),
+ confirmtext = link_dom.getAttribute( "confirm" ),
href = link_dom.getAttribute( "href" ),
target = link_dom.getAttribute( "target" );
+
+ // no href - no function (gen. a label)
if (!href) {
options[ link.text() ] = null;
+
} else {
options[ link.text() ] = function() {
+
+ // if theres confirm text, send the dialog
if ( !confirmtext || confirm( confirmtext ) ) {
var f;
+ // relocate the center panel
if ( target == "_parent" ) {
window.parent.location = href;
+
+ // relocate the entire window
} else if ( target == "_top" ) {
window.top.location = href;
+
+ //??...wot?
} else if ( target == "demo" ) {
// Http request target is a window named
// demolocal on the local box
@@ -154,6 +186,8 @@
f = window.open( href,target );
f.creator = self;
}
+
+ // relocate this panel
} else {
window.location = href;
}
@@ -161,6 +195,7 @@
};
}
});
+ // locate the element with the id corresponding to the menu's popupmenu attr
var box = $( "#" + menu.attr( 'popupmenu' ) );
// For menus with clickable link text, make clicking on the link go through instead
@@ -170,6 +205,7 @@
return true;
});
+ // attach the click events and menu box building to the box element
make_popupmenu(box, options);
box.addClass("popup");
menu.remove();
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/libs/underscore.js
--- a/static/scripts/libs/underscore.js
+++ b/static/scripts/libs/underscore.js
@@ -1,10 +1,7 @@
-// Underscore.js 1.3.1
+// Underscore.js 1.4.0
+// http://underscorejs.org
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
-// Underscore is freely distributable under the MIT license.
-// Portions of Underscore are inspired or borrowed from Prototype,
-// Oliver Steele's Functional, and John Resig's Micro-Templating.
-// For all details and documentation:
-// http://documentcloud.github.com/underscore
+// Underscore may be freely distributed under the MIT license.
(function() {
@@ -24,7 +21,9 @@
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
- var slice = ArrayProto.slice,
+ var push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ concat = ArrayProto.concat,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
@@ -46,7 +45,11 @@
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
- var _ = function(obj) { return new wrapper(obj); };
+ var _ = function(obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this._wrapped = obj;
+ };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
@@ -62,7 +65,7 @@
}
// Current version.
- _.VERSION = '1.3.1';
+ _.VERSION = '1.4.0';
// Collection Functions
// --------------------
@@ -71,12 +74,11 @@
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
- if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
- if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
@@ -91,12 +93,10 @@
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
- if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
- if (obj.length === +obj.length) results.length = obj.length;
return results;
};
@@ -104,7 +104,6 @@
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
- if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
@@ -125,14 +124,26 @@
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
- if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
- return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
- var reversed = _.toArray(obj).reverse();
- if (context && !initial) iterator = _.bind(iterator, context);
- return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
+ var length = obj.length;
+ if (length !== +length) {
+ var keys = _.keys(obj);
+ length = keys.length;
+ }
+ each(obj, function(value, index, list) {
+ index = keys ? keys[--length] : --length;
+ if (!initial) {
+ memo = obj[index];
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, obj[index], index, list);
+ }
+ });
+ if (!initial) throw new TypeError('Reduce of empty array with no initial value');
+ return memo;
};
// Return the first value which passes a truth test. Aliased as `detect`.
@@ -152,7 +163,6 @@
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
- if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
@@ -163,7 +173,6 @@
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
- if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
@@ -174,13 +183,13 @@
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
var result = true;
- if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
- return result;
+ return !!result;
};
// Determine if at least one element in the object matches a truth test.
@@ -189,7 +198,6 @@
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
- if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
@@ -197,11 +205,10 @@
return !!result;
};
- // Determine if a given value is included in the array or object using `===`.
- // Aliased as `contains`.
- _.include = _.contains = function(obj, target) {
+ // Determine if the array or object contains a given value (using `===`).
+ // Aliased as `include`.
+ _.contains = _.include = function(obj, target) {
var found = false;
- if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
@@ -213,7 +220,7 @@
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
- return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
+ return (_.isFunction(method) ? method : value[method]).apply(value, args);
});
};
@@ -222,9 +229,25 @@
return _.map(obj, function(value){ return value[key]; });
};
+ // Convenience version of a common use case of `filter`: selecting only objects
+ // with specific `key:value` pairs.
+ _.where = function(obj, attrs) {
+ if (_.isEmpty(attrs)) return [];
+ return _.filter(obj, function(value) {
+ for (var key in attrs) {
+ if (attrs[key] !== value[key]) return false;
+ }
+ return true;
+ });
+ };
+
// Return the maximum element or (element-based computation).
+ // Can't optimize arrays of integers longer than 65,535 elements.
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
_.max = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.max.apply(Math, obj);
+ }
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
@@ -236,7 +259,9 @@
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.min.apply(Math, obj);
+ }
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
@@ -248,81 +273,107 @@
// Shuffle an array.
_.shuffle = function(obj) {
- var shuffled = [], rand;
- each(obj, function(value, index, list) {
- if (index == 0) {
- shuffled[0] = value;
- } else {
- rand = Math.floor(Math.random() * (index + 1));
- shuffled[index] = shuffled[rand];
- shuffled[rand] = value;
- }
+ var rand;
+ var index = 0;
+ var shuffled = [];
+ each(obj, function(value) {
+ rand = _.random(index++);
+ shuffled[index - 1] = shuffled[rand];
+ shuffled[rand] = value;
});
return shuffled;
};
+ // An internal function to generate lookup iterators.
+ var lookupIterator = function(value) {
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+ };
+
// Sort the object's values by a criterion produced by an iterator.
- _.sortBy = function(obj, iterator, context) {
+ _.sortBy = function(obj, value, context) {
+ var iterator = lookupIterator(value);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
+ index : index,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
- var a = left.criteria, b = right.criteria;
- return a < b ? -1 : a > b ? 1 : 0;
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0) return 1;
+ if (a < b || b === void 0) return -1;
+ }
+ return left.index < right.index ? -1 : 1;
}), 'value');
};
+ // An internal function used for aggregate "group by" operations.
+ var group = function(obj, value, context, behavior) {
+ var result = {};
+ var iterator = lookupIterator(value);
+ each(obj, function(value, index) {
+ var key = iterator.call(context, value, index, obj);
+ behavior(result, key, value);
+ });
+ return result;
+ };
+
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
- _.groupBy = function(obj, val) {
- var result = {};
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
- each(obj, function(value, index) {
- var key = iterator(value, index);
- (result[key] || (result[key] = [])).push(value);
+ _.groupBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key, value) {
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
});
- return result;
};
- // Use a comparator function to figure out at what index an object should
- // be inserted so as to maintain order. Uses binary search.
- _.sortedIndex = function(array, obj, iterator) {
- iterator || (iterator = _.identity);
+ // Counts instances of an object that group by a certain criterion. Pass
+ // either a string attribute to count by, or a function that returns the
+ // criterion.
+ _.countBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key, value) {
+ if (!_.has(result, key)) result[key] = 0;
+ result[key]++;
+ });
+ };
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator, context) {
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
+ var value = iterator.call(context, obj);
var low = 0, high = array.length;
while (low < high) {
- var mid = (low + high) >> 1;
- iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
+ var mid = (low + high) >>> 1;
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
- _.toArray = function(iterable) {
- if (!iterable) return [];
- if (iterable.toArray) return iterable.toArray();
- if (_.isArray(iterable)) return slice.call(iterable);
- if (_.isArguments(iterable)) return slice.call(iterable);
- return _.values(iterable);
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (obj.length === +obj.length) return slice.call(obj);
+ return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
- return _.toArray(obj).length;
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
- // values in the array. Aliased as `head`. The **guard** check allows it to work
- // with `_.map`.
- _.first = _.head = function(array, n, guard) {
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
- // Returns everything but the last entry of the array. Especcialy useful on
+ // Returns everything but the last entry of the array. Especially useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
@@ -340,12 +391,12 @@
}
};
- // Returns everything but the first entry of the array. Aliased as `tail`.
- // Especially useful on the arguments object. Passing an **index** will return
- // the rest of the values in the array from that index onward. The **guard**
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+ // Especially useful on the arguments object. Passing an **n** will return
+ // the rest N values in the array. The **guard**
// check allows it to work with `_.map`.
- _.rest = _.tail = function(array, index, guard) {
- return slice.call(array, (index == null) || guard ? 1 : index);
+ _.rest = _.tail = _.drop = function(array, n, guard) {
+ return slice.call(array, (n == null) || guard ? 1 : n);
};
// Trim out all falsy values from an array.
@@ -353,13 +404,21 @@
return _.filter(array, function(value){ return !!value; });
};
+ // Internal implementation of a recursive `flatten` function.
+ var flatten = function(input, shallow, output) {
+ each(input, function(value) {
+ if (_.isArray(value)) {
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
+ } else {
+ output.push(value);
+ }
+ });
+ return output;
+ };
+
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
- return _.reduce(array, function(memo, value) {
- if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
- memo[memo.length] = value;
- return memo;
- }, []);
+ return flatten(array, shallow, []);
};
// Return a version of the array that does not contain the specified value(s).
@@ -370,28 +429,28 @@
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
- _.uniq = _.unique = function(array, isSorted, iterator) {
- var initial = iterator ? _.map(array, iterator) : array;
- var result = [];
- _.reduce(initial, function(memo, el, i) {
- if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
- memo[memo.length] = el;
- result[result.length] = array[i];
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
+ var initial = iterator ? _.map(array, iterator, context) : array;
+ var results = [];
+ var seen = [];
+ each(initial, function(value, index) {
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+ seen.push(value);
+ results.push(array[index]);
}
- return memo;
- }, []);
- return result;
+ });
+ return results;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
- return _.uniq(_.flatten(arguments, true));
+ return _.uniq(concat.apply(ArrayProto, arguments));
};
// Produce an array that contains every item shared between all the
- // passed-in arrays. (Aliased as "intersect" for back-compat.)
- _.intersection = _.intersect = function(array) {
+ // passed-in arrays.
+ _.intersection = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
@@ -403,8 +462,8 @@
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
- var rest = _.flatten(slice.call(arguments, 1));
- return _.filter(array, function(value){ return !_.include(rest, value); });
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
@@ -413,10 +472,27 @@
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
- for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
+ for (var i = 0; i < length; i++) {
+ results[i] = _.pluck(args, "" + i);
+ }
return results;
};
+ // Converts lists into objects. Pass either a single array of `[key, value]`
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
+ // the corresponding values.
+ _.object = function(list, values) {
+ var result = {};
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0]] = list[i][1];
+ }
+ }
+ return result;
+ };
+
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
@@ -424,23 +500,25 @@
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
- if (array == null) return -1;
- var i, l;
+ var i = 0, l = array.length;
if (isSorted) {
- i = _.sortedIndex(array, item);
- return array[i] === item ? i : -1;
+ if (typeof isSorted == 'number') {
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
+ } else {
+ i = _.sortedIndex(array, item);
+ return array[i] === item ? i : -1;
+ }
}
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
- for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+ for (; i < l; i++) if (array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
- _.lastIndexOf = function(array, item) {
- if (array == null) return -1;
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
- var i = array.length;
- while (i--) if (i in array && array[i] === item) return i;
+ _.lastIndexOf = function(array, item, fromIndex) {
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item, fromIndex);
+ var i = (fromIndex != null ? fromIndex : array.length);
+ while (i--) if (array[i] === item) return i;
return -1;
};
@@ -514,7 +592,7 @@
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
- return setTimeout(function(){ return func.apply(func, args); }, wait);
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
@@ -526,39 +604,46 @@
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
- var context, args, timeout, throttling, more;
+ var context, args, timeout, throttling, more, result;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
- if (more) func.apply(context, args);
+ if (more) {
+ result = func.apply(context, args);
+ }
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
- func.apply(context, args);
+ throttling = true;
+ result = func.apply(context, args);
}
whenDone();
- throttling = true;
+ return result;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
- // N milliseconds.
- _.debounce = function(func, wait) {
- var timeout;
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
+ var timeout, result;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
- func.apply(context, args);
+ if (!immediate) result = func.apply(context, args);
};
+ var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
+ if (callNow) result = func.apply(context, args);
+ return result;
};
};
@@ -569,7 +654,9 @@
return function() {
if (ran) return memo;
ran = true;
- return memo = func.apply(this, arguments);
+ memo = func.apply(this, arguments);
+ func = null;
+ return memo;
};
};
@@ -578,7 +665,8 @@
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
- var args = [func].concat(slice.call(arguments, 0));
+ var args = [func];
+ push.apply(args, arguments);
return wrapper.apply(this, args);
};
};
@@ -600,7 +688,9 @@
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
- if (--times < 1) { return func.apply(this, arguments); }
+ if (--times < 1) {
+ return func.apply(this, arguments);
+ }
};
};
@@ -618,7 +708,23 @@
// Retrieve the values of an object's properties.
_.values = function(obj) {
- return _.map(obj, _.identity);
+ var values = [];
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
+ return values;
+ };
+
+ // Convert an object into a list of `[key, value]` pairs.
+ _.pairs = function(obj) {
+ var pairs = [];
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
+ return pairs;
+ };
+
+ // Invert the keys and values of an object. The values must be serializable.
+ _.invert = function(obj) {
+ var result = {};
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
+ return result;
};
// Return a sorted list of the function names available on the object.
@@ -641,6 +747,26 @@
return obj;
};
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ each(keys, function(key) {
+ if (key in obj) copy[key] = obj[key];
+ });
+ return copy;
+ };
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ for (var key in obj) {
+ if (!_.contains(keys, key)) copy[key] = obj[key];
+ }
+ return copy;
+ };
+
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
@@ -665,19 +791,16 @@
return obj;
};
- // Internal recursive comparison function.
- function eq(a, b, stack) {
+ // Internal recursive comparison function for `isEqual`.
+ var eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal .
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
- if (a._chain) a = a._wrapped;
- if (b._chain) b = b._wrapped;
- // Invoke a custom `isEqual` method if one is provided.
- if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
- if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
+ if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
@@ -707,14 +830,15 @@
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
- var length = stack.length;
+ var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
- if (stack[length] == a) return true;
+ if (aStack[length] == a) return bStack[length] == b;
}
// Add the first object to the stack of traversed objects.
- stack.push(a);
+ aStack.push(a);
+ bStack.push(b);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
@@ -724,20 +848,24 @@
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
- // Ensure commutative equality for sparse arrays.
- if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
}
}
} else {
- // Objects with different constructors are not equivalent.
- if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+ return false;
+ }
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
- if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
}
}
// Ensure that both objects contain the same number of properties.
@@ -749,18 +877,20 @@
}
}
// Remove the first object from the stack of traversed objects.
- stack.pop();
+ aStack.pop();
+ bStack.pop();
return result;
- }
+ };
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
- return eq(a, b, []);
+ return eq(a, b, [], []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
+ if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
@@ -768,7 +898,7 @@
// Is a given value a DOM element?
_.isElement = function(obj) {
- return !!(obj && obj.nodeType == 1);
+ return !!(obj && obj.nodeType === 1);
};
// Is a given value an array?
@@ -782,35 +912,36 @@
return obj === Object(obj);
};
- // Is a given variable an arguments object?
- _.isArguments = function(obj) {
- return toString.call(obj) == '[object Arguments]';
- };
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+ _['is' + name] = function(obj) {
+ return toString.call(obj) == '[object ' + name + ']';
+ };
+ });
+
+ // Define a fallback version of the method in browsers (ahem, IE), where
+ // there isn't any inspectable "Arguments" type.
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
- // Is a given value a function?
- _.isFunction = function(obj) {
- return toString.call(obj) == '[object Function]';
+ // Optimize `isFunction` if appropriate.
+ if (typeof (/./) !== 'function') {
+ _.isFunction = function(obj) {
+ return typeof obj === 'function';
+ };
+ }
+
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return _.isNumber(obj) && isFinite(obj);
};
- // Is a given value a string?
- _.isString = function(obj) {
- return toString.call(obj) == '[object String]';
- };
-
- // Is a given value a number?
- _.isNumber = function(obj) {
- return toString.call(obj) == '[object Number]';
- };
-
- // Is the given value `NaN`?
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
_.isNaN = function(obj) {
- // `NaN` is the only value for which `===` is not reflexive.
- return obj !== obj;
+ return _.isNumber(obj) && obj != +obj;
};
// Is a given value a boolean?
@@ -818,16 +949,6 @@
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
- // Is a given value a date?
- _.isDate = function(obj) {
- return toString.call(obj) == '[object Date]';
- };
-
- // Is the given value a regular expression?
- _.isRegExp = function(obj) {
- return toString.call(obj) == '[object RegExp]';
- };
-
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
@@ -838,7 +959,8 @@
return obj === void 0;
};
- // Has own property?
+ // Shortcut function for checking if an object has a given property directly
+ // on itself (in other words, not on a prototype).
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
@@ -859,20 +981,65 @@
};
// Run a function **n** times.
- _.times = function (n, iterator, context) {
+ _.times = function(n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
- // Escape a string for HTML interpolation.
- _.escape = function(string) {
- return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
+ // Return a random integer between min and max (inclusive).
+ _.random = function(min, max) {
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + (0 | Math.random() * (max - min + 1));
};
- // Add your own custom functions to the Underscore object, ensuring that
- // they're correctly added to the OOP wrapper as well.
+ // List of HTML entities for escaping.
+ var entityMap = {
+ escape: {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '/': '/'
+ }
+ };
+ entityMap.unescape = _.invert(entityMap.escape);
+
+ // Regexes containing the keys and values listed immediately above.
+ var entityRegexes = {
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+ };
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ _.each(['escape', 'unescape'], function(method) {
+ _[method] = function(string) {
+ if (string == null) return '';
+ return ('' + string).replace(entityRegexes[method], function(match) {
+ return entityMap[method][match];
+ });
+ };
+ });
+
+ // If the value of the named property is a function then invoke it;
+ // otherwise, return it.
+ _.result = function(object, property) {
+ if (object == null) return null;
+ var value = object[property];
+ return _.isFunction(value) ? value.call(object) : value;
+ };
+
+ // Add your own custom functions to the Underscore object.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
- addToWrapper(name, _[name] = obj[name]);
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return result.call(this, func.apply(_, args));
+ };
});
};
@@ -895,41 +1062,72 @@
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
- var noMatch = /.^/;
+ var noMatch = /(.)^/;
- // Within an interpolation, evaluation, or escaping, remove HTML escaping
- // that had been previously added.
- var unescape = function(code) {
- return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
};
+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
- _.template = function(str, data) {
- var c = _.templateSettings;
- var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
- 'with(obj||{}){__p.push(\'' +
- str.replace(/\\/g, '\\\\')
- .replace(/'/g, "\\'")
- .replace(c.escape || noMatch, function(match, code) {
- return "',_.escape(" + unescape(code) + "),'";
- })
- .replace(c.interpolate || noMatch, function(match, code) {
- return "'," + unescape(code) + ",'";
- })
- .replace(c.evaluate || noMatch, function(match, code) {
- return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
- })
- .replace(/\r/g, '\\r')
- .replace(/\n/g, '\\n')
- .replace(/\t/g, '\\t')
- + "');}return __p.join('');";
- var func = new Function('obj', '_', tmpl);
- if (data) return func(data, _);
- return function(data) {
- return func.call(this, data, _);
+ _.template = function(text, data, settings) {
+ settings = _.defaults({}, settings, _.templateSettings);
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = new RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset)
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
+ source +=
+ escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
+ interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
+ evaluate ? "';\n" + evaluate + "\n__p+='" : '';
+ index = offset + match.length;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + "return __p;\n";
+
+ try {
+ var render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ if (data) return render(data, _);
+ var template = function(data) {
+ return render.call(this, data, _);
};
+
+ // Provide the compiled function source as a convenience for precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+ return template;
};
// Add a "chain" function, which will delegate to the wrapper.
@@ -937,29 +1135,15 @@
return _(obj).chain();
};
- // The OOP Wrapper
+ // OOP
// ---------------
-
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
- var wrapper = function(obj) { this._wrapped = obj; };
-
- // Expose `wrapper.prototype` as `_.prototype`
- _.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
- var result = function(obj, chain) {
- return chain ? _(obj).chain() : obj;
- };
-
- // A method to easily add functions to the OOP wrapper.
- var addToWrapper = function(name, func) {
- wrapper.prototype[name] = function() {
- var args = slice.call(arguments);
- unshift.call(args, this._wrapped);
- return result(func.apply(_, args), this._chain);
- };
+ var result = function(obj) {
+ return this._chain ? _(obj).chain() : obj;
};
// Add all of the Underscore functions to the wrapper object.
@@ -968,32 +1152,35 @@
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
- wrapper.prototype[name] = function() {
- var wrapped = this._wrapped;
- method.apply(wrapped, arguments);
- var length = wrapped.length;
- if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
- return result(wrapped, this._chain);
+ _.prototype[name] = function() {
+ var obj = this._wrapped;
+ method.apply(obj, arguments);
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+ return result.call(this, obj);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
- wrapper.prototype[name] = function() {
- return result(method.apply(this._wrapped, arguments), this._chain);
+ _.prototype[name] = function() {
+ return result.call(this, method.apply(this._wrapped, arguments));
};
});
- // Start chaining a wrapped Underscore object.
- wrapper.prototype.chain = function() {
- this._chain = true;
- return this;
- };
+ _.extend(_.prototype, {
- // Extracts the result from a wrapped and chained object.
- wrapper.prototype.value = function() {
- return this._wrapped;
- };
+ // Start chaining a wrapped Underscore object.
+ chain: function() {
+ this._chain = true;
+ return this;
+ },
+
+ // Extracts the result from a wrapped and chained object.
+ value: function() {
+ return this._wrapped;
+ }
+
+ });
}).call(this);
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/mvc/history.js
--- a/static/scripts/mvc/history.js
+++ b/static/scripts/mvc/history.js
@@ -1,4 +1,8 @@
-/*
+//define([
+// "../mvc/base-mvc"
+//
+//], function(){
+/* =============================================================================
Backbone.js implementation of history panel
TODO:
@@ -10,6 +14,8 @@
_render_displayApps
_render_downloadButton
widget building (popupmenu, etc.)
+ history.mako js: updater, etc.
+ have info bodies prev. opened, redisplay on refresh
don't draw body until it's first unhide event
all history.mako js -> this
@@ -30,26 +36,7 @@
move inline styles into base.less
add classes, ids on empty divs
watch the magic strings
-*/
-
-//==============================================================================
-
-//==============================================================================
-//TODO: move to Galaxy obj./namespace, decorate for current page (as GalaxyPaths)
-/*
-var Localizable = {
- localizedStrings : {},
- setLocalizedString : function( str, localizedString ){
- this.localizedStrings[ str ] = localizedString;
- },
- localize : function( str ){
- if( str in this.localizedStrings ){ return this.localizedStrings[ str ]; }
- return str;
- }
-};
-var LocalizableView = LoggingView.extend( Localizable );
-*/
-//TODO: wire up to views
+============================================================================= */
//==============================================================================
// jq plugin?
@@ -204,12 +191,8 @@
// set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)
itemWrapper.find( '.tooltip' ).tooltip({ placement : 'bottom' });
- //TODO: broken
- var popupmenus = itemWrapper.find( '[popupmenu]' );
- popupmenus.each( function( i, menu ){
- menu = $( menu );
- make_popupmenu( menu );
- });
+ // we can potentially skip this step and call popupmenu directly on the download button
+ make_popup_menus( itemWrapper );
//TODO: better transition/method than this...
this.$el.children().remove();
@@ -343,54 +326,25 @@
// ................................................................................ primary actions
_render_primaryActionButtons : function( buttonRenderingFuncs ){
- var primaryActionButtons = $( '<div/>' ),
+ var primaryActionButtons = $( '<div/>' ).attr( 'id', 'primary-actions-' + this.model.get( 'id' ) ),
view = this;
_.each( buttonRenderingFuncs, function( fn ){
- primaryActionButtons.append( fn.call( view ) );
+ var render_return = fn.call( view );
+ primaryActionButtons.append( render_return );
});
return primaryActionButtons;
},
_render_downloadButton : function(){
- // return either: a single download icon-button (if there are no meta files)
- // or a popupmenu with links to download assoc. meta files (if there are meta files)
-
// don't show anything if the data's been purged
if( this.model.get( 'purged' ) ){ return null; }
- var downloadLink = linkHTMLTemplate({
- title : 'Download',
- href : this.model.get( 'download_url' ),
- classes : [ 'icon-button', 'tooltip', 'disk' ]
- });
+ // return either: a single download icon-button (if there are no meta files)
+ // or a popupmenu with links to download assoc. meta files (if there are meta files)
+ var downloadLinkHTML = HistoryItemView.templates.downloadLinks( this.model.toJSON() );
+ this.log( '_render_downloadButton, downloadLinkHTML:', downloadLinkHTML );
- // if no metafiles, return only the main download link
- var download_meta_urls = this.model.get( 'download_meta_urls' );
- if( !download_meta_urls ){
- return downloadLink;
- }
-
- // build the popupmenu for downloading main, meta files
- var popupmenu = $( '<div popupmenu="dataset-' + this.model.get( 'id' ) + '-popup"></div>' );
- popupmenu.append( linkHTMLTemplate({
- text : 'Download Dataset',
- title : 'Download',
- href : this.model.get( 'download_url' ),
- classes : [ 'icon-button', 'tooltip', 'disk' ]
- }));
- popupmenu.append( '<a>Additional Files</a>' );
- for( file_type in download_meta_urls ){
- popupmenu.append( linkHTMLTemplate({
- text : 'Download ' + file_type,
- href : download_meta_urls[ file_type ],
- classes : [ 'action-button' ]
- }));
- }
- var menuButton = $( ( '<div style="float:left;" class="menubutton split popup"'
- + ' id="dataset-${dataset_id}-popup"></div>' ) );
- menuButton.append( downloadLink );
- popupmenu.append( menuButton );
- return popupmenu;
+ return $( downloadLinkHTML );
},
//NOTE: button renderers have the side effect of caching their IconButtonViews to this view
@@ -451,8 +405,12 @@
// ................................................................................ secondary actions
_render_secondaryActionButtons : function( buttonRenderingFuncs ){
// move to the right (same level as primary)
- var secondaryActionButtons = $( '<div style="float: right;"></div>' ),
+ var secondaryActionButtons = $( '<div/>' ),
view = this;
+ secondaryActionButtons
+ .attr( 'style', 'float: right;' )
+ .attr( 'id', 'secondary-actions-' + this.model.get( 'id' ) );
+
_.each( buttonRenderingFuncs, function( fn ){
secondaryActionButtons.append( fn.call( view ) );
});
@@ -501,54 +459,23 @@
},
_render_displayApps : function(){
- if( !this.model.get( 'display_apps' ) ){ return null; }
- var displayApps = this.model.get( 'displayApps' ),
- displayAppsDiv = $( '<div/>' ),
- displayAppSpan = $( '<span/>' );
-
- this.log( this + 'displayApps:', displayApps );
- ////TODO: grrr...somethings not in the right scope here
- //for( app_name in displayApps ){
- // //TODO: to template
- // var display_app = displayApps[ app_name ],
- // display_app_HTML = app_name + ' ';
- // for( location_name in display_app ){
- // display_app_HTML += linkHTMLTemplate({
- // text : location_name,
- // href : display_app[ location_name ].url,
- // target : display_app[ location_name ].target
- // }) + ' ';
- // }
- // display_app_span.append( display_app_HTML );
- //}
- //displayAppsDiv.append( display_app_span );
+ // render links to external genome display applications (igb, gbrowse, etc.)
+ if( !this.model.hasData() ){ return null; }
- //displayAppsDiv.append( '<br />' );
-
- //var display_appsDiv = $( '<div/>' );
- //if( this.model.get( 'display_apps' ) ){
- //
- // var display_apps = this.model.get( 'display_apps' ),
- // display_app_span = $( '<span/>' );
- //
- // //TODO: grrr...somethings not in the right scope here
- // for( app_name in display_apps ){
- // //TODO: to template
- // var display_app = display_apps[ app_name ],
- // display_app_HTML = app_name + ' ';
- // for( location_name in display_app ){
- // display_app_HTML += linkHTMLTemplate({
- // text : location_name,
- // href : display_app[ location_name ].url,
- // target : display_app[ location_name ].target
- // }) + ' ';
- // }
- // display_app_span.append( display_app_HTML );
- // }
- // display_appsDiv.append( display_app_span );
- //}
- ////display_appsDiv.append( '<br />' );
- //parent.append( display_appsDiv );
+ var displayAppsDiv = $( '<div/>' ).addClass( 'display-apps' );
+ if( !_.isEmpty( this.model.get( 'display_types' ) ) ){
+ this.log( this + 'display_types:', this.model.get( 'display_types' ) );
+ //TODO:?? does this ever get used?
+ displayAppsDiv.append(
+ HistoryItemView.templates.displayApps({ displayApps : this.model.toJSON().display_types })
+ );
+ }
+ if( !_.isEmpty( this.model.get( 'display_apps' ) ) ){
+ this.log( this + 'display_apps:', this.model.get( 'display_apps' ) );
+ displayAppsDiv.append(
+ HistoryItemView.templates.displayApps({ displayApps : this.model.toJSON().display_apps })
+ );
+ }
return displayAppsDiv;
},
@@ -815,9 +742,11 @@
messages : 'template-history-warning-messages',
titleLink : 'template-history-titleLink',
hdaSummary : 'template-history-hdaSummary',
+ downloadLinks : 'template-history-downloadLinks',
failedMetadata : 'template-history-failedMetaData',
tagArea : 'template-history-tagArea',
- annotationArea : 'template-history-annotationArea'
+ annotationArea : 'template-history-annotationArea',
+ displayApps : 'template-history-displayApps'
}
});
@@ -967,163 +896,10 @@
//==============================================================================
-function createMockHistoryData(){
- mockHistory = {};
- mockHistory.data = {
-
- template : {
- id : 'a799d38679e985db',
- name : 'template',
- data_type : 'fastq',
- file_size : 226297533,
- genome_build : '?',
- metadata_data_lines : 0,
- metadata_dbkey : '?',
- metadata_sequences : 0,
- misc_blurb : '215.8 MB',
- misc_info : 'uploaded fastq file (misc_info)',
- model_class : 'HistoryDatasetAssociation',
- download_url : '',
- state : 'ok',
- visible : true,
- deleted : false,
- purged : false,
-
- hid : 0,
- //TODO: move to history
- for_editing : true,
- //for_editing : false,
-
- //?? not needed
- //can_edit : true,
- //can_edit : false,
-
- accessible : true,
-
- //TODO: move into model functions (build there (and cache?))
- //!! be careful with adding these accrd. to permissions
- //!! IOW, don't send them via template/API if the user doesn't have perms to use
- //!! (even if they don't show up)
- undelete_url : '',
- purge_url : '',
- unhide_url : '',
-
- display_url : 'example.com/display ',
- edit_url : 'example.com/edit ',
- delete_url : 'example.com/delete ',
-
- show_params_url : 'example.com/show_params ',
- rerun_url : 'example.com/rerun ',
-
- retag_url : 'example.com/retag ',
- annotate_url : 'example.com/annotate ',
-
- peek : [
- '<table cellspacing="0" cellpadding="3"><tr><th>1.QNAME</th><th>2.FLAG</th><th>3.RNAME</th><th>4.POS</th><th>5.MAPQ</th><th>6.CIGAR</th><th>7.MRNM</th><th>8.MPOS</th><th>9.ISIZE</th><th>10.SEQ</th><th>11.QUAL</th><th>12.OPT</th></tr>',
- '<tr><td colspan="100%">@SQ SN:gi|87159884|ref|NC_007793.1| LN:2872769</td></tr>',
- '<tr><td colspan="100%">@PG ID:bwa PN:bwa VN:0.5.9-r16</td></tr>',
- '<tr><td colspan="100%">HWUSI-EAS664L:15:64HOJAAXX:1:1:13280:968 73 gi|87159884|ref|NC_007793.1| 2720169 37 101M = 2720169 0 NAATATGACATTATTTTCAAAACAGCTGAAAATTTAGACGTACCGATTTATCTACATCCCGCGCCAGTTAACAGTGACATTTATCAATCATACTATAAAGG !!!!!!!!!!$!!!$!!!!!$!!!!!!$!$!$$$!!$!!$!!!!!!!!!!!$!</td></tr>',
- '<tr><td colspan="100%">!!!$!$!$$!!$$!!$!!!!!!!!!!!!!!!!!!!!!!!!!!$!!$!! XT:A:U NM:i:1 SM:i:37 AM:i:0 X0:i:1 X1:i:0 XM:i:1 XO:i:0 XG:i:0 MD:Z:0A100</td></tr>',
- '<tr><td colspan="100%">HWUSI-EAS664L:15:64HOJAAXX:1:1:13280:968 133 gi|87159884|ref|NC_007793.1| 2720169 0 * = 2720169 0 NAAACTGTGGCTTCGTTNNNNNNNNNNNNNNNGTGANNNNNNNNNNNNNNNNNNNGNNNNNNNNNNNNNNNNNNNNCNAANNNNNNNNNNNNNNNNNNNNN !!!!!!!!!!!!$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</td></tr>',
- '<tr><td colspan="100%">!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</td></tr>',
- '</table>'
- ].join( '' )
- }
-
- };
- _.extend( mockHistory.data, {
-
- notAccessible :
- _.extend( _.clone( mockHistory.data.template ),
- { accessible : false }),
-
- //deleted, purged, visible
- deleted :
- _.extend( _.clone( mockHistory.data.template ),
- { deleted : true,
- delete_url : '',
- purge_url : 'example.com/purge ',
- undelete_url : 'example.com/undelete ' }),
- purgedNotDeleted :
- _.extend( _.clone( mockHistory.data.template ),
- { purged : true,
- delete_url : '' }),
- notvisible :
- _.extend( _.clone( mockHistory.data.template ),
- { visible : false,
- unhide_url : 'example.com/unhide ' }),
-
- hasDisplayApps :
- _.extend( _.clone( mockHistory.data.template ),
- { display_apps : {
- 'display in IGB' : {
- Web: "/display_application/63cd3858d057a6d1/igb_bam/Web",
- Local: "/display_application/63cd3858d057a6d1/igb_bam/Local"
- }
- }
- }
- ),
- canTrackster :
- _.extend( _.clone( mockHistory.data.template ),
- { trackster_urls : {
- 'data-url' : "example.com/trackster-data ",
- 'action-url' : "example.com/trackster-action ",
- 'new-url' : "example.com/trackster-new "
- }
- }
- ),
- zeroSize :
- _.extend( _.clone( mockHistory.data.template ),
- { file_size : 0 }),
-
- hasMetafiles :
- _.extend( _.clone( mockHistory.data.template ), {
- download_meta_urls : {
- 'bam_index' : "example.com/bam-index "
- }
- }),
-
- //states
- upload :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.UPLOAD }),
- queued :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.QUEUED }),
- running :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.RUNNING }),
- empty :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.EMPTY }),
- error :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.ERROR,
- report_error_url: 'example.com/report_err ' }),
- discarded :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.DISCARDED }),
- setting_metadata :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.SETTING_METADATA }),
- failed_metadata :
- _.extend( _.clone( mockHistory.data.template ),
- { state : HistoryItem.STATES.FAILED_METADATA })
-/*
-*/
- });
-
- $( document ).ready( function(){
- //mockHistory.views.deleted.logger = console;
- mockHistory.items = {};
- mockHistory.views = {};
- for( key in mockHistory.data ){
- mockHistory.items[ key ] = new HistoryItem( mockHistory.data[ key ] );
- mockHistory.items[ key ].set( 'name', key );
- mockHistory.views[ key ] = new HistoryItemView({ model : mockHistory.items[ key ] });
- //console.debug( 'view: ', mockHistory.views[ key ] );
- $( 'body' ).append( mockHistory.views[ key ].render() );
- }
- });
-}
-
+//return {
+// HistoryItem : HistoryItem,
+// HitoryItemView : HistoryItemView,
+// HistoryCollection : HistoryCollection,
+// History : History,
+// HistoryView : HistoryView
+//};});
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/galaxy.base.js
--- a/static/scripts/packed/galaxy.base.js
+++ b/static/scripts/packed/galaxy.base.js
@@ -1,1 +1,1 @@
-(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a<c.length&&!window.requestAnimationFrame;++a){window.requestAnimationFrame=window[c[a]+"RequestAnimationFrame"];window.cancelRequestAnimationFrame=window[c[a]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame){window.requestAnimationFrame=function(h,e){var d=new Date().getTime();var f=Math.max(0,16-(d-b));var g=window.setTimeout(function(){h(d+f)},f);b=d+f;return g}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame=function(d){clearTimeout(d)}}}());if(!Array.indexOf){Array.prototype.indexOf=function(c){for(var b=0,a=this.length;b<a;b++){if(this[b]==c){return b}}return -1}}function obj_length(c){if(c.length!==undefined){return c.length}var b=0;for(var a in c){b++}return b}$.fn.makeAbsolute=function(a){return this.each(function(){var b=$(this);var c=b.position();b.css({position:"absolute",marginLeft:0,marginTop:0,top:c.top,left:c.left,right:$(window).width()-(c.left+b.width())});if(a){b.remove().appendTo("body")}})};function make_popupmenu(b,c){var a=(b.data("menu_options"));b.data("menu_options",c);if(a){return}b.bind("click.show_popup",function(d){$(".popmenu-wrapper").remove();setTimeout(function(){var g=$("<ul class='dropdown-menu' id='"+b.attr("id")+"-menu'></ul>");var f=b.data("menu_options");if(obj_length(f)<=0){$("<li>No Options.</li>").appendTo(g)}$.each(f,function(j,i){if(i){g.append($("<li></li>").append($("<a href='#'></a>").html(j).click(i)))}else{g.append($("<li></li>").addClass("head").append($("<a href='#'></a>").html(j)))}});var h=$("<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(){jQuery("div[popupmenu]").each(function(){var a={};var c=$(this);c.find("a").each(function(){var f=$(this),h=f.get(0);var d=h.getAttribute("confirm"),e=h.getAttribute("href"),g=h.getAttribute("target");if(!e){a[f.text()]=null}else{a[f.text()]=function(){if(!d||confirm(d)){var i;if(g=="_parent"){window.parent.location=e}else{if(g=="_top"){window.top.location=e}else{if(g=="demo"){if(i===undefined||i.closed){i=window.open(e,g);i.creator=self}}else{window.location=e}}}}}}});var b=$("#"+c.attr("popupmenu"));b.find("a").bind("click",function(d){d.stopPropagation();return true});make_popupmenu(b,a);b.addClass("popup");c.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(d<o){return -1}else{if(d>o){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);i<c;i++){m=parseFloat(n[i])||n[i];f=parseFloat(e[i])||e[i];if(m<f){return -1}else{if(m>f){return 1}}}return 0}function replace_big_select_inputs(a,c,b){if(!jQuery().autocomplete){return}if(a===undefined){a=20}if(c===undefined){c=3000}var b=b||$("select");b.each(function(){var e=$(this);var h=e.find("option").length;if((h<a)||(h>c)){return}if(e.attr("multiple")==="multiple"){return}if(e.hasClass("no-autocomplete")){return}var n=e.attr("value");var d=$("<input type='text' class='text-and-autocomplete-select'></input>");d.attr("size",40);d.attr("name",e.attr("name"));d.attr("id",e.attr("id"));d.click(function(){var o=$(this).val();$(this).val("Loading...");$(this).showAllInCache();$(this).val(o);$(this).select()});var f=[];var j={};e.children("option").each(function(){var p=$(this).text();var o=$(this).attr("value");f.push(p);j[p]=o;j[o]=o;if(o==n){d.attr("value",p)}});if(n===""||n==="?"){d.attr("value","Click to Search or Select")}if(e.attr("name")=="dbkey"){f=f.sort(naturalSort)}var g={selectFirst:false,autoFill:false,mustMatch:false,matchContains:true,max:c,minChars:0,hideForLessThanMinChars:false};d.autocomplete(f,g);e.replaceWith(d);var l=function(){var p=d.attr("value");var o=j[p];if(o!==null&&o!==undefined){d.attr("value",o)}else{if(n!==""){d.attr("value",n)}else{d.attr("value","?")}}};d.parents("form").submit(function(){l()});$(document).bind("convert_to_values",function(){l()});if(e.attr("refresh_on_change")=="true"){var i=e.attr("refresh_on_change_values"),m=e.attr("last_selected_value");if(i!==undefined){i=i.split(",")}var k=function(){var o=j[d.attr("value")];if(m!==o&&o!==null&&o!==undefined){if(i!==undefined&&$.inArray(o,i)===-1&&$.inArray(m,i)===-1){return}d.attr("value",o);$(window).trigger("refresh_on_change");d.parents("form").submit()}};d.bind("result",k);d.keyup(function(o){if(o.keyCode===13){k()}});d.keydown(function(o){if(o.keyCode===13){return false}})}})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("<br>")}a.addClass("editable-text");if(b){b(m)}};var h=a.text(),k,j;if(e){k=$("<textarea/>").attr({rows:c,cols:d}).text($.trim(h)).keyup(function(m){if(m.keyCode===27){i(h)}});j=$("<button/>").text("Done").click(function(){i(k.val());return false})}else{k=$("<input type='text'/>").attr({value:$.trim(h),size:d}).blur(function(){i(h)}).keyup(function(m){if(m.keyCode===27){$(this).trigger("blur")}else{if(m.keyCode===13){i($(this).val())}}})}a.text("");a.append(k);if(j){a.append(j)}k.focus();k.select();l.stopPropagation()});if(f){a.attr("title",f).tooltip()}return a};function async_save_text(d,f,e,a,c,h,i,g,b){if(c===undefined){c=30}if(i===undefined){i=4}$("#"+d).live("click",function(){if($("#renaming-active").length>0){return}var l=$("#"+f),k=l.text(),j;if(h){j=$("<textarea></textarea>").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("<input type='text'></input>").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("<em>None</em>")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("<a href='javascript:void(0);'></a>").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("select[refresh_on_change='true']").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tooltip){$(".tooltip").tooltip({placement:"top"})}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})});
\ No newline at end of file
+(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a<c.length&&!window.requestAnimationFrame;++a){window.requestAnimationFrame=window[c[a]+"RequestAnimationFrame"];window.cancelRequestAnimationFrame=window[c[a]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame){window.requestAnimationFrame=function(h,e){var d=new Date().getTime();var f=Math.max(0,16-(d-b));var g=window.setTimeout(function(){h(d+f)},f);b=d+f;return g}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame=function(d){clearTimeout(d)}}}());if(!Array.indexOf){Array.prototype.indexOf=function(c){for(var b=0,a=this.length;b<a;b++){if(this[b]==c){return b}}return -1}}function obj_length(c){if(c.length!==undefined){return c.length}var b=0;for(var a in c){b++}return b}$.fn.makeAbsolute=function(a){return this.each(function(){var b=$(this);var c=b.position();b.css({position:"absolute",marginLeft:0,marginTop:0,top:c.top,left:c.left,right:$(window).width()-(c.left+b.width())});if(a){b.remove().appendTo("body")}})};function make_popupmenu(b,c){var a=(b.data("menu_options"));b.data("menu_options",c);if(a){return}b.bind("click.show_popup",function(d){$(".popmenu-wrapper").remove();setTimeout(function(){var g=$("<ul class='dropdown-menu' id='"+b.attr("id")+"-menu'></ul>");var f=b.data("menu_options");if(obj_length(f)<=0){$("<li>No Options.</li>").appendTo(g)}$.each(f,function(j,i){if(i){g.append($("<li></li>").append($("<a href='#'></a>").html(j).click(i)))}else{g.append($("<li></li>").addClass("head").append($("<a href='#'></a>").html(j)))}});var h=$("<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]=function(){if(!e||confirm(e)){var j;if(h=="_parent"){window.parent.location=f}else{if(h=="_top"){window.top.location=f}else{if(h=="demo"){if(j===undefined||j.closed){j=window.open(f,h);j.creator=self}}else{window.location=f}}}}}}});var c=$("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(d<o){return -1}else{if(d>o){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);i<c;i++){m=parseFloat(n[i])||n[i];f=parseFloat(e[i])||e[i];if(m<f){return -1}else{if(m>f){return 1}}}return 0}function replace_big_select_inputs(a,c,b){if(!jQuery().autocomplete){return}if(a===undefined){a=20}if(c===undefined){c=3000}var b=b||$("select");b.each(function(){var e=$(this);var h=e.find("option").length;if((h<a)||(h>c)){return}if(e.attr("multiple")==="multiple"){return}if(e.hasClass("no-autocomplete")){return}var n=e.attr("value");var d=$("<input type='text' class='text-and-autocomplete-select'></input>");d.attr("size",40);d.attr("name",e.attr("name"));d.attr("id",e.attr("id"));d.click(function(){var o=$(this).val();$(this).val("Loading...");$(this).showAllInCache();$(this).val(o);$(this).select()});var f=[];var j={};e.children("option").each(function(){var p=$(this).text();var o=$(this).attr("value");f.push(p);j[p]=o;j[o]=o;if(o==n){d.attr("value",p)}});if(n===""||n==="?"){d.attr("value","Click to Search or Select")}if(e.attr("name")=="dbkey"){f=f.sort(naturalSort)}var g={selectFirst:false,autoFill:false,mustMatch:false,matchContains:true,max:c,minChars:0,hideForLessThanMinChars:false};d.autocomplete(f,g);e.replaceWith(d);var l=function(){var p=d.attr("value");var o=j[p];if(o!==null&&o!==undefined){d.attr("value",o)}else{if(n!==""){d.attr("value",n)}else{d.attr("value","?")}}};d.parents("form").submit(function(){l()});$(document).bind("convert_to_values",function(){l()});if(e.attr("refresh_on_change")=="true"){var i=e.attr("refresh_on_change_values"),m=e.attr("last_selected_value");if(i!==undefined){i=i.split(",")}var k=function(){var o=j[d.attr("value")];if(m!==o&&o!==null&&o!==undefined){if(i!==undefined&&$.inArray(o,i)===-1&&$.inArray(m,i)===-1){return}d.attr("value",o);$(window).trigger("refresh_on_change");d.parents("form").submit()}};d.bind("result",k);d.keyup(function(o){if(o.keyCode===13){k()}});d.keydown(function(o){if(o.keyCode===13){return false}})}})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("<br>")}a.addClass("editable-text");if(b){b(m)}};var h=a.text(),k,j;if(e){k=$("<textarea/>").attr({rows:c,cols:d}).text($.trim(h)).keyup(function(m){if(m.keyCode===27){i(h)}});j=$("<button/>").text("Done").click(function(){i(k.val());return false})}else{k=$("<input type='text'/>").attr({value:$.trim(h),size:d}).blur(function(){i(h)}).keyup(function(m){if(m.keyCode===27){$(this).trigger("blur")}else{if(m.keyCode===13){i($(this).val())}}})}a.text("");a.append(k);if(j){a.append(j)}k.focus();k.select();l.stopPropagation()});if(f){a.attr("title",f).tooltip()}return a};function async_save_text(d,f,e,a,c,h,i,g,b){if(c===undefined){c=30}if(i===undefined){i=4}$("#"+d).live("click",function(){if($("#renaming-active").length>0){return}var l=$("#"+f),k=l.text(),j;if(h){j=$("<textarea></textarea>").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("<input type='text'></input>").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("<em>None</em>")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("<a href='javascript:void(0);'></a>").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("select[refresh_on_change='true']").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tooltip){$(".tooltip").tooltip({placement:"top"})}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/libs/underscore.js
--- a/static/scripts/packed/libs/underscore.js
+++ b/static/scripts/packed/libs/underscore.js
@@ -1,1 +1,1 @@
-(function(){var x=this;var t=x._;var b={};var j=Array.prototype,D=Object.prototype,G=Function.prototype;var v=j.slice,z=j.unshift,y=D.toString,p=D.hasOwnProperty;var n=j.forEach,i=j.map,B=j.reduce,e=j.reduceRight,m=j.filter,a=j.every,A=j.some,w=j.indexOf,f=j.lastIndexOf,c=Array.isArray,C=Object.keys,k=G.bind;var F=function(I){return new g(I)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=F}exports._=F}else{x._=F}F.VERSION="1.3.1";var d=F.each=F.forEach=function(N,M,L){if(N==null){return}if(n&&N.forEach===n){N.forEach(M,L)}else{if(N.length===+N.length){for(var K=0,I=N.length;K<I;K++){if(K in N&&M.call(L,N[K],K,N)===b){return}}}else{for(var J in N){if(F.has(N,J)){if(M.call(L,N[J],J,N)===b){return}}}}}};F.map=F.collect=function(L,K,J){var I=[];if(L==null){return I}if(i&&L.map===i){return L.map(K,J)}d(L,function(O,M,N){I[I.length]=K.call(J,O,M,N)});if(L.length===+L.length){I.length=L.length}return I};F.reduce=F.foldl=F.inject=function(M,L,I,K){var J=arguments.length>2;if(M==null){M=[]}if(B&&M.reduce===B){if(K){L=F.bind(L,K)}return J?M.reduce(L,I):M.reduce(L)}d(M,function(P,N,O){if(!J){I=P;J=true}else{I=L.call(K,I,P,N,O)}});if(!J){throw new TypeError("Reduce of empty array with no initial value")}return I};F.reduceRight=F.foldr=function(M,L,I,K){var J=arguments.length>2;if(M==null){M=[]}if(e&&M.reduceRight===e){if(K){L=F.bind(L,K)}return J?M.reduceRight(L,I):M.reduceRight(L)}var N=F.toArray(M).reverse();if(K&&!J){L=F.bind(L,K)}return J?F.reduce(N,L,I,K):F.reduce(N,L)};F.find=F.detect=function(L,K,J){var I;r(L,function(O,M,N){if(K.call(J,O,M,N)){I=O;return true}});return I};F.filter=F.select=function(L,K,J){var I=[];if(L==null){return I}if(m&&L.filter===m){return L.filter(K,J)}d(L,function(O,M,N){if(K.call(J,O,M,N)){I[I.length]=O}});return I};F.reject=function(L,K,J){var I=[];if(L==null){return I}d(L,function(O,M,N){if(!K.call(J,O,M,N)){I[I.length]=O}});return I};F.every=F.all=function(L,K,J){var I=true;if(L==null){return I}if(a&&L.every===a){return L.every(K,J)}d(L,function(O,M,N){if(!(I=I&&K.call(J,O,M,N))){return b}});return I};var r=F.some=F.any=function(L,K,J){K||(K=F.identity);var I=false;if(L==null){return I}if(A&&L.some===A){return L.some(K,J)}d(L,function(O,M,N){if(I||(I=K.call(J,O,M,N))){return b}});return !!I};F.include=F.contains=function(K,J){var I=false;if(K==null){return I}if(w&&K.indexOf===w){return K.indexOf(J)!=-1}I=r(K,function(L){return L===J});return I};F.invoke=function(J,K){var I=v.call(arguments,2);return F.map(J,function(L){return(F.isFunction(K)?K||L:L[K]).apply(L,I)})};F.pluck=function(J,I){return F.map(J,function(K){return K[I]})};F.max=function(L,K,J){if(!K&&F.isArray(L)){return Math.max.apply(Math,L)}if(!K&&F.isEmpty(L)){return -Infinity}var I={computed:-Infinity};d(L,function(P,M,O){var N=K?K.call(J,P,M,O):P;N>=I.computed&&(I={value:P,computed:N})});return I.value};F.min=function(L,K,J){if(!K&&F.isArray(L)){return Math.min.apply(Math,L)}if(!K&&F.isEmpty(L)){return Infinity}var I={computed:Infinity};d(L,function(P,M,O){var N=K?K.call(J,P,M,O):P;N<I.computed&&(I={value:P,computed:N})});return I.value};F.shuffle=function(K){var I=[],J;d(K,function(N,L,M){if(L==0){I[0]=N}else{J=Math.floor(Math.random()*(L+1));I[L]=I[J];I[J]=N}});return I};F.sortBy=function(K,J,I){return F.pluck(F.map(K,function(N,L,M){return{value:N,criteria:J.call(I,N,L,M)}}).sort(function(O,N){var M=O.criteria,L=N.criteria;return M<L?-1:M>L?1:0}),"value")};F.groupBy=function(K,L){var I={};var J=F.isFunction(L)?L:function(M){return M[L]};d(K,function(O,M){var N=J(O,M);(I[N]||(I[N]=[])).push(O)});return I};F.sortedIndex=function(N,M,K){K||(K=F.identity);var I=0,L=N.length;while(I<L){var J=(I+L)>>1;K(N[J])<K(M)?I=J+1:L=J}return I};F.toArray=function(I){if(!I){return[]}if(I.toArray){return I.toArray()}if(F.isArray(I)){return v.call(I)}if(F.isArguments(I)){return v.call(I)}return F.values(I)};F.size=function(I){return F.toArray(I).length};F.first=F.head=function(K,J,I){return(J!=null)&&!I?v.call(K,0,J):K[0]};F.initial=function(K,J,I){return v.call(K,0,K.length-((J==null)||I?1:J))};F.last=function(K,J,I){if((J!=null)&&!I){return v.call(K,Math.max(K.length-J,0))}else{return K[K.length-1]}};F.rest=F.tail=function(K,I,J){return v.call(K,(I==null)||J?1:I)};F.compact=function(I){return F.filter(I,function(J){return !!J})};F.flatten=function(J,I){return F.reduce(J,function(K,L){if(F.isArray(L)){return K.concat(I?L:F.flatten(L))}K[K.length]=L;return K},[])};F.without=function(I){return F.difference(I,v.call(arguments,1))};F.uniq=F.unique=function(M,L,K){var J=K?F.map(M,K):M;var I=[];F.reduce(J,function(N,P,O){if(0==O||(L===true?F.last(N)!=P:!F.include(N,P))){N[N.length]=P;I[I.length]=M[O]}return N},[]);return I};F.union=function(){return F.uniq(F.flatten(arguments,true))};F.intersection=F.intersect=function(J){var I=v.call(arguments,1);return F.filter(F.uniq(J),function(K){return F.every(I,function(L){return F.indexOf(L,K)>=0})})};F.difference=function(J){var I=F.flatten(v.call(arguments,1));return F.filter(J,function(K){return !F.include(I,K)})};F.zip=function(){var I=v.call(arguments);var L=F.max(F.pluck(I,"length"));var K=new Array(L);for(var J=0;J<L;J++){K[J]=F.pluck(I,""+J)}return K};F.indexOf=function(M,K,L){if(M==null){return -1}var J,I;if(L){J=F.sortedIndex(M,K);return M[J]===K?J:-1}if(w&&M.indexOf===w){return M.indexOf(K)}for(J=0,I=M.length;J<I;J++){if(J in M&&M[J]===K){return J}}return -1};F.lastIndexOf=function(K,J){if(K==null){return -1}if(f&&K.lastIndexOf===f){return K.lastIndexOf(J)}var I=K.length;while(I--){if(I in K&&K[I]===J){return I}}return -1};F.range=function(N,L,M){if(arguments.length<=1){L=N||0;N=0}M=arguments[2]||1;var J=Math.max(Math.ceil((L-N)/M),0);var I=0;var K=new Array(J);while(I<J){K[I++]=N;N+=M}return K};var h=function(){};F.bind=function H(L,J){var K,I;if(L.bind===k&&k){return k.apply(L,v.call(arguments,1))}if(!F.isFunction(L)){throw new TypeError}I=v.call(arguments,2);return K=function(){if(!(this instanceof K)){return L.apply(J,I.concat(v.call(arguments)))}h.prototype=L.prototype;var N=new h;var M=L.apply(N,I.concat(v.call(arguments)));if(Object(M)===M){return M}return N}};F.bindAll=function(J){var I=v.call(arguments,1);if(I.length==0){I=F.functions(J)}d(I,function(K){J[K]=F.bind(J[K],J)});return J};F.memoize=function(K,J){var I={};J||(J=F.identity);return function(){var L=J.apply(this,arguments);return F.has(I,L)?I[L]:(I[L]=K.apply(this,arguments))}};F.delay=function(J,K){var I=v.call(arguments,2);return setTimeout(function(){return J.apply(J,I)},K)};F.defer=function(I){return F.delay.apply(F,[I,1].concat(v.call(arguments,1)))};F.throttle=function(N,P){var L,I,O,M,K;var J=F.debounce(function(){K=M=false},P);return function(){L=this;I=arguments;var Q=function(){O=null;if(K){N.apply(L,I)}J()};if(!O){O=setTimeout(Q,P)}if(M){K=true}else{N.apply(L,I)}J();M=true}};F.debounce=function(I,K){var J;return function(){var N=this,M=arguments;var L=function(){J=null;I.apply(N,M)};clearTimeout(J);J=setTimeout(L,K)}};F.once=function(K){var I=false,J;return function(){if(I){return J}I=true;return J=K.apply(this,arguments)}};F.wrap=function(I,J){return function(){var K=[I].concat(v.call(arguments,0));return J.apply(this,K)}};F.compose=function(){var I=arguments;return function(){var J=arguments;for(var K=I.length-1;K>=0;K--){J=[I[K].apply(this,J)]}return J[0]}};F.after=function(J,I){if(J<=0){return I()}return function(){if(--J<1){return I.apply(this,arguments)}}};F.keys=C||function(K){if(K!==Object(K)){throw new TypeError("Invalid object")}var J=[];for(var I in K){if(F.has(K,I)){J[J.length]=I}}return J};F.values=function(I){return F.map(I,F.identity)};F.functions=F.methods=function(K){var J=[];for(var I in K){if(F.isFunction(K[I])){J.push(I)}}return J.sort()};F.extend=function(I){d(v.call(arguments,1),function(J){for(var K in J){I[K]=J[K]}});return I};F.defaults=function(I){d(v.call(arguments,1),function(J){for(var K in J){if(I[K]==null){I[K]=J[K]}}});return I};F.clone=function(I){if(!F.isObject(I)){return I}return F.isArray(I)?I.slice():F.extend({},I)};F.tap=function(J,I){I(J);return J};function E(L,K,J){if(L===K){return L!==0||1/L==1/K}if(L==null||K==null){return L===K}if(L._chain){L=L._wrapped}if(K._chain){K=K._wrapped}if(L.isEqual&&F.isFunction(L.isEqual)){return L.isEqual(K)}if(K.isEqual&&F.isFunction(K.isEqual)){return K.isEqual(L)}var O=y.call(L);if(O!=y.call(K)){return false}switch(O){case"[object String]":return L==String(K);case"[object Number]":return L!=+L?K!=+K:(L==0?1/L==1/K:L==+K);case"[object Date]":case"[object Boolean]":return +L==+K;case"[object RegExp]":return L.source==K.source&&L.global==K.global&&L.multiline==K.multiline&&L.ignoreCase==K.ignoreCase}if(typeof L!="object"||typeof K!="object"){return false}var P=J.length;while(P--){if(J[P]==L){return true}}J.push(L);var N=0,I=true;if(O=="[object Array]"){N=L.length;I=N==K.length;if(I){while(N--){if(!(I=N in L==N in K&&E(L[N],K[N],J))){break}}}}else{if("constructor" in L!="constructor" in K||L.constructor!=K.constructor){return false}for(var M in L){if(F.has(L,M)){N++;if(!(I=F.has(K,M)&&E(L[M],K[M],J))){break}}}if(I){for(M in K){if(F.has(K,M)&&!(N--)){break}}I=!N}}J.pop();return I}F.isEqual=function(J,I){return E(J,I,[])};F.isEmpty=function(J){if(F.isArray(J)||F.isString(J)){return J.length===0}for(var I in J){if(F.has(J,I)){return false}}return true};F.isElement=function(I){return !!(I&&I.nodeType==1)};F.isArray=c||function(I){return y.call(I)=="[object Array]"};F.isObject=function(I){return I===Object(I)};F.isArguments=function(I){return y.call(I)=="[object Arguments]"};if(!F.isArguments(arguments)){F.isArguments=function(I){return !!(I&&F.has(I,"callee"))}}F.isFunction=function(I){return y.call(I)=="[object Function]"};F.isString=function(I){return y.call(I)=="[object String]"};F.isNumber=function(I){return y.call(I)=="[object Number]"};F.isNaN=function(I){return I!==I};F.isBoolean=function(I){return I===true||I===false||y.call(I)=="[object Boolean]"};F.isDate=function(I){return y.call(I)=="[object Date]"};F.isRegExp=function(I){return y.call(I)=="[object RegExp]"};F.isNull=function(I){return I===null};F.isUndefined=function(I){return I===void 0};F.has=function(J,I){return p.call(J,I)};F.noConflict=function(){x._=t;return this};F.identity=function(I){return I};F.times=function(L,K,J){for(var I=0;I<L;I++){K.call(J,I)}};F.escape=function(I){return(""+I).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};F.mixin=function(I){d(F.functions(I),function(J){s(J,F[J]=I[J])})};var l=0;F.uniqueId=function(I){var J=l++;return I?I+J:J};F.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/;var q=function(I){return I.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};F.template=function(L,K){var M=F.templateSettings;var I="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+L.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(M.escape||u,function(N,O){return"',_.escape("+q(O)+"),'"}).replace(M.interpolate||u,function(N,O){return"',"+q(O)+",'"}).replace(M.evaluate||u,function(N,O){return"');"+q(O).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";var J=new Function("obj","_",I);if(K){return J(K,F)}return function(N){return J.call(this,N,F)}};F.chain=function(I){return F(I).chain()};var g=function(I){this._wrapped=I};F.prototype=g.prototype;var o=function(J,I){return I?F(J).chain():J};var s=function(I,J){g.prototype[I]=function(){var K=v.call(arguments);z.call(K,this._wrapped);return o(J.apply(F,K),this._chain)}};F.mixin(F);d(["pop","push","reverse","shift","sort","splice","unshift"],function(I){var J=j[I];g.prototype[I]=function(){var K=this._wrapped;J.apply(K,arguments);var L=K.length;if((I=="shift"||I=="splice")&&L===0){delete K[0]}return o(K,this._chain)}});d(["concat","join","slice"],function(I){var J=j[I];g.prototype[I]=function(){return o(J.apply(this._wrapped,arguments),this._chain)}});g.prototype.chain=function(){this._chain=true;return this};g.prototype.value=function(){return this._wrapped}}).call(this);
\ No newline at end of file
+(function(){var w=this;var k=w._;var E={};var D=Array.prototype,g=Object.prototype,r=Function.prototype;var I=D.push,o=D.slice,y=D.concat,B=D.unshift,d=g.toString,j=g.hasOwnProperty;var M=D.forEach,q=D.map,F=D.reduce,c=D.reduceRight,b=D.filter,C=D.every,p=D.some,n=D.indexOf,l=D.lastIndexOf,u=Array.isArray,f=Object.keys,G=r.bind;var N=function(O){if(O instanceof N){return O}if(!(this instanceof N)){return new N(O)}this._wrapped=O};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=N}exports._=N}else{w._=N}N.VERSION="1.4.0";var J=N.each=N.forEach=function(T,S,R){if(M&&T.forEach===M){T.forEach(S,R)}else{if(T.length===+T.length){for(var Q=0,O=T.length;Q<O;Q++){if(S.call(R,T[Q],Q,T)===E){return}}}else{for(var P in T){if(N.has(T,P)){if(S.call(R,T[P],P,T)===E){return}}}}}};N.map=N.collect=function(R,Q,P){var O=[];if(q&&R.map===q){return R.map(Q,P)}J(R,function(U,S,T){O[O.length]=Q.call(P,U,S,T)});return O};N.reduce=N.foldl=N.inject=function(S,R,O,Q){var P=arguments.length>2;if(F&&S.reduce===F){if(Q){R=N.bind(R,Q)}return P?S.reduce(R,O):S.reduce(R)}J(S,function(V,T,U){if(!P){O=V;P=true}else{O=R.call(Q,O,V,T,U)}});if(!P){throw new TypeError("Reduce of empty array with no initial value")}return O};N.reduceRight=N.foldr=function(U,R,O,Q){var P=arguments.length>2;if(c&&U.reduceRight===c){if(Q){R=N.bind(R,Q)}return arguments.length>2?U.reduceRight(R,O):U.reduceRight(R)}var T=U.length;if(T!==+T){var S=N.keys(U);T=S.length}J(U,function(X,V,W){V=S?S[--T]:--T;if(!P){O=U[V];P=true}else{O=R.call(Q,O,U[V],V,W)}});if(!P){throw new TypeError("Reduce of empty array with no initial value")}return O};N.find=N.detect=function(R,Q,P){var O;A(R,function(U,S,T){if(Q.call(P,U,S,T)){O=U;return true}});return O};N.filter=N.select=function(R,Q,P){var O=[];if(b&&R.filter===b){return R.filter(Q,P)}J(R,function(U,S,T){if(Q.call(P,U,S,T)){O[O.length]=U}});return O};N.reject=function(R,Q,P){var O=[];J(R,function(U,S,T){if(!Q.call(P,U,S,T)){O[O.length]=U}});return O};N.every=N.all=function(R,Q,P){Q||(Q=N.identity);var O=true;if(C&&R.every===C){return R.every(Q,P)}J(R,function(U,S,T){if(!(O=O&&Q.call(P,U,S,T))){return E}});return !!O};var A=N.some=N.any=function(R,Q,P){Q||(Q=N.identity);var O=false;if(p&&R.some===p){return R.some(Q,P)}J(R,function(U,S,T){if(O||(O=Q.call(P,U,S,T))){return E}});return !!O};N.contains=N.include=function(Q,P){var O=false;if(n&&Q.indexOf===n){return Q.indexOf(P)!=-1}O=A(Q,function(R){return R===P});return O};N.invoke=function(P,Q){var O=o.call(arguments,2);return N.map(P,function(R){return(N.isFunction(Q)?Q:R[Q]).apply(R,O)})};N.pluck=function(P,O){return N.map(P,function(Q){return Q[O]})};N.where=function(P,O){if(N.isEmpty(O)){return[]}return N.filter(P,function(R){for(var Q in O){if(O[Q]!==R[Q]){return false}}return true})};N.max=function(R,Q,P){if(!Q&&N.isArray(R)&&R[0]===+R[0]&&R.length<65535){return Math.max.apply(Math,R)}if(!Q&&N.isEmpty(R)){return -Infinity}var O={computed:-Infinity};J(R,function(V,S,U){var T=Q?Q.call(P,V,S,U):V;T>=O.computed&&(O={value:V,computed:T})});return O.value};N.min=function(R,Q,P){if(!Q&&N.isArray(R)&&R[0]===+R[0]&&R.length<65535){return Math.min.apply(Math,R)}if(!Q&&N.isEmpty(R)){return Infinity}var O={computed:Infinity};J(R,function(V,S,U){var T=Q?Q.call(P,V,S,U):V;T<O.computed&&(O={value:V,computed:T})});return O.value};N.shuffle=function(R){var Q;var P=0;var O=[];J(R,function(S){Q=N.random(P++);O[P-1]=O[Q];O[Q]=S});return O};var a=function(O){return N.isFunction(O)?O:function(P){return P[O]}};N.sortBy=function(R,Q,O){var P=a(Q);return N.pluck(N.map(R,function(U,S,T){return{value:U,index:S,criteria:P.call(O,U,S,T)}}).sort(function(V,U){var T=V.criteria;var S=U.criteria;if(T!==S){if(T>S||T===void 0){return 1}if(T<S||S===void 0){return -1}}return V.index<U.index?-1:1}),"value")};var t=function(T,S,P,R){var O={};var Q=a(S);J(T,function(W,U){var V=Q.call(P,W,U,T);R(O,V,W)});return O};N.groupBy=function(Q,P,O){return t(Q,P,O,function(R,S,T){(N.has(R,S)?R[S]:(R[S]=[])).push(T)})};N.countBy=function(Q,P,O){return t(Q,P,O,function(R,S,T){if(!N.has(R,S)){R[S]=0}R[S]++})};N.sortedIndex=function(V,U,R,Q){R=R==null?N.identity:a(R);var T=R.call(Q,U);var O=0,S=V.length;while(O<S){var P=(O+S)>>>1;R.call(Q,V[P])<T?O=P+1:S=P}return O};N.toArray=function(O){if(!O){return[]}if(O.length===+O.length){return o.call(O)}return N.values(O)};N.size=function(O){return(O.length===+O.length)?O.length:N.keys(O).length};N.first=N.head=N.take=function(Q,P,O){return(P!=null)&&!O?o.call(Q,0,P):Q[0]};N.initial=function(Q,P,O){return o.call(Q,0,Q.length-((P==null)||O?1:P))};N.last=function(Q,P,O){if((P!=null)&&!O){return o.call(Q,Math.max(Q.length-P,0))}else{return Q[Q.length-1]}};N.rest=N.tail=N.drop=function(Q,P,O){return o.call(Q,(P==null)||O?1:P)};N.compact=function(O){return N.filter(O,function(P){return !!P})};var x=function(P,Q,O){J(P,function(R){if(N.isArray(R)){Q?I.apply(O,R):x(R,Q,O)}else{O.push(R)}});return O};N.flatten=function(P,O){return x(P,O,[])};N.without=function(O){return N.difference(O,o.call(arguments,1))};N.uniq=N.unique=function(U,T,S,R){var P=S?N.map(U,S,R):U;var Q=[];var O=[];J(P,function(W,V){if(T?(!V||O[O.length-1]!==W):!N.contains(O,W)){O.push(W);Q.push(U[V])}});return Q};N.union=function(){return N.uniq(y.apply(D,arguments))};N.intersection=function(P){var O=o.call(arguments,1);return N.filter(N.uniq(P),function(Q){return N.every(O,function(R){return N.indexOf(R,Q)>=0})})};N.difference=function(P){var O=y.apply(D,o.call(arguments,1));return N.filter(P,function(Q){return !N.contains(O,Q)})};N.zip=function(){var O=o.call(arguments);var R=N.max(N.pluck(O,"length"));var Q=new Array(R);for(var P=0;P<R;P++){Q[P]=N.pluck(O,""+P)}return Q};N.object=function(S,Q){var O={};for(var R=0,P=S.length;R<P;R++){if(Q){O[S[R]]=Q[R]}else{O[S[R][0]]=S[R][1]}}return O};N.indexOf=function(S,Q,R){var P=0,O=S.length;if(R){if(typeof R=="number"){P=(R<0?Math.max(0,O+R):R)}else{P=N.sortedIndex(S,Q);return S[P]===Q?P:-1}}if(n&&S.indexOf===n){return S.indexOf(Q,R)}for(;P<O;P++){if(S[P]===Q){return P}}return -1};N.lastIndexOf=function(R,Q,P){if(l&&R.lastIndexOf===l){return R.lastIndexOf(Q,P)}var O=(P!=null?P:R.length);while(O--){if(R[O]===Q){return O}}return -1};N.range=function(T,R,S){if(arguments.length<=1){R=T||0;T=0}S=arguments[2]||1;var P=Math.max(Math.ceil((R-T)/S),0);var O=0;var Q=new Array(P);while(O<P){Q[O++]=T;T+=S}return Q};var H=function(){};N.bind=function e(R,P){var Q,O;if(R.bind===G&&G){return G.apply(R,o.call(arguments,1))}if(!N.isFunction(R)){throw new TypeError}O=o.call(arguments,2);return Q=function(){if(!(this instanceof Q)){return R.apply(P,O.concat(o.call(arguments)))}H.prototype=R.prototype;var T=new H;var S=R.apply(T,O.concat(o.call(arguments)));if(Object(S)===S){return S}return T}};N.bindAll=function(P){var O=o.call(arguments,1);if(O.length==0){O=N.functions(P)}J(O,function(Q){P[Q]=N.bind(P[Q],P)});return P};N.memoize=function(Q,P){var O={};P||(P=N.identity);return function(){var R=P.apply(this,arguments);return N.has(O,R)?O[R]:(O[R]=Q.apply(this,arguments))}};N.delay=function(P,Q){var O=o.call(arguments,2);return setTimeout(function(){return P.apply(null,O)},Q)};N.defer=function(O){return N.delay.apply(N,[O,1].concat(o.call(arguments,1)))};N.throttle=function(Q,R){var P,T,U,V,S,W;var O=N.debounce(function(){S=V=false},R);return function(){P=this;T=arguments;var X=function(){U=null;if(S){W=Q.apply(P,T)}O()};if(!U){U=setTimeout(X,R)}if(V){S=true}else{V=true;W=Q.apply(P,T)}O();return W}};N.debounce=function(Q,S,P){var R,O;return function(){var W=this,V=arguments;var U=function(){R=null;if(!P){O=Q.apply(W,V)}};var T=P&&!R;clearTimeout(R);R=setTimeout(U,S);if(T){O=Q.apply(W,V)}return O}};N.once=function(Q){var O=false,P;return function(){if(O){return P}O=true;P=Q.apply(this,arguments);Q=null;return P}};N.wrap=function(O,P){return function(){var Q=[O];I.apply(Q,arguments);return P.apply(this,Q)}};N.compose=function(){var O=arguments;return function(){var P=arguments;for(var Q=O.length-1;Q>=0;Q--){P=[O[Q].apply(this,P)]}return P[0]}};N.after=function(P,O){if(P<=0){return O()}return function(){if(--P<1){return O.apply(this,arguments)}}};N.keys=f||function(Q){if(Q!==Object(Q)){throw new TypeError("Invalid object")}var P=[];for(var O in Q){if(N.has(Q,O)){P[P.length]=O}}return P};N.values=function(Q){var O=[];for(var P in Q){if(N.has(Q,P)){O.push(Q[P])}}return O};N.pairs=function(Q){var P=[];for(var O in Q){if(N.has(Q,O)){P.push([O,Q[O]])}}return P};N.invert=function(Q){var O={};for(var P in Q){if(N.has(Q,P)){O[Q[P]]=P}}return O};N.functions=N.methods=function(Q){var P=[];for(var O in Q){if(N.isFunction(Q[O])){P.push(O)}}return P.sort()};N.extend=function(O){J(o.call(arguments,1),function(P){for(var Q in P){O[Q]=P[Q]}});return O};N.pick=function(P){var Q={};var O=y.apply(D,o.call(arguments,1));J(O,function(R){if(R in P){Q[R]=P[R]}});return Q};N.omit=function(Q){var R={};var P=y.apply(D,o.call(arguments,1));for(var O in Q){if(!N.contains(P,O)){R[O]=Q[O]}}return R};N.defaults=function(O){J(o.call(arguments,1),function(P){for(var Q in P){if(O[Q]==null){O[Q]=P[Q]}}});return O};N.clone=function(O){if(!N.isObject(O)){return O}return N.isArray(O)?O.slice():N.extend({},O)};N.tap=function(P,O){O(P);return P};var K=function(V,U,P,Q){if(V===U){return V!==0||1/V==1/U}if(V==null||U==null){return V===U}if(V instanceof N){V=V._wrapped}if(U instanceof N){U=U._wrapped}var S=d.call(V);if(S!=d.call(U)){return false}switch(S){case"[object String]":return V==String(U);case"[object Number]":return V!=+V?U!=+U:(V==0?1/V==1/U:V==+U);case"[object Date]":case"[object Boolean]":return +V==+U;case"[object RegExp]":return V.source==U.source&&V.global==U.global&&V.multiline==U.multiline&&V.ignoreCase==U.ignoreCase}if(typeof V!="object"||typeof U!="object"){return false}var O=P.length;while(O--){if(P[O]==V){return Q[O]==U}}P.push(V);Q.push(U);var X=0,Y=true;if(S=="[object Array]"){X=V.length;Y=X==U.length;if(Y){while(X--){if(!(Y=K(V[X],U[X],P,Q))){break}}}}else{var T=V.constructor,R=U.constructor;if(T!==R&&!(N.isFunction(T)&&(T instanceof T)&&N.isFunction(R)&&(R instanceof R))){return false}for(var W in V){if(N.has(V,W)){X++;if(!(Y=N.has(U,W)&&K(V[W],U[W],P,Q))){break}}}if(Y){for(W in U){if(N.has(U,W)&&!(X--)){break}}Y=!X}}P.pop();Q.pop();return Y};N.isEqual=function(P,O){return K(P,O,[],[])};N.isEmpty=function(P){if(P==null){return true}if(N.isArray(P)||N.isString(P)){return P.length===0}for(var O in P){if(N.has(P,O)){return false}}return true};N.isElement=function(O){return !!(O&&O.nodeType===1)};N.isArray=u||function(O){return d.call(O)=="[object Array]"};N.isObject=function(O){return O===Object(O)};J(["Arguments","Function","String","Number","Date","RegExp"],function(O){N["is"+O]=function(P){return d.call(P)=="[object "+O+"]"}});if(!N.isArguments(arguments)){N.isArguments=function(O){return !!(O&&N.has(O,"callee"))}}if(typeof(/./)!=="function"){N.isFunction=function(O){return typeof O==="function"}}N.isFinite=function(O){return N.isNumber(O)&&isFinite(O)};N.isNaN=function(O){return N.isNumber(O)&&O!=+O};N.isBoolean=function(O){return O===true||O===false||d.call(O)=="[object Boolean]"};N.isNull=function(O){return O===null};N.isUndefined=function(O){return O===void 0};N.has=function(P,O){return j.call(P,O)};N.noConflict=function(){w._=k;return this};N.identity=function(O){return O};N.times=function(R,Q,P){for(var O=0;O<R;O++){Q.call(P,O)}};N.random=function(P,O){if(O==null){O=P;P=0}return P+(0|Math.random()*(O-P+1))};var m={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};m.unescape=N.invert(m.escape);var L={escape:new RegExp("["+N.keys(m.escape).join("")+"]","g"),unescape:new RegExp("("+N.keys(m.unescape).join("|")+")","g")};N.each(["escape","unescape"],function(O){N[O]=function(P){if(P==null){return""}return(""+P).replace(L[O],function(Q){return m[O][Q]})}});N.result=function(O,Q){if(O==null){return null}var P=O[Q];return N.isFunction(P)?P.call(O):P};N.mixin=function(O){J(N.functions(O),function(P){var Q=N[P]=O[P];N.prototype[P]=function(){var R=[this._wrapped];I.apply(R,arguments);return s.call(this,Q.apply(N,R))}})};var z=0;N.uniqueId=function(O){var P=z++;return O?O+P:P};N.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var v=/(.)^/;var h={"'":"'","\\":"\\","\r":"r","\n":"n","\t":"t","\u2028":"u2028","\u2029":"u2029"};var i=/\\|'|\r|\n|\t|\u2028|\u2029/g;N.template=function(W,R,Q){Q=N.defaults({},Q,N.templateSettings);var S=new RegExp([(Q.escape||v).source,(Q.interpolate||v).source,(Q.evaluate||v).source].join("|")+"|$","g");var T=0;var O="__p+='";W.replace(S,function(Y,Z,X,ab,aa){O+=W.slice(T,aa).replace(i,function(ac){return"\\"+h[ac]});O+=Z?"'+\n((__t=("+Z+"))==null?'':_.escape(__t))+\n'":X?"'+\n((__t=("+X+"))==null?'':__t)+\n'":ab?"';\n"+ab+"\n__p+='":"";T=aa+Y.length});O+="';\n";if(!Q.variable){O="with(obj||{}){\n"+O+"}\n"}O="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+O+"return __p;\n";try{var P=new Function(Q.variable||"obj","_",O)}catch(U){U.source=O;throw U}if(R){return P(R,N)}var V=function(X){return P.call(this,X,N)};V.source="function("+(Q.variable||"obj")+"){\n"+O+"}";return V};N.chain=function(O){return N(O).chain()};var s=function(O){return this._chain?N(O).chain():O};N.mixin(N);J(["pop","push","reverse","shift","sort","splice","unshift"],function(O){var P=D[O];N.prototype[O]=function(){var Q=this._wrapped;P.apply(Q,arguments);if((O=="shift"||O=="splice")&&Q.length===0){delete Q[0]}return s.call(this,Q)}});J(["concat","join","slice"],function(O){var P=D[O];N.prototype[O]=function(){return s.call(this,P.apply(this._wrapped,arguments))}});N.extend(N.prototype,{chain:function(){this._chain=true;return this},value:function(){return this._wrapped}})}).call(this);
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/mvc/data.js
--- a/static/scripts/packed/mvc/data.js
+++ b/static/scripts/packed/mvc/data.js
@@ -1,1 +1,1 @@
-var Dataset=Backbone.RelationalModel.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda"},urlRoot:galaxy_paths.get("datasets_url")});var DatasetCollection=Backbone.Collection.extend({model:Dataset});
\ No newline at end of file
+define(["libs/backbone/backbone-relational"],function(){var a=Backbone.RelationalModel.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda"},urlRoot:galaxy_paths.get("datasets_url")});var b=Backbone.Collection.extend({model:a});return{Dataset:a,DatasetCollection:b}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/mvc/history.js
--- a/static/scripts/packed/mvc/history.js
+++ b/static/scripts/packed/mvc/history.js
@@ -1,1 +1,1 @@
-function linkHTMLTemplate(b,a){if(!b){return"<a></a>"}a=a||"a";var c=["<"+a];for(key in b){var d=b[key];if(d===""){continue}switch(key){case"text":continue;case"classes":key="class";d=(b.classes.join)?(b.classes.join(" ")):(b.classes);default:c.push([" ",key,'="',d,'"'].join(""))}}c.push(">");if("text" in b){c.push(b.text)}c.push("</"+a+">");return c.join("")}var HistoryItem=BaseModel.extend(LoggableMixin).extend({defaults:{id:null,name:"",data_type:null,file_size:0,genome_build:null,metadata_data_lines:0,metadata_dbkey:null,metadata_sequences:0,misc_blurb:"",misc_info:"",model_class:"",state:"",deleted:false,purged:false,visible:true,for_editing:true,bodyIsShown:false},initialize:function(){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",HistoryItem.STATES.NOT_VIEWABLE)}},isEditable:function(){return(!(this.get("deleted")||this.get("purged")))},hasData:function(){return(this.get("file_size")>0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a+=':"'+this.get("name")+'"'}return"HistoryItem("+a+")"}});HistoryItem.STATES={NOT_VIEWABLE:"not_viewable",NEW:"new",UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",OK:"ok",EMPTY:"empty",ERROR:"error",DISCARDED:"discarded",SETTING_METADATA:"setting_metadata",FAILED_METADATA:"failed_metadata"};var HistoryItemView=BaseView.extend(LoggableMixin).extend({tagName:"div",className:"historyItemContainer",initialize:function(){this.log(this+".initialize:",this,this.model)},render:function(){var d=this.model.get("id"),c=this.model.get("state");this.clearReferences();this.$el.attr("id","historyItemContainer-"+d);var a=$("<div/>").attr("id","historyItem-"+d).addClass("historyItemWrapper").addClass("historyItem").addClass("historyItem-"+c);a.append(this._render_warnings());a.append(this._render_titleBar());this.body=$(this._render_body());a.append(this.body);a.find(".tooltip").tooltip({placement:"bottom"});var b=a.find("[popupmenu]");b.each(function(e,f){f=$(f);make_popupmenu(f)});this.$el.children().remove();return this.$el.append(a)},clearReferences:function(){this.displayButton=null;this.editButton=null;this.deleteButton=null;this.errButton=null},_render_warnings:function(){return $(jQuery.trim(HistoryItemView.templates.messages(this.model.toJSON())))},_render_titleBar:function(){var a=$('<div class="historyItemTitleBar" style="overflow: hidden"></div>');a.append(this._render_titleButtons());a.append('<span class="state-icon"></span>');a.append(this._render_titleLink());return a},_render_titleButtons:function(){var a=$('<div class="historyItemButtons"></div>');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_displayButton:function(){if(this.model.get("state")===HistoryItem.STATES.UPLOAD){return null}displayBtnData=(this.model.get("purged"))?({title:"Cannot display datasets removed from disk",enabled:false,icon_class:"display"}):({title:"Display data in browser",href:this.model.get("display_url"),target:(this.model.get("for_editing"))?("galaxy_main"):(null),icon_class:"display"});this.displayButton=new IconButtonView({model:new IconButton(displayBtnData)});return this.displayButton.render().$el},_render_editButton:function(){if((this.model.get("state")===HistoryItem.STATES.UPLOAD)||(!this.model.get("for_editing"))){return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:"Edit attributes",href:this.model.get("edit_url"),target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false}if(a){b.title="Undelete dataset to edit attributes"}else{if(c){b.title="Cannot edit attributes of datasets removed from disk"}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if(!this.model.get("for_editing")){return null}var a={title:"Delete",href:this.model.get("delete_url"),target:"galaxy_main",id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete"};if((this.model.get("deleted")||this.model.get("purged"))&&(!this.model.get("delete_url"))){a={title:"Dataset is already deleted",icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(a)});return this.deleteButton.render().$el},_render_titleLink:function(){return $(jQuery.trim(HistoryItemView.templates.titleLink(this.model.toJSON())))},_render_hdaSummary:function(){var a=this.model.toJSON();if(this.model.get("metadata_dbkey")==="?"&&this.model.isEditable()){_.extend(a,{dbkey_unknown_and_editable:true})}return HistoryItemView.templates.hdaSummary(a)},_render_primaryActionButtons:function(c){var b=$("<div/>"),a=this;_.each(c,function(d){b.append(d.call(a))});return b},_render_downloadButton:function(){if(this.model.get("purged")){return null}var a=linkHTMLTemplate({title:"Download",href:this.model.get("download_url"),classes:["icon-button","tooltip","disk"]});var d=this.model.get("download_meta_urls");if(!d){return a}var c=$('<div popupmenu="dataset-'+this.model.get("id")+'-popup"></div>');c.append(linkHTMLTemplate({text:"Download Dataset",title:"Download",href:this.model.get("download_url"),classes:["icon-button","tooltip","disk"]}));c.append("<a>Additional Files</a>");for(file_type in d){c.append(linkHTMLTemplate({text:"Download "+file_type,href:d[file_type],classes:["action-button"]}))}var b=$(('<div style="float:left;" class="menubutton split popup" id="dataset-${dataset_id}-popup"></div>'));b.append(a);c.append(b);return c},_render_errButton:function(){if((this.model.get("state")!==HistoryItem.STATES.ERROR)||(!this.model.get("for_editing"))){return null}this.errButton=new IconButtonView({model:new IconButton({title:"View or report this error",href:this.model.get("report_error_url"),target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_showParamsButton:function(){this.showParamsButton=new IconButtonView({model:new IconButton({title:"View details",href:this.model.get("show_params_url"),target:"galaxy_main",icon_class:"information"})});return this.showParamsButton.render().$el},_render_rerunButton:function(){if(!this.model.get("for_editing")){return null}this.rerunButton=new IconButtonView({model:new IconButton({title:"Run this job again",href:this.model.get("rerun_url"),target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_tracksterButton:function(){var a=this.model.get("trackster_urls");if(!(this.model.hasData())||!(this.model.get("for_editing"))||!(a)){return null}this.tracksterButton=new IconButtonView({model:new IconButton({title:"View in Trackster",icon_class:"chart_curve"})});this.errButton.render();this.errButton.$el.addClass("trackster-add").attr({"data-url":a["data-url"],"action-url":a["action-url"],"new-url":a["new-url"]});return this.errButton.$el},_render_secondaryActionButtons:function(b){var c=$('<div style="float: right;"></div>'),a=this;_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||!(this.model.get("for_editing"))||(!this.model.get("retag_url"))){return null}this.tagButton=new IconButtonView({model:new IconButton({title:"Edit dataset tags",target:"galaxy_main",href:this.model.get("retag_url"),icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||!(this.model.get("for_editing"))||(!this.model.get("annotate_url"))){return null}this.annotateButton=new IconButtonView({model:new IconButton({title:"Edit dataset annotation",target:"galaxy_main",href:this.model.get("annotate_url"),icon_class:"annotate"})});return this.annotateButton.render().$el},_render_tagArea:function(){if(this.model.get("retag_url")){return null}return $(HistoryItemView.templates.tagArea(this.model.toJSON()))},_render_annotationArea:function(){if(!this.model.get("annotate_url")){return null}return $(HistoryItemView.templates.annotationArea(this.model.toJSON()))},_render_displayApps:function(){if(!this.model.get("display_apps")){return null}var a=this.model.get("displayApps"),c=$("<div/>"),b=$("<span/>");this.log(this+"displayApps:",a);return c},_render_peek:function(){if(!this.model.get("peek")){return null}return $("<div/>").append($("<pre/>").attr("id","peek"+this.model.get("id")).addClass("peek").append(this.model.get("peek")))},_render_body_not_viewable:function(a){a.append($("<div>You do not have permission to view dataset.</div>"))},_render_body_uploading:function(a){a.append($("<div>Dataset is uploading</div>"))},_render_body_queued:function(a){a.append($("<div>Job is waiting to run.</div>"));a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_running:function(a){a.append("<div>Job is currently running.</div>");a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_error:function(a){if(!this.model.get("purged")){a.append($("<div>"+this.model.get("misc_blurb")+"</div>"))}a.append(("An error occurred running this job: <i>"+$.trim(this.model.get("misc_info"))+"</i>"));a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_errButton,this._render_showParamsButton,this._render_rerunButton]))},_render_body_discarded:function(a){a.append("<div>The job creating this dataset was cancelled before completion.</div>");a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_setting_metadata:function(a){a.append($("<div>Metadata is being auto-detected.</div>"))},_render_body_empty:function(a){a.append($("<div>No data: <i>"+this.model.get("misc_blurb")+"</i></div>"));a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_failed_metadata:function(a){a.append($(HistoryItemView.templates.failedMetadata(this.model.toJSON())));this._render_body_ok(a)},_render_body_ok:function(a){a.append(this._render_hdaSummary());a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_errButton,this._render_showParamsButton,this._render_rerunButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('<div class="clear"/>');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayApps());a.append(this._render_peek())},_render_body:function(){var b=this.model.get("state");var a=$("<div/>").attr("id","info-"+this.model.get("id")).addClass("historyItemBody").attr("style","display: block");switch(b){case HistoryItem.STATES.NOT_VIEWABLE:this._render_body_not_viewable(a);break;case HistoryItem.STATES.UPLOAD:this._render_body_uploading(a);break;case HistoryItem.STATES.QUEUED:this._render_body_queued(a);break;case HistoryItem.STATES.RUNNING:this._render_body_running(a);break;case HistoryItem.STATES.ERROR:this._render_body_error(a);break;case HistoryItem.STATES.DISCARDED:this._render_body_discarded(a);break;case HistoryItem.STATES.SETTING_METADATA:this._render_body_setting_metadata(a);break;case HistoryItem.STATES.EMPTY:this._render_body_empty(a);break;case HistoryItem.STATES.FAILED_METADATA:this._render_body_failed_metadata(a);break;case HistoryItem.STATES.OK:this._render_body_ok(a);break;default:a.append($('<div>Error: unknown dataset state "'+b+'".</div>'))}a.append('<div style="clear: both"></div>');if(this.model.get("bodyIsShown")===false){a.hide()}return a},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(b){this.log(this+".loadAndDisplayTags",b);var c=this.$el.find(".tag-area"),a=c.find(".tag-elt");if(c.is(":hidden")){if(!a.html()){$.ajax({url:this.model.get("ajax_get_tag_url"),error:function(){alert("Tagging failed")},success:function(d){a.html(d);a.find(".tooltip").tooltip();c.slideDown("fast")}})}else{c.slideDown("fast")}}else{c.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.model.get("ajax_set_annotation_url");if(d.is(":hidden")){if(!c.html()){$.ajax({url:this.model.get("ajax_get_annotation_url"),error:function(){alert("Annotations failed")},success:function(e){if(e===""){e="<em>Describe or add notes to dataset</em>"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toggleBodyVisibility:function(){this.log(this+".toggleBodyVisibility");this.$el.find(".historyItemBody").toggle()},toString:function(){var a=(this.model)?(this.model+""):("");return"HistoryItemView("+a+")"}});HistoryItemView.templates=CompiledTemplateLoader.getTemplates({"common-templates.html":{warningMsg:"template-warningmessagesmall"},"history-templates.html":{messages:"template-history-warning-messages",titleLink:"template-history-titleLink",hdaSummary:"template-history-hdaSummary",failedMetadata:"template-history-failedMetaData",tagArea:"template-history-tagArea",annotationArea:"template-history-annotationArea"}});var HistoryCollection=Backbone.Collection.extend({model:HistoryItem,toString:function(){return("HistoryCollection()")}});var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",state_details:{discarded:0,empty:0,error:0,failed_metadata:0,ok:0,queued:0,running:0,setting_metadata:0,upload:0}},initialize:function(b,a){this.log(this+".initialize",b,a);this.items=new HistoryCollection()},loadDatasetsAsHistoryItems:function(c){var a=this,b=this.get("id"),d=this.get("state_details");_.each(c,function(f,e){a.log("loading dataset: ",f,e);var h=new HistoryItem(_.extend(f,{history_id:b}));a.log("as History:",h);a.items.add(h);var g=f.state;d[g]+=1});this.set("state_details",d);this._stateFromStateDetails();return this},_stateFromStateDetails:function(){this.set("state","");var a=this.get("state_details");if((a.error>0)||(a.failed_metadata>0)){this.set("state",HistoryItem.STATES.ERROR)}else{if((a.running>0)||(a.setting_metadata>0)){this.set("state",HistoryItem.STATES.RUNNING)}else{if(a.queued>0){this.set("state",HistoryItem.STATES.QUEUED)}else{if(a.ok===this.items.length){this.set("state",HistoryItem.STATES.OK)}else{throw ("_stateFromStateDetails: unable to determine history state from state details: "+this.state_details)}}}}return this},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HistoryView=BaseView.extend(LoggableMixin).extend({el:"body.historyPage",initialize:function(){this.log(this+".initialize");this.itemViews=[];var a=this;this.model.items.each(function(c){var b=new HistoryItemView({model:c});a.itemViews.push(b)})},render:function(){this.log(this+".render");var a=$("<div/>");_.each(this.itemViews,function(b){a.prepend(b.render())});this.$el.append(a.children());a.remove()},toString:function(){var a=this.model.get("name")||"";return"HistoryView("+a+")"}});function createMockHistoryData(){mockHistory={};mockHistory.data={template:{id:"a799d38679e985db",name:"template",data_type:"fastq",file_size:226297533,genome_build:"?",metadata_data_lines:0,metadata_dbkey:"?",metadata_sequences:0,misc_blurb:"215.8 MB",misc_info:"uploaded fastq file (misc_info)",model_class:"HistoryDatasetAssociation",download_url:"",state:"ok",visible:true,deleted:false,purged:false,hid:0,for_editing:true,accessible:true,undelete_url:"",purge_url:"",unhide_url:"",display_url:"example.com/display ",edit_url:"example.com/edit ",delete_url:"example.com/delete ",show_params_url:"example.com/show_params ",rerun_url:"example.com/rerun ",retag_url:"example.com/retag ",annotate_url:"example.com/annotate ",peek:['<table cellspacing="0" cellpadding="3"><tr><th>1.QNAME</th><th>2.FLAG</th><th>3.RNAME</th><th>4.POS</th><th>5.MAPQ</th><th>6.CIGAR</th><th>7.MRNM</th><th>8.MPOS</th><th>9.ISIZE</th><th>10.SEQ</th><th>11.QUAL</th><th>12.OPT</th></tr>','<tr><td colspan="100%">@SQ SN:gi|87159884|ref|NC_007793.1| LN:2872769</td></tr>','<tr><td colspan="100%">@PG ID:bwa PN:bwa VN:0.5.9-r16</td></tr>','<tr><td colspan="100%">HWUSI-EAS664L:15:64HOJAAXX:1:1:13280:968 73 gi|87159884|ref|NC_007793.1| 2720169 37 101M = 2720169 0 NAATATGACATTATTTTCAAAACAGCTGAAAATTTAGACGTACCGATTTATCTACATCCCGCGCCAGTTAACAGTGACATTTATCAATCATACTATAAAGG !!!!!!!!!!$!!!$!!!!!$!!!!!!$!$!$$$!!$!!$!!!!!!!!!!!$!</td></tr>','<tr><td colspan="100%">!!!$!$!$$!!$$!!$!!!!!!!!!!!!!!!!!!!!!!!!!!$!!$!! XT:A:U NM:i:1 SM:i:37 AM:i:0 X0:i:1 X1:i:0 XM:i:1 XO:i:0 XG:i:0 MD:Z:0A100</td></tr>','<tr><td colspan="100%">HWUSI-EAS664L:15:64HOJAAXX:1:1:13280:968 133 gi|87159884|ref|NC_007793.1| 2720169 0 * = 2720169 0 NAAACTGTGGCTTCGTTNNNNNNNNNNNNNNNGTGANNNNNNNNNNNNNNNNNNNGNNNNNNNNNNNNNNNNNNNNCNAANNNNNNNNNNNNNNNNNNNNN !!!!!!!!!!!!$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</td></tr>','<tr><td colspan="100%">!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</td></tr>',"</table>"].join("")}};_.extend(mockHistory.data,{notAccessible:_.extend(_.clone(mockHistory.data.template),{accessible:false}),deleted:_.extend(_.clone(mockHistory.data.template),{deleted:true,delete_url:"",purge_url:"example.com/purge ",undelete_url:"example.com/undelete "}),purgedNotDeleted:_.extend(_.clone(mockHistory.data.template),{purged:true,delete_url:""}),notvisible:_.extend(_.clone(mockHistory.data.template),{visible:false,unhide_url:"example.com/unhide "}),hasDisplayApps:_.extend(_.clone(mockHistory.data.template),{display_apps:{"display in IGB":{Web:"/display_application/63cd3858d057a6d1/igb_bam/Web",Local:"/display_application/63cd3858d057a6d1/igb_bam/Local"}}}),canTrackster:_.extend(_.clone(mockHistory.data.template),{trackster_urls:{"data-url":"example.com/trackster-data ","action-url":"example.com/trackster-action ","new-url":"example.com/trackster-new "}}),zeroSize:_.extend(_.clone(mockHistory.data.template),{file_size:0}),hasMetafiles:_.extend(_.clone(mockHistory.data.template),{download_meta_urls:{bam_index:"example.com/bam-index "}}),upload:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.UPLOAD}),queued:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.QUEUED}),running:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.RUNNING}),empty:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.EMPTY}),error:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.ERROR,report_error_url:"example.com/report_err "}),discarded:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.DISCARDED}),setting_metadata:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.SETTING_METADATA}),failed_metadata:_.extend(_.clone(mockHistory.data.template),{state:HistoryItem.STATES.FAILED_METADATA})});$(document).ready(function(){mockHistory.items={};mockHistory.views={};for(key in mockHistory.data){mockHistory.items[key]=new HistoryItem(mockHistory.data[key]);mockHistory.items[key].set("name",key);mockHistory.views[key]=new HistoryItemView({model:mockHistory.items[key]});$("body").append(mockHistory.views[key].render())}})};
\ No newline at end of file
+function linkHTMLTemplate(b,a){if(!b){return"<a></a>"}a=a||"a";var c=["<"+a];for(key in b){var d=b[key];if(d===""){continue}switch(key){case"text":continue;case"classes":key="class";d=(b.classes.join)?(b.classes.join(" ")):(b.classes);default:c.push([" ",key,'="',d,'"'].join(""))}}c.push(">");if("text" in b){c.push(b.text)}c.push("</"+a+">");return c.join("")}var HistoryItem=BaseModel.extend(LoggableMixin).extend({defaults:{id:null,name:"",data_type:null,file_size:0,genome_build:null,metadata_data_lines:0,metadata_dbkey:null,metadata_sequences:0,misc_blurb:"",misc_info:"",model_class:"",state:"",deleted:false,purged:false,visible:true,for_editing:true,bodyIsShown:false},initialize:function(){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",HistoryItem.STATES.NOT_VIEWABLE)}},isEditable:function(){return(!(this.get("deleted")||this.get("purged")))},hasData:function(){return(this.get("file_size")>0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a+=':"'+this.get("name")+'"'}return"HistoryItem("+a+")"}});HistoryItem.STATES={NOT_VIEWABLE:"not_viewable",NEW:"new",UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",OK:"ok",EMPTY:"empty",ERROR:"error",DISCARDED:"discarded",SETTING_METADATA:"setting_metadata",FAILED_METADATA:"failed_metadata"};var HistoryItemView=BaseView.extend(LoggableMixin).extend({tagName:"div",className:"historyItemContainer",initialize:function(){this.log(this+".initialize:",this,this.model)},render:function(){var c=this.model.get("id"),b=this.model.get("state");this.clearReferences();this.$el.attr("id","historyItemContainer-"+c);var a=$("<div/>").attr("id","historyItem-"+c).addClass("historyItemWrapper").addClass("historyItem").addClass("historyItem-"+b);a.append(this._render_warnings());a.append(this._render_titleBar());this.body=$(this._render_body());a.append(this.body);a.find(".tooltip").tooltip({placement:"bottom"});make_popup_menus(a);this.$el.children().remove();return this.$el.append(a)},clearReferences:function(){this.displayButton=null;this.editButton=null;this.deleteButton=null;this.errButton=null},_render_warnings:function(){return $(jQuery.trim(HistoryItemView.templates.messages(this.model.toJSON())))},_render_titleBar:function(){var a=$('<div class="historyItemTitleBar" style="overflow: hidden"></div>');a.append(this._render_titleButtons());a.append('<span class="state-icon"></span>');a.append(this._render_titleLink());return a},_render_titleButtons:function(){var a=$('<div class="historyItemButtons"></div>');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_displayButton:function(){if(this.model.get("state")===HistoryItem.STATES.UPLOAD){return null}displayBtnData=(this.model.get("purged"))?({title:"Cannot display datasets removed from disk",enabled:false,icon_class:"display"}):({title:"Display data in browser",href:this.model.get("display_url"),target:(this.model.get("for_editing"))?("galaxy_main"):(null),icon_class:"display"});this.displayButton=new IconButtonView({model:new IconButton(displayBtnData)});return this.displayButton.render().$el},_render_editButton:function(){if((this.model.get("state")===HistoryItem.STATES.UPLOAD)||(!this.model.get("for_editing"))){return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:"Edit attributes",href:this.model.get("edit_url"),target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false}if(a){b.title="Undelete dataset to edit attributes"}else{if(c){b.title="Cannot edit attributes of datasets removed from disk"}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if(!this.model.get("for_editing")){return null}var a={title:"Delete",href:this.model.get("delete_url"),target:"galaxy_main",id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete"};if((this.model.get("deleted")||this.model.get("purged"))&&(!this.model.get("delete_url"))){a={title:"Dataset is already deleted",icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(a)});return this.deleteButton.render().$el},_render_titleLink:function(){return $(jQuery.trim(HistoryItemView.templates.titleLink(this.model.toJSON())))},_render_hdaSummary:function(){var a=this.model.toJSON();if(this.model.get("metadata_dbkey")==="?"&&this.model.isEditable()){_.extend(a,{dbkey_unknown_and_editable:true})}return HistoryItemView.templates.hdaSummary(a)},_render_primaryActionButtons:function(c){var b=$("<div/>").attr("id","primary-actions-"+this.model.get("id")),a=this;_.each(c,function(d){var e=d.call(a);b.append(e)});return b},_render_downloadButton:function(){if(this.model.get("purged")){return null}var a=HistoryItemView.templates.downloadLinks(this.model.toJSON());this.log("_render_downloadButton, downloadLinkHTML:",a);return $(a)},_render_errButton:function(){if((this.model.get("state")!==HistoryItem.STATES.ERROR)||(!this.model.get("for_editing"))){return null}this.errButton=new IconButtonView({model:new IconButton({title:"View or report this error",href:this.model.get("report_error_url"),target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_showParamsButton:function(){this.showParamsButton=new IconButtonView({model:new IconButton({title:"View details",href:this.model.get("show_params_url"),target:"galaxy_main",icon_class:"information"})});return this.showParamsButton.render().$el},_render_rerunButton:function(){if(!this.model.get("for_editing")){return null}this.rerunButton=new IconButtonView({model:new IconButton({title:"Run this job again",href:this.model.get("rerun_url"),target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_tracksterButton:function(){var a=this.model.get("trackster_urls");if(!(this.model.hasData())||!(this.model.get("for_editing"))||!(a)){return null}this.tracksterButton=new IconButtonView({model:new IconButton({title:"View in Trackster",icon_class:"chart_curve"})});this.errButton.render();this.errButton.$el.addClass("trackster-add").attr({"data-url":a["data-url"],"action-url":a["action-url"],"new-url":a["new-url"]});return this.errButton.$el},_render_secondaryActionButtons:function(b){var c=$("<div/>"),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||!(this.model.get("for_editing"))||(!this.model.get("retag_url"))){return null}this.tagButton=new IconButtonView({model:new IconButton({title:"Edit dataset tags",target:"galaxy_main",href:this.model.get("retag_url"),icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||!(this.model.get("for_editing"))||(!this.model.get("annotate_url"))){return null}this.annotateButton=new IconButtonView({model:new IconButton({title:"Edit dataset annotation",target:"galaxy_main",href:this.model.get("annotate_url"),icon_class:"annotate"})});return this.annotateButton.render().$el},_render_tagArea:function(){if(this.model.get("retag_url")){return null}return $(HistoryItemView.templates.tagArea(this.model.toJSON()))},_render_annotationArea:function(){if(!this.model.get("annotate_url")){return null}return $(HistoryItemView.templates.annotationArea(this.model.toJSON()))},_render_displayApps:function(){if(!this.model.hasData()){return null}var a=$("<div/>").addClass("display-apps");if(!_.isEmpty(this.model.get("display_types"))){this.log(this+"display_types:",this.model.get("display_types"));a.append(HistoryItemView.templates.displayApps({displayApps:this.model.toJSON().display_types}))}if(!_.isEmpty(this.model.get("display_apps"))){this.log(this+"display_apps:",this.model.get("display_apps"));a.append(HistoryItemView.templates.displayApps({displayApps:this.model.toJSON().display_apps}))}return a},_render_peek:function(){if(!this.model.get("peek")){return null}return $("<div/>").append($("<pre/>").attr("id","peek"+this.model.get("id")).addClass("peek").append(this.model.get("peek")))},_render_body_not_viewable:function(a){a.append($("<div>You do not have permission to view dataset.</div>"))},_render_body_uploading:function(a){a.append($("<div>Dataset is uploading</div>"))},_render_body_queued:function(a){a.append($("<div>Job is waiting to run.</div>"));a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_running:function(a){a.append("<div>Job is currently running.</div>");a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_error:function(a){if(!this.model.get("purged")){a.append($("<div>"+this.model.get("misc_blurb")+"</div>"))}a.append(("An error occurred running this job: <i>"+$.trim(this.model.get("misc_info"))+"</i>"));a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_errButton,this._render_showParamsButton,this._render_rerunButton]))},_render_body_discarded:function(a){a.append("<div>The job creating this dataset was cancelled before completion.</div>");a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_setting_metadata:function(a){a.append($("<div>Metadata is being auto-detected.</div>"))},_render_body_empty:function(a){a.append($("<div>No data: <i>"+this.model.get("misc_blurb")+"</i></div>"));a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_failed_metadata:function(a){a.append($(HistoryItemView.templates.failedMetadata(this.model.toJSON())));this._render_body_ok(a)},_render_body_ok:function(a){a.append(this._render_hdaSummary());a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_errButton,this._render_showParamsButton,this._render_rerunButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('<div class="clear"/>');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayApps());a.append(this._render_peek())},_render_body:function(){var b=this.model.get("state");var a=$("<div/>").attr("id","info-"+this.model.get("id")).addClass("historyItemBody").attr("style","display: block");switch(b){case HistoryItem.STATES.NOT_VIEWABLE:this._render_body_not_viewable(a);break;case HistoryItem.STATES.UPLOAD:this._render_body_uploading(a);break;case HistoryItem.STATES.QUEUED:this._render_body_queued(a);break;case HistoryItem.STATES.RUNNING:this._render_body_running(a);break;case HistoryItem.STATES.ERROR:this._render_body_error(a);break;case HistoryItem.STATES.DISCARDED:this._render_body_discarded(a);break;case HistoryItem.STATES.SETTING_METADATA:this._render_body_setting_metadata(a);break;case HistoryItem.STATES.EMPTY:this._render_body_empty(a);break;case HistoryItem.STATES.FAILED_METADATA:this._render_body_failed_metadata(a);break;case HistoryItem.STATES.OK:this._render_body_ok(a);break;default:a.append($('<div>Error: unknown dataset state "'+b+'".</div>'))}a.append('<div style="clear: both"></div>');if(this.model.get("bodyIsShown")===false){a.hide()}return a},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(b){this.log(this+".loadAndDisplayTags",b);var c=this.$el.find(".tag-area"),a=c.find(".tag-elt");if(c.is(":hidden")){if(!a.html()){$.ajax({url:this.model.get("ajax_get_tag_url"),error:function(){alert("Tagging failed")},success:function(d){a.html(d);a.find(".tooltip").tooltip();c.slideDown("fast")}})}else{c.slideDown("fast")}}else{c.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.model.get("ajax_set_annotation_url");if(d.is(":hidden")){if(!c.html()){$.ajax({url:this.model.get("ajax_get_annotation_url"),error:function(){alert("Annotations failed")},success:function(e){if(e===""){e="<em>Describe or add notes to dataset</em>"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toggleBodyVisibility:function(){this.log(this+".toggleBodyVisibility");this.$el.find(".historyItemBody").toggle()},toString:function(){var a=(this.model)?(this.model+""):("");return"HistoryItemView("+a+")"}});HistoryItemView.templates=CompiledTemplateLoader.getTemplates({"common-templates.html":{warningMsg:"template-warningmessagesmall"},"history-templates.html":{messages:"template-history-warning-messages",titleLink:"template-history-titleLink",hdaSummary:"template-history-hdaSummary",downloadLinks:"template-history-downloadLinks",failedMetadata:"template-history-failedMetaData",tagArea:"template-history-tagArea",annotationArea:"template-history-annotationArea",displayApps:"template-history-displayApps"}});var HistoryCollection=Backbone.Collection.extend({model:HistoryItem,toString:function(){return("HistoryCollection()")}});var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",state_details:{discarded:0,empty:0,error:0,failed_metadata:0,ok:0,queued:0,running:0,setting_metadata:0,upload:0}},initialize:function(b,a){this.log(this+".initialize",b,a);this.items=new HistoryCollection()},loadDatasetsAsHistoryItems:function(c){var a=this,b=this.get("id"),d=this.get("state_details");_.each(c,function(f,e){a.log("loading dataset: ",f,e);var h=new HistoryItem(_.extend(f,{history_id:b}));a.log("as History:",h);a.items.add(h);var g=f.state;d[g]+=1});this.set("state_details",d);this._stateFromStateDetails();return this},_stateFromStateDetails:function(){this.set("state","");var a=this.get("state_details");if((a.error>0)||(a.failed_metadata>0)){this.set("state",HistoryItem.STATES.ERROR)}else{if((a.running>0)||(a.setting_metadata>0)){this.set("state",HistoryItem.STATES.RUNNING)}else{if(a.queued>0){this.set("state",HistoryItem.STATES.QUEUED)}else{if(a.ok===this.items.length){this.set("state",HistoryItem.STATES.OK)}else{throw ("_stateFromStateDetails: unable to determine history state from state details: "+this.state_details)}}}}return this},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HistoryView=BaseView.extend(LoggableMixin).extend({el:"body.historyPage",initialize:function(){this.log(this+".initialize");this.itemViews=[];var a=this;this.model.items.each(function(c){var b=new HistoryItemView({model:c});a.itemViews.push(b)})},render:function(){this.log(this+".render");var a=$("<div/>");_.each(this.itemViews,function(b){a.prepend(b.render())});this.$el.append(a.children());a.remove()},toString:function(){var a=this.model.get("name")||"";return"HistoryView("+a+")"}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/mvc/tools.js
--- a/static/scripts/packed/mvc/tools.js
+++ b/static/scripts/packed/mvc/tools.js
@@ -1,1 +1,1 @@
-var ServerStateDeferred=Backbone.Model.extend({defaults:{ajax_settings:{},interval:1000,success_fn:function(a){return true}},go:function(){var d=$.Deferred(),c=this,f=c.get("ajax_settings"),e=c.get("success_fn"),b=c.get("interval"),a=function(){$.ajax(f).success(function(g){if(e(g)){d.resolve(g)}else{setTimeout(a,b)}})};a();return d}});var BaseModel=Backbone.RelationalModel.extend({defaults:{name:null,hidden:false},show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},is_visible:function(){return !this.attributes.hidden}});var Tool=BaseModel.extend({defaults:{description:null,target:null,inputs:[]},relations:[{type:Backbone.HasMany,key:"inputs",relatedModel:"ToolInput",reverseRelation:{key:"tool",includeInJSON:false}}],urlRoot:galaxy_paths.get("tool_url"),copy:function(b){var c=new Tool(this.toJSON());if(b){var a=new Backbone.Collection();c.get("inputs").each(function(d){if(d.get_samples()){a.push(d)}});c.set("inputs",a)}return c},apply_search_results:function(a){(_.indexOf(a,this.attributes.id)!==-1?this.show():this.hide());return this.is_visible()},set_input_value:function(a,b){this.get("inputs").find(function(c){return c.get("name")===a}).set("value",b)},set_input_values:function(b){var a=this;_.each(_.keys(b),function(c){a.set_input_value(c,b[c])})},run:function(){return this._run()},rerun:function(b,a){return this._run({action:"rerun",target_dataset_id:b.id,regions:a})},get_inputs_dict:function(){var a={};this.get("inputs").each(function(b){a[b.get("name")]=b.get("value")});return a},_run:function(c){var d=_.extend({tool_id:this.id,inputs:this.get_inputs_dict()},c);var b=$.Deferred(),a=new ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(d),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(e){return e!=="pending"}});$.when(a.go()).then(function(e){b.resolve(new DatasetCollection().reset(e))});return b}});var ToolInput=Backbone.RelationalModel.extend({defaults:{name:null,label:null,type:null,value:null,num_samples:5},initialize:function(){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new ToolInput(this.toJSON())},get_samples:function(){var b=this.get("type"),a=null;if(b==="number"){a=d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}else{if(b==="select"){a=_.map(this.get("options"),function(c){return c[0]})}}return a}});var ToolCollection=Backbone.Collection.extend({model:Tool});var ToolPanelLabel=BaseModel.extend({});var ToolPanelSection=BaseModel.extend({defaults:{elems:[],open:false},clear_search_results:function(){_.each(this.attributes.elems,function(a){a.show()});this.show();this.set("open",false)},apply_search_results:function(b){var c=true,a;_.each(this.attributes.elems,function(d){if(d instanceof ToolPanelLabel){a=d;a.hide()}else{if(d instanceof Tool){if(d.apply_search_results(b)){c=false;if(a){a.show()}}}}});if(c){this.hide()}else{this.show();this.set("open",true)}}});var ToolSearch=BaseModel.extend({defaults:{search_hint_string:"search tools",min_chars_for_search:3,spinner_url:"",clear_btn_url:"",search_url:"",visible:true,query:"",results:null,clear_key:27},initialize:function(){this.on("change:query",this.do_search)},do_search:function(){var c=this.attributes.query;if(c.length<this.attributes.min_chars_for_search){this.set("results",null);return}var b=c+"*";if(this.timer){clearTimeout(this.timer)}$("#search-clear-btn").hide();$("#search-spinner").show();var a=this;this.timer=setTimeout(function(){$.get(a.attributes.search_url,{query:b},function(d){a.set("results",d);$("#search-spinner").hide();$("#search-clear-btn").show()},"json")},200)},clear_search:function(){this.set("query","");this.set("results",null)}});var ToolPanel=Backbone.Collection.extend({url:"/tools",tools:new ToolCollection(),parse:function(a){var b=function(e){var d=e.type;if(d==="tool"){return new Tool(e)}else{if(d==="section"){var c=_.map(e.elems,b);e.elems=c;return new ToolPanelSection(e)}else{if(d==="label"){return new ToolPanelLabel(e)}}}};return _.map(a,b)},initialize:function(a){this.tool_search=a.tool_search;this.tool_search.on("change:results",this.apply_search_results,this);this.on("reset",this.populate_tools,this)},populate_tools:function(){var a=this;a.tools=new ToolCollection();this.each(function(b){if(b instanceof ToolPanelSection){_.each(b.attributes.elems,function(c){if(c instanceof Tool){a.tools.push(c)}})}else{if(b instanceof Tool){a.tools.push(b)}}})},clear_search_results:function(){this.each(function(a){if(a instanceof ToolPanelSection){a.clear_search_results()}else{a.show()}})},apply_search_results:function(){var b=this.tool_search.attributes.results;if(b===null){this.clear_search_results();return}var a=null;this.each(function(c){if(c instanceof ToolPanelLabel){a=c;a.hide()}else{if(c instanceof Tool){if(c.apply_search_results(b)){if(a){a.show()}}}else{a=null;c.apply_search_results(b)}}})}});var BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){(this.model.attributes.hidden?this.$el.hide():this.$el.show())}});var ToolLinkView=BaseView.extend({tagName:"div",template:Handlebars.templates.tool_link,render:function(){this.$el.append(this.template(this.model.toJSON()));return this}});var ToolPanelLabelView=BaseView.extend({tagName:"div",className:"toolPanelLabel",render:function(){this.$el.append($("<span/>").text(this.model.attributes.name));return this}});var ToolPanelSectionView=BaseView.extend({tagName:"div",className:"toolSectionWrapper",template:Handlebars.templates.panel_section,initialize:function(){BaseView.prototype.initialize.call(this);this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(this.template(this.model.toJSON()));var a=this.$el.find(".toolSectionBody");_.each(this.model.attributes.elems,function(b){if(b instanceof Tool){var c=new ToolLinkView({model:b,className:"toolTitle"});c.render();a.append(c.$el)}else{if(b instanceof ToolPanelLabel){var d=new ToolPanelLabelView({model:b});d.render();a.append(d.$el)}else{}}});return this},events:{"click .toolSectionTitle > a":"toggle"},toggle:function(){this.model.set("open",!this.model.attributes.open)},update_open:function(){(this.model.attributes.open?this.$el.children(".toolSectionBody").slideDown("fast"):this.$el.children(".toolSectionBody").slideUp("fast"))}});var ToolSearchView=Backbone.View.extend({tagName:"div",id:"tool-search",className:"bar",template:Handlebars.templates.tool_search,events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){this.$el.append(this.template(this.model.toJSON()));if(!this.model.is_visible()){this.$el.hide()}return this},focus_and_select:function(){this.$el.find(":input").focus().select()},clear:function(){this.model.clear_search();this.$el.find(":input").val(this.model.attributes.search_hint_string);this.focus_and_select();return false},query_changed:function(a){if((this.model.attributes.clear_key)&&(this.model.attributes.clear_key===a.which)){this.clear();return false}this.model.set("query",this.$el.find(":input").val())}});var ToolPanelView=Backbone.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.collection.tool_search.on("change:results",this.handle_search_results,this)},render:function(){var a=this;var b=new ToolSearchView({model:this.collection.tool_search});b.render();a.$el.append(b.$el);this.collection.each(function(d){if(d instanceof ToolPanelSection){var c=new ToolPanelSectionView({model:d});c.render();a.$el.append(c.$el)}else{if(d instanceof Tool){var e=new ToolLinkView({model:d,className:"toolTitleNoSection"});e.render();a.$el.append(e.$el)}else{if(d instanceof ToolPanelLabel){var f=new ToolPanelLabelView({model:d});f.render();a.$el.append(f.$el)}}}});a.$el.find("a.tool-link").click(function(f){var d=$(this).attr("class").split(/\s+/)[0],c=a.collection.tools.get(d);a.trigger("tool_link_click",f,c)});return this},handle_search_results:function(){var a=this.collection.tool_search.attributes.results;if(a&&a.length===0){$("#search-no-results").show()}else{$("#search-no-results").hide()}}});var ToolFormView=Backbone.View.extend({className:"toolForm",template:Handlebars.templates.tool_form,render:function(){this.$el.children().remove();this.$el.append(this.template(this.model.toJSON()))}});var IntegratedToolMenuAndView=Backbone.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new ToolPanelView({collection:this.collection});this.tool_form_view=new ToolFormView()},render:function(){this.tool_panel_view.render();this.tool_panel_view.$el.css("float","left");this.$el.append(this.tool_panel_view.$el);this.tool_form_view.$el.hide();this.$el.append(this.tool_form_view.$el);var a=this;this.tool_panel_view.on("tool_link_click",function(c,b){c.preventDefault();a.show_tool(b)})},show_tool:function(b){var a=this;b.fetch().done(function(){a.tool_form_view.model=b;a.tool_form_view.render();a.tool_form_view.$el.show();$("#left").width("650px")})}});
\ No newline at end of file
+define(["libs/underscore","viz/trackster/util","mvc/data","libs/backbone/backbone-relational"],function(q,a,r){var f=Backbone.RelationalModel.extend({defaults:{name:null,hidden:false},show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},is_visible:function(){return !this.attributes.hidden}});var k=Backbone.RelationalModel.extend({defaults:{name:null,label:null,type:null,value:null,num_samples:5},initialize:function(){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new k(this.toJSON())},get_samples:function(){var u=this.get("type"),t=null;if(u==="number"){t=d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}else{if(u==="select"){t=q.map(this.get("options"),function(v){return v[0]})}}return t}});var e=f.extend({defaults:{description:null,target:null,inputs:[]},relations:[{type:Backbone.HasMany,key:"inputs",relatedModel:k,reverseRelation:{key:"tool",includeInJSON:false}}],urlRoot:galaxy_paths.get("tool_url"),copy:function(u){var v=new e(this.toJSON());if(u){var t=new Backbone.Collection();v.get("inputs").each(function(w){if(w.get_samples()){t.push(w)}});v.set("inputs",t)}return v},apply_search_results:function(t){(q.indexOf(t,this.attributes.id)!==-1?this.show():this.hide());return this.is_visible()},set_input_value:function(t,u){this.get("inputs").find(function(v){return v.get("name")===t}).set("value",u)},set_input_values:function(u){var t=this;q.each(q.keys(u),function(v){t.set_input_value(v,u[v])})},run:function(){return this._run()},rerun:function(u,t){return this._run({action:"rerun",target_dataset_id:u.id,regions:t})},get_inputs_dict:function(){var t={};this.get("inputs").each(function(u){t[u.get("name")]=u.get("value")});return t},_run:function(v){var w=q.extend({tool_id:this.id,inputs:this.get_inputs_dict()},v);var u=$.Deferred(),t=new a.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(w),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(x){return x!=="pending"}});$.when(t.go()).then(function(x){u.resolve(new r.DatasetCollection().reset(x))});return u}});var i=Backbone.Collection.extend({model:e});var m=f.extend({});var p=f.extend({defaults:{elems:[],open:false},clear_search_results:function(){q.each(this.attributes.elems,function(t){t.show()});this.show();this.set("open",false)},apply_search_results:function(u){var v=true,t;q.each(this.attributes.elems,function(w){if(w instanceof m){t=w;t.hide()}else{if(w instanceof e){if(w.apply_search_results(u)){v=false;if(t){t.show()}}}}});if(v){this.hide()}else{this.show();this.set("open",true)}}});var b=f.extend({defaults:{search_hint_string:"search tools",min_chars_for_search:3,spinner_url:"",clear_btn_url:"",search_url:"",visible:true,query:"",results:null,clear_key:27},initialize:function(){this.on("change:query",this.do_search)},do_search:function(){var v=this.attributes.query;if(v.length<this.attributes.min_chars_for_search){this.set("results",null);return}var u=v+"*";if(this.timer){clearTimeout(this.timer)}$("#search-clear-btn").hide();$("#search-spinner").show();var t=this;this.timer=setTimeout(function(){$.get(t.attributes.search_url,{query:u},function(w){t.set("results",w);$("#search-spinner").hide();$("#search-clear-btn").show()},"json")},200)},clear_search:function(){this.set("query","");this.set("results",null)}});var j=Backbone.Collection.extend({url:"/tools",tools:new i(),parse:function(t){var u=function(x){var w=x.type;if(w==="tool"){return new e(x)}else{if(w==="section"){var v=q.map(x.elems,u);x.elems=v;return new p(x)}else{if(w==="label"){return new m(x)}}}};return q.map(t,u)},initialize:function(t){this.tool_search=t.tool_search;this.tool_search.on("change:results",this.apply_search_results,this);this.on("reset",this.populate_tools,this)},populate_tools:function(){var t=this;t.tools=new i();this.each(function(u){if(u instanceof p){q.each(u.attributes.elems,function(v){if(v instanceof e){t.tools.push(v)}})}else{if(u instanceof e){t.tools.push(u)}}})},clear_search_results:function(){this.each(function(t){if(t instanceof p){t.clear_search_results()}else{t.show()}})},apply_search_results:function(){var u=this.tool_search.attributes.results;if(u===null){this.clear_search_results();return}var t=null;this.each(function(v){if(v instanceof m){t=v;t.hide()}else{if(v instanceof e){if(v.apply_search_results(u)){if(t){t.show()}}}else{t=null;v.apply_search_results(u)}}})}});var n=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){(this.model.attributes.hidden?this.$el.hide():this.$el.show())}});var h=n.extend({tagName:"div",template:Handlebars.templates.tool_link,render:function(){this.$el.append(this.template(this.model.toJSON()));return this}});var c=n.extend({tagName:"div",className:"toolPanelLabel",render:function(){this.$el.append($("<span/>").text(this.model.attributes.name));return this}});var g=n.extend({tagName:"div",className:"toolSectionWrapper",template:Handlebars.templates.panel_section,initialize:function(){n.prototype.initialize.call(this);this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(this.template(this.model.toJSON()));var t=this.$el.find(".toolSectionBody");q.each(this.model.attributes.elems,function(u){if(u instanceof e){var v=new h({model:u,className:"toolTitle"});v.render();t.append(v.$el)}else{if(u instanceof m){var w=new c({model:u});w.render();t.append(w.$el)}else{}}});return this},events:{"click .toolSectionTitle > a":"toggle"},toggle:function(){this.model.set("open",!this.model.attributes.open)},update_open:function(){(this.model.attributes.open?this.$el.children(".toolSectionBody").slideDown("fast"):this.$el.children(".toolSectionBody").slideUp("fast"))}});var l=Backbone.View.extend({tagName:"div",id:"tool-search",className:"bar",template:Handlebars.templates.tool_search,events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){this.$el.append(this.template(this.model.toJSON()));if(!this.model.is_visible()){this.$el.hide()}return this},focus_and_select:function(){this.$el.find(":input").focus().select()},clear:function(){this.model.clear_search();this.$el.find(":input").val(this.model.attributes.search_hint_string);this.focus_and_select();return false},query_changed:function(t){if((this.model.attributes.clear_key)&&(this.model.attributes.clear_key===t.which)){this.clear();return false}this.model.set("query",this.$el.find(":input").val())}});var s=Backbone.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.collection.tool_search.on("change:results",this.handle_search_results,this)},render:function(){var t=this;var u=new l({model:this.collection.tool_search});u.render();t.$el.append(u.$el);this.collection.each(function(w){if(w instanceof p){var v=new g({model:w});v.render();t.$el.append(v.$el)}else{if(w instanceof e){var x=new h({model:w,className:"toolTitleNoSection"});x.render();t.$el.append(x.$el)}else{if(w instanceof m){var y=new c({model:w});y.render();t.$el.append(y.$el)}}}});t.$el.find("a.tool-link").click(function(x){var w=$(this).attr("class").split(/\s+/)[0],v=t.collection.tools.get(w);t.trigger("tool_link_click",x,v)});return this},handle_search_results:function(){var t=this.collection.tool_search.attributes.results;if(t&&t.length===0){$("#search-no-results").show()}else{$("#search-no-results").hide()}}});var o=Backbone.View.extend({className:"toolForm",template:Handlebars.templates.tool_form,render:function(){this.$el.children().remove();this.$el.append(this.template(this.model.toJSON()))}});var d=Backbone.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new s({collection:this.collection});this.tool_form_view=new o()},render:function(){this.tool_panel_view.render();this.tool_panel_view.$el.css("float","left");this.$el.append(this.tool_panel_view.$el);this.tool_form_view.$el.hide();this.$el.append(this.tool_form_view.$el);var t=this;this.tool_panel_view.on("tool_link_click",function(v,u){v.preventDefault();t.show_tool(u)})},show_tool:function(u){var t=this;u.fetch().done(function(){t.tool_form_view.model=u;t.tool_form_view.render();t.tool_form_view.$el.show();$("#left").width("650px")})}});return{Tool:e,ToolSearch:b,ToolPanel:j,ToolPanelView:s,ToolFormView:o}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/templates/compiled/helpers-common-templates.js
--- a/static/scripts/packed/templates/compiled/helpers-common-templates.js
+++ b/static/scripts/packed/templates/compiled/helpers-common-templates.js
@@ -1,1 +1,1 @@
-Handlebars.registerPartial("clearFloatDiv",function(a){return'<div class="clear"></div>'});Handlebars.registerPartial("iconButton",function(c,b){var a="";a+=(c.enabled)?("<a"):("<span");if(c.title){a+=' title="'+c.title+'"'}a+=' class="icon-button';if(c.isMenuButton){a+=" menu-button"}if(c.title){a+=" tooltip"}a+=" "+c.icon_class;if(!c.enabled){a+="_disabled"}a+='"';if(c.id){a+=' id="'+c.id+'"'}a+=' href="'+((c.href)?(c.href):("javascript:void(0);"))+'"';if(c.target){a+=' target="'+c.target+'"'}if(!c.visible){a+=' style="display: none;"'}a+=">"+((c.enabled)?("</a>"):("</span>"));return a});Handlebars.registerHelper("warningmessagesmall",function(a){return'<div class="warningmessagesmall"><strong>'+a.fn(this)+"</strong></div>"});
\ No newline at end of file
+Handlebars.registerPartial("clearFloatDiv",function(a){return'<div class="clear"></div>'});Handlebars.registerHelper("warningmessagesmall",function(a){return'<div class="warningmessagesmall"><strong>'+a.fn(this)+"</strong></div>"});Handlebars.registerPartial("iconButton",function(c,b){var a="";a+=(c.enabled)?("<a"):("<span");if(c.title){a+=' title="'+c.title+'"'}a+=' class="icon-button';if(c.isMenuButton){a+=" menu-button"}if(c.title){a+=" tooltip"}a+=" "+c.icon_class;if(!c.enabled){a+="_disabled"}a+='"';if(c.id){a+=' id="'+c.id+'"'}a+=' href="'+((c.href)?(c.href):("javascript:void(0);"))+'"';if(c.target){a+=' target="'+c.target+'"'}if(!c.visible){a+=' style="display: none;"'}a+=">"+((c.enabled)?("</a>"):("</span>"));return a});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/templates/compiled/template-history-displayApps.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-history-displayApps.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-displayApps"]=b(function(g,l,f,k,j){f=f||g.helpers;var c,h="function",i=this.escapeExpression,m=this;function e(r,q){var o="",p,n;o+="\n ";n=f.label;if(n){p=n.call(r,{hash:{}})}else{p=r.label;p=typeof p===h?p():p}o+=i(p)+"\n ";p=r.links;p=f.each.call(r,p,{hash:{},inverse:m.noop,fn:m.program(2,d,q)});if(p||p===0){o+=p}o+="\n <br />\n";return o}function d(r,q){var o="",p,n;o+='\n <a target="';n=f.target;if(n){p=n.call(r,{hash:{}})}else{p=r.target;p=typeof p===h?p():p}o+=i(p)+'" href="';n=f.href;if(n){p=n.call(r,{hash:{}})}else{p=r.href;p=typeof p===h?p():p}o+=i(p)+'">';n=f.text;if(n){p=n.call(r,{hash:{}})}else{p=r.text;p=typeof p===h?p():p}o+=i(p)+"</a>\n ";return o}c=l.displayApps;c=f.each.call(l,c,{hash:{},inverse:m.noop,fn:m.program(1,e,j)});if(c||c===0){return c}else{return""}})})();
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/templates/compiled/template-history-downloadLinks.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-history-downloadLinks.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-downloadLinks"]=b(function(g,l,f,k,j){f=f||g.helpers;var c,h="function",i=this.escapeExpression,m=this;function e(s,r){var p="",q,o;p+='\n<div popupmenu="dataset-';o=f.id;if(o){q=o.call(s,{hash:{}})}else{q=s.id;q=typeof q===h?q():q}p+=i(q)+'-popup">\n <a class="action-button" href="';o=f.download_url;if(o){q=o.call(s,{hash:{}})}else{q=s.download_url;q=typeof q===h?q():q}p+=i(q)+'">Download Dataset</a>\n <a>Additional Files</a>\n ';q=s.meta_files;q=f.each.call(s,q,{hash:{},inverse:m.noop,fn:m.program(2,d,r)});if(q||q===0){p+=q}p+='\n</div>\n<div style="float:left;" class="menubutton split popup" id="dataset-';o=f.id;if(o){q=o.call(s,{hash:{}})}else{q=s.id;q=typeof q===h?q():q}p+=i(q)+'-popup">\n <a href="';o=f.download_url;if(o){q=o.call(s,{hash:{}})}else{q=s.download_url;q=typeof q===h?q():q}p+=i(q)+'" title="Download" class="icon-button disk tooltip"></a>\n</div>\n';return p}function d(s,r){var p="",q,o;p+='\n <a class="action-button" href="';o=f.meta_download_url;if(o){q=o.call(s,{hash:{}})}else{q=s.meta_download_url;q=typeof q===h?q():q}p+=i(q)+'">Download ';o=f.meta_file_type;if(o){q=o.call(s,{hash:{}})}else{q=s.meta_file_type;q=typeof q===h?q():q}p+=i(q)+"</a>\n ";return p}function n(p,o){return'\n<a href="" title="Download" class="icon-button disk tooltip"></a>\n'}c=l.meta_files;c=f["if"].call(l,c,{hash:{},inverse:m.program(4,n,j),fn:m.program(1,e,j)});if(c||c===0){return c}else{return""}})})();
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/viz/circster.js
--- a/static/scripts/packed/viz/circster.js
+++ b/static/scripts/packed/viz/circster.js
@@ -1,1 +1,1 @@
-define(["libs/d3","viz/visualization"],function(e,f){var d=function(){this.initialize&&this.initialize.apply(this,arguments)};d.extend=Backbone.Model.extend;var c=Backbone.View.extend({className:"circster",initialize:function(h){this.total_gap=h.total_gap;this.genome=h.genome;this.dataset_arc_height=h.dataset_arc_height;this.track_gap=5},render:function(){var j=this,l=this.dataset_arc_height,m=j.$el.width(),h=j.$el.height(),k=(Math.min(m,h)/2-this.model.get("tracks").length*(this.dataset_arc_height+this.track_gap));var i=e.select(j.$el[0]).append("svg").attr("width",m).attr("height",h).attr("pointer-events","all").append("svg:g").call(e.behavior.zoom().on("zoom",function(){i.attr("transform","translate("+e.event.translate+") scale("+e.event.scale+")")})).attr("transform","translate("+m/2+","+h/2+")").append("svg:g");this.model.get("tracks").each(function(n,p){var q=n.get("genome_wide_data"),s=k+p*(l+j.track_gap),o=(q instanceof f.GenomeWideBigWigData?g:a);var r=new o({track:n,radius_start:s,radius_end:s+l,genome:j.genome,total_gap:j.total_gap});r.render(i)})}});var b=d.extend({initialize:function(h){this.options=h},render:function(m){var h=this.chroms_layout(),k=this.genome_data_layout();var n=this.options.radius_start,j=this.options.radius_end,p=m.append("g").attr("id","inner-arc"),o=e.svg.arc().innerRadius(n).outerRadius(j),i=p.selectAll("#inner-arc>path").data(h).enter().append("path").attr("d",o).style("stroke","#ccc").style("fill","#ccc").append("title").text(function(r){return r.data.chrom});var q=this.options.track.get("prefs"),l=q.block_color;_.each(k,function(r){if(!r){return}var u=m.append("g"),t=e.svg.arc().innerRadius(n),s=u.selectAll("path").data(r).enter();s.append("path").attr("d",t).style("stroke",l)})},chroms_layout:function(){var i=this.options.genome.get_chroms_info(),k=e.layout.pie().value(function(m){return m.len}).sort(null),l=k(i),h=this.options.total_gap/i.length,j=_.map(l,function(o,n){var m=o.endAngle-h;o.endAngle=(m>o.startAngle?m:o.startAngle);return o});return j},chrom_data_layout:function(k,j,i,l,h){},genome_data_layout:function(){var i=this,h=this.chroms_layout(),m=this.options.track.get("genome_wide_data"),l=this.options.radius_start,j=this.options.radius_end,k=_.zip(h,m.get("data")),n=_.map(k,function(p){var q=p[0],o=p[1];return i.chrom_data_layout(q,o,l,j,m.get("min"),m.get("max"))});return n}});var a=b.extend({chrom_data_layout:function(r,i,o,n,k,p){if(!i||typeof i==="string"){return null}var l=i[0],q=i[3],j=e.scale.linear().domain([k,p]).range([o,n]),m=e.layout.pie().value(function(s){return q}).startAngle(r.startAngle).endAngle(r.endAngle),h=m(l);_.each(l,function(s,t){h[t].outerRadius=j(s[1])});return h}});var g=b.extend({chrom_data_layout:function(q,i,o,n,k,p){var l=i.data;if(l.length===0){return}var j=e.scale.linear().domain([k,p]).range([o,n]),m=e.layout.pie().value(function(s,r){if(r+1===l.length){return 0}return l[r+1][0]-l[r][0]}).startAngle(q.startAngle).endAngle(q.endAngle),h=m(l);_.each(l,function(r,s){h[s].outerRadius=j(r[1])});return h}});return{CircsterView:c}});
\ No newline at end of file
+define(["libs/underscore","libs/d3","viz/visualization"],function(i,k,j){var e=function(){this.initialize&&this.initialize.apply(this,arguments)};e.extend=Backbone.Model.extend;var l=Backbone.Model.extend({is_visible:function(p,m){var n=p.getBoundingClientRect(),o=$("svg")[0].getBoundingClientRect();if(n.right<0||n.left>o.right||n.bottom<0||n.top>o.bottom){return false}return true}});var f=Backbone.Model.extend({defaults:{prefs:{color:"#ccc"}}});var b=Backbone.View.extend({className:"circster",initialize:function(m){this.total_gap=m.total_gap;this.genome=m.genome;this.dataset_arc_height=m.dataset_arc_height;this.track_gap=5;this.label_arc_height=20},render:function(){var u=this,r=this.dataset_arc_height,m=u.$el.width(),t=u.$el.height(),n=Math.min(m,t)/2-this.model.get("tracks").length*(this.dataset_arc_height+this.track_gap)-(this.label_arc_height+this.track_gap),s=this.model.get("tracks");var p=k.select(u.$el[0]).append("svg").attr("width",m).attr("height",t).attr("pointer-events","all").append("svg:g").call(k.behavior.zoom().on("zoom",function(){p.attr("transform","translate("+k.event.translate+") scale("+k.event.scale+")");var v=new l(),w={};s.each(function(x){w[x.id]=[]});k.selectAll("path.chrom-data").filter(function(y,x){return v.is_visible(this,p)}).each(function(z,x){var y=$.data(this,"chrom_data");w[y.track.id].push(y.chrom)})})).attr("transform","translate("+m/2+","+t/2+")").append("svg:g");s.each(function(v,x){var z=n+x*(r+u.track_gap),w=(v.get("track_type")==="LineTrack"?g:h);var y=new w({track:v,track_index:x,radius_start:z,radius_end:z+r,genome:u.genome,total_gap:u.total_gap});y.render(p)});var q=n+s.length*(r+u.track_gap)+u.track_gap;var o=new a({track:new f(),track_index:s.length,radius_start:q,radius_end:q,genome:u.genome,total_gap:u.total_gap});o.render(p)}});var c=e.extend({initialize:function(m){this.options=m;this.options.bg_stroke="ccc";this.options.bg_fill="ccc"},render:function(r){var o=r.append("g").attr("id","parent-"+this.options.track_index);var m=this._chroms_layout(),s=this.options.radius_start,p=this.options.radius_end,t=k.svg.arc().innerRadius(s).outerRadius(p),n=o.selectAll("g").data(m).enter().append("svg:g");n.append("path").attr("d",t).style("stroke",this.options.bg_stroke).style("fill",this.options.bg_fill).append("title").text(function(v){return v.data.chrom});this.render_data(o);var u=this.options.track.get("prefs"),q=u.block_color;if(!q){q=u.color}o.selectAll("path.chrom-data").style("stroke",q).style("fill",q)},_chroms_layout:function(){var n=this.options.genome.get_chroms_info(),p=k.layout.pie().value(function(r){return r.len}).sort(null),q=p(n),m=this.options.total_gap/n.length,o=i.map(q,function(t,s){var r=t.endAngle-m;t.endAngle=(r>t.startAngle?r:t.startAngle);return t});return o},render_chrom_data:function(o,p,q,n,r,m){},render_data:function(q){var v=this,u=this._chroms_layout(),o=this.options.track,n=this.options.radius_start,s=this.options.radius_end,t=o.get_genome_wide_data(this.options.genome),r=i.zip(u,t),m=this.get_bounds(t),p=i.map(r,function(w){var x=w[0],y=w[1];return v.render_chrom_data(q,x,y,n,s,m.min,m.max)});return p}});var a=c.extend({initialize:function(m){this.options=m;this.options.bg_stroke="fff";this.options.bg_fill="fff"},render_data:function(n){var m=n.selectAll("g");m.selectAll("path").attr("id",function(o){return"label-"+o.data.chrom});m.append("svg:text").filter(function(o){return o.endAngle-o.startAngle>0.08}).attr("text-anchor","middle").append("svg:textPath").attr("xlink:href",function(o){return"#label-"+o.data.chrom}).attr("startOffset","25%").text(function(o){return o.data.chrom})}});var d=c.extend({render_quantitative_data:function(q,w,p,t,s,o,u){var r=k.scale.linear().domain([o,u]).range([t,s]);var n=k.scale.linear().domain([0,p.length]).range([w.startAngle,w.endAngle]);var y=k.svg.line.radial().interpolate("linear").radius(function(z){return r(z[1])}).angle(function(A,z){return n(z)});var m=k.svg.area.radial().interpolate(y.interpolate()).innerRadius(r(0)).outerRadius(y.radius()).angle(y.angle());var v=q.datum(p),x=v.append("path").attr("class","chrom-data").attr("d",m);$.data(x[0][0],"chrom_data",{track:this.options.track,chrom:w.data.chrom})},get_bounds:function(){}});var h=d.extend({render_chrom_data:function(o,r,p,n,s,q,m){if(!p||typeof p==="string"){return null}return this.render_quantitative_data(o,r,p.data,n,s,q,m)},get_bounds:function(n){var m=i.map(n,function(o){if(!o||typeof o==="string"){return 0}return o.max});return{min:0,max:(m&&typeof m!=="string"?i.max(m):0)}}});var g=d.extend({render_chrom_data:function(o,r,p,n,t,q,m){var s=p.data;if(s.length===0){return}return this.render_quantitative_data(o,r,s,n,t,q,m)},get_bounds:function(n){var m=i.flatten(i.map(n,function(o){if(o){return i.map(o.data,function(q){return q[1]})}else{return 0}}));return{min:i.min(m),max:i.max(m)}}});return{CircsterView:b}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/viz/scatterplot.js
--- /dev/null
+++ b/static/scripts/packed/viz/scatterplot.js
@@ -0,0 +1,1 @@
+define(["../libs/underscore","../libs/d3","../mvc/base-mvc"],function(){function b(e){var h=this,g=10,f=12,d=8,c=5;this.log=function(){if(this.debugging&&console&&console.debug){var i=Array.prototype.slice.call(arguments);i.unshift(this.toString());console.debug.apply(null,i)}};this.log("new TwoVarScatterplot:",e);this.defaults={id:"TwoVarScatterplot",containerSelector:"body",maxDataPoints:30000,bubbleRadius:4,entryAnimDuration:500,xNumTicks:10,yNumTicks:10,xAxisLabelBumpY:40,yAxisLabelBumpX:-35,width:500,height:500,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,xMin:null,xMax:null,yMin:null,yMax:null,xLabel:"X",yLabel:"Y"};this.config=_.extend({},this.defaults,e);this.updateConfig=function(i){_.extend(this.config,i)};this.toString=function(){return this.config.id};this.translateStr=function(i,j){return"translate("+i+","+j+")"};this.rotateStr=function(j,i,k){return"rotate("+j+","+i+","+k+")"};this.svg=d3.select(this.config.containerSelector).append("svg:svg").attr("class","chart").style("display","none");this.content=this.svg.append("svg:g").attr("class","content");this.xAxis=this.content.append("g").attr("class","axis").attr("id","x-axis");this.xAxisLabel=this.xAxis.append("text").attr("class","axis-label").attr("id","x-axis-label");this.yAxis=this.content.append("g").attr("class","axis").attr("id","y-axis");this.yAxisLabel=this.yAxis.append("text").attr("class","axis-label").attr("id","y-axis-label");this.log("built svg:",d3.selectAll("svg"));this.adjustChartDimensions=function(){this.svg.attr("width",this.config.width+(this.config.marginRight+this.config.marginLeft)).attr("height",this.config.height+(this.config.marginTop+this.config.marginBottom)).style("display","block");this.content=this.svg.select("g.content").attr("transform",this.translateStr(this.config.marginLeft,this.config.marginTop))};this.preprocessData=function(i){return i.slice(0,this.config.maxDataPoints)};this.setUpDomains=function(i,j){this.xMin=this.config.xMin||d3.min(i);this.xMax=this.config.xMax||d3.max(i);this.yMin=this.config.yMin||d3.min(j);this.yMax=this.config.yMax||d3.max(j)};this.setUpScales=function(){this.xScale=d3.scale.linear().domain([this.xMin,this.xMax]).range([0,this.config.width]),this.yScale=d3.scale.linear().domain([this.yMin,this.yMax]).range([this.config.height,0])};this.setUpXAxis=function(){this.xAxisFn=d3.svg.axis().scale(this.xScale).ticks(this.config.xNumTicks).orient("bottom");this.xAxis.attr("transform",this.translateStr(0,this.config.height)).call(this.xAxisFn);this.log("xAxis:",this.xAxis);this.xLongestLabel=d3.max(_.map([this.xMin,this.xMax],function(i){return(String(i)).length}));this.log("xLongestLabel:",this.xLongestLabel);if(this.xLongestLabel>=c){this.xAxis.selectAll("g").filter(":nth-child(odd)").style("display","none")}this.xAxisLabel.attr("x",this.config.width/2).attr("y",this.config.xAxisLabelBumpY).attr("text-anchor","middle").text(this.config.xLabel);this.log("xAxisLabel:",this.xAxisLabel)};this.setUpYAxis=function(){this.yAxisFn=d3.svg.axis().scale(this.yScale).ticks(this.config.yNumTicks).orient("left");this.yAxis.call(this.yAxisFn);this.log("yAxis:",this.yAxis);this.yLongestLabel=d3.max(_.map([this.yMin,this.yMax],function(j){return(String(j)).length}));this.log("yLongestLabel:",this.yLongestLabel);var i=this.yLongestLabel*g+(d);if(this.config.yAxisLabelBumpX>-(i)){this.config.yAxisLabelBumpX=-(i)}if(this.config.marginLeft<i){this.config.marginLeft=i+f;this.adjustChartDimensions()}this.log("this.config.yAxisLableBumpx, this.config.marginLeft:",this.config.yAxisLabelBumpX,this.config.marginLeft);this.yAxisLabel.attr("x",this.config.yAxisLabelBumpX).attr("y",this.config.height/2).attr("text-anchor","middle").attr("transform",this.rotateStr(-90,this.config.yAxisLabelBumpX,this.config.height/2)).text(this.config.yLabel);this.log("yAxisLabel:",this.yAxisLabel)};this.renderGrid=function(){this.vGridLines=this.content.selectAll("line.v-grid-line").data(this.xScale.ticks(this.xAxisFn.ticks()[0]));this.vGridLines.enter().append("svg:line").classed("grid-line v-grid-line",true);this.vGridLines.attr("x1",this.xScale).attr("y1",0).attr("x2",this.xScale).attr("y2",this.config.height);this.vGridLines.exit().remove();this.log("vGridLines:",this.vGridLines);this.hGridLines=this.content.selectAll("line.h-grid-line").data(this.yScale.ticks(this.yAxisFn.ticks()[0]));this.hGridLines.enter().append("svg:line").classed("grid-line h-grid-line",true);this.hGridLines.attr("x1",0).attr("y1",this.yScale).attr("x2",this.config.width).attr("y2",this.yScale);this.hGridLines.exit().remove();this.log("hGridLines:",this.hGridLines)};this.glyphEnterState=function(i){};this.glyphFinalState=function(i){};this.glyphExitState=function(i){};this.renderDatapoints=function(i,l){var k=function(n,m){return h.xScale(i[m])};var j=function(n,m){return h.yScale(l[m])};this.datapoints=this.content.selectAll(".glyph").data(i);this.datapoints.enter().append("svg:circle").attr("class","glyph").attr("cx",k).attr("cy",0).attr("r",0);this.datapoints.transition().duration(this.config.entryAnimDuration).attr("cx",k).attr("cy",j).attr("r",this.config.bubbleRadius);this.datapoints.exit().transition().duration(this.config.entryAnimDuration).attr("cy",this.config.height).attr("r",0).style("fill-opacity",0).remove();this.log(this.datapoints,"glyphs rendered")};this.render=function(i,j){this.log("renderScatterplot",i.length,j.length,this.config);i=this.preprocessData(i);j=this.preprocessData(j);this.log("xCol len",i.length,"yCol len",j.length);this.setUpDomains(i,j);this.log("xMin, xMax, yMin, yMax:",this.xMin,this.xMax,this.yMin,this.yMax);this.setUpScales();this.adjustChartDimensions();this.setUpXAxis();this.setUpYAxis();this.renderGrid();this.renderDatapoints(i,j)}}var a=BaseView.extend(LoggableMixin).extend({tagName:"form",className:"scatterplot-settings-form",events:{"click #render-button":"renderScatterplot"},initialize:function(c){if(!c||!c.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=c.dataset}this.apiDatasetsURL=c.apiDatasetsURL;this.chartConfig=c.chartConfig||{};this.log("this.chartConfig:",this.chartConfig);this.plot=new b(this.chartConfig)},render:function(){var c=this,e="",d="";this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ");_.each(this.dataset.metadata_column_types,function(h,g){if(h==="int"||h==="float"){var f="column "+g;if(c.dataset.metadata_column_names){f=c.dataset.metadata_column_names[g]}d+='<option value="'+g+'">'+f+"</column>"}});e+='<div id="x-column-input">';e+='<label for="">Data column for X: </label><select name="x-column">'+d+"</select>";e+="</div>";e+='<div id="y-column-input">';e+='<label for="">Data column for Y: </label><select name="y-column">'+d+"</select>";e+="</div>";e+='<input id="render-button" type="button" value="Draw" />';e+='<div class="clear"></div>';this.$el.append(e);this.$el.find("#render-button");return this},renderScatterplot:function(){var d=this,e=this.apiDatasetsURL+"/"+this.dataset.id+"?data_type=raw_data&",i=this.$el.find('[name="x-column"]'),j=i.val(),g=i.children('[value="'+j+'"]').text(),h=this.$el.find('[name="y-column"]'),f=h.val(),c=h.children('[value="'+f+'"]').text();this.log(g,c);this.chartConfig.xLabel=g;this.chartConfig.yLabel=c;d.plot.updateConfig(this.chartConfig);e+=jQuery.param({columns:"["+[j,f]+"]"});this.log("url:",e);jQuery.ajax({url:e,dataType:"json",success:function(k){d.endpoint=k.endpoint;d.plot.render(_.map(k.data,function(l){return l[0]}),_.map(k.data,function(l){return l[1]}))},error:function(m,k,l){alert("ERROR:"+k+"\n"+l)}})}});return{ScatterplotView:a}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/viz/sweepster.js
--- a/static/scripts/packed/viz/sweepster.js
+++ b/static/scripts/packed/viz/sweepster.js
@@ -1,1 +1,1 @@
-define(["libs/d3","viz/trackster/util","viz/visualization","viz/trackster/tracks"],function(m,e,l,f){var k=Backbone.Model.extend({defaults:{inputs:null,values:null}});var n=Backbone.RelationalModel.extend({defaults:{tool:null,tree_data:null},initialize:function(p){var o=this;this.get("tool").get("inputs").each(function(q){if(!q.get_samples()){return}q.on("change:min change:max change:num_samples",function(r){if(r.get("in_ptree")){o.set_tree_data()}},o);q.on("change:in_ptree",function(r){if(r.get("in_ptree")){o.add_param(r)}else{o.remove_param(r)}o.set_tree_data()},o)});if(p.config){_.each(p.config,function(r){var q=o.get("tool").get("inputs").find(function(s){return s.get("name")===r.name});o.add_param(q);q.set(r)})}},add_param:function(o){if(o.get("ptree_index")){return}o.set("in_ptree",true);o.set("ptree_index",this.get_tree_params().length)},remove_param:function(o){o.set("in_ptree",false);o.set("ptree_index",null);_(this.get_tree_params()).each(function(p,q){p.set("ptree_index",q+1)})},set_tree_data:function(){var p=_.map(this.get_tree_params(),function(r){return{param:r,samples:r.get_samples()}});var o=0,q=function(u,r){var w=u[r],v=w.param,t=v.get("label"),s=w.samples;if(u.length-1===r){return _.map(s,function(x){return{id:o++,name:x,param:v,value:x}})}return _.map(s,function(x){return{id:o++,name:x,param:v,value:x,children:q(u,r+1)}})};this.set("tree_data",{name:"Root",id:o++,children:(p.length!==0?q(p,0):null)})},get_tree_params:function(){return _(this.get("tool").get("inputs").where({in_ptree:true})).sortBy(function(o){return o.get("ptree_index")})},get_num_leaves:function(){return this.get_tree_params().reduce(function(o,p){return o*p.get_samples().length},1)},get_node_settings:function(s){var q=this.get("tool").get_inputs_dict();var t=s.parent;if(t){while(t.depth!==0){q[t.param.get("name")]=t.value;t=t.parent}}var o=this,p=function(v,u){if(v.param){u[v.param.get("name")]=v.value}if(!v.children){return new k({inputs:o.get("tool").get("inputs"),values:u})}else{return _.flatten(_.map(v.children,function(w){return p(w,_.clone(u))}))}},r=p(s,q);if(!_.isArray(r)){r=[r]}return r},get_connected_nodes:function(q){var r=function(s){if(!s.children){return s}else{return _.flatten([s,_.map(s.children,function(t){return r(t)})])}};var p=[],o=q.parent;while(o){p.push(o);o=o.parent}return _.flatten([p,r(q)])},get_leaf:function(p){var q=this.get("tree_data"),o=function(r){return _.find(r,function(s){return p[s.param.get("name")]===s.value})};while(q.children){q=o(q.children)}return q},toJSON:function(){return this.get_tree_params().map(function(o){return{name:o.get("name"),min:o.get("min"),max:o.get("max"),num_samples:o.get("num_samples")}})}});var c=Backbone.RelationalModel.extend({defaults:{track:null,mode:"Pack",settings:null,regions:null},relations:[{type:Backbone.HasMany,key:"regions",relatedModel:l.GenomeRegion}],initialize:function(o){if(o.track){var p=_.extend({data_url:galaxy_paths.get("raw_data_url"),converted_datasets_state_url:galaxy_paths.get("dataset_state_url")},o.track);this.set("track",f.object_from_template(p,{},null))}},same_settings:function(o){var p=this.get("settings"),q=o.get("settings");for(var r in p){if(!q[r]||p[r]!==q[r]){return false}}return true},toJSON:function(){return{track:this.get("track").to_dict(),settings:this.get("settings"),regions:this.get("regions")}}});var a=Backbone.Collection.extend({model:c});var g=l.Visualization.extend({defaults:_.extend({},l.Visualization.prototype.defaults,{dataset:null,tool:null,parameter_tree:null,regions:null,tracks:null,default_mode:"Pack"}),relations:[{type:Backbone.HasOne,key:"dataset",relatedModel:Dataset},{type:Backbone.HasOne,key:"tool",relatedModel:Tool},{type:Backbone.HasMany,key:"regions",relatedModel:l.GenomeRegion},{type:Backbone.HasMany,key:"tracks",relatedModel:c}],initialize:function(o){var p=this.get("tool").copy(true);this.set("tool_with_samplable_inputs",p);this.set("parameter_tree",new n({tool:p,config:o.tree_config}))},add_track:function(o){this.get("tracks").add(o)},toJSON:function(){return{id:this.get("id"),title:"Parameter exploration for dataset '"+this.get("dataset").get("name")+"'",type:"sweepster",dataset_id:this.get("dataset").id,tool_id:this.get("tool").id,regions:this.get("regions").toJSON(),tree_config:this.get("parameter_tree").toJSON(),tracks:this.get("tracks").toJSON()}}});var j=Backbone.View.extend({tagName:"tr",TILE_LEN:250,initialize:function(o){this.canvas_manager=o.canvas_manager;this.render();this.model.on("change:track change:mode",this.draw_tiles,this)},render:function(){var t=this.model.get("settings"),p=t.get("values"),r=$("<td/>").addClass("settings").appendTo(this.$el),q=$("<div/>").addClass("track-info").hide().appendTo(r);q.append($("<div/>").css("font-weight","bold").text("Track Settings"));t.get("inputs").each(function(v){q.append(v.get("label")+": "+p[v.get("name")]+"<br/>")});var o=this,u=$("<button/>").appendTo(q).text("Run on complete dataset").click(function(){q.toggle();o.trigger("run_on_dataset",t)});var s=create_icon_buttons_menu([{title:"Settings",icon_class:"gear track-settings",on_click:function(){q.toggle()}},{title:"Remove",icon_class:"cross-circle",on_click:function(){o.$el.remove();$(".bs-tooltip").remove()}}]);r.prepend(s.$el);this.model.get("regions").each(function(){o.$el.append($("<td/>").addClass("tile").html($("<img/>").attr("src",galaxy_paths.get("image_path")+"/loading_large_white_bg.gif")))});if(this.model.get("track")){this.draw_tiles()}},draw_tiles:function(){var p=this,o=this.model.get("track"),r=this.model.get("regions"),q=this.$el.find("td.tile");if(!o){return}$.when(o.data_manager.data_is_ready()).then(function(s){r.each(function(v,u){var t=v.length()/p.TILE_LEN,x=1/t,w=p.model.get("mode");$.when(o.data_manager.get_data(v,w,t,{})).then(function(z){var y=p.canvas_manager.new_canvas();y.width=p.TILE_LEN;y.height=o.get_canvas_height(z,w,x,y.width);o.draw_tile(z,y.getContext("2d"),w,t,v,x);$(q[u]).empty().append(y)})})})}});var b=Backbone.View.extend({number_input_template:'<div class="form-row-input sweep"><input class="min" type="text" size="6" value="<%= min %>"> - <input class="max" type="text" size="6" value="<%= max %>"> samples: <input class="num_samples" type="text" size="1" value="<%= num_samples %>"></div>',select_input_template:'<div class="form-row-input sweep"><%= options %></div>',initialize:function(o){this.$el=o.tool_row;this.render()},render:function(){var p=this.model,t=p.get("type"),v=this.$el.find(".form-row-input"),r=null;v.find(":input").change(function(){p.set("value",$(this).val())});if(t==="number"){r=$(_.template(this.number_input_template,this.model.toJSON()))}else{if(t==="select"){var q=_.map(this.$el.find("select option"),function(w){return $(w).val()}),s=q.join(", ");r=$(_.template(this.select_input_template,{options:s}))}}r.insertAfter(v);var o=this,u=create_icon_buttons_menu([{title:"Add parameter to tree",icon_class:"plus-button",on_click:function(){p.set("in_ptree",true);v.hide();r.show();$(this).hide();o.$el.find(".icon-button.toggle").show()}},{title:"Remove parameter from tree",icon_class:"toggle",on_click:function(){p.set("in_ptree",false);r.hide();v.show();$(this).hide();o.$el.find(".icon-button.plus-button").show()}}],{});this.$el.prepend(u.$el);if(p.get("in_ptree")){v.hide();o.$el.find(".icon-button.plus-button").hide()}else{o.$el.find(".icon-button.toggle").hide();r.hide()}_.each(["min","max","num_samples"],function(w){r.find("."+w).change(function(){p.set(w,parseFloat($(this).val()))})})}});var i=Backbone.View.extend({className:"tree-design",initialize:function(o){this.render()},render:function(){var q=new ToolFormView({model:this.model.get("tool")});q.render();this.$el.append(q.$el);var p=this,o=p.model.get("tool").get("inputs");this.$el.find(".form-row").not(".form-actions").each(function(r){var s=new b({model:o.at(r),tool_row:$(this)})})}});var h=Backbone.View.extend({className:"tool-parameter-tree",initialize:function(o){this.model.on("change:tree_data",this.render,this)},render:function(){this.$el.children().remove();var w=this.model.get_tree_params();if(!w.length){return}this.width=100*(2+w.length);this.height=15*this.model.get_num_leaves();var v=this;var u=m.layout.cluster().size([this.height,this.width-160]);var q=m.svg.diagonal().projection(function(x){return[x.y,x.x]});var o=u.nodes(this.model.get("tree_data"));var r=_.uniq(_.pluck(o,"y"));_.each(w,function(A,z){var y=r[z+1],B=$("#center").position().left;v.$el.append($("<div>").addClass("label").text(A.get("label")).css("left",y+B))});var p=m.select(this.$el[0]).append("svg").attr("width",this.width).attr("height",this.height+30).append("g").attr("transform","translate(40, 20)");var t=p.selectAll("path.link").data(u.links(o)).enter().append("path").attr("class","link").attr("d",q);var s=p.selectAll("g.node").data(o).enter().append("g").attr("class","node").attr("transform",function(x){return"translate("+x.y+","+x.x+")"}).on("mouseover",function(y){var x=_.pluck(v.model.get_connected_nodes(y),"id");s.filter(function(z){return _.find(x,function(A){return A===z.id})!==undefined}).style("fill","#f00")}).on("mouseout",function(){s.style("fill","#000")});s.append("circle").attr("r",9);s.append("text").attr("dx",function(x){return x.children?-12:12}).attr("dy",3).attr("text-anchor",function(x){return x.children?"end":"start"}).text(function(x){return x.name})}});var d=Backbone.View.extend({className:"Sweepster",helpText:"<div><h4>Getting Started</h4><ol><li>Create a parameter tree by using the icons next to the tool's parameter names to add or remove parameters.<li>Adjust the tree by using parameter inputs to select min, max, and number of samples<li>Run the tool with different settings by clicking on tree nodes</ol></div>",initialize:function(p){this.canvas_manager=new l.CanvasManager(this.$el.parents("body"));this.tool_param_tree_view=new h({model:this.model.get("parameter_tree")});this.track_collection_container=$("<table/>").addClass("tracks");this.model.get("parameter_tree").on("change:tree_data",this.handle_node_clicks,this);var o=this;this.model.get("tracks").each(function(q){q.get("track").view=o});this.block_color=e.get_random_color();this.reverse_strand_color=e.get_random_color([this.block_color,"#ffffff"])},render:function(){var u=new i({model:this.model.get("parameter_tree")});$("#left").append(u.$el);var x=this,r=x.model.get("regions"),v=$("<tr/>").appendTo(this.track_collection_container);r.each(function(y){v.append($("<th>").text(y.toString()))});v.children().first().attr("colspan",2);var s=$("<div>").addClass("tiles");$("#right").append(s.append(this.track_collection_container));x.model.get("tracks").each(function(y){x.add_track(y)});var w=$(this.helpText).addClass("help"),t=create_icon_buttons_menu([{title:"Close",icon_class:"cross-circle",on_click:function(){$(".bs-tooltip").remove();w.remove()}}]);w.prepend(t.$el.css("float","right"));$("#center").append(w);this.tool_param_tree_view.render();$("#center").append(this.tool_param_tree_view.$el);this.handle_node_clicks();var q=create_icon_buttons_menu([{icon_class:"chevron-expand",title:"Set display mode"},{icon_class:"cross-circle",title:"Close",on_click:function(){window.location="${h.url_for( controller='visualization', action='list' )}"}}],{tooltip_config:{placement:"bottom"}});var p=["Squish","Pack"],o={};_.each(p,function(y){o[y]=function(){x.model.set("default_mode",y);x.model.get("tracks").each(function(z){z.set("mode",y)})}});make_popupmenu(q.$el.find(".chevron-expand"),o);q.$el.attr("style","float: right");$("#right .unified-panel-header-inner").append(q.$el)},run_tool_on_dataset:function(p){var o=this.model.get("tool"),r=o.get("name"),q=this.model.get("dataset");o.set_input_values(p.get("values"));$.when(o.rerun(q)).then(function(s){});show_modal("Running "+r+" on complete dataset",r+" is running on dataset '"+q.get("name")+"'. Outputs are in the dataset's history.",{Ok:function(){hide_modal()}})},add_track:function(r){var p=this,q=this.model.get("parameter_tree");p.model.add_track(r);var o=new j({model:r,canvas_manager:p.canvas_manager});o.on("run_on_dataset",p.run_tool_on_dataset,p);p.track_collection_container.append(o.$el);o.$el.hover(function(){var t=q.get_leaf(r.get("settings").get("values"));var s=_.pluck(q.get_connected_nodes(t),"id");m.select(p.tool_param_tree_view.$el[0]).selectAll("g.node").filter(function(u){return _.find(s,function(v){return v===u.id})!==undefined}).style("fill","#f00")},function(){m.select(p.tool_param_tree_view.$el[0]).selectAll("g.node").style("fill","#000")});return r},handle_node_clicks:function(){var o=this,p=this.model.get("parameter_tree"),r=this.model.get("regions"),q=m.select(this.tool_param_tree_view.$el[0]).selectAll("g.node");q.on("click",function(x,u){var t=o.model.get("tool"),w=o.model.get("dataset"),v=p.get_node_settings(x),s=$.Deferred();if(v.length>=10){show_modal("Whoa there cowboy!","You clicked on a node to try "+o.model.get("tool").get("name")+" with "+v.length+" different combinations of settings. You can only run 10 jobs at a time.",{Ok:function(){hide_modal();s.resolve(false)}})}else{s.resolve(true)}$.when(s).then(function(y){if(!y){return}var z=_.map(v,function(A){var B=new c({settings:A,regions:r,mode:o.model.get("default_mode")});o.add_track(B);return B});_.each(z,function(B,A){setTimeout(function(){t.set_input_values(B.get("settings").get("values"));$.when(t.rerun(w,r)).then(function(D){var E=_.extend({data_url:galaxy_paths.get("raw_data_url"),converted_datasets_state_url:galaxy_paths.get("dataset_state_url")},D.first().get("track_config")),C=f.object_from_template(E,o,null);C.data_manager.set("data_type","raw_data");C.prefs.block_color=o.block_color;C.prefs.reverse_strand_color=o.reverse_strand_color;B.set("track",C)})},A*10000)})})})}});return{SweepsterVisualization:g,SweepsterVisualizationView:d}});
\ No newline at end of file
+define(["libs/d3","viz/trackster/util","viz/visualization","viz/trackster/tracks","mvc/tools","mvc/data"],function(o,f,n,h,g,d){var m=Backbone.Model.extend({defaults:{inputs:null,values:null}});var p=Backbone.RelationalModel.extend({defaults:{tool:null,tree_data:null},initialize:function(r){var q=this;this.get("tool").get("inputs").each(function(s){if(!s.get_samples()){return}s.on("change:min change:max change:num_samples",function(t){if(t.get("in_ptree")){q.set_tree_data()}},q);s.on("change:in_ptree",function(t){if(t.get("in_ptree")){q.add_param(t)}else{q.remove_param(t)}q.set_tree_data()},q)});if(r.config){_.each(r.config,function(t){var s=q.get("tool").get("inputs").find(function(u){return u.get("name")===t.name});q.add_param(s);s.set(t)})}},add_param:function(q){if(q.get("ptree_index")){return}q.set("in_ptree",true);q.set("ptree_index",this.get_tree_params().length)},remove_param:function(q){q.set("in_ptree",false);q.set("ptree_index",null);_(this.get_tree_params()).each(function(r,s){r.set("ptree_index",s+1)})},set_tree_data:function(){var r=_.map(this.get_tree_params(),function(t){return{param:t,samples:t.get_samples()}});var q=0,s=function(w,t){var y=w[t],x=y.param,v=x.get("label"),u=y.samples;if(w.length-1===t){return _.map(u,function(z){return{id:q++,name:z,param:x,value:z}})}return _.map(u,function(z){return{id:q++,name:z,param:x,value:z,children:s(w,t+1)}})};this.set("tree_data",{name:"Root",id:q++,children:(r.length!==0?s(r,0):null)})},get_tree_params:function(){return _(this.get("tool").get("inputs").where({in_ptree:true})).sortBy(function(q){return q.get("ptree_index")})},get_num_leaves:function(){return this.get_tree_params().reduce(function(q,r){return q*r.get_samples().length},1)},get_node_settings:function(u){var s=this.get("tool").get_inputs_dict();var v=u.parent;if(v){while(v.depth!==0){s[v.param.get("name")]=v.value;v=v.parent}}var q=this,r=function(x,w){if(x.param){w[x.param.get("name")]=x.value}if(!x.children){return new m({inputs:q.get("tool").get("inputs"),values:w})}else{return _.flatten(_.map(x.children,function(y){return r(y,_.clone(w))}))}},t=r(u,s);if(!_.isArray(t)){t=[t]}return t},get_connected_nodes:function(s){var t=function(u){if(!u.children){return u}else{return _.flatten([u,_.map(u.children,function(v){return t(v)})])}};var r=[],q=s.parent;while(q){r.push(q);q=q.parent}return _.flatten([r,t(s)])},get_leaf:function(r){var s=this.get("tree_data"),q=function(t){return _.find(t,function(u){return r[u.param.get("name")]===u.value})};while(s.children){s=q(s.children)}return s},toJSON:function(){return this.get_tree_params().map(function(q){return{name:q.get("name"),min:q.get("min"),max:q.get("max"),num_samples:q.get("num_samples")}})}});var c=Backbone.RelationalModel.extend({defaults:{track:null,mode:"Pack",settings:null,regions:null},relations:[{type:Backbone.HasMany,key:"regions",relatedModel:n.GenomeRegion}],initialize:function(q){if(q.track){var r=_.extend({data_url:galaxy_paths.get("raw_data_url"),converted_datasets_state_url:galaxy_paths.get("dataset_state_url")},q.track);this.set("track",h.object_from_template(r,{},null))}},same_settings:function(q){var r=this.get("settings"),s=q.get("settings");for(var t in r){if(!s[t]||r[t]!==s[t]){return false}}return true},toJSON:function(){return{track:this.get("track").to_dict(),settings:this.get("settings"),regions:this.get("regions")}}});var a=Backbone.Collection.extend({model:c});var i=n.Visualization.extend({defaults:_.extend({},n.Visualization.prototype.defaults,{dataset:null,tool:null,parameter_tree:null,regions:null,tracks:null,default_mode:"Pack"}),relations:[{type:Backbone.HasOne,key:"dataset",relatedModel:d.Dataset},{type:Backbone.HasOne,key:"tool",relatedModel:g.Tool},{type:Backbone.HasMany,key:"regions",relatedModel:n.GenomeRegion},{type:Backbone.HasMany,key:"tracks",relatedModel:c}],initialize:function(q){var r=this.get("tool").copy(true);this.set("tool_with_samplable_inputs",r);this.set("parameter_tree",new p({tool:r,config:q.tree_config}))},add_track:function(q){this.get("tracks").add(q)},toJSON:function(){return{id:this.get("id"),title:"Parameter exploration for dataset '"+this.get("dataset").get("name")+"'",type:"sweepster",dataset_id:this.get("dataset").id,tool_id:this.get("tool").id,regions:this.get("regions").toJSON(),tree_config:this.get("parameter_tree").toJSON(),tracks:this.get("tracks").toJSON()}}});var l=Backbone.View.extend({tagName:"tr",TILE_LEN:250,initialize:function(q){this.canvas_manager=q.canvas_manager;this.render();this.model.on("change:track change:mode",this.draw_tiles,this)},render:function(){var v=this.model.get("settings"),r=v.get("values"),t=$("<td/>").addClass("settings").appendTo(this.$el),s=$("<div/>").addClass("track-info").hide().appendTo(t);s.append($("<div/>").css("font-weight","bold").text("Track Settings"));v.get("inputs").each(function(x){s.append(x.get("label")+": "+r[x.get("name")]+"<br/>")});var q=this,w=$("<button/>").appendTo(s).text("Run on complete dataset").click(function(){s.toggle();q.trigger("run_on_dataset",v)});var u=create_icon_buttons_menu([{title:"Settings",icon_class:"gear track-settings",on_click:function(){s.toggle()}},{title:"Remove",icon_class:"cross-circle",on_click:function(){q.$el.remove();$(".bs-tooltip").remove()}}]);t.prepend(u.$el);this.model.get("regions").each(function(){q.$el.append($("<td/>").addClass("tile").html($("<img/>").attr("src",galaxy_paths.get("image_path")+"/loading_large_white_bg.gif")))});if(this.model.get("track")){this.draw_tiles()}},draw_tiles:function(){var r=this,q=this.model.get("track"),t=this.model.get("regions"),s=this.$el.find("td.tile");if(!q){return}$.when(q.data_manager.data_is_ready()).then(function(u){t.each(function(x,w){var v=x.length()/r.TILE_LEN,z=1/v,y=r.model.get("mode");$.when(q.data_manager.get_data(x,y,v,{})).then(function(B){var A=r.canvas_manager.new_canvas();A.width=r.TILE_LEN;A.height=q.get_canvas_height(B,y,z,A.width);q.draw_tile(B,A.getContext("2d"),y,v,x,z);$(s[w]).empty().append(A)})})})}});var b=Backbone.View.extend({number_input_template:'<div class="form-row-input sweep"><input class="min" type="text" size="6" value="<%= min %>"> - <input class="max" type="text" size="6" value="<%= max %>"> samples: <input class="num_samples" type="text" size="1" value="<%= num_samples %>"></div>',select_input_template:'<div class="form-row-input sweep"><%= options %></div>',initialize:function(q){this.$el=q.tool_row;this.render()},render:function(){var r=this.model,v=r.get("type"),x=this.$el.find(".form-row-input"),t=null;x.find(":input").change(function(){r.set("value",$(this).val())});if(v==="number"){t=$(_.template(this.number_input_template,this.model.toJSON()))}else{if(v==="select"){var s=_.map(this.$el.find("select option"),function(y){return $(y).val()}),u=s.join(", ");t=$(_.template(this.select_input_template,{options:u}))}}t.insertAfter(x);var q=this,w=create_icon_buttons_menu([{title:"Add parameter to tree",icon_class:"plus-button",on_click:function(){r.set("in_ptree",true);x.hide();t.show();$(this).hide();q.$el.find(".icon-button.toggle").show()}},{title:"Remove parameter from tree",icon_class:"toggle",on_click:function(){r.set("in_ptree",false);t.hide();x.show();$(this).hide();q.$el.find(".icon-button.plus-button").show()}}],{});this.$el.prepend(w.$el);if(r.get("in_ptree")){x.hide();q.$el.find(".icon-button.plus-button").hide()}else{q.$el.find(".icon-button.toggle").hide();t.hide()}_.each(["min","max","num_samples"],function(y){t.find("."+y).change(function(){r.set(y,parseFloat($(this).val()))})})}});var k=Backbone.View.extend({className:"tree-design",initialize:function(q){this.render()},render:function(){var s=new g.ToolFormView({model:this.model.get("tool")});s.render();this.$el.append(s.$el);var r=this,q=r.model.get("tool").get("inputs");this.$el.find(".form-row").not(".form-actions").each(function(t){var u=new b({model:q.at(t),tool_row:$(this)})})}});var j=Backbone.View.extend({className:"tool-parameter-tree",initialize:function(q){this.model.on("change:tree_data",this.render,this)},render:function(){this.$el.children().remove();var y=this.model.get_tree_params();if(!y.length){return}this.width=100*(2+y.length);this.height=15*this.model.get_num_leaves();var x=this;var w=o.layout.cluster().size([this.height,this.width-160]);var s=o.svg.diagonal().projection(function(z){return[z.y,z.x]});var q=w.nodes(this.model.get("tree_data"));var t=_.uniq(_.pluck(q,"y"));_.each(y,function(B,A){var z=t[A+1],C=$("#center").position().left;x.$el.append($("<div>").addClass("label").text(B.get("label")).css("left",z+C))});var r=o.select(this.$el[0]).append("svg").attr("width",this.width).attr("height",this.height+30).append("g").attr("transform","translate(40, 20)");var v=r.selectAll("path.link").data(w.links(q)).enter().append("path").attr("class","link").attr("d",s);var u=r.selectAll("g.node").data(q).enter().append("g").attr("class","node").attr("transform",function(z){return"translate("+z.y+","+z.x+")"}).on("mouseover",function(A){var z=_.pluck(x.model.get_connected_nodes(A),"id");u.filter(function(B){return _.find(z,function(C){return C===B.id})!==undefined}).style("fill","#f00")}).on("mouseout",function(){u.style("fill","#000")});u.append("circle").attr("r",9);u.append("text").attr("dx",function(z){return z.children?-12:12}).attr("dy",3).attr("text-anchor",function(z){return z.children?"end":"start"}).text(function(z){return z.name})}});var e=Backbone.View.extend({className:"Sweepster",helpText:"<div><h4>Getting Started</h4><ol><li>Create a parameter tree by using the icons next to the tool's parameter names to add or remove parameters.<li>Adjust the tree by using parameter inputs to select min, max, and number of samples<li>Run the tool with different settings by clicking on tree nodes</ol></div>",initialize:function(r){this.canvas_manager=new n.CanvasManager(this.$el.parents("body"));this.tool_param_tree_view=new j({model:this.model.get("parameter_tree")});this.track_collection_container=$("<table/>").addClass("tracks");this.model.get("parameter_tree").on("change:tree_data",this.handle_node_clicks,this);var q=this;this.model.get("tracks").each(function(s){s.get("track").view=q});this.block_color=f.get_random_color();this.reverse_strand_color=f.get_random_color([this.block_color,"#ffffff"])},render:function(){var w=new k({model:this.model.get("parameter_tree")});$("#left").append(w.$el);var z=this,t=z.model.get("regions"),x=$("<tr/>").appendTo(this.track_collection_container);t.each(function(A){x.append($("<th>").text(A.toString()))});x.children().first().attr("colspan",2);var u=$("<div>").addClass("tiles");$("#right").append(u.append(this.track_collection_container));z.model.get("tracks").each(function(A){z.add_track(A)});var y=$(this.helpText).addClass("help"),v=create_icon_buttons_menu([{title:"Close",icon_class:"cross-circle",on_click:function(){$(".bs-tooltip").remove();y.remove()}}]);y.prepend(v.$el.css("float","right"));$("#center").append(y);this.tool_param_tree_view.render();$("#center").append(this.tool_param_tree_view.$el);this.handle_node_clicks();var s=create_icon_buttons_menu([{icon_class:"chevron-expand",title:"Set display mode"},{icon_class:"cross-circle",title:"Close",on_click:function(){window.location="${h.url_for( controller='visualization', action='list' )}"}}],{tooltip_config:{placement:"bottom"}});var r=["Squish","Pack"],q={};_.each(r,function(A){q[A]=function(){z.model.set("default_mode",A);z.model.get("tracks").each(function(B){B.set("mode",A)})}});make_popupmenu(s.$el.find(".chevron-expand"),q);s.$el.attr("style","float: right");$("#right .unified-panel-header-inner").append(s.$el)},run_tool_on_dataset:function(r){var q=this.model.get("tool"),t=q.get("name"),s=this.model.get("dataset");q.set_input_values(r.get("values"));$.when(q.rerun(s)).then(function(u){});show_modal("Running "+t+" on complete dataset",t+" is running on dataset '"+s.get("name")+"'. Outputs are in the dataset's history.",{Ok:function(){hide_modal()}})},add_track:function(t){var r=this,s=this.model.get("parameter_tree");r.model.add_track(t);var q=new l({model:t,canvas_manager:r.canvas_manager});q.on("run_on_dataset",r.run_tool_on_dataset,r);r.track_collection_container.append(q.$el);q.$el.hover(function(){var v=s.get_leaf(t.get("settings").get("values"));var u=_.pluck(s.get_connected_nodes(v),"id");o.select(r.tool_param_tree_view.$el[0]).selectAll("g.node").filter(function(w){return _.find(u,function(x){return x===w.id})!==undefined}).style("fill","#f00")},function(){o.select(r.tool_param_tree_view.$el[0]).selectAll("g.node").style("fill","#000")});return t},handle_node_clicks:function(){var q=this,r=this.model.get("parameter_tree"),t=this.model.get("regions"),s=o.select(this.tool_param_tree_view.$el[0]).selectAll("g.node");s.on("click",function(z,w){var v=q.model.get("tool"),y=q.model.get("dataset"),x=r.get_node_settings(z),u=$.Deferred();if(x.length>=10){show_modal("Whoa there cowboy!","You clicked on a node to try "+q.model.get("tool").get("name")+" with "+x.length+" different combinations of settings. You can only run 10 jobs at a time.",{Ok:function(){hide_modal();u.resolve(false)}})}else{u.resolve(true)}$.when(u).then(function(A){if(!A){return}var B=_.map(x,function(C){var D=new c({settings:C,regions:t,mode:q.model.get("default_mode")});q.add_track(D);return D});_.each(B,function(D,C){setTimeout(function(){v.set_input_values(D.get("settings").get("values"));$.when(v.rerun(y,t)).then(function(F){var G=_.extend({data_url:galaxy_paths.get("raw_data_url"),converted_datasets_state_url:galaxy_paths.get("dataset_state_url")},F.first().get("track_config")),E=h.object_from_template(G,q,null);E.data_manager.set("data_type","raw_data");E.prefs.block_color=q.block_color;E.prefs.reverse_strand_color=q.reverse_strand_color;D.set("track",E)})},C*10000)})})})}});return{SweepsterVisualization:i,SweepsterVisualizationView:e}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/viz/trackster/filters.js
--- /dev/null
+++ b/static/scripts/packed/viz/trackster/filters.js
@@ -0,0 +1,1 @@
+define(["libs/underscore"],function(c){var f=c.extend;var a=function(g){this.manager=null;this.name=g.name;this.index=g.index;this.tool_id=g.tool_id;this.tool_exp_name=g.tool_exp_name};f(a.prototype,{to_dict:function(){return{name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name}}});var d=function(i,h,g){return $("<a/>").attr("href","javascript:void(0);").attr("title",i).addClass("icon-button").addClass(h).tooltip().click(g)};var e=function(o){a.call(this,o);this.low=("low" in o?o.low:-Number.MAX_VALUE);this.high=("high" in o?o.high:Number.MAX_VALUE);this.min=("min" in o?o.min:Number.MAX_VALUE);this.max=("max" in o?o.max:-Number.MAX_VALUE);this.container=null;this.slider=null;this.slider_label=null;var k=function(p,q,r){p.click(function(){var w=q.text(),u=parseFloat(r.slider("option","max")),t=(u<=1?4:u<=1000000?u.toString().length:6),v=false,s=$(this).parents(".slider-row");s.addClass("input");if(r.slider("option","values")){t=2*t+1;v=true}q.text("");$("<input type='text'/>").attr("size",t).attr("maxlength",t).attr("value",w).appendTo(q).focus().select().click(function(x){x.stopPropagation()}).blur(function(){$(this).remove();q.text(w);s.removeClass("input")}).keyup(function(B){if(B.keyCode===27){$(this).trigger("blur")}else{if(B.keyCode===13){var z=r.slider("option","min"),x=r.slider("option","max"),A=function(C){return(isNaN(C)||C>x||C<z)},y=$(this).val();if(!v){y=parseFloat(y);if(A(y)){alert("Parameter value must be in the range ["+z+"-"+x+"]");return $(this)}}else{y=y.split("-");y=[parseFloat(y[0]),parseFloat(y[1])];if(A(y[0])||A(y[1])){alert("Parameter value must be in the range ["+z+"-"+x+"]");return $(this)}}r.slider((v?"values":"value"),y);s.removeClass("input")}}})})};var h=this;h.parent_div=$("<div/>").addClass("filter-row slider-row");var g=$("<div/>").addClass("elt-label").appendTo(h.parent_div),m=$("<span/>").addClass("slider-name").text(h.name+" ").appendTo(g),i=$("<span/>").text(this.low+"-"+this.high),j=$("<span/>").addClass("slider-value").appendTo(g).append("[").append(i).append("]");h.values_span=i;var l=$("<div/>").addClass("slider").appendTo(h.parent_div);h.control_element=$("<div/>").attr("id",h.name+"-filter-control").appendTo(l);h.control_element.slider({range:true,min:this.min,max:this.max,step:this.get_slider_step(this.min,this.max),values:[this.low,this.high],slide:function(p,q){h.slide(p,q)},change:function(p,q){h.control_element.slider("option","slide").call(h.control_element,p,q)}});h.slider=h.control_element;h.slider_label=i;k(j,i,h.control_element);var n=$("<div/>").addClass("display-controls").appendTo(h.parent_div);this.transparency_icon=d("Use filter for data transparency","layer-transparent",function(){if(h.manager.alpha_filter!==h){h.manager.alpha_filter=h;h.manager.parent_div.find(".layer-transparent").removeClass("active").hide();h.transparency_icon.addClass("active").show()}else{h.manager.alpha_filter=null;h.transparency_icon.removeClass("active")}h.manager.track.request_draw(true,true)}).appendTo(n).hide();this.height_icon=d("Use filter for data height","arrow-resize-090",function(){if(h.manager.height_filter!==h){h.manager.height_filter=h;h.manager.parent_div.find(".arrow-resize-090").removeClass("active").hide();h.height_icon.addClass("active").show()}else{h.manager.height_filter=null;h.height_icon.removeClass("active")}h.manager.track.request_draw(true,true)}).appendTo(n).hide();h.parent_div.hover(function(){h.transparency_icon.show();h.height_icon.show()},function(){if(h.manager.alpha_filter!==h){h.transparency_icon.hide()}if(h.manager.height_filter!==h){h.height_icon.hide()}});$("<div style='clear: both;'/>").appendTo(h.parent_div)};f(e.prototype,{to_dict:function(){var g=a.prototype.to_dict.call(this);return f(g,{type:"number",min:this.min,max:this.max,low:this.low,high:this.high})},copy:function(){return new e({name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name})},get_slider_step:function(i,g){var h=g-i;return(h<=2?0.01:1)},slide:function(i,j){var h=j.values;this.values_span.text(h[0]+"-"+h[1]);this.low=h[0];this.high=h[1];var g=this;setTimeout(function(){if(h[0]===g.low&&h[1]===g.high){g.manager.track.request_draw(true,true)}},25)},applies_to:function(g){if(g.length>this.index){return true}return false},_keep_val:function(g){return(isNaN(g)||(g>=this.low&&g<=this.high))},keep:function(h){if(!this.applies_to(h)){return true}var k=this;var l=h[this.index];if(l instanceof Array){var j=true;for(var g=0;g<l.length;g++){if(!this._keep_val(l[g])){j=false;break}}return j}else{return this._keep_val(h[this.index])}},update_attrs:function(k){var g=false;if(!this.applies_to(k)){return g}var h=k[this.index];if(!(h instanceof Array)){h=[h]}for(var j=0;j<h.length;j++){var l=h[j];if(l<this.min){this.min=Math.floor(l);g=true}if(l>this.max){this.max=Math.ceil(l);g=true}}return g},update_ui_elt:function(){if(this.min<this.max){this.parent_div.show()}else{this.parent_div.hide()}var h=this.slider.slider("option","min"),g=this.slider.slider("option","max");if(this.min<h||this.max>g){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",this.get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var b=function(j,p){this.track=j;this.alpha_filter=null;this.height_filter=null;this.filters=[];this.parent_div=$("<div/>").addClass("filters").hide();this.parent_div.bind("drag",function(i){i.stopPropagation()}).click(function(i){i.stopPropagation()}).bind("dblclick",function(i){i.stopPropagation()}).bind("keydown",function(i){i.stopPropagation()});if(p&&"filters" in p){var g=("alpha_filter" in p?p.alpha_filter:null),k=("height_filter" in p?p.height_filter:null),m=p.filters,h;for(var n=0;n<m.length;n++){if(m[n].type==="number"){h=new e(m[n]);this.add_filter(h);if(h.name===g){this.alpha_filter=h;h.transparency_icon.addClass("active").show()}if(h.name===k){this.height_filter=h;h.height_icon.addClass("active").show()}}else{console.log("ERROR: unsupported filter: ",name,type)}}if("visible" in p&&p.visible){this.parent_div.show()}}if(this.filters.length!==0){var q=$("<div/>").addClass("param-row").appendTo(this.parent_div);var o=$("<input type='submit'/>").attr("value","Run on complete dataset").appendTo(q);var l=this;o.click(function(){l.run_on_dataset()})}};f(b.prototype,{show:function(){this.parent_div.show()},hide:function(){this.parent_div.hide()},toggle:function(){this.parent_div.toggle()},visible:function(){return this.parent_div.is(":visible")},to_dict:function(){var k={},j=[],h;for(var g=0;g<this.filters.length;g++){h=this.filters[g];j.push(h.to_dict())}k.filters=j;k.alpha_filter=(this.alpha_filter?this.alpha_filter.name:null);k.height_filter=(this.height_filter?this.height_filter.name:null);k.visible=this.parent_div.is(":visible");return k},copy:function(h){var j=new b(h);for(var g=0;g<this.filters.length;g++){j.add_filter(this.filters[g].copy())}return j},add_filter:function(g){g.manager=this;this.parent_div.append(g.parent_div);this.filters.push(g)},remove_all:function(){this.filters=[];this.parent_div.children().remove()},init_filters:function(){for(var g=0;g<this.filters.length;g++){var h=this.filters[g];h.update_ui_elt()}},clear_filters:function(){for(var g=0;g<this.filters.length;g++){var h=this.filters[g];h.slider.slider("option","values",[h.min,h.max])}this.alpha_filter=null;this.height_filter=null;this.parent_div.find(".icon-button").hide()},run_on_dataset:function(){var n=function(q,i,p){if(!(i in q)){q[i]=p}return q[i]};var m={},o,g;for(var l=0;l<this.filters.length;l++){o=this.filters[l];if(o.tool_id){if(o.min!==o.low){g=n(m,o.tool_id,[]);g[g.length]=o.tool_exp_name+" >= "+o.low}if(o.max!==o.high){g=n(m,o.tool_id,[]);g[g.length]=o.tool_exp_name+" <= "+o.high}}}var h=[];for(var k in m){h[h.length]=[k,m[k]]}(function j(u,r){var p=r[0],q=p[0],t=p[1],s="("+t.join(") and (")+")",i={cond:s,input:u,target_dataset_id:u,tool_id:q};r=r.slice(1);$.getJSON(run_tool_url,i,function(v){if(v.error){show_modal("Filter Dataset","Error running tool "+q,{Close:hide_modal})}else{if(r.length===0){show_modal("Filtering Dataset","Filter(s) are running on the complete dataset. Outputs are in dataset's history.",{Close:hide_modal})}else{j(v.dataset_id,r)}}})})(this.track.dataset_id,h)}});return{FiltersManager:b,NumberFilter:e}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/viz/trackster/painters.js
--- a/static/scripts/packed/viz/trackster/painters.js
+++ b/static/scripts/packed/viz/trackster/painters.js
@@ -1,1 +1,1 @@
-define(["libs/underscore"],function(_){var extend=_.extend;var BEFORE=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005,AFTER=1006;var compute_overlap=function(first_region,second_region){var first_start=first_region[0],first_end=first_region[1],second_start=second_region[0],second_end=second_region[1],overlap;if(first_start<second_start){if(first_end<second_start){overlap=BEFORE}else{if(first_end<=second_end){overlap=OVERLAP_START}else{overlap=CONTAINS}}}else{if(first_start>second_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q<dashes;q++,x1+=dashX,y1+=dashY){if(q%2!==0){continue}ctx.fillRect(x1,y1,dashLen,1)}};var drawDownwardEquilateralTriangle=function(ctx,down_vertex_x,down_vertex_y,side_len){var x1=down_vertex_x-side_len/2,x2=down_vertex_x+side_len/2,y=down_vertex_y-Math.sqrt(side_len*3/2);ctx.beginPath();ctx.moveTo(x1,y);ctx.lineTo(x2,y);ctx.lineTo(down_vertex_x,down_vertex_y);ctx.lineTo(x1,y);ctx.strokeStyle=this.fillStyle;ctx.fill();ctx.stroke();ctx.closePath()};var Scaler=function(default_val){this.default_val=(default_val?default_val:1)};Scaler.prototype.gen_val=function(input){return this.default_val};var Painter=function(data,view_start,view_end,prefs,mode){this.data=data;this.view_start=view_start;this.view_end=view_end;this.prefs=extend({},this.default_prefs,prefs);this.mode=mode};Painter.prototype.default_prefs={};Painter.prototype.draw=function(ctx,width,height,w_scale){};var SummaryTreePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode)};SummaryTreePainter.prototype.default_prefs={show_counts:false};SummaryTreePainter.prototype.draw=function(ctx,width,height,w_scale){var view_start=this.view_start,points=this.data.data,max=(this.prefs.histogram_max?this.prefs.histogram_max:this.data.max),base_y=height;delta_x_px=Math.ceil(this.data.delta*w_scale);ctx.save();for(var i=0,len=points.length;i<len;i++){var x=Math.floor((points[i][0]-view_start)*w_scale);var y=points[i][1];if(!y){continue}var y_px=y/max*height;if(y!==0&&y_px<1){y_px=1}ctx.fillStyle=this.prefs.block_color;ctx.fillRect(x,base_y-y_px,delta_x_px,y_px);var text_padding_req_x=4;if(this.prefs.show_counts&&(ctx.measureText(y).width+text_padding_req_x)<delta_x_px){ctx.fillStyle=this.prefs.label_color;ctx.textAlign="center";ctx.fillText(y,x+(delta_x_px/2),10)}}ctx.restore()};var LinePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][1])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][1])}this.prefs.max_value=max_value}};LinePainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Histogram",color:"#000",overflow_color:"#F66"};LinePainter.prototype.draw=function(ctx,width,height,w_scale){var in_path=false,min_value=this.prefs.min_value,max_value=this.prefs.max_value,vertical_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data;ctx.save();var y_zero=Math.round(height+min_value/vertical_range*height);if(mode!=="Intensity"){ctx.fillStyle="#aaa";ctx.fillRect(0,y_zero,width,1)}ctx.beginPath();var x_scaled,y,delta_x_px;if(data.length>1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var pref_color=parseInt(this.prefs.color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;i<len;i++){ctx.fillStyle=ctx.strokeStyle=this.prefs.color;x_scaled=Math.round((data[i][0]-view_start-1)*w_scale);y=data[i][1];var top_overflow=false,bot_overflow=false;if(y===null){if(in_path&&mode==="Filled"){ctx.lineTo(x_scaled,height_px)}in_path=false;continue}if(y<min_value){bot_overflow=true;y=min_value}else{if(y>max_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=this.prefs.color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i<this.feature_positions[slot].length;i++){feature_dict=this.feature_positions[slot][i];if(x>=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;i<len;i++){var feature=data[i],feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],slot=(slots&&slots[feature_uid]!==undefined?slots[feature_uid]:null);if((feature_start<view_end&&feature_end>view_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2]-1,feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;k<k_len;k++){var block=feature_blocks[k],block_start=Math.floor(Math.max(0,(block[0]-tile_low)*w_scale)),block_end=Math.ceil(Math.min(width,Math.max((block[1]-1-tile_low)*w_scale))),last_block_start,last_block_end;if(block_start>block_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_end<thick_start)){var block_thick_start=Math.max(block_start,thick_start),block_thick_end=Math.min(block_end,thick_end);ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height);if(feature_blocks.length===1&&mode==="Pack"){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}if(block_thick_start+14<block_thick_end){block_thick_start+=2;block_thick_end-=2}ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height)}}if(this.draw_individual_connectors&&last_block_start){this.draw_connector(ctx,last_block_start,last_block_end,block_start,block_end,y_center)}last_block_start=block_start;last_block_end=block_end}if(mode==="Pack"){ctx.globalAlpha=1;ctx.fillStyle="white";var hscale_factor=this.height_scaler.gen_val(feature),new_height=Math.ceil(thick_height*hscale_factor),ws_height=Math.round((thick_height-new_height)/2);if(hscale_factor!==1){ctx.fillRect(f_start,cur_y_center+1,f_end-f_start,ws_height);ctx.fillRect(f_start,cur_y_center+thick_height-ws_height+1,f_end-f_start,ws_height)}}}ctx.globalAlpha=1;if(feature_name&&mode==="Pack"&&feature_start>tile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null)};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,orig_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=0,char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color);var draw_last=[];if((mode==="Pack"||this.mode==="Auto")&&orig_seq!==undefined&&w_scale>char_width_px){gap=Math.round(w_scale/2)}if(!cigar){cigar=[[0,orig_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id<len;cig_id++){var cig=cigar[cig_id],cig_op="MIDNSHP=X"[cig[0]],cig_len=cig[1];if(cig_op==="H"||cig_op==="S"){base_offset-=cig_len}var seq_start=(feature_start-1)+base_offset,s_start=Math.floor(Math.max(0,(seq_start-tile_low)*w_scale)),s_end=Math.floor(Math.max(0,(seq_start+cig_len-tile_low)*w_scale));if(s_start===s_end){s_end+=1}switch(cig_op){case"H":break;case"S":case"M":case"=":if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=orig_seq.slice(seq_offset,seq_offset+cig_len);if(gap>0){ctx.fillStyle=block_color;ctx.fillRect(s_start-gap,y_center+1,s_end-s_start,9);ctx.fillStyle=CONNECTOR_COLOR;for(var c=0,str_len=seq.length;c<str_len;c++){if(this.prefs.show_differences){if(this.ref_seq){var ref_char=this.ref_seq[seq_start-tile_low+c];if(!ref_char||ref_char.toLowerCase()===seq[c].toLowerCase()){continue}}else{continue}}if(seq_start+c>=tile_low&&seq_start+c<=tile_high){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start,y_center+9)}}}else{ctx.fillStyle=block_color;ctx.fillRect(s_start,y_center+4,s_end-s_start,SQUISH_FEATURE_HEIGHT)}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start-gap,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="red";ctx.fillRect(s_start-gap,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=orig_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&orig_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;c<str_len;c++){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start-(s_end-s_start)/2,y_center)}}else{ctx.fillStyle="yellow";ctx.fillRect(x_center,y_center+(this.mode!=="Dense"?2:5),s_end-s_start,(mode!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((mode==="Pack"||this.mode==="Auto")&&orig_seq!==undefined&&w_scale>char_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break;case"X":seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i<draw_last.length;i++){item=draw_last[i];type=item.type;data=item.data;if(type==="text"){ctx.save();ctx.font="bold "+ctx.font;ctx.fillText(data[0],data[1],data[2]);ctx.restore()}else{if(type==="triangle"){drawDownwardEquilateralTriangle(ctx,data[0],data[1],data[2])}}}},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],f_start=Math.floor(Math.max(0,(feature_start-tile_low)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low)*w_scale))),y_center=(mode==="Dense"?0:(0+slot))*y_scale,label_color=this.prefs.label_color,gap=0;if((mode==="Pack"||this.mode==="Auto")&&w_scale>ctx.canvas.manager.char_width_px){var gap=Math.round(w_scale/2)}if(feature[5] instanceof Array){var b1_start=Math.floor(Math.max(0,(feature[4][0]-tile_low)*w_scale)),b1_end=Math.ceil(Math.min(width,Math.max(0,(feature[4][1]-tile_low)*w_scale))),b2_start=Math.floor(Math.max(0,(feature[5][0]-tile_low)*w_scale)),b2_end=Math.ceil(Math.min(width,Math.max(0,(feature[5][1]-tile_low)*w_scale))),connector=true;if(feature[4][1]>=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end-gap,y_center+5,b2_start-gap,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;var tile_index=1;if(tile_index===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING-gap,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING-gap,y_center+8)}}return[0,0]}});var ArcLinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){LinkedFeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.longest_feature_length=this.calculate_longest_feature_length();this.draw_background_connector=false;this.draw_individual_connectors=true};extend(ArcLinkedFeaturePainter.prototype,FeaturePainter.prototype,LinkedFeaturePainter.prototype,{calculate_longest_feature_length:function(){var longest_feature_length=0;for(var i=0,len=this.data.length;i<len;i++){var feature=this.data[i],feature_start=feature[1],feature_end=feature[2];longest_feature_length=Math.max(longest_feature_length,feature_end-feature_start)}return longest_feature_length},get_top_padding:function(width){var view_range=this.view_end-this.view_start,w_scale=width/view_range;return Math.min(128,Math.ceil((this.longest_feature_length/2)*w_scale))},draw_connector:function(ctx,block1_start,block1_end,block2_start,block2_end,y_center){var x_center=(block1_end+block2_start)/2,radius=block2_start-x_center;var angle1=Math.PI,angle2=0;if(radius>0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g<b?6:0);break;case g:h=(b-r)/d+2;break;case b:h=(r-g)/d+4;break}h/=6}return{h:h*360,s:s,l:l,a:a}},toARGB:function(){var argb=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+argb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][5])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][5])}this.prefs.max_value=max_value}};DiagonalHeatmapPainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Heatmap",pos_color:"4169E1",neg_color:"FF8C00"};DiagonalHeatmapPainter.prototype.draw=function(ctx,width,height,w_scale){var min_value=this.prefs.min_value,max_value=this.prefs.max_value,value_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data,invsqrt2=1/Math.sqrt(2);var ramp=(new SplitRamp(this.prefs.neg_color,"FFFFFF",this.prefs.pos_color,min_value,max_value));var d,s1,e1,s2,e2,value;var scale=function(p){return(p-view_start)*w_scale};ctx.save();ctx.rotate(-45*Math.PI/180);ctx.scale(invsqrt2,invsqrt2);for(var i=0,len=data.length;i<len;i++){d=data[i];s1=scale(d[1]);e1=scale(d[2]);s2=scale(d[4]);e2=scale(d[5]);value=d[6];ctx.fillStyle=(ramp.map_value(value));ctx.fillRect(s1,s2,(e1-s1),(e2-s2))}ctx.restore()};return{Scaler:Scaler,SummaryTreePainter:SummaryTreePainter,LinePainter:LinePainter,LinkedFeaturePainter:LinkedFeaturePainter,ReadPainter:ReadPainter,ArcLinkedFeaturePainter:ArcLinkedFeaturePainter,DiagonalHeatmapPainter:DiagonalHeatmapPainter}});
\ No newline at end of file
+define(["libs/underscore"],function(_){var extend=_.extend;var BEFORE=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005,AFTER=1006;var compute_overlap=function(first_region,second_region){var first_start=first_region[0],first_end=first_region[1],second_start=second_region[0],second_end=second_region[1],overlap;if(first_start<second_start){if(first_end<second_start){overlap=BEFORE}else{if(first_end<=second_end){overlap=OVERLAP_START}else{overlap=CONTAINS}}}else{if(first_start>second_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q<dashes;q++,x1+=dashX,y1+=dashY){if(q%2!==0){continue}ctx.fillRect(x1,y1,dashLen,1)}};var drawDownwardEquilateralTriangle=function(ctx,down_vertex_x,down_vertex_y,side_len){var x1=down_vertex_x-side_len/2,x2=down_vertex_x+side_len/2,y=down_vertex_y-Math.sqrt(side_len*3/2);ctx.beginPath();ctx.moveTo(x1,y);ctx.lineTo(x2,y);ctx.lineTo(down_vertex_x,down_vertex_y);ctx.lineTo(x1,y);ctx.strokeStyle=this.fillStyle;ctx.fill();ctx.stroke();ctx.closePath()};var Scaler=function(default_val){this.default_val=(default_val?default_val:1)};Scaler.prototype.gen_val=function(input){return this.default_val};var Painter=function(data,view_start,view_end,prefs,mode){this.data=data;this.view_start=view_start;this.view_end=view_end;this.prefs=extend({},this.default_prefs,prefs);this.mode=mode};Painter.prototype.default_prefs={};Painter.prototype.draw=function(ctx,width,height,w_scale){};var SummaryTreePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode)};SummaryTreePainter.prototype.default_prefs={show_counts:false};SummaryTreePainter.prototype.draw=function(ctx,width,height,w_scale){var view_start=this.view_start,points=this.data.data,max=(this.prefs.histogram_max?this.prefs.histogram_max:this.data.max),base_y=height;delta_x_px=Math.ceil(this.data.delta*w_scale);ctx.save();for(var i=0,len=points.length;i<len;i++){var x=Math.floor((points[i][0]-view_start)*w_scale);var y=points[i][1];if(!y){continue}var y_px=y/max*height;if(y!==0&&y_px<1){y_px=1}ctx.fillStyle=this.prefs.block_color;ctx.fillRect(x,base_y-y_px,delta_x_px,y_px);var text_padding_req_x=4;if(this.prefs.show_counts&&(ctx.measureText(y).width+text_padding_req_x)<delta_x_px){ctx.fillStyle=this.prefs.label_color;ctx.textAlign="center";ctx.fillText(y,x+(delta_x_px/2),10)}}ctx.restore()};var LinePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][1])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][1])}this.prefs.max_value=max_value}};LinePainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Histogram",color:"#000",overflow_color:"#F66"};LinePainter.prototype.draw=function(ctx,width,height,w_scale){var in_path=false,min_value=this.prefs.min_value,max_value=this.prefs.max_value,vertical_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data;ctx.save();var y_zero=Math.round(height+min_value/vertical_range*height);if(mode!=="Intensity"){ctx.fillStyle="#aaa";ctx.fillRect(0,y_zero,width,1)}ctx.beginPath();var x_scaled,y,delta_x_px;if(data.length>1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var pref_color=parseInt(this.prefs.color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;i<len;i++){ctx.fillStyle=ctx.strokeStyle=this.prefs.color;x_scaled=Math.round((data[i][0]-view_start-1)*w_scale);y=data[i][1];var top_overflow=false,bot_overflow=false;if(y===null){if(in_path&&mode==="Filled"){ctx.lineTo(x_scaled,height_px)}in_path=false;continue}if(y<min_value){bot_overflow=true;y=min_value}else{if(y>max_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=this.prefs.color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i<this.feature_positions[slot].length;i++){feature_dict=this.feature_positions[slot][i];if(x>=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;i<len;i++){var feature=data[i],feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],slot=(slots&&slots[feature_uid]!==undefined?slots[feature_uid]:null);if((feature_start<view_end&&feature_end>view_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2]-1,feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;k<k_len;k++){var block=feature_blocks[k],block_start=Math.floor(Math.max(0,(block[0]-tile_low)*w_scale)),block_end=Math.ceil(Math.min(width,Math.max((block[1]-1-tile_low)*w_scale))),last_block_start,last_block_end;if(block_start>block_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_end<thick_start)){var block_thick_start=Math.max(block_start,thick_start),block_thick_end=Math.min(block_end,thick_end);ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height);if(feature_blocks.length===1&&mode==="Pack"){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}if(block_thick_start+14<block_thick_end){block_thick_start+=2;block_thick_end-=2}ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height)}}if(this.draw_individual_connectors&&last_block_start){this.draw_connector(ctx,last_block_start,last_block_end,block_start,block_end,y_center)}last_block_start=block_start;last_block_end=block_end}if(mode==="Pack"){ctx.globalAlpha=1;ctx.fillStyle="white";var hscale_factor=this.height_scaler.gen_val(feature),new_height=Math.ceil(thick_height*hscale_factor),ws_height=Math.round((thick_height-new_height)/2);if(hscale_factor!==1){ctx.fillRect(f_start,cur_y_center+1,f_end-f_start,ws_height);ctx.fillRect(f_start,cur_y_center+thick_height-ws_height+1,f_end-f_start,ws_height)}}}ctx.globalAlpha=1;if(feature_name&&mode==="Pack"&&feature_start>tile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null)};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,ref_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=0,char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color);var draw_last=[];if((mode==="Pack"||this.mode==="Auto")&&ref_seq!==undefined&&w_scale>char_width_px){gap=Math.round(w_scale/2)}if(!cigar){cigar=[[0,ref_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id<len;cig_id++){var cig=cigar[cig_id],cig_op="MIDNSHP=X"[cig[0]],cig_len=cig[1];if(cig_op==="H"||cig_op==="S"){base_offset-=cig_len}var seq_start=(feature_start-1)+base_offset,s_start=Math.floor(Math.max(0,(seq_start-tile_low)*w_scale)),s_end=Math.floor(Math.max(0,(seq_start+cig_len-tile_low)*w_scale));if(s_start===s_end){s_end+=1}switch(cig_op){case"H":break;case"S":case"M":case"=":if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=ref_seq.slice(seq_offset-1,seq_offset+cig_len);if(gap>0){ctx.fillStyle=block_color;ctx.fillRect(s_start-gap,y_center+1,s_end-s_start,9);ctx.fillStyle=CONNECTOR_COLOR;for(var c=0,str_len=seq.length;c<str_len;c++){if(this.prefs.show_differences){if(this.ref_seq){var ref_char=this.ref_seq[seq_start-tile_low+c];if(!ref_char||ref_char.toLowerCase()===seq[c].toLowerCase()){continue}}else{continue}}if(seq_start+c>=tile_low&&seq_start+c<=tile_high){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start,y_center+9)}}}else{ctx.fillStyle=block_color;ctx.fillRect(s_start,y_center+4,s_end-s_start,SQUISH_FEATURE_HEIGHT)}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start-gap,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="red";ctx.fillRect(s_start-gap,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=ref_seq.slice(seq_offset-1,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&ref_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;c<str_len;c++){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start-(s_end-s_start)/2,y_center)}}else{ctx.fillStyle="yellow";ctx.fillRect(x_center,y_center+(this.mode!=="Dense"?2:5),s_end-s_start,(mode!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((mode==="Pack"||this.mode==="Auto")&&ref_seq!==undefined&&w_scale>char_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break;case"X":seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i<draw_last.length;i++){item=draw_last[i];type=item.type;data=item.data;if(type==="text"){ctx.save();ctx.font="bold "+ctx.font;ctx.fillText(data[0],data[1],data[2]);ctx.restore()}else{if(type==="triangle"){drawDownwardEquilateralTriangle(ctx,data[0],data[1],data[2])}}}},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],f_start=Math.floor(Math.max(0,(feature_start-tile_low)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low)*w_scale))),y_center=(mode==="Dense"?0:(0+slot))*y_scale,label_color=this.prefs.label_color,gap=0;if((mode==="Pack"||this.mode==="Auto")&&w_scale>ctx.canvas.manager.char_width_px){var gap=Math.round(w_scale/2)}if(feature[5] instanceof Array){var b1_start=Math.floor(Math.max(0,(feature[4][0]-tile_low)*w_scale)),b1_end=Math.ceil(Math.min(width,Math.max(0,(feature[4][1]-tile_low)*w_scale))),b2_start=Math.floor(Math.max(0,(feature[5][0]-tile_low)*w_scale)),b2_end=Math.ceil(Math.min(width,Math.max(0,(feature[5][1]-tile_low)*w_scale))),connector=true;if(feature[4][1]>=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end-gap,y_center+5,b2_start-gap,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;var tile_index=1;if(tile_index===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING-gap,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING-gap,y_center+8)}}return[0,0]}});var ArcLinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){LinkedFeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.longest_feature_length=this.calculate_longest_feature_length();this.draw_background_connector=false;this.draw_individual_connectors=true};extend(ArcLinkedFeaturePainter.prototype,FeaturePainter.prototype,LinkedFeaturePainter.prototype,{calculate_longest_feature_length:function(){var longest_feature_length=0;for(var i=0,len=this.data.length;i<len;i++){var feature=this.data[i],feature_start=feature[1],feature_end=feature[2];longest_feature_length=Math.max(longest_feature_length,feature_end-feature_start)}return longest_feature_length},get_top_padding:function(width){var view_range=this.view_end-this.view_start,w_scale=width/view_range;return Math.min(128,Math.ceil((this.longest_feature_length/2)*w_scale))},draw_connector:function(ctx,block1_start,block1_end,block2_start,block2_end,y_center){var x_center=(block1_end+block2_start)/2,radius=block2_start-x_center;var angle1=Math.PI,angle2=0;if(radius>0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g<b?6:0);break;case g:h=(b-r)/d+2;break;case b:h=(r-g)/d+4;break}h/=6}return{h:h*360,s:s,l:l,a:a}},toARGB:function(){var argb=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+argb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][5])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][5])}this.prefs.max_value=max_value}};DiagonalHeatmapPainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Heatmap",pos_color:"4169E1",neg_color:"FF8C00"};DiagonalHeatmapPainter.prototype.draw=function(ctx,width,height,w_scale){var min_value=this.prefs.min_value,max_value=this.prefs.max_value,value_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data,invsqrt2=1/Math.sqrt(2);var ramp=(new SplitRamp(this.prefs.neg_color,"FFFFFF",this.prefs.pos_color,min_value,max_value));var d,s1,e1,s2,e2,value;var scale=function(p){return(p-view_start)*w_scale};ctx.save();ctx.rotate(-45*Math.PI/180);ctx.scale(invsqrt2,invsqrt2);for(var i=0,len=data.length;i<len;i++){d=data[i];s1=scale(d[1]);e1=scale(d[2]);s2=scale(d[4]);e2=scale(d[5]);value=d[6];ctx.fillStyle=(ramp.map_value(value));ctx.fillRect(s1,s2,(e1-s1),(e2-s2))}ctx.restore()};return{Scaler:Scaler,SummaryTreePainter:SummaryTreePainter,LinePainter:LinePainter,LinkedFeaturePainter:LinkedFeaturePainter,ReadPainter:ReadPainter,ArcLinkedFeaturePainter:ArcLinkedFeaturePainter,DiagonalHeatmapPainter:DiagonalHeatmapPainter}});
\ No newline at end of file
diff -r 20e1f9fb8da9975b130047aece6d3ee3d26482fd -r 08dbb6b88aeb89bb37b0dfa7641a463bd2a4fa52 static/scripts/packed/viz/trackster/tracks.js
--- a/static/scripts/packed/viz/trackster/tracks.js
+++ b/static/scripts/packed/viz/trackster/tracks.js
@@ -1,1 +1,1 @@
-define(["libs/underscore","viz/visualization","viz/trackster/util","viz/trackster/slotting","viz/trackster/painters"],function(ae,y,m,v,M){var r=ae.extend;var R=m.get_random_color;var Y=function(af){return("isResolved" in af)};var o={};var l=function(af,ag){o[af.attr("id")]=ag};var n=function(af,ah,aj,ai){aj=".group";var ag={};o[af.attr("id")]=ai;af.bind("drag",{handle:"."+ah,relative:true},function(ar,at){var aq=$(this),aw=$(this).parent(),an=aw.children(),ap=o[$(this).attr("id")],am,al,au,ak,ao;al=$(this).parents(aj);if(al.length!==0){au=al.position().top;ak=au+al.outerHeight();if(at.offsetY<au){$(this).insertBefore(al);var av=o[al.attr("id")];av.remove_drawable(ap);av.container.add_drawable_before(ap,av);return}else{if(at.offsetY>ak){$(this).insertAfter(al);var av=o[al.attr("id")];av.remove_drawable(ap);av.container.add_drawable(ap);return}}}al=null;for(ao=0;ao<an.length;ao++){am=$(an.get(ao));au=am.position().top;ak=au+am.outerHeight();if(am.is(aj)&&this!==am.get(0)&&at.offsetY>=au&&at.offsetY<=ak){if(at.offsetY-au<ak-at.offsetY){am.find(".content-div").prepend(this)}else{am.find(".content-div").append(this)}if(ap.container){ap.container.remove_drawable(ap)}o[am.attr("id")].add_drawable(ap);return}}for(ao=0;ao<an.length;ao++){am=$(an.get(ao));if(at.offsetY<am.position().top&&!(am.hasClass("reference-track")||am.hasClass("intro"))){break}}if(ao===an.length){if(this!==an.get(ao-1)){aw.append(this);o[aw.attr("id")].move_drawable(ap,ao)}}else{if(this!==an.get(ao)){$(this).insertBefore(an.get(ao));o[aw.attr("id")].move_drawable(ap,(at.deltaY>0?ao-1:ao))}}}).bind("dragstart",function(){ag["border-top"]=af.css("border-top");ag["border-bottom"]=af.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ag)})};exports.moveable=n;var ad=16,H=9,E=20,B=100,J=12000,U=400,L=5000,x=100,p="There was an error in indexing this dataset. ",K="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",F="No data for this chrom/contig.",w="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",z="Tool cannot be rerun: ",a="Loading data...",X="Ready for display",S=10,I=20;function Z(ag,af){if(!af){af=0}var ah=Math.pow(10,af);return Math.round(ag*ah)/ah}var s=function(ag,af,ai){if(!s.id_counter){s.id_counter=0}this.id=s.id_counter++;this.name=ai.name;this.view=ag;this.container=af;this.config=new G({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:ai.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=ai.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("<div/>").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("<div style='clear: both'/>"));this.header_div.dblclick(function(aj){aj.stopPropagation()});var ah=this;this.container_div.hover(function(){ah.icons_div.show()},function(){ah.icons_div.hide()});$("<div style='clear: both'/>").appendTo(this.container_div)}};s.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(af){if(af.content_visible){af.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");af.hide_contents();af.content_visible=false}else{af.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");af.content_visible=true;af.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ag){var ai=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},af=function(){ag.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ah=function(aj){if((aj.keyCode||aj.which)===27){ai()}else{if((aj.keyCode||aj.which)===13){af()}}};$(window).bind("keypress.check_enter_esc",ah);show_modal("Configure",ag.config.build_form(),{Cancel:ai,OK:af})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(af){$(".bs-tooltip").remove();af.remove()}}];r(s.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(af){this.old_name=this.name;this.name=af;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var af=this.view;this.container_div.hide(0,function(){$(this).remove();af.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ag,al,ak,aj,af,ai){var ah=this;this.action_icons[ag]=$("<a/>").attr("href","javascript:void(0);").attr("title",al).addClass("icon-button").addClass(ak).tooltip().click(function(){aj(ah)}).appendTo(this.icons_div);if(ai){this.action_icons[ag].hide()}},build_action_icons:function(af){var ah;for(var ag=0;ag<af.length;ag++){ah=af[ag];this.add_action_icon(ah.name,ah.title,ah.css_class,ah.on_click_fn,ah.prepend,ah.hide)}},update_icons:function(){},hide_contents:function(){},show_contents:function(){},get_drawables:function(){}});var A=function(ag,af,ah){s.call(this,ag,af,ah);this.obj_type=ah.obj_type;this.drawables=[]};r(A.prototype,s.prototype,{unpack_drawables:function(ah){this.drawables=[];var ag;for(var af=0;af<ah.length;af++){ag=q(ah[af],this.view,this);this.add_drawable(ag)}},init:function(){for(var af=0;af<this.drawables.length;af++){this.drawables[af].init()}},_draw:function(){for(var af=0;af<this.drawables.length;af++){this.drawables[af]._draw()}},to_dict:function(){var ag=[];for(var af=0;af<this.drawables.length;af++){ag.push(this.drawables[af].to_dict())}return{name:this.name,prefs:this.prefs,obj_type:this.obj_type,drawables:ag}},add_drawable:function(af){this.drawables.push(af);af.container=this;this.changed()},add_drawable_before:function(ah,af){this.changed();var ag=this.drawables.indexOf(af);if(ag!==-1){this.drawables.splice(ag,0,ah);return true}return false},replace_drawable:function(ah,af,ag){var ai=this.drawables.indexOf(ah);if(ai!==-1){this.drawables[ai]=af;if(ag){ah.container_div.replaceWith(af.container_div)}this.changed()}return ai},remove_drawable:function(ag){var af=this.drawables.indexOf(ag);if(af!==-1){this.drawables.splice(af,1);ag.container=null;this.changed();return true}return false},move_drawable:function(ag,ah){var af=this.drawables.indexOf(ag);if(af!==-1){this.drawables.splice(af,1);this.drawables.splice(ah,0,ag);this.changed();return true}return false},get_drawables:function(){return this.drawables}});var Q=function(ag,af,ai){r(ai,{obj_type:"DrawableGroup",drag_handle_class:"group-handle"});A.call(this,ag,af,ai);this.content_div=$("<div/>").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);l(this.container_div,this);l(this.content_div,this);n(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new aa(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in ai){this.unpack_drawables(ai.drawables)}if("filters" in ai){var ah=this.filters_manager;this.filters_manager=new aa(this,ai.filters);ah.parent_div.replaceWith(this.filters_manager.parent_div);if(ai.filters.visible){this.setup_multitrack_filtering()}}};r(Q.prototype,s.prototype,A.prototype,{action_icons_def:[s.prototype.action_icons_def[0],s.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(af){$(".bs-tooltip").remove();af.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(af){if(af.filters_manager.visible()){af.filters_manager.clear_filters();af._restore_filter_managers()}else{af.setup_multitrack_filtering();af.request_draw(true)}af.filters_manager.toggle()}},s.prototype.action_icons_def[2]],build_container_div:function(){var af=$("<div/>").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(af)}return af},build_header_div:function(){var af=$("<div/>").addClass("track-header");af.append($("<div/>").addClass(this.drag_handle_class));this.name_div=$("<div/>").addClass("track-name").text(this.name).appendTo(af);return af},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ah=this.drawables.length;if(ah===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ah===1){if(this.drawables[0] instanceof h){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var ao,an,al,ar=true,aj=this.drawables[0].get_type(),af=0;for(ao=0;ao<ah;ao++){al=this.drawables[ao];if(al.get_type()!==aj){can_composite=false;break}if(al instanceof d){af++}}if(ar||af===1){this.action_icons.composite_icon.show()}else{this.action_icons.composite_icon.hide();$(".bs-tooltip").remove()}if(af>1&&af===this.drawables.length){var at={},ag;al=this.drawables[0];for(an=0;an<al.filters_manager.filters.length;an++){ag=al.filters_manager.filters[an];at[ag.name]=[ag]}for(ao=1;ao<this.drawables.length;ao++){al=this.drawables[ao];for(an=0;an<al.filters_manager.filters.length;an++){ag=al.filters_manager.filters[an];if(ag.name in at){at[ag.name].push(ag)}}}this.filters_manager.remove_all();var ai,ak,am,ap;for(var aq in at){ai=at[aq];if(ai.length===af){ak=new V({name:ai[0].name,index:ai[0].index});this.filters_manager.add_filter(ak)}}if(this.filters_manager.filters.length>0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var af=0;af<this.drawables.length;af++){this.drawables[af].filters_manager=this.saved_filters_managers[af]}this.saved_filters_managers=[]},setup_multitrack_filtering:function(){if(this.filters_manager.filters.length>0){this.saved_filters_managers=[];for(var af=0;af<this.drawables.length;af++){drawable=this.drawables[af];this.saved_filters_managers.push(drawable.filters_manager);drawable.filters_manager=this.filters_manager}}this.filters_manager.init_filters()},show_composite_track:function(){var aj=[];for(var ag=0;ag<this.drawables.length;ag++){aj.push(this.drawables[ag].name)}var ah="Composite Track of "+this.drawables.length+" tracks ("+aj.join(", ")+")";var ai=new h(this.view,this.view,{name:ah,drawables:this.drawables});var af=this.container.replace_drawable(this,ai,true);ai.request_draw()},add_drawable:function(af){A.prototype.add_drawable.call(this,af);this.update_icons()},remove_drawable:function(af){A.prototype.remove_drawable.call(this,af);this.update_icons()},to_dict:function(){if(this.filters_manager.visible()){this._restore_filter_managers()}var af=r(A.prototype.to_dict.call(this),{filters:this.filters_manager.to_dict()});if(this.filters_manager.visible()){this.setup_multitrack_filtering()}return af},request_draw:function(af,ah){for(var ag=0;ag<this.drawables.length;ag++){this.drawables[ag].request_draw(af,ah)}}});var ac=function(af){r(af,{obj_type:"View"});A.call(this,"View",af.container,af);this.chrom=null;this.vis_id=af.vis_id;this.dbkey=af.dbkey;this.label_tracks=[];this.tracks_to_be_redrawn=[];this.max_low=0;this.max_high=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.load_chroms_deferred=null;this.init();this.canvas_manager=new CanvasManager(this.container.get(0).ownerDocument);this.reset()};ae.extend(ac.prototype,Backbone.Events);r(ac.prototype,A.prototype,{init:function(){this.requested_redraw=false;var ah=this.container,af=this;this.top_container=$("<div/>").addClass("top-container").appendTo(ah);this.browser_content_div=$("<div/>").addClass("content").css("position","relative").appendTo(ah);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(ah);this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("<div/>").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;l(this.viewport_container,af);this.intro_div=$("<div/>").addClass("intro").appendTo(this.viewport_container).hide();var ai=$("<div/>").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){add_datasets(add_datasets_url,add_track_async_url,function(aj){ae.each(aj,function(ak){af.add_drawable(q(ak,af,af))})})});this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("<div/>").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("<div/>").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("<a/>").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var ag=function(aj){if(aj.type==="focusout"||(aj.keyCode||aj.which)===13||(aj.keyCode||aj.which)===27){if((aj.keyCode||aj.which)!==27){af.go_to($(this).val())}$(this).hide();$(this).val("");af.location_span.show();af.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",ag).appendTo(this.nav_controls);this.location_span=$("<span/>").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){af.location_span.hide();af.chrom_select.hide();af.nav_input.val(af.chrom+":"+af.low+"-"+af.high);af.nav_input.css("display","inline-block");af.nav_input.select();af.nav_input.focus();af.nav_input.autocomplete({source:function(al,aj){var am=[],ak=$.map(af.get_drawables(),function(an){return an.data_manager.search_features(al.term).success(function(ao){am=am.concat(ao)})});$.when.apply($,ak).done(function(){aj($.map(am,function(an){return{label:an[0],value:an[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("<a/>").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){af.zoom_out();af.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a/>").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){af.zoom_in();af.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){af.change_chrom(af.chrom_select.val())});this.browser_content_div.click(function(aj){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(aj){af.zoom_in(aj.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(aj,ak){this.current_x=ak.offsetX}).bind("drag",function(aj,al){var am=al.offsetX-this.current_x;this.current_x=al.offsetX;var ak=Math.round(am/af.viewport_container.width()*(af.max_high-af.max_low));af.move_delta(-ak)});this.overview_close.click(function(){af.reset_overview()});this.viewport_container.bind("draginit",function(aj,ak){if(aj.clientX>af.viewport_container.width()-16){return false}}).bind("dragstart",function(aj,ak){ak.original_low=af.low;ak.current_height=aj.clientY;ak.current_x=ak.offsetX}).bind("drag",function(al,an){var aj=$(this);var ao=an.offsetX-an.current_x;var ak=aj.scrollTop()-(al.clientY-an.current_height);aj.scrollTop(ak);an.current_height=al.clientY;an.current_x=an.offsetX;var am=Math.round(ao/af.viewport_container.width()*(af.high-af.low));af.move_delta(am)}).bind("mousewheel",function(al,an,ak,aj){if(ak){ak*=50;var am=Math.round(-ak/af.viewport_container.width()*(af.high-af.low));af.move_delta(am)}});this.top_labeltrack.bind("dragstart",function(aj,ak){return $("<div />").css({height:af.browser_content_div.height()+af.top_labeltrack.height()+af.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(an,ao){$(ao.proxy).css({left:Math.min(an.pageX,ao.startX)-af.container.offset().left,width:Math.abs(an.pageX-ao.startX)});var ak=Math.min(an.pageX,ao.startX)-af.container.offset().left,aj=Math.max(an.pageX,ao.startX)-af.container.offset().left,am=(af.high-af.low),al=af.viewport_container.width();af.update_location(Math.round(ak/al*am)+af.low,Math.round(aj/al*am)+af.low)}).bind("dragend",function(ao,ap){var ak=Math.min(ao.pageX,ap.startX),aj=Math.max(ao.pageX,ap.startX),am=(af.high-af.low),al=af.viewport_container.width(),an=af.low;af.low=Math.round(ak/al*am)+an;af.high=Math.round(aj/al*am)+an;$(ap.proxy).remove();af.request_redraw()});this.add_label_track(new ab(this,{content_div:this.top_labeltrack}));this.add_label_track(new ab(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){af.resize_window()},500)});$(document).bind("redraw",function(){af.redraw()});this.reset();$(window).trigger("resize")},changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ag,ai,af,aj){if(this.timer){clearTimeout(this.timer)}if(aj){var ah=this;this.timer=setTimeout(function(){ah.trigger("navigate",ag+":"+ai+"-"+af)},500)}else{view.trigger("navigate",ag+":"+ai+"-"+af)}},update_location:function(af,ah){this.location_span.text(commatize(af)+" - "+commatize(ah));this.nav_input.val(this.chrom+":"+commatize(af)+"-"+commatize(ah));var ag=view.chrom_select.val();if(ag!==""){this.trigger_navigate(ag,view.low,view.high,true)}},load_chroms:function(ah){ah.num=x;var af=this,ag=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ah,dataType:"json",success:function(aj){if(aj.chrom_info.length===0){return}if(aj.reference){af.add_label_track(new C(af))}af.chrom_data=aj.chrom_info;var am='<option value="">Select Chrom/Contig</option>';for(var al=0,ai=af.chrom_data.length;al<ai;al++){var ak=af.chrom_data[al].chrom;am+='<option value="'+ak+'">'+ak+"</option>"}if(aj.prev_chroms){am+='<option value="previous">Previous '+x+"</option>"}if(aj.next_chroms){am+='<option value="next">Next '+x+"</option>"}af.chrom_select.html(am);af.chrom_start_index=aj.start_index;ag.resolve(aj)},error:function(){alert("Could not load chroms for this dbkey:",af.dbkey)}});return ag},change_chrom:function(ak,ag,am){var ah=this;if(!ah.chrom_data){ah.load_chroms_deferred.then(function(){ah.change_chrom(ak,ag,am)});return}if(!ak||ak==="None"){return}if(ak==="previous"){ah.load_chroms({low:this.chrom_start_index-x});return}if(ak==="next"){ah.load_chroms({low:this.chrom_start_index+x});return}var al=$.grep(ah.chrom_data,function(an,ao){return an.chrom===ak})[0];if(al===undefined){ah.load_chroms({chrom:ak},function(){ah.change_chrom(ak,ag,am)});return}else{if(ak!==ah.chrom){ah.chrom=ak;ah.chrom_select.val(ah.chrom);ah.max_high=al.len-1;ah.reset();ah.request_redraw(true);for(var aj=0,af=ah.drawables.length;aj<af;aj++){var ai=ah.drawables[aj];if(ai.init){ai.init()}}if(ah.reference_track){ah.reference_track.init()}}if(ag!==undefined&&am!==undefined){ah.low=Math.max(ag,0);ah.high=Math.min(am,ah.max_high)}else{ah.low=0;ah.high=ah.max_high}ah.reset_overview();ah.request_redraw()}},go_to:function(aj){aj=aj.replace(/ |,/g,"");var an=this,af,ai,ag=aj.split(":"),al=ag[0],am=ag[1];if(am!==undefined){try{var ak=am.split("-");af=parseInt(ak[0],10);ai=parseInt(ak[1],10)}catch(ah){return false}}an.change_chrom(al,af,ai)},move_fraction:function(ah){var af=this;var ag=af.high-af.low;this.move_delta(ah*ag)},move_delta:function(ai){var af=this;var ah=af.high-af.low;if(af.low-ai<af.max_low){af.low=af.max_low;af.high=af.max_low+ah}else{if(af.high-ai>af.max_high){af.high=af.max_high;af.low=af.max_high-ah}else{af.high-=ai;af.low-=ai}}af.request_redraw();var ag=af.chrom_select.val();this.trigger_navigate(ag,af.low,af.high,true)},add_drawable:function(af){A.prototype.add_drawable.call(this,af);af.init();this.changed();this.update_intro_div()},add_label_track:function(af){af.view=this;af.init();this.label_tracks.push(af)},remove_drawable:function(ah,ag){A.prototype.remove_drawable.call(this,ah);if(ag){var af=this;ah.container_div.hide(0,function(){$(this).remove();af.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(an,af,am,ao){var al=this,ak=(ao?[ao]:al.drawables),ah;var ag;for(var aj=0;aj<ak.length;aj++){ag=ak[aj];ah=-1;for(var ai=0;ai<al.tracks_to_be_redrawn.length;ai++){if(al.tracks_to_be_redrawn[ai][0]===ag){ah=ai;break}}if(ah<0){al.tracks_to_be_redrawn.push([ag,af,am])}else{al.tracks_to_be_redrawn[aj][1]=af;al.tracks_to_be_redrawn[aj][2]=am}}if(!this.requested_redraw){requestAnimationFrame(function(){al._redraw(an)});this.requested_redraw=true}},_redraw:function(ap){this.requested_redraw=false;var am=this.low,ai=this.high;if(am<this.max_low){am=this.max_low}if(ai>this.max_high){ai=this.max_high}var ao=this.high-this.low;if(this.high!==0&&ao<this.min_separation){ai=am+this.min_separation}this.low=Math.floor(am);this.high=Math.ceil(ai);this.update_location(this.low,this.high);this.resolution_b_px=(this.high-this.low)/this.viewport_container.width();this.resolution_px_b=this.viewport_container.width()/(this.high-this.low);var af=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var al=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var aq=13;this.overview_box.css({left:af,width:Math.max(aq,al)}).show();if(al<aq){this.overview_box.css("left",af-(aq-al)/2)}if(this.overview_highlight){this.overview_highlight.css({left:af,width:al})}if(!ap){var ah,ag,an;for(var aj=0,ak=this.tracks_to_be_redrawn.length;aj<ak;aj++){ah=this.tracks_to_be_redrawn[aj][0];ag=this.tracks_to_be_redrawn[aj][1];an=this.tracks_to_be_redrawn[aj][2];if(ah){ah._draw(ag,an)}}this.tracks_to_be_redrawn=[];for(aj=0,ak=this.label_tracks.length;aj<ak;aj++){this.label_tracks[aj]._draw()}}},zoom_in:function(ag,ah){if(this.max_high===0||this.high-this.low<=this.min_separation){return}var ai=this.high-this.low,aj=ai/2+this.low,af=(ai/this.zoom_factor)/2;if(ag){aj=ag/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(aj-af);this.high=Math.round(aj+af);this.changed();this.request_redraw()},zoom_out:function(){if(this.max_high===0){return}var ag=this.high-this.low,ah=ag/2+this.low,af=(ag*this.zoom_factor)/2;this.low=Math.round(ah-af);this.high=Math.round(ah+af);this.changed();this.request_redraw()},resize_window:function(){this.viewport_container.height(this.container.height()-this.top_container.height()-this.bottom_container.height());this.request_redraw()},set_overview:function(ah){if(this.overview_drawable){if(this.overview_drawable.dataset_id===ah.dataset_id){return}this.overview_viewport.find(".track").remove()}var ag=ah.copy({content_div:this.overview_viewport}),af=this;ag.header_div.hide();ag.is_overview=true;af.overview_drawable=ag;this.overview_drawable.postdraw_actions=function(){af.overview_highlight.show().height(af.overview_drawable.content_div.height());af.overview_viewport.height(af.overview_drawable.content_div.height()+af.overview_box.outerHeight());af.overview_close.show();af.resize_window()};af.overview_drawable.request_draw();this.changed()},reset_overview:function(){$(".bs-tooltip").remove();this.overview_viewport.find(".track-tile").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide();view.resize_window();view.overview_drawable=null}});var t=function(ah,am,ai){this.track=ah;this.name=am.name;this.params=[];var au=am.params;for(var aj=0;aj<au.length;aj++){var ao=au[aj],ag=ao.name,at=ao.label,ak=unescape(ao.html),av=ao.value,aq=ao.type;if(aq==="number"){this.params.push(new f(ag,at,ak,(ag in ai?ai[ag]:av),ao.min,ao.max))}else{if(aq==="select"){this.params.push(new O(ag,at,ak,(ag in ai?ai[ag]:av)))}else{console.log("WARNING: unrecognized tool parameter type:",ag,aq)}}}this.parent_div=$("<div/>").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(ax){ax.stopPropagation()}).click(function(ax){ax.stopPropagation()}).bind("dblclick",function(ax){ax.stopPropagation()});var ar=$("<div class='tool-name'>").appendTo(this.parent_div).text(this.name);var ap=this.params;var an=this;$.each(this.params,function(ay,aB){var aA=$("<div>").addClass("param-row").appendTo(an.parent_div);var ax=$("<div>").addClass("param-label").text(aB.label).appendTo(aA);var az=$("<div/>").addClass("param-input").html(aB.html).appendTo(aA);az.find(":input").val(aB.value);$("<div style='clear: both;'/>").appendTo(aA)});this.parent_div.find("input").click(function(){$(this).select()});var aw=$("<div>").addClass("param-row").appendTo(this.parent_div);var al=$("<input type='submit'>").attr("value","Run on complete dataset").appendTo(aw);var af=$("<input type='submit'>").attr("value","Run on visible region").css("margin-left","3em").appendTo(aw);af.click(function(){an.run_on_region()});al.click(function(){an.run_on_dataset()});if("visible" in ai&&ai.visible){this.parent_div.show()}};r(t.prototype,{update_params:function(){for(var af=0;af<this.params.length;af++){this.params[af].update_value()}},state_dict:function(){var ag={};for(var af=0;af<this.params.length;af++){ag[this.params[af].name]=this.params[af].value}ag.visible=this.parent_div.is(":visible");return ag},get_param_values_dict:function(){var af={};this.parent_div.find(":input").each(function(){var ag=$(this).attr("name"),ah=$(this).val();af[ag]=ah});return af},get_param_values:function(){var af=[];this.parent_div.find(":input").each(function(){var ag=$(this).attr("name"),ah=$(this).val();if(ag){af[af.length]=ah}});return af},run_on_dataset:function(){var af=this;af.run({target_dataset_id:this.track.original_dataset_id,tool_id:af.name},null,function(ag){show_modal(af.name+" is Running",af.name+" is running on the complete dataset. Tool outputs are in dataset's history.",{Close:hide_modal})})},run_on_region:function(){var ag={target_dataset_id:this.track.original_dataset_id,action:"rerun",tool_id:this.name,regions:[{chrom:this.track.view.chrom,start:this.track.view.low,end:this.track.view.high}]},ak=this.track,ah=ag.tool_id+ak.tool_region_and_parameters_str(ag.chrom,ag.low,ag.high),af;if(ak.container===view){var aj=new Q(view,view,{name:this.name});var ai=ak.container.replace_drawable(ak,aj,false);aj.container_div.insertBefore(ak.view.content_div.children()[ai]);aj.add_drawable(ak);ak.container_div.appendTo(aj.content_div);af=aj}else{af=ak.container}var al=new ak.constructor(view,af,{name:ah,hda_ldda:"hda"});al.init_for_tool_data();al.change_mode(ak.mode);al.set_filters_manager(ak.filters_manager.copy(al));al.update_icons();af.add_drawable(al);al.tiles_div.text("Starting job.");this.update_params();this.run(ag,al,function(am){al.set_dataset(new Dataset(am));al.tiles_div.text("Running job.");al.init()})},run:function(af,ah,ai){af.inputs=this.get_param_values_dict();var ag=new ServerStateDeferred({ajax_settings:{url:galaxy_paths.get("tool_url"),data:JSON.stringify(af),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(aj){return aj!=="pending"}});$.when(ag.go()).then(function(aj){if(aj==="no converter"){ah.container_div.addClass("error");ah.content_div.text(K)}else{if(aj.error){ah.container_div.addClass("error");ah.content_div.text(z+aj.message)}else{ai(aj)}}})}});var O=function(ag,af,ah,ai){this.name=ag;this.label=af;this.html=$(ah);this.value=ai};r(O.prototype,{update_value:function(){this.value=$(this.html).val()}});var f=function(ah,ag,aj,ak,ai,af){O.call(this,ah,ag,aj,ak);this.min=ai;this.max=af};r(f.prototype,O.prototype,{update_value:function(){O.prototype.update_value.call(this);this.value=parseFloat(this.value)}});var g=function(af){this.manager=null;this.name=af.name;this.index=af.index;this.tool_id=af.tool_id;this.tool_exp_name=af.tool_exp_name};r(g.prototype,{to_dict:function(){return{name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name}}});var c=function(ah,ag,af){return $("<a/>").attr("href","javascript:void(0);").attr("title",ah).addClass("icon-button").addClass(ag).tooltip().click(af)};var V=function(an){g.call(this,an);this.low=("low" in an?an.low:-Number.MAX_VALUE);this.high=("high" in an?an.high:Number.MAX_VALUE);this.min=("min" in an?an.min:Number.MAX_VALUE);this.max=("max" in an?an.max:-Number.MAX_VALUE);this.container=null;this.slider=null;this.slider_label=null;var aj=function(ao,ap,aq){ao.click(function(){var aw=ap.text(),au=parseFloat(aq.slider("option","max")),at=(au<=1?4:au<=1000000?au.toString().length:6),av=false,ar=$(this).parents(".slider-row");ar.addClass("input");if(aq.slider("option","values")){at=2*at+1;av=true}ap.text("");$("<input type='text'/>").attr("size",at).attr("maxlength",at).attr("value",aw).appendTo(ap).focus().select().click(function(ax){ax.stopPropagation()}).blur(function(){$(this).remove();ap.text(aw);ar.removeClass("input")}).keyup(function(aB){if(aB.keyCode===27){$(this).trigger("blur")}else{if(aB.keyCode===13){var az=aq.slider("option","min"),ax=aq.slider("option","max"),aA=function(aC){return(isNaN(aC)||aC>ax||aC<az)},ay=$(this).val();if(!av){ay=parseFloat(ay);if(aA(ay)){alert("Parameter value must be in the range ["+az+"-"+ax+"]");return $(this)}}else{ay=ay.split("-");ay=[parseFloat(ay[0]),parseFloat(ay[1])];if(aA(ay[0])||aA(ay[1])){alert("Parameter value must be in the range ["+az+"-"+ax+"]");return $(this)}}aq.slider((av?"values":"value"),ay);ar.removeClass("input")}}})})};var ag=this;ag.parent_div=$("<div/>").addClass("filter-row slider-row");var af=$("<div/>").addClass("elt-label").appendTo(ag.parent_div),al=$("<span/>").addClass("slider-name").text(ag.name+" ").appendTo(af),ah=$("<span/>").text(this.low+"-"+this.high),ai=$("<span/>").addClass("slider-value").appendTo(af).append("[").append(ah).append("]");ag.values_span=ah;var ak=$("<div/>").addClass("slider").appendTo(ag.parent_div);ag.control_element=$("<div/>").attr("id",ag.name+"-filter-control").appendTo(ak);ag.control_element.slider({range:true,min:this.min,max:this.max,step:this.get_slider_step(this.min,this.max),values:[this.low,this.high],slide:function(ao,ap){ag.slide(ao,ap)},change:function(ao,ap){ag.control_element.slider("option","slide").call(ag.control_element,ao,ap)}});ag.slider=ag.control_element;ag.slider_label=ah;aj(ai,ah,ag.control_element);var am=$("<div/>").addClass("display-controls").appendTo(ag.parent_div);this.transparency_icon=c("Use filter for data transparency","layer-transparent",function(){if(ag.manager.alpha_filter!==ag){ag.manager.alpha_filter=ag;ag.manager.parent_div.find(".layer-transparent").removeClass("active").hide();ag.transparency_icon.addClass("active").show()}else{ag.manager.alpha_filter=null;ag.transparency_icon.removeClass("active")}ag.manager.track.request_draw(true,true)}).appendTo(am).hide();this.height_icon=c("Use filter for data height","arrow-resize-090",function(){if(ag.manager.height_filter!==ag){ag.manager.height_filter=ag;ag.manager.parent_div.find(".arrow-resize-090").removeClass("active").hide();ag.height_icon.addClass("active").show()}else{ag.manager.height_filter=null;ag.height_icon.removeClass("active")}ag.manager.track.request_draw(true,true)}).appendTo(am).hide();ag.parent_div.hover(function(){ag.transparency_icon.show();ag.height_icon.show()},function(){if(ag.manager.alpha_filter!==ag){ag.transparency_icon.hide()}if(ag.manager.height_filter!==ag){ag.height_icon.hide()}});$("<div style='clear: both;'/>").appendTo(ag.parent_div)};r(V.prototype,{to_dict:function(){var af=g.prototype.to_dict.call(this);return r(af,{type:"number",min:this.min,max:this.max,low:this.low,high:this.high})},copy:function(){return new V({name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name})},get_slider_step:function(ah,af){var ag=af-ah;return(ag<=2?0.01:1)},slide:function(ah,ai){var ag=ai.values;this.values_span.text(ag[0]+"-"+ag[1]);this.low=ag[0];this.high=ag[1];var af=this;setTimeout(function(){if(ag[0]===af.low&&ag[1]===af.high){af.manager.track.request_draw(true,true)}},25)},applies_to:function(af){if(af.length>this.index){return true}return false},_keep_val:function(af){return(isNaN(af)||(af>=this.low&&af<=this.high))},keep:function(ag){if(!this.applies_to(ag)){return true}var ai=this;var aj=ag[this.index];if(aj instanceof Array){var ah=true;for(var af=0;af<aj.length;af++){if(!this._keep_val(aj[af])){ah=false;break}}return ah}else{return this._keep_val(ag[this.index])}},update_attrs:function(ai){var af=false;if(!this.applies_to(ai)){return af}var ag=ai[this.index];if(!(ag instanceof Array)){ag=[ag]}for(var ah=0;ah<ag.length;ah++){var aj=ag[ah];if(aj<this.min){this.min=Math.floor(aj);af=true}if(aj>this.max){this.max=Math.ceil(aj);af=true}}return af},update_ui_elt:function(){if(this.min<this.max){this.parent_div.show()}else{this.parent_div.hide()}var ag=this.slider.slider("option","min"),af=this.slider.slider("option","max");if(this.min<ag||this.max>af){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",this.get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var aa=function(ah,an){this.track=ah;this.alpha_filter=null;this.height_filter=null;this.filters=[];this.parent_div=$("<div/>").addClass("filters").hide();this.parent_div.bind("drag",function(ap){ap.stopPropagation()}).click(function(ap){ap.stopPropagation()}).bind("dblclick",function(ap){ap.stopPropagation()}).bind("keydown",function(ap){ap.stopPropagation()});if(an&&"filters" in an){var af=("alpha_filter" in an?an.alpha_filter:null),ai=("height_filter" in an?an.height_filter:null),ak=an.filters,ag;for(var al=0;al<ak.length;al++){if(ak[al].type==="number"){ag=new V(ak[al]);this.add_filter(ag);if(ag.name===af){this.alpha_filter=ag;ag.transparency_icon.addClass("active").show()}if(ag.name===ai){this.height_filter=ag;ag.height_icon.addClass("active").show()}}else{console.log("ERROR: unsupported filter: ",name,type)}}if("visible" in an&&an.visible){this.parent_div.show()}}if(this.filters.length!==0){var ao=$("<div/>").addClass("param-row").appendTo(this.parent_div);var am=$("<input type='submit'/>").attr("value","Run on complete dataset").appendTo(ao);var aj=this;am.click(function(){aj.run_on_dataset()})}};r(aa.prototype,{show:function(){this.parent_div.show()},hide:function(){this.parent_div.hide()},toggle:function(){this.parent_div.toggle()},visible:function(){return this.parent_div.is(":visible")},to_dict:function(){var ai={},ah=[],ag;for(var af=0;af<this.filters.length;af++){ag=this.filters[af];ah.push(ag.to_dict())}ai.filters=ah;ai.alpha_filter=(this.alpha_filter?this.alpha_filter.name:null);ai.height_filter=(this.height_filter?this.height_filter.name:null);ai.visible=this.parent_div.is(":visible");return ai},copy:function(ag){var ah=new aa(ag);for(var af=0;af<this.filters.length;af++){ah.add_filter(this.filters[af].copy())}return ah},add_filter:function(af){af.manager=this;this.parent_div.append(af.parent_div);this.filters.push(af)},remove_all:function(){this.filters=[];this.parent_div.children().remove()},init_filters:function(){for(var af=0;af<this.filters.length;af++){var ag=this.filters[af];ag.update_ui_elt()}},clear_filters:function(){for(var af=0;af<this.filters.length;af++){var ag=this.filters[af];ag.slider.slider("option","values",[ag.min,ag.max])}this.alpha_filter=null;this.height_filter=null;this.parent_div.find(".icon-button").hide()},run_on_dataset:function(){var al=function(ap,an,ao){if(!(an in ap)){ap[an]=ao}return ap[an]};var ak={},am,af;for(var aj=0;aj<this.filters.length;aj++){am=this.filters[aj];if(am.tool_id){if(am.min!==am.low){af=al(ak,am.tool_id,[]);af[af.length]=am.tool_exp_name+" >= "+am.low}if(am.max!==am.high){af=al(ak,am.tool_id,[]);af[af.length]=am.tool_exp_name+" <= "+am.high}}}var ag=[];for(var ai in ak){ag[ag.length]=[ai,ak[ai]]}(function ah(au,aq){var ao=aq[0],ap=ao[0],at=ao[1],ar="("+at.join(") and (")+")",an={cond:ar,input:au,target_dataset_id:au,tool_id:ap},aq=aq.slice(1);$.getJSON(run_tool_url,an,function(av){if(av.error){show_modal("Filter Dataset","Error running tool "+ap,{Close:hide_modal})}else{if(aq.length===0){show_modal("Filtering Dataset","Filter(s) are running on the complete dataset. Outputs are in dataset's history.",{Close:hide_modal})}else{ah(av.dataset_id,aq)}}})})(this.track.dataset_id,ag)}});var D=function(af,ag){M.Scaler.call(this,ag);this.filter=af};D.prototype.gen_val=function(af){if(this.filter.high===Number.MAX_VALUE||this.filter.low===-Number.MAX_VALUE||this.filter.low===this.filter.high){return this.default_val}return((parseFloat(af[this.filter.index])-this.filter.low)/(this.filter.high-this.filter.low))};var G=function(af){this.track=af.track;this.params=af.params;this.values={};this.restore_values((af.saved_values?af.saved_values:{}));this.onchange=af.onchange};r(G.prototype,{restore_values:function(af){var ag=this;$.each(this.params,function(ah,ai){if(af[ai.key]!==undefined){ag.values[ai.key]=af[ai.key]}else{ag.values[ai.key]=ai.default_value}})},build_form:function(){var ai=this;var af=$("<div />");var ah;function ag(an,aj){for(var ar=0;ar<an.length;ar++){ah=an[ar];if(ah.hidden){continue}var al="param_"+ar;var aw=ai.values[ah.key];var ay=$("<div class='form-row' />").appendTo(aj);ay.append($("<label />").attr("for",al).text(ah.label+":"));if(ah.type==="bool"){ay.append($('<input type="checkbox" />').attr("id",al).attr("name",al).attr("checked",aw))}else{if(ah.type==="text"){ay.append($('<input type="text"/>').attr("id",al).val(aw).click(function(){$(this).select()}))}else{if(ah.type==="select"){var au=$("<select />").attr("id",al);for(var ap=0;ap<ah.options.length;ap++){$("<option/>").text(ah.options[ap].label).attr("value",ah.options[ap].value).appendTo(au)}au.val(aw);ay.append(au)}else{if(ah.type==="color"){var ax=$("<div/>").appendTo(ay),at=$("<input />").attr("id",al).attr("name",al).val(aw).css("float","left").appendTo(ax).click(function(aA){$(".bs-tooltip").removeClass("in");var az=$(this).siblings(".bs-tooltip").addClass("in");az.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top-($(az).height()/2)+($(this).height()/2)}).show();az.click(function(aB){aB.stopPropagation()});$(document).bind("click.color-picker",function(){az.hide();$(document).unbind("click.color-picker")});aA.stopPropagation()}),aq=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(ax).attr("title","Set new random color").tooltip(),av=$("<div class='bs-tooltip right' style='position: absolute;' />").appendTo(ax).hide(),am=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(av),ak=$("<div class='tooltip-arrow'></div>").appendTo(av),ao=$.farbtastic(am,{width:100,height:100,callback:at,color:aw});ax.append($("<div/>").css("clear","both"));(function(az){aq.click(function(){az.setColor(R())})})(ao)}else{ay.append($("<input />").attr("id",al).attr("name",al).val(aw))}}}}if(ah.help){ay.append($("<div class='help'/>").text(ah.help))}}}ag(this.params,af);return af},update_from_form:function(af){var ah=this;var ag=false;$.each(this.params,function(ai,ak){if(!ak.hidden){var al="param_"+ai;var aj=af.find("#"+al).val();if(ak.type==="float"){aj=parseFloat(aj)}else{if(ak.type==="int"){aj=parseInt(aj)}else{if(ak.type==="bool"){aj=af.find("#"+al).is(":checked")}}}if(aj!==ah.values[ak.key]){ah.values[ak.key]=aj;ag=true}}});if(ag){this.onchange();this.track.changed()}}});var b=function(af,aj,ah,ag,ai){this.track=af;this.region=aj;this.low=aj.get("start");this.high=aj.get("end");this.resolution=ah;this.html_elt=$("<div class='track-tile'/>").append(ag).height($(ag).attr("height"));this.data=ai;this.stale=false};b.prototype.predisplay_actions=function(){};var k=function(af,ak,ah,ag,ai,aj){b.call(this,af,ak,ah,ag,ai);this.max_val=aj};r(k.prototype,b.prototype);var P=function(ai,aq,aj,ah,al,at,am,au,ag,ap){b.call(this,ai,aq,aj,ah,al);this.mode=am;this.all_slotted=ag;this.feature_mapper=ap;this.has_icons=false;if(au){this.has_icons=true;var an=this;ah=this.html_elt.children()[0],message_div=$("<div/>").addClass("tile-message").css({height:E-1,width:ah.width}).prependTo(this.html_elt);var ao=new GenomeRegion({chrom:ai.view.chrom,start:this.low,end:this.high}),ar=al.length,ak=$("<a href='javascript:void(0);'/>").addClass("icon more-down").attr("title","For speed, only the first "+ar+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),af=$("<a href='javascript:void(0);'/>").addClass("icon more-across").attr("title","For speed, only the first "+ar+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ak.click(function(){an.stale=true;ai.data_manager.get_more_data(ao,ai.mode,an.resolution,{},ai.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();ai.request_draw(true)}).dblclick(function(av){av.stopPropagation()});af.click(function(){an.stale=true;ai.data_manager.get_more_data(ao,ai.mode,an.resolution,{},ai.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();ai.request_draw(true)}).dblclick(function(av){av.stopPropagation()})}};r(P.prototype,b.prototype);P.prototype.predisplay_actions=function(){var ag=this,af={};if(ag.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ar){if(!this.hovered){return}var am=$(this).offset(),aq=ar.pageX-am.left,ap=ar.pageY-am.top,aw=ag.feature_mapper.get_feature_data(aq,ap),an=(aw?aw[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!an||$(this).attr("id")!==an.toString()){$(this).remove()}});if(aw){var ai=af[an];if(!ai){var an=aw[0],at={name:aw[3],start:aw[1],end:aw[2],strand:aw[4]},al=ag.track.filters_manager.filters,ak;for(var ao=0;ao<al.length;ao++){ak=al[ao];at[ak.name]=aw[ak.index]}var ai=$("<div/>").attr("id",an).addClass("feature-popup"),ax=$("<table/>"),av,au,ay;for(av in at){au=at[av];ay=$("<tr/>").appendTo(ax);$("<th/>").appendTo(ay).text(av);$("<td/>").attr("align","left").appendTo(ay).text(typeof(au)==="number"?Z(au,2):au)}ai.append($("<div class='feature-popup-inner'>").append(ax));af[an]=ai}ai.appendTo($(this).parents(".track-content").children(".overlay"));var aj=aq+parseInt(ag.html_elt.css("left"))-ai.width()/2,ah=ap+parseInt(ag.html_elt.css("top"))+7;ai.css("left",aj+"px").css("top",ah+"px")}else{if(!ar.isPropagationStopped()){ar.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ar)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var i=function(ag,af,ah){r(ah,{drag_handle_class:"draghandle"});s.call(this,ag,af,ah);this.dataset=new Dataset({id:ah.dataset_id,hda_ldda:ah.hda_ldda});this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ah?ah.data_query_wait:L);this.data_manager=("data_manager" in ah?ah.data_manager:new y.GenomeDataManager({dataset:this.dataset,data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("<div class='track-content'>").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ah)||ah.resize){this.add_resize_handle()}}};r(i.prototype,s.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},s.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(af){af.view.set_overview(af)}},s.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(af){if(af.filters_manager.visible()){af.filters_manager.clear_filters()}else{af.filters_manager.init_filters()}af.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(af){af.dynamic_tool_div.toggle();if(af.dynamic_tool_div.is(":visible")){af.set_name(af.name+af.tool_region_and_parameters_str())}else{af.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(af){var ai='<strong>Tool</strong>: <%= track.tool.name %><br/><strong>Dataset</strong>: <%= track.name %><br/><strong>Region(s)</strong>: <select name="regions"><option value="cur">current viewing area</option><option value="bookmarks">bookmarks</option><option value="both">current viewing area and bookmarks</option></select>',ah=ae.template(ai,{track:af});var ak=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ag=function(){var am=$('select[name="regions"] option:selected').val(),ao,al=new GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),an=ae.map($(".bookmark"),function(ap){return new GenomeRegion({from_str:$(ap).children(".position").text()})});if(am==="cur"){ao=[al]}else{if(am==="bookmarks"){ao=an}else{ao=[al].concat(an)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:af.dataset_id,hda_ldda:af.hda_ldda,regions:JSON.stringify(new Backbone.Collection(ao).toJSON())})},aj=function(al){if((al.keyCode||al.which)===27){ak()}else{if((al.keyCode||al.which)===13){ag()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ah,{No:ak,Yes:ag})}},s.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset_id&&s.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("<div/>").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var af=$("<div class='track-header'/>");if(this.view.editor){this.drag_div=$("<div/>").addClass(this.drag_handle_class).appendTo(af)}this.name_div=$("<div/>").addClass("track-name").appendTo(af).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return af},on_resize:function(){},add_resize_handle:function(){var af=this;var ai=false;var ah=false;var ag=$("<div class='track-resize'>");$(af.container_div).hover(function(){if(af.content_visible){ai=true;ag.show()}},function(){ai=false;if(!ah){ag.hide()}});ag.hide().bind("dragstart",function(aj,ak){ah=true;ak.original_height=$(af.content_div).height()}).bind("drag",function(ak,al){var aj=Math.min(Math.max(al.original_height+al.deltaY,af.min_height_px),af.max_height_px);$(af.tiles_div).css("height",aj);af.visible_height_px=(af.max_height_px===aj?0:aj);af.on_resize()}).bind("dragend",function(aj,ak){af.tile_cache.clear();ah=false;if(!ai){ag.hide()}af.config.values.height=af.visible_height_px;af.changed()}).appendTo(af.container_div)},set_display_modes:function(ai,al){this.display_modes=ai;this.mode=(al?al:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ag=this,aj={};for(var ah=0,af=ag.display_modes.length;ah<af;ah++){var ak=ag.display_modes[ah];aj[ak]=function(am){return function(){ag.change_mode(am);ag.icons_div.show();ag.container_div.mouseleave(function(){ag.icons_div.hide()})}}(ak)}make_popupmenu(this.action_icons.mode_icon,aj)},build_action_icons:function(){s.prototype.build_action_icons.call(this,this.action_icons_def);if(this.display_modes!==undefined){this.set_display_modes(this.display_modes)}},hide_contents:function(){this.tiles_div.hide();this.container_div.find(".yaxislabel, .track-resize").hide()},show_contents:function(){this.tiles_div.show();this.container_div.find(".yaxislabel, .track-resize").show();this.request_draw()},get_type:function(){if(this instanceof ab){return"LabelTrack"}else{if(this instanceof C){return"ReferenceTrack"}else{if(this instanceof j){return"LineTrack"}else{if(this instanceof W){return"ReadTrack"}else{if(this instanceof T){return"VcfTrack"}else{if(this instanceof h){return"CompositeTrack"}else{if(this instanceof d){return"FeatureTrack"}}}}}}}return""},init:function(){var ag=this;ag.enabled=false;ag.tile_cache.clear();ag.data_manager.clear();ag.content_div.css("height","auto");ag.tiles_div.children().remove();ag.container_div.removeClass("nodata error pending");if(!ag.dataset_id){return}var af=$.Deferred(),ah={hda_ldda:ag.hda_ldda,data_type:this.dataset_check_type,chrom:ag.view.chrom};$.getJSON(this.dataset.url(),ah,function(ai){if(!ai||ai==="error"||ai.kind==="error"){ag.container_div.addClass("error");ag.tiles_div.text(p);if(ai.message){var aj=$(" <a href='javascript:void(0);'></a>").text("View error").click(function(){show_modal("Trackster Error","<pre>"+ai.message+"</pre>",{Close:hide_modal})});ag.tiles_div.append(aj)}}else{if(ai==="no converter"){ag.container_div.addClass("error");ag.tiles_div.text(K)}else{if(ai==="no data"||(ai.data!==undefined&&(ai.data===null||ai.data.length===0))){ag.container_div.addClass("nodata");ag.tiles_div.text(F)}else{if(ai==="pending"){ag.container_div.addClass("pending");ag.tiles_div.html(w);setTimeout(function(){ag.init()},ag.data_query_wait)}else{if(ai==="data"||ai.status==="data"){if(ai.valid_chroms){ag.valid_chroms=ai.valid_chroms;ag.update_icons()}ag.tiles_div.text(X);if(ag.view.chrom){ag.tiles_div.text("");ag.tiles_div.css("height",ag.visible_height_px+"px");ag.enabled=true;$.when(ag.predraw_init()).done(function(){af.resolve();ag.container_div.removeClass("nodata error pending");ag.request_draw()})}else{af.resolve()}}}}}}});this.update_icons();return af},predraw_init:function(){},get_drawables:function(){return this}});var N=function(ah,ag,ai){i.call(this,ah,ag,ai);var af=this;n(af.container_div,af.drag_handle_class,".group",af);this.filters_manager=new aa(this,("filters" in ai?ai.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in ai&&ai.tool?new t(this,ai.tool,ai.tool_state):null);this.tile_cache=new y.Cache(S);if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("<div/>").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("<div/>").addClass("overlay").appendTo(this.content_div);if(ai.mode){this.change_mode(ai.mode)}};r(N.prototype,s.prototype,i.prototype,{action_icons_def:i.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(af){$(".bs-tooltip").remove();af.slotters[af.view.resolution_px_b].max_rows*=2;af.request_draw(true)},hide:true}]),copy:function(af){var ag=this.to_dict();r(ag,{data_manager:this.data_manager});var ah=new this.constructor(this.view,af,ag);ah.change_mode(this.mode);ah.enabled=this.enabled;return ah},set_filters_manager:function(af){this.filters_manager=af;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},change_mode:function(ag){var af=this