galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
November 2013
- 1 participants
- 208 discussions
commit/galaxy-central: guerler: Upload: Use nginx_upload_path setting
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c3f3523b6580/
Changeset: c3f3523b6580
User: guerler
Date: 2013-11-19 20:56:07
Summary: Upload: Use nginx_upload_path setting
Affected #: 2 files
diff -r 95a7a6be8791721f97f33f56e4c5bb56787ec181 -r c3f3523b6580c23feab1ca90d1333ca05dc3abaa static/scripts/galaxy.upload.js
--- a/static/scripts/galaxy.upload.js
+++ b/static/scripts/galaxy.upload.js
@@ -47,8 +47,13 @@
}
},
+ // options
+ options : {
+ nginx_upload_path : ''
+ },
+
// initialize
- initialize : function()
+ initialize : function(options)
{
// wait for galaxy history panel (workaround due to the use of iframes)
if (!Galaxy.currHistoryPanel)
@@ -108,6 +113,11 @@
// insert default back to array
self.select_genome.unshift(def);
});
+
+ // read in options
+ if (options) {
+ this.options = _.defaults(options, this.options);
+ }
},
// mouse over
@@ -206,7 +216,7 @@
return null;
// configure uploadbox
- this.uploadbox.configure({url : galaxy_config.root + "api/tools", paramname : "files_0|file_data"});
+ this.uploadbox.configure({url : galaxy_config.root + this.options.nginx_upload_path, paramname : "files_0|file_data"});
// configure tool
tool_input = {};
diff -r 95a7a6be8791721f97f33f56e4c5bb56787ec181 -r c3f3523b6580c23feab1ca90d1333ca05dc3abaa templates/webapps/galaxy/galaxy.masthead.mako
--- a/templates/webapps/galaxy/galaxy.masthead.mako
+++ b/templates/webapps/galaxy/galaxy.masthead.mako
@@ -34,6 +34,7 @@
masthead_config = {
## inject configuration
'brand' : app.config.get("brand", ""),
+ 'nginx_upload_path' : app.config.get("nginx_upload_path", "api/tools"),
'use_remote_user' : app.config.use_remote_user,
'remote_user_logout_href' : app.config.remote_user_logout_href,
'enable_cloud_launch' : app.config.get_bool('enable_cloud_launch', False),
@@ -98,7 +99,7 @@
});
## add upload plugin
- ##Galaxy.upload = new mod_upload.GalaxyUpload();
+ ##Galaxy.upload = new mod_upload.GalaxyUpload(masthead_config);
## add quota meter to masthead
Galaxy.quotaMeter = new UserQuotaMeter({
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jgoecks: Make viz_filter_cols metadata attribute optional.
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/95a7a6be8791/
Changeset: 95a7a6be8791
User: jgoecks
Date: 2013-11-19 20:23:32
Summary: Make viz_filter_cols metadata attribute optional.
Affected #: 2 files
diff -r 5ebf2d14c2eea6bd8425e22ded37b7346b65c282 -r 95a7a6be8791721f97f33f56e4c5bb56787ec181 lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -389,7 +389,7 @@
MetadataElement( name="endCol", default=3, desc="End column", param=metadata.ColumnParameter )
MetadataElement( name="strandCol", desc="Strand column (click box & select)", param=metadata.ColumnParameter, optional=True, no_value=0 )
MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True, visible=False )
- MetadataElement( name="viz_filter_cols", desc="Score column for visualization", default=[4], param=metadata.ColumnParameter, multiple=True )
+ MetadataElement( name="viz_filter_cols", desc="Score column for visualization", default=[4], param=metadata.ColumnParameter, optional=True, multiple=True )
###do we need to repeat these? they are the same as should be inherited from interval type
def set_meta( self, dataset, overwrite = True, **kwd ):
diff -r 5ebf2d14c2eea6bd8425e22ded37b7346b65c282 -r 95a7a6be8791721f97f33f56e4c5bb56787ec181 lib/galaxy/datatypes/tabular.py
--- a/lib/galaxy/datatypes/tabular.py
+++ b/lib/galaxy/datatypes/tabular.py
@@ -652,7 +652,7 @@
MetadataElement( name="columns", default=10, desc="Number of columns", readonly=True, visible=False )
MetadataElement( name="column_types", default=['str','int','str','str','str','int','str','list','str','str'], param=metadata.ColumnTypesParameter, desc="Column types", readonly=True, visible=False )
- MetadataElement( name="viz_filter_cols", desc="Score column for visualization", default=[5], param=metadata.ColumnParameter, multiple=True, visible=False )
+ MetadataElement( name="viz_filter_cols", desc="Score column for visualization", default=[5], param=metadata.ColumnParameter, optional=True, multiple=True, visible=False )
MetadataElement( name="sample_names", default=[], desc="Sample names", readonly=True, visible=False, optional=True, no_value=[] )
def sniff( self, filename ):
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
10 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0c559492c88a/
Changeset: 0c559492c88a
User: kellrott
Date: 2013-10-30 08:49:54
Summary: Merged galaxy/galaxy-central into default
Affected #: 47 files
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/galaxy.base.js
--- a/static/scripts/galaxy.base.js
+++ b/static/scripts/galaxy.base.js
@@ -295,7 +295,6 @@
use_textarea = ("use_textarea" in config_dict ? config_dict.use_textarea : false),
on_finish = ("on_finish" in config_dict ? config_dict.on_finish : null),
help_text = ("help_text" in config_dict ? config_dict.help_text : null);
-
// Add element behavior.
var container = $(this);
@@ -326,7 +325,7 @@
};
// Create input element(s) for editing.
- var cur_text = container.text(),
+ var cur_text = ("cur_text" in config_dict ? config_dict.cur_text : container.text() ),
input_elt, button_elt;
if (use_textarea) {
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/galaxy.upload.js
--- a/static/scripts/galaxy.upload.js
+++ b/static/scripts/galaxy.upload.js
@@ -206,7 +206,7 @@
return null;
// configure uploadbox
- this.uploadbox.configure({url : galaxy_config.root + "api/tools/", paramname : "files_0|file_data"});
+ this.uploadbox.configure({url : galaxy_config.root + "api/tools", paramname : "files_0|file_data"});
// configure tool
tool_input = {};
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/mvc/base-mvc.js
--- a/static/scripts/mvc/base-mvc.js
+++ b/static/scripts/mvc/base-mvc.js
@@ -219,60 +219,50 @@
//==============================================================================
-function LoadingIndicator( $where ){
- var self = this,
- $indicator;
+function LoadingIndicator( $where, options ){
+ options = options || {};
+ var self = this;
- function setPosition(){
- // even tho pos is 'fixed' - give illusion of width 100% and margin by manually setting width, offset
- var padding = 4,
- width = $indicator.parent().width() || $where.width(),
- offset = $indicator.parent().offset() || $where.offset();
+ function render(){
+ var html = [
+ '<div class="loading-indicator">',
+ '<span class="fa-icon-spinner fa-icon-spin fa-icon-large" style="color: grey"></span>',
+ '<span style="margin-left: 8px; color: grey"><i>loading...</i></span>',
+ '</div>'
+ ].join( '\n' );
- $indicator.outerWidth( width - ( padding * 2 ) );
- // have to use css top, left and not offset (wont work when indicator is hidden)
- $indicator.css({ top: offset.top + padding + 'px' , left: offset.left + padding + 'px' });
+ return $( html ).css( options.css || {
+ 'position' : 'fixed',
+ 'margin' : '6px 0px 0px 10px',
+ 'opacity' : '0.85'
+ }).hide();
}
- function render(){
- var $spinner = $( '<span class="fa-icon-spinner fa-icon-spin fa-icon-large"></span>')
- .css({ 'color' : 'grey', 'font-size' : '16px' });
- var $message = $( '<i>loading...</i>' )
- .css({ 'color' : 'grey', 'margin-left' : '8px' });
- $indicator = $( '<div/>' ).addClass( 'loading-indicator' )
- .css({
- 'position' : 'fixed',
- 'padding' : '4px',
- 'text-align' : 'center',
- 'background-color' : 'white',
- 'opacity' : '0.85',
- 'border-radius' : '3px'
- })
- .append( $spinner, $message )
- //NOTE: insert as sibling to $where
- .insertBefore( $where );
- setPosition();
- return $indicator.hide();
- }
+ self.show = function( msg, speed, callback ){
+ msg = msg || 'loading...';
+ speed = speed || 'fast';
+ // since position is fixed - we insert as sibling
+ self.$indicator = render().insertBefore( $where );
+ self.message( msg );
+ self.$indicator.fadeIn( speed, callback );
+ return self;
+ };
- self.show = function( speed, callback ){
- speed = speed || 'fast';
- setPosition();
- $indicator.fadeIn( speed, callback );
- // not using full fadeOut allows using scroll to still work
- //$whatIsLoading.fadeTo( speed, 0.0001, callback );
- return self;
+ self.message = function( msg ){
+ self.$indicator.find( 'i' ).text( msg );
};
self.hide = function( speed, callback ){
speed = speed || 'fast';
- //$whatIsLoading.fadeTo( speed, 1.0, function(){
- // if( callback ){ callback(); }
- //});
- $indicator.fadeOut( speed, callback );
+ if( self.$indicator && self.$indicator.size() ){
+ self.$indicator.fadeOut( speed, function(){
+ self.$indicator.remove();
+ if( callback ){ callback(); }
+ });
+ } else {
+ if( callback ){ callback(); }
+ }
return self;
};
- $indicator = render();
return self;
}
-
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/mvc/dataset/hda-base.js
--- a/static/scripts/mvc/dataset/hda-base.js
+++ b/static/scripts/mvc/dataset/hda-base.js
@@ -19,7 +19,8 @@
//logger : console,
tagName : "div",
- className : "historyItemContainer",
+ className : "dataset hda history-panel-hda",
+ id : function(){ return 'hda-' + this.model.get( 'id' ); },
fxSpeed : 'fast',
@@ -67,55 +68,50 @@
// ......................................................................... render main
/** Render this HDA, set up ui.
- * @fires rendered:ready when rendered and NO running HDAs
- * @fires rendered when rendered and running HDAs
- * @fires rendered:initial on first render with running HDAs
- * @fires rendered:initial:ready when first rendered and NO running HDAs
+ * @param {Boolean} fade whether or not to fade out/in when re-rendering
+ * @fires rendered when rendered
+ * @fires rendered:ready when first rendered and NO running HDAs
* @returns {Object} this HDABaseView
*/
- render : function(){
- var view = this,
- id = this.model.get( 'id' ),
- state = this.model.get( 'state' ),
- itemWrapper = $( '<div/>' ).attr( 'id', 'historyItem-' + id ),
- initialRender = ( this.$el.children().size() === 0 );
-
- this.$el.attr( 'id', 'historyItemContainer-' + id );
+ render : function( fade ){
+ fade = ( fade === undefined )?( true ):( fade );
+ var view = this;
//HACK: hover exit doesn't seem to be called on prev. tooltips when RE-rendering - so: no tooltip hide
// handle that here by removing previous view's tooltips
this.$el.find("[title]").tooltip( "destroy" );
- /** web controller urls for functions relating to this hda. */
+ // re-get web controller urls for functions relating to this hda. (new model data may have changed this)
this.urls = this.model.urls();
- itemWrapper
- .addClass( 'historyItemWrapper' ).addClass( 'historyItem' )
- .addClass( 'historyItem-' + state );
+ // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.
+ var $newRender = $( HDABaseView.templates.skeleton( this.model.toJSON() ) );
+ $newRender.find( '.dataset-primary-actions' ).append( this._render_titleButtons() );
+ $newRender.children( '.dataset-body' ).replaceWith( this._render_body() );
+ this._setUpBehaviors( $newRender );
- itemWrapper.append( this._render_warnings() );
- itemWrapper.append( this._render_titleBar() );
-
- //NOTE: only sets behaviors on title and warnings - body will set up it's own
- this._setUpBehaviors( itemWrapper );
-
- this.body = $( this._render_body() );
- itemWrapper.append( this.body );
-
- // transition...
- this.$el.fadeOut( this.fxSpeed, function(){
- view.$el.children().remove();
- view.$el.append( itemWrapper ).fadeIn( view.fxSpeed, function(){
- view.log( view + ' rendered:', view.$el );
-
- var renderedEventName = 'rendered';
- if( initialRender ){
- renderedEventName += ':initial';
- } else if( view.model.inReadyState() ){
- renderedEventName += ':ready';
- }
- view.trigger( renderedEventName );
- });
+ // fade the old render out (if desired)
+ if( fade ){
+ $( view ).queue( function( next ){ this.$el.fadeOut( view.fxSpeed, next ); });
+ }
+ // empty the old render, update to any new HDA state, swap in the new render contents
+ $( view ).queue( function( next ){
+ this.$el.empty()
+ .attr( 'class', view.className ).addClass( 'state-' + view.model.get( 'state' ) )
+ .append( $newRender.children() );
+ next();
+ });
+ // fade the new in
+ if( fade ){
+ $( view ).queue( function( next ){ this.$el.fadeIn( view.fxSpeed, next ); });
+ }
+ // trigger an event to know we're ready
+ $( view ).queue( function( next ){
+ this.trigger( 'rendered', view );
+ if( this.model.inReadyState() ){
+ this.trigger( 'rendered:ready', view );
+ }
+ next();
});
return this;
},
@@ -130,35 +126,39 @@
$container.find( '[title]' ).tooltip({ placement : 'bottom' });
},
- // ................................................................................ RENDER titlebar
- /** Render any hda warnings including: is deleted, is purged, is hidden.
- * (including links to further actions (undelete, etc.))
- * @returns {jQuery} rendered DOM
- */
- _render_warnings : function(){
- // jQ errs on building dom with whitespace - if there are no messages, trim -> ''
- return $( jQuery.trim( HDABaseView.templates.messages( this.model.toJSON() )));
- },
-
- /** Render the part of an hda always shown (whether the body is expanded or not): title link, title buttons.
- * @returns {jQuery} rendered DOM
- */
- _render_titleBar : function(){
- var titleBar = $( '<div class="historyItemTitleBar" style="overflow: hidden"></div>' );
- titleBar.append( this._render_titleButtons() );
- titleBar.append( '<span class="state-icon"></span>' );
- titleBar.append( this._render_titleLink() );
- return titleBar;
- },
-
+ // ................................................................................ titlebar buttons
/** Render icon-button group for the common, most easily accessed actions.
* @returns {jQuery} rendered DOM
*/
_render_titleButtons : function(){
- // render the display, edit attr and delete icon-buttons
- var buttonDiv = $( '<div class="historyItemButtons"></div>' );
- buttonDiv.append( this._render_displayButton() );
- return buttonDiv;
+ // render just the display for read-only
+ return [ this._render_displayButton() ];
+ },
+
+ _render_iconButton : function( options ){
+ options = options || {};
+ //options.classes = [ 'icon-button', 'menu-button' ].concat( options.classes || [] );
+ options.classes = [ 'icon-btn' ].concat( options.classes || [] );
+ //options.classes = [ 'btn' ].concat( options.classes || [] );
+ //options.classes = options.classes || [];
+ if( options.disabled ){
+ options.classes.push( 'disabled' );
+ }
+
+ var button = [
+ '<a class="', options.classes.join( ' ' ), '"',
+ (( options.title )? ( ' title="' + options.title + '"' ):( '' )),
+ (( options.target )? ( ' target="' + options.target + '"' ):( '' )),
+ ' href="', (( options.href )?( options.href ):( 'javascript:void(0);' )), '">',
+ // could go with something less specific here - like 'html'
+ '<span class="', options.faIcon, '"></span>',
+ '</a>'
+ ].join( '' );
+ button = $( button );
+ if( _.isFunction( options.onClick ) ){
+ button.click( options.onClick );
+ }
+ return button;
},
/** Render icon-button to display this hda in the galaxy main iframe.
@@ -168,12 +168,12 @@
// don't show display if not viewable or not accessible
// (do show if in error, running)
if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
+ || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.DISCARDED )
|| ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW )
|| ( !this.model.get( 'accessible' ) ) ){
this.displayButton = null;
return null;
}
- //NOTE: line 88 in history_common.mako should be handled by the url template generation
var displayBtnData = {
icon_class : 'display',
@@ -210,52 +210,48 @@
this.displayButton = new IconButtonView({ model : new IconButton( displayBtnData ) });
return this.displayButton.render().$el;
+ //displayBtnData.faIcon = 'fa-icon-eye-open';
+ //return this._render_iconButton( displayBtnData );
},
- /** Render the hid and hda.name as a link (that will expand the body).
- * @returns {jQuery} rendered DOM
- */
- _render_titleLink : function(){
- return $( jQuery.trim( HDABaseView.templates.titleLink( this.model.toJSON() )));
- },
-
- // ......................................................................... body
- /** Render the data/metadata summary (format, size, misc info, etc.).
- * @returns {jQuery} rendered DOM
- */
- _render_hdaSummary : function(){
- var modelData = _.extend( this.model.toJSON(), { urls: this.urls } );
- return HDABaseView.templates.hdaSummary( modelData );
- },
-
// ......................................................................... primary actions
- /** Render the icon-buttons gen. placed underneath the hda summary (e.g. download, show params, etc.)
- * @param {Array} buttonRenderingFuncs array of rendering functions appending the results in order
- * @returns {jQuery} rendered DOM
- */
- _render_primaryActionButtons : function( buttonRenderingFuncs ){
- var view = this,
- primaryActionButtons = $( '<div/>' ).attr( 'id', 'primary-actions-' + this.model.get( 'id' ) );
- _.each( buttonRenderingFuncs, function( fn ){
- primaryActionButtons.append( fn.call( view ) );
- });
- return primaryActionButtons;
- },
-
/** Render icon-button/popupmenu to download the data (and/or the associated meta files (bai, etc.)) for this hda.
* @returns {jQuery} rendered DOM
*/
_render_downloadButton : function(){
// don't show anything if the data's been purged
if( this.model.get( 'purged' ) || !this.model.hasData() ){ return null; }
-
+ var urls = this.urls,
+ meta_files = this.model.get( 'meta_files' );
+
// return either: a single download icon-button (if there are no meta files)
+ if( _.isEmpty( meta_files ) ){
+ return $([ '<a href="', urls.download, '" title="', _l( 'Download' ),
+ '" class="icon-button disk"></a>' ].join( '' ) );
+ }
+
// or a popupmenu with links to download assoc. meta files (if there are meta files)
- var downloadLinkHTML = HDABaseView.templates.downloadLinks(
- _.extend( this.model.toJSON(), { urls: this.urls } )
- );
- //this.log( this + '_render_downloadButton, downloadLinkHTML:', downloadLinkHTML );
- return $( downloadLinkHTML.trim() );
+//TODO: Popupmenu
+ var menuId = 'dataset-' + this.model.get( 'id' ) + '-popup',
+ html = [
+ '<div popupmenu="' + menuId + '">',
+ '<a class="action-button" href="' + urls.download + '">', _l( 'Download Dataset' ), '</a>',
+ '<a>' + _l( 'Additional Files' ) + '</a>',
+
+ _.map( meta_files, function( meta_file ){
+ return [
+ '<a class="action-button" href="', urls.meta_download + meta_file.file_type, '">',
+ _l( 'Download' ), ' ', meta_file.file_type,
+ '</a>'
+ ].join( '' );
+ }).join( '\n' ),
+
+ '</div>',
+ '<div style="float:left;" class="menubutton split popup" id="' + menuId + '">',
+ '<a href="' + urls.download + '" title="' + _l( 'Download' ) + '" class="icon-button disk"></a>',
+ '</div>'
+ ].join( '\n' );
+ return $( html );
},
/** Render icon-button to show the input and output (stdout/err) for the job that created this hda.
@@ -263,62 +259,12 @@
*/
_render_showParamsButton : function(){
// gen. safe to show in all cases
- this.showParamsButton = new IconButtonView({ model : new IconButton({
+ return new IconButtonView({ model : new IconButton({
title : _l( 'View details' ),
href : this.urls.show_params,
target : 'galaxy_main',
icon_class : 'information'
- }) });
- return this.showParamsButton.render().$el;
- },
-
- // ......................................................................... other elements
- /** Render the area for display application links.
- * @returns {jQuery} rendered DOM
- */
- _render_displayAppArea : function(){
- return $( '<div/>' ).addClass( 'display-apps' );
- },
-
- /** Render links to external genome display applications (igb, gbrowse, etc.).
- * @param {jQuery} $parent the jq node to search for .display-apps and render into to (defaults to this.$el)
- */
-//TODO: move into visualization button
- _render_displayApps : function( $parent ){
- $parent = $parent || this.$el;
- var $displayAppsDiv = $parent.find( 'div.display-apps' ),
- display_types = this.model.get( 'display_types' ),
- display_apps = this.model.get( 'display_apps' );
-
- if( ( !this.model.hasData() )
- || ( !$parent || !$parent.length )
- || ( !$displayAppsDiv.length ) ){
- return;
- }
-
- $displayAppsDiv.html( null );
- if( !_.isEmpty( display_types ) ){
- //this.log( this + 'display_types:', this.model.get( 'urls' ).display_types );
- $displayAppsDiv.append( HDABaseView.templates.displayApps({ displayApps : display_types }) );
- }
- if( !_.isEmpty( display_apps ) ){
- //this.log( this + 'display_apps:', this.model.get( 'urls' ).display_apps );
- $displayAppsDiv.append( HDABaseView.templates.displayApps({ displayApps : display_apps }) );
- }
- },
-
- /** Render the data peek.
- * @returns {jQuery} rendered DOM
- */
- _render_peek : function(){
- var peek = this.model.get( 'peek' );
- if( !peek ){ return null; }
- return $( '<div/>' ).append(
- $( '<pre/>' )
- .attr( 'id', 'peek' + this.model.get( 'id' ) )
- .addClass( 'peek' )
- .append( peek )
- );
+ }) }).render().$el;
},
// ......................................................................... state body renderers
@@ -326,169 +272,142 @@
* @returns {jQuery} rendered DOM
*/
_render_body : function(){
- var $body = $( '<div/>' )
- .attr( 'id', 'info-' + this.model.get( 'id' ) )
- .addClass( 'historyItemBody' )
- .attr( 'style', 'display: none' );
-
+ var $body = $( '<div>Error: unknown dataset state "' + this.model.get( 'state' ) + '".</div>' ),
+ // cheesy: get function by assumed matching name
+ renderFn = this[ '_render_body_' + this.model.get( 'state' ) ];
+ if( _.isFunction( renderFn ) ){
+ $body = renderFn.call( this );
+ }
+ // only render the body html if it's being shown
if( this.expanded ){
- // only render the body html if it's being shown
- this._render_body_html( $body );
$body.show();
}
return $body;
},
- /** Render the (expanded) body of an HDA, dispatching to other functions based on the HDA state
- * @param {jQuery} body the body element to append the html to
- */
- _render_body_html : function( $body ){
- //this.log( this + '_render_body' );
- $body.empty();
-
- var modelState = this.model.get( 'state' );
- // cheesy get function by assumed matching name
- var renderFnName = '_render_body_' + modelState,
- renderFn = this[ renderFnName ];
- if( _.isFunction( renderFn ) ){
- this[ renderFnName ]( $body );
- } else {
- $body.append( $( '<div>Error: unknown dataset state "' + this.model.get( 'state' ) + '".</div>' ) );
- }
- $body.append( '<div style="clear: both"></div>' );
- this._setUpBehaviors( $body );
+ /** helper for rendering the body in the common cases */
+ _render_stateBodyHelper : function( body, primaryButtonArray ){
+ primaryButtonArray = primaryButtonArray || [];
+ var view = this,
+ $body = $( HDABaseView.templates.body( _.extend( this.model.toJSON(), { body: body })));
+ $body.find( '.dataset-actions .left' ).append(
+ _.map( primaryButtonArray, function( renderingFn ){
+ return renderingFn.call( view );
+ })
+ );
+ return $body;
},
/** Render a new dataset - this should be a transient state that's never shown
* in case it does tho, we'll make sure there's some information here
* @param {jQuery} parent DOM to which to append this body
*/
- _render_body_new : function( parent ){
- var newMsg = _l( 'This is a new dataset and not all of its data are available yet' );
- parent.append( $( '<div>' + _l( newMsg ) + '</div>' ) );
+ _render_body_new : function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'This is a new dataset and not all of its data are available yet' ) + '</div>'
+ );
+ },
+ /** Render inaccessible, not-owned by curr user. */
+ _render_body_noPermission : function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'You do not have permission to view this dataset' ) + '</div>'
+ );
+ },
+ /** Render an HDA which was deleted during upload. */
+ _render_body_discarded : function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'The job creating this dataset was cancelled before completion' ) + '</div>',
+ this.defaultPrimaryActionButtonRenderers
+ );
+ },
+ /** Render an HDA whose job is queued. */
+ _render_body_queued : function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'This job is waiting to run' ) + '</div>',
+ this.defaultPrimaryActionButtonRenderers
+ );
+ },
+ /** Render an HDA still being uploaded. */
+ _render_body_upload : function(){
+ return this._render_stateBodyHelper( '<div>' + _l( 'This dataset is currently uploading' ) + '</div>' );
+ },
+ /** Render an HDA where the metadata is still being determined. */
+ _render_body_setting_metadata : function(){
+ return this._render_stateBodyHelper( '<div>' + _l( 'Metadata is being auto-detected' ) + '</div>' );
+ },
+ /** Render an HDA whose job is running. */
+ _render_body_running : function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'This job is currently running' ) + '</div>',
+ this.defaultPrimaryActionButtonRenderers
+ );
+ },
+ /** Render an HDA whose job is paused. */
+ _render_body_paused: function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'This job is paused. Use the "Resume Paused Jobs" in the history menu to resume' ) + '</div>',
+ this.defaultPrimaryActionButtonRenderers
+ );
},
- /** Render inaccessible, not-owned by curr user.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_noPermission : function( parent ){
- parent.append( $( '<div>' + _l( 'You do not have permission to view this dataset' ) + '</div>' ) );
- },
-
- /** Render an HDA still being uploaded.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_upload : function( parent ){
- parent.append( $( '<div>' + _l( 'Dataset is uploading' ) + '</div>' ) );
+ /** Render an HDA whose job has failed. */
+ _render_body_error : function(){
+ var html = _l( 'An error occurred with this dataset' )
+ + ': <i>' + $.trim( this.model.get( 'misc_info' ) ) + '</i>';
+ if( !this.model.get( 'purged' ) ){
+ html = '<div>' + this.model.get( 'misc_blurb' ) + '</div>' + html;
+ }
+ return this._render_stateBodyHelper( html,
+ this.defaultPrimaryActionButtonRenderers.concat([ this._render_downloadButton ])
+ );
},
- /** Render an HDA whose job is queued.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_queued : function( parent ){
- parent.append( $( '<div>' + _l( 'Job is waiting to run' ) + '</div>' ) );
- parent.append( this._render_primaryActionButtons( this.defaultPrimaryActionButtonRenderers ));
- },
-
- /** Render an HDA whose job is paused.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_paused: function( parent ){
- parent.append( $( '<div>' + _l( 'Job is paused. '
- + 'Use the "Resume Paused Jobs" in the history menu to resume' ) + '</div>' ) );
- parent.append( this._render_primaryActionButtons( this.defaultPrimaryActionButtonRenderers ));
+ /** Render an empty/no data HDA. */
+ _render_body_empty : function(){
+ return this._render_stateBodyHelper(
+ '<div>' + _l( 'No data' ) + ': <i>' + this.model.get( 'misc_blurb' ) + '</i></div>',
+ this.defaultPrimaryActionButtonRenderers
+ );
},
- /** Render an HDA whose job is running.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_running : function( parent ){
- parent.append( '<div>' + _l( 'Job is currently running' ) + '</div>' );
- parent.append( this._render_primaryActionButtons( this.defaultPrimaryActionButtonRenderers ));
- },
-
- /** Render an HDA whose job has failed.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_error : function( parent ){
- if( !this.model.get( 'purged' ) ){
- parent.append( $( '<div>' + this.model.get( 'misc_blurb' ) + '</div>' ) );
- }
- parent.append( ( _l( 'An error occurred with this dataset' ) + ': '
- + '<i>' + $.trim( this.model.get( 'misc_info' ) ) + '</i>' ) );
- parent.append( this._render_primaryActionButtons(
- this.defaultPrimaryActionButtonRenderers.concat([ this._render_downloadButton ])
- ));
- },
-
- /** Render an HDA which was deleted during upload.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_discarded : function( parent ){
- parent.append( '<div>' + _l( 'The job creating this dataset was cancelled before completion' ) + '.</div>' );
- parent.append( this._render_primaryActionButtons( this.defaultPrimaryActionButtonRenderers ));
- },
-
- /** Render an HDA where the metadata is still being determined.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_setting_metadata : function( parent ){
- parent.append( $( '<div>' + _l( 'Metadata is being auto-detected' ) + '.</div>' ) );
- },
-
- /** Render an empty/no data HDA.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_empty : function( parent ){
- parent.append( $( '<div>' + _l( 'No data' ) + ': <i>' + this.model.get( 'misc_blurb' ) + '</i></div>' ) );
- parent.append( this._render_primaryActionButtons( this.defaultPrimaryActionButtonRenderers ));
- },
-
- /** Render an HDA where the metadata wasn't produced correctly.
- * @param {jQuery} parent DOM to which to append this body
- */
- _render_body_failed_metadata : function( parent ){
- // add a message box about the failure at the top of the body...
- parent.append( $( HDABaseView.templates.failedMetadata(
- _.extend( this.model.toJSON(), { urls: this.urls } )
- )));
- //...then render the remaining body as STATES.OK (only diff between these states is the box above)
- this._render_body_ok( parent );
+ /** Render an HDA where the metadata wasn't produced correctly. */
+ _render_body_failed_metadata : function(){
+ // add a message box about the failure at the top of the body then render the remaining body as STATES.OK
+ var $warning = $( '<div class="warningmessagesmall"></div>' )
+ .append( $( '<strong/>' ).text( _l( 'An error occurred setting the metadata for this dataset' ) ) ),
+ $body = this._render_body_ok();
+ $body.prepend( $warning );
+ return $body;
},
/** Render an HDA that's done running and where everything worked.
* @param {jQuery} parent DOM to which to append this body
*/
- _render_body_ok : function( parent ){
+ _render_body_ok : function(){
// most common state renderer and the most complicated
- parent.append( this._render_hdaSummary() );
+ var view = this,
+ $body = $( HDABaseView.templates.body( this.model.toJSON() ) ),
+ // prepend the download btn to the defaults and render
+ btnRenderers = [ this._render_downloadButton ].concat( this.defaultPrimaryActionButtonRenderers );
+ $body.find( '.dataset-actions .left' ).append(
+ _.map( btnRenderers, function( renderingFn ){
+ return renderingFn.call( view );
+ }));
- // return shortened form if del'd
+ // return shortened form if del'd (no display apps or peek?)
if( this.model.isDeletedOrPurged() ){
- parent.append( this._render_primaryActionButtons([
- this._render_downloadButton,
- this._render_showParamsButton
- ]));
- return;
+ return $body;
}
-
- //NOTE: change the order here
- parent.append( this._render_primaryActionButtons([
- this._render_downloadButton,
- this._render_showParamsButton
- ]));
- parent.append( '<div class="clear"/>' );
-
- parent.append( this._render_displayAppArea() );
- this._render_displayApps( parent );
- parent.append( this._render_peek() );
+
+ //this._render_displayApps( $body.children( '.dataset-display-applications' ) );
+ return $body;
},
// ......................................................................... events
/** event map */
events : {
// expand the body when the title is clicked
- 'click .historyItemTitle' : 'toggleBodyVisibility'
+ 'click .dataset-title-bar' : 'toggleBodyVisibility'
},
/** Show or hide the body/details of an HDA.
@@ -499,7 +418,8 @@
* @fires body-collapsed when a body has been collapsed
*/
toggleBodyVisibility : function( event, expand ){
- expand = ( expand === undefined )?( !this.body.is( ':visible' ) ):( expand );
+ var $body = this.$el.find( '.dataset-body' );
+ expand = ( expand === undefined )?( !$body.is( ':visible' ) ):( expand );
if( expand ){
this.expandBody();
} else {
@@ -514,15 +434,14 @@
var hdaView = this;
function _renderBodyAndExpand(){
- hdaView._render_body_html( hdaView.body );
- hdaView.body.slideDown( hdaView.fxSpeed, function(){
+ hdaView.render( false ).$el.children( '.dataset-body' ).slideDown( hdaView.fxSpeed, function(){
hdaView.expanded = true;
hdaView.trigger( 'body-expanded', hdaView.model.get( 'id' ) );
});
}
// fetch first if no details in the model
if( this.model.inReadyState() && !this.model.hasDetails() ){
- this.model.fetch().done( function( model ){
+ this.model.fetch({ silent: true }).always( function( model ){
_renderBodyAndExpand();
});
} else {
@@ -535,7 +454,7 @@
*/
collapseBody : function(){
var hdaView = this;
- this.body.slideUp( hdaView.fxSpeed, function(){
+ this.$el.children( '.dataset-body' ).slideUp( hdaView.fxSpeed, function(){
hdaView.expanded = false;
hdaView.trigger( 'body-collapsed', hdaView.model.get( 'id' ) );
});
@@ -564,14 +483,8 @@
//------------------------------------------------------------------------------ TEMPLATES
HDABaseView.templates = {
- warningMsg : Handlebars.templates[ 'template-warningmessagesmall' ],
-
- messages : Handlebars.templates[ 'template-hda-warning-messages' ],
- titleLink : Handlebars.templates[ 'template-hda-titleLink' ],
- hdaSummary : Handlebars.templates[ 'template-hda-hdaSummary' ],
- downloadLinks : Handlebars.templates[ 'template-hda-downloadLinks' ],
- failedMetadata : Handlebars.templates[ 'template-hda-failedMetadata' ],
- displayApps : Handlebars.templates[ 'template-hda-displayApps' ]
+ skeleton : Handlebars.templates[ 'template-hda-skeleton' ],
+ body : Handlebars.templates[ 'template-hda-body' ]
};
//==============================================================================
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/mvc/dataset/hda-edit.js
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -34,28 +34,6 @@
];
},
- /** Set up js behaviors, event handlers for elements within the given container.
- * Overridden from hda-base.
- * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)
- */
- _setUpBehaviors : function( $container ){
- hdaBase.HDABaseView.prototype._setUpBehaviors.call( this, $container );
- //var hdaView = this;
- },
-
- // ......................................................................... render warnings
- /** Render any hda warnings including: is deleted, is purged, is hidden.
- * Overrides _render_warnings to include links to further actions (undelete, etc.)).
- * @returns {Object} the templated urls
- * @see HDABaseView#_render_warnings
- */
- _render_warnings : function(){
- // jQ errs on building dom with whitespace - if there are no messages, trim -> ''
- return $( jQuery.trim( hdaBase.HDABaseView.templates.messages(
- _.extend( this.model.toJSON(), { urls: this.urls } )
- )));
- },
-
// ......................................................................... edit attr, delete
/** Render icon-button group for the common, most easily accessed actions.
* Overrides _render_titleButtons to include edit and delete buttons.
@@ -64,11 +42,10 @@
*/
_render_titleButtons : function(){
// render the display, edit attr and delete icon-buttons
- var buttonDiv = $( '<div class="historyItemButtons"></div>' );
- buttonDiv.append( this._render_displayButton() );
- buttonDiv.append( this._render_editButton() );
- buttonDiv.append( this._render_deleteButton() );
- return buttonDiv;
+ return hdaBase.HDABaseView.prototype._render_titleButtons.call( this ).concat([
+ this._render_editButton(),
+ this._render_deleteButton()
+ ]);
},
//TODO: move titleButtons into state renderers, remove state checks in the buttons
@@ -81,6 +58,7 @@
// DO show if in error (ala previous history panel)
if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW )
|| ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.UPLOAD )
+ || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.DISCARDED )
|| ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
|| ( !this.model.get( 'accessible' ) ) ){
this.editButton = null;
@@ -106,8 +84,11 @@
}
}
- this.editButton = new IconButtonView({ model : new IconButton( editBtnData ) });
- return this.editButton.render().$el;
+ //this.editButton = new IconButtonView({ model : new IconButton( editBtnData ) });
+ //return this.editButton.render().$el;
+ return new IconButtonView({ model : new IconButton( editBtnData ) }).render().$el;
+ //editBtnData.faIcon = 'fa-icon-pencil';
+ //return this._render_iconButton( editBtnData );
},
/** Render icon-button to delete this hda.
@@ -123,12 +104,10 @@
}
var self = this,
- id = 'historyItemDeleter-' + self.model.get( 'id' ),
delete_url = self.urls[ 'delete' ],
deleteBtnData = {
title : _l( 'Delete' ),
href : delete_url,
- id : id,
icon_class : 'delete',
on_click : function() {
// ...bler... tooltips being left behind in DOM (hover out never called on deletion)
@@ -143,24 +122,11 @@
enabled : false
};
}
- this.deleteButton = new IconButtonView({ model : new IconButton( deleteBtnData ) });
- return this.deleteButton.render().$el;
- },
-
- // ......................................................................... render body
- /** Render the data/metadata summary (format, size, misc info, etc.).
- * Overrides _render_hdaSummary to include edit link in dbkey.
- * @see HDABaseView#_render_hdaSummary
- * @returns {jQuery} rendered DOM
- */
- _render_hdaSummary : function(){
- var modelData = _.extend( this.model.toJSON(), { urls: this.urls } );
- // if there's no dbkey and it's editable : pass a flag to the template to render a link to editing in the '?'
- if( this.model.get( 'metadata_dbkey' ) === '?'
- && !this.model.isDeletedOrPurged() ){
- _.extend( modelData, { dbkey_unknown_and_editable : true });
- }
- return hdaBase.HDABaseView.templates.hdaSummary( modelData );
+ //this.deleteButton = new IconButtonView({ model : new IconButton( deleteBtnData ) });
+ //return this.deleteButton.render().$el;
+ return new IconButtonView({ model : new IconButton( deleteBtnData ) }).render().$el;
+ //deleteBtnData.faIcon = 'fa-icon-remove';
+ //return this._render_iconButton( deleteBtnData );
},
// ......................................................................... primary actions
@@ -173,26 +139,24 @@
return null;
}
- this.errButton = new IconButtonView({ model : new IconButton({
+ return new IconButtonView({ model : new IconButton({
title : _l( 'View or report this error' ),
href : this.urls.report_error,
target : 'galaxy_main',
icon_class : 'bug'
- })});
- return this.errButton.render().$el;
+ })}).render().$el;
},
/** Render icon-button to re-run the job that created this hda.
* @returns {jQuery} rendered DOM
*/
_render_rerunButton : function(){
- this.rerunButton = new IconButtonView({ model : new IconButton({
+ return new IconButtonView({ model : new IconButton({
title : _l( 'Run this job again' ),
href : this.urls.rerun,
target : 'galaxy_main',
icon_class : 'arrow-circle'
- }) });
- return this.rerunButton.render().$el;
+ }) }).render().$el;
},
/** Render an icon-button or popupmenu based on the number of applicable visualizations
@@ -309,24 +273,6 @@
},
// ......................................................................... secondary actions
- /** Render secondary actions: currently tagging and annotation (if user is allowed).
- * @param {Array} buttonRenderingFuncs array of rendering functions appending the results in order
- * @returns {jQuery} rendered DOM
- */
- _render_secondaryActionButtons : function( buttonRenderingFuncs ){
- // move to the right (same level as primary)
- 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 ) );
- });
- return secondaryActionButtons;
- },
-
/** Render icon-button to load and display tagging html.
* @returns {jQuery} rendered DOM
*/
@@ -336,13 +282,12 @@
return null;
}
- this.tagButton = new IconButtonView({ model : new IconButton({
+ return new IconButtonView({ model : new IconButton({
title : _l( 'Edit dataset tags' ),
target : 'galaxy_main',
href : this.urls.tags.get,
icon_class : 'tags'
- })});
- return this.tagButton.render().$el;
+ })}).render().$el;
},
/** Render icon-button to load and display annotation html.
@@ -354,74 +299,76 @@
return null;
}
- this.annotateButton = new IconButtonView({ model : new IconButton({
+ return new IconButtonView({ model : new IconButton({
title : _l( 'Edit dataset annotation' ),
target : 'galaxy_main',
icon_class : 'annotate'
- })});
- return this.annotateButton.render().$el;
+ })}).render().$el;
},
// ......................................................................... state body renderers
+ /** Render an HDA where the metadata wasn't produced correctly.
+ * Overridden to add a link to dataset/edit
+ * @see HDABaseView#_render_body_failed_metadata
+ */
+ _render_body_failed_metadata : function(){
+ // add a message box about the failure at the top of the body then render the remaining body as STATES.OK
+ var $link = $( '<a/>' ).attr({ href: this.urls.edit, target: 'galaxy_main' })
+ .text( _l( 'set it manually or retry auto-detection' ) ),
+ $span = $( '<span/>' ).text( _l( 'You may be able to' ) + ' ' ).append( $link ),
+ $body = hdaBase.HDABaseView.prototype._render_body_failed_metadata.call( this );
+ $body.find( '.warningmessagesmall strong' ).append( $span );
+ return $body;
+ },
+
/** Render an HDA whose job has failed.
* Overrides _render_body_error to prepend error report button to primary actions strip.
- * @param {jQuery} parent DOM to which to append this body
* @see HDABaseView#_render_body_error
*/
- _render_body_error : function( parent ){
- hdaBase.HDABaseView.prototype._render_body_error.call( this, parent );
- var primaryActions = parent.find( '#primary-actions-' + this.model.get( 'id' ) );
- primaryActions.prepend( this._render_errButton() );
+ _render_body_error : function(){
+ var $body = hdaBase.HDABaseView.prototype._render_body_error.call( this );
+ $body.find( '.dataset-actions .left' ).prepend( this._render_errButton() );
+ return $body;
},
-
+
/** Render an HDA that's done running and where everything worked.
* Overrides _render_body_ok to add tag/annotation functionality and additional primary actions
* @param {jQuery} parent DOM to which to append this body
* @see HDABaseView#_render_body_ok
*/
- _render_body_ok : function( parent ){
- // most common state renderer and the most complicated
- parent.append( this._render_hdaSummary() );
+ _render_body_ok : function(){
+ var $body = hdaBase.HDABaseView.prototype._render_body_ok.call( this );
+ // return shortened form if del'd
+ if( this.model.isDeletedOrPurged() ){
+ return $body;
+ }
+ this.makeDbkeyEditLink( $body );
- // return shortened form if del'd
- //TODO: is this correct? maybe only on purged
- if( this.model.isDeletedOrPurged() ){
- parent.append( this._render_primaryActionButtons([
- this._render_downloadButton,
- this._render_showParamsButton,
- this._render_rerunButton
- ]));
- return;
+ // more actions/buttons
+ $body.find( '.dataset-actions .left' ).append( this._render_visualizationsButton() );
+ $body.find( '.dataset-actions .right' ).append([
+ this._render_tagButton(),
+ this._render_annotateButton()
+ ]);
+ return $body;
+ },
+
+ makeDbkeyEditLink : function( $body ){
+ // make the dbkey a link to editing
+ if( this.model.get( 'metadata_dbkey' ) === '?'
+ && !this.model.isDeletedOrPurged() ){
+ $body.find( '.dataset-dbkey .value' ).replaceWith(
+ $( '<a target="galaxy_main">?</a>' ).attr( 'href', this.urls.edit ) );
}
-
- //NOTE: change the order here
- parent.append( this._render_primaryActionButtons([
- this._render_downloadButton,
- this._render_showParamsButton,
- this._render_rerunButton,
- this._render_visualizationsButton
- ]));
- parent.append( this._render_secondaryActionButtons([
- this._render_tagButton,
- this._render_annotateButton
- ]));
- parent.append( '<div class="clear"/>' );
-
- parent.append( this._render_tagArea() );
- parent.append( this._render_annotationArea() );
-
- parent.append( this._render_displayAppArea() );
- this._render_displayApps( parent );
- parent.append( this._render_peek() );
},
// ......................................................................... events
/** event map */
events : {
- 'click .historyItemTitle' : 'toggleBodyVisibility',
- 'click .historyItemUndelete' : function( ev ){ this.model.undelete(); return false; },
- 'click .historyItemUnhide' : function( ev ){ this.model.unhide(); return false; },
- 'click .historyItemPurge' : 'confirmPurge',
+ 'click .dataset-title-bar' : 'toggleBodyVisibility',
+ 'click .dataset-undelete' : function( ev ){ this.model.undelete(); return false; },
+ 'click .dataset-unhide' : function( ev ){ this.model.unhide(); return false; },
+ 'click .dataset-purge' : 'confirmPurge',
'click a.icon-button.tags' : 'loadAndDisplayTags',
'click a.icon-button.annotate' : 'loadAndDisplayAnnotation'
@@ -430,115 +377,87 @@
/** listener for item purge */
confirmPurge : function _confirmPurge( ev ){
//TODO: confirm dialog
- this.model.purge({ url: this.urls.purge });
+ this.model.purge();
return false;
},
// ......................................................................... tags
- /** Render area to display tags.
- * @returns {jQuery} rendered DOM
- */
-//TODO: into sub-MV
- _render_tagArea : function(){
- if( !this.hasUser || !this.urls.tags.set ){ return null; }
- return $( HDAEditView.templates.tagArea(
- _.extend( this.model.toJSON(), { urls: this.urls } )
- ).trim() );
- },
-
/** Find the tag area and, if initial: load the html (via ajax) for displaying them; otherwise, unhide/hide
*/
//TODO: into sub-MV
loadAndDisplayTags : function( event ){
- //BUG: broken with latest
- //TODO: this is a drop in from history.mako - should use MV as well
this.log( this + '.loadAndDisplayTags', event );
var view = this,
- tagArea = this.$el.find( '.tag-area' ),
- tagElt = tagArea.find( '.tag-elt' );
+ $tagArea = this.$el.find( '.tags-display' ),
+ $tagElt = $tagArea.find( '.tags' );
// Show or hide tag area; if showing tag area and it's empty, fill it.
- if( tagArea.is( ":hidden" ) ){
- if( !jQuery.trim( tagElt.html() ) ){
+ if( $tagArea.is( ":hidden" ) ){
+ if( !jQuery.trim( $tagElt.html() ) ){
// Need to fill tag element.
- $.ajax({
- //TODO: the html from this breaks a couple of times
- url: this.urls.tags.get,
- error: function( xhr, status, error ){
- view.log( "Tagging failed", xhr, status, error );
- view.trigger( 'error', view, xhr, {}, _l( "Tagging failed" ) );
- },
- success: function(tag_elt_html) {
- tagElt.html(tag_elt_html);
- tagElt.find("[title]").tooltip();
- tagArea.slideDown( view.fxSpeed );
- }
+ var xhr = $.ajax( this.urls.tags.get );
+ xhr.fail( function( xhr, status, error ){
+ view.log( "Tagging failed", xhr, status, error );
+ view.trigger( 'error', view, xhr, {}, _l( "Tagging failed" ) );
+ });
+ xhr.done( function( tagHtml ){
+ $tagElt.html( tagHtml );
+ $tagElt.find( "[title]" ).tooltip();
+ $tagArea.slideDown( view.fxSpeed );
});
} else {
// Tag element is filled; show.
- tagArea.slideDown( view.fxSpeed );
+ $tagArea.slideDown( view.fxSpeed );
}
} else {
// Hide.
- tagArea.slideUp( view.fxSpeed );
+ $tagArea.slideUp( view.fxSpeed );
}
return false;
},
// ......................................................................... annotations
- /** Render area to display annotation.
- * @returns {jQuery} rendered DOM
- */
-//TODO: into sub-MV
- _render_annotationArea : function(){
- if( !this.hasUser || !this.urls.annotation.get ){ return null; }
- return $( HDAEditView.templates.annotationArea(
- _.extend( this.model.toJSON(), { urls: this.urls } )
- ).trim() );
- },
-
/** Find the annotation area and, if initial: load the html (via ajax) for displaying them; otherwise, unhide/hide
*/
loadAndDisplayAnnotation : function( event ){
//TODO: this is a drop in from history.mako - should use MV as well
this.log( this + '.loadAndDisplayAnnotation', event );
var view = this,
- annotationArea = this.$el.find( '.annotation-area' ),
- annotationElem = annotationArea.find( '.annotation-elt' ),
+ $annotationArea = this.$el.find( '.annotation-display' ),
+ $annotationElem = $annotationArea.find( '.annotation' ),
setAnnotationUrl = this.urls.annotation.set;
// Show or hide annotation area; if showing annotation area and it's empty, fill it.
- if ( annotationArea.is( ":hidden" ) ){
- if( !jQuery.trim( annotationElem.html() ) ){
+ if ( $annotationArea.is( ":hidden" ) ){
+ if( !jQuery.trim( $annotationElem.html() ) ){
// Need to fill annotation element.
- $.ajax({
- url: this.urls.annotation.get,
- error: function(){
- view.log( "Annotation failed", xhr, status, error );
- view.trigger( 'error', view, xhr, {}, _l( "Annotation failed" ) );
- },
- success: function( htmlFromAjax ){
- if( htmlFromAjax === "" ){
- htmlFromAjax = "<em>" + _l( "Describe or add notes to dataset" ) + "</em>";
+ var xhr = $.ajax( this.urls.annotation.get );
+ xhr.fail( function( xhr, status, error ){
+ view.log( "Annotation failed", xhr, status, error );
+ view.trigger( 'error', view, xhr, {}, _l( "Annotation failed" ) );
+ });
+ xhr.done( function( html ){
+ html = html || "<em>" + _l( "Describe or add notes to dataset" ) + "</em>";
+ $annotationElem.html( html );
+ $annotationArea.find( "[title]" ).tooltip();
+
+ $annotationElem.make_text_editable({
+ use_textarea: true,
+ on_finish: function( newAnnotation ){
+ $annotationElem.text( newAnnotation );
+ view.model.save({ annotation: newAnnotation }, { silent: true })
+ .fail( function(){
+ $annotationElem.text( view.model.previous( 'annotation' ) );
+ });
}
- annotationElem.html( htmlFromAjax );
- annotationArea.find("[title]").tooltip();
-
- async_save_text(
- annotationElem.attr("id"), annotationElem.attr("id"),
- setAnnotationUrl,
- "new_annotation", 18, true, 4
- );
- annotationArea.slideDown( view.fxSpeed );
- }
+ });
+ $annotationArea.slideDown( view.fxSpeed );
});
} else {
- annotationArea.slideDown( view.fxSpeed );
+ $annotationArea.slideDown( view.fxSpeed );
}
-
} else {
- // Hide.
- annotationArea.slideUp( view.fxSpeed );
+ $annotationArea.slideUp( view.fxSpeed );
}
return false;
},
@@ -551,12 +470,6 @@
}
});
-//------------------------------------------------------------------------------
-HDAEditView.templates = {
- tagArea : Handlebars.templates[ 'template-hda-tagArea' ],
- annotationArea : Handlebars.templates[ 'template-hda-annotationArea' ]
-};
-
//==============================================================================
//TODO: these belong somewhere else
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/mvc/dataset/hda-model.js
--- a/static/scripts/mvc/dataset/hda-model.js
+++ b/static/scripts/mvc/dataset/hda-model.js
@@ -68,8 +68,9 @@
'purge' : galaxy_config.root + 'datasets/' + id + '/purge_async',
'display' : galaxy_config.root + 'datasets/' + id + '/display/?preview=True',
+ 'edit' : galaxy_config.root + 'datasets/' + id + '/edit',
+
'download' : galaxy_config.root + 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),
- 'edit' : galaxy_config.root + 'datasets/' + id + '/edit',
'report_error' : galaxy_config.root + 'dataset/errors?id=' + id,
'rerun' : galaxy_config.root + 'tool_runner/rerun?id=' + id,
'show_params' : galaxy_config.root + 'datasets/' + id + '/show_params',
@@ -80,19 +81,9 @@
'tags' : { 'get': galaxy_config.root + 'tag/get_tagging_elt_async?item_id='
+ id + '&item_class=HistoryDatasetAssociation',
'set': galaxy_config.root + 'tag/retag?item_id='
- + id + '&item_class=HistoryDatasetAssociation' }
+ + id + '&item_class=HistoryDatasetAssociation' },
+ 'meta_download' : galaxy_config.root + 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='
};
- // download links to assoc. metadata files (bam indeces, etc.)
- var meta_files = this.get( 'meta_files' );
- if( meta_files ){
- urls.meta_download = _.map( meta_files, function( meta_file ){
- return {
- url : galaxy_config.root + 'dataset/get_metadata_file?hda_id='
- + id + '&metadata_name=' + meta_file.file_type,
- file_type : meta_file.file_type
- };
- });
- }
return urls;
},
@@ -173,6 +164,7 @@
},
// ........................................................................ ajax
+
/** save this HDA, _Mark_ing it as deleted (just a flag) */
'delete' : function _delete( options ){
return this.save( { deleted: true }, options );
@@ -192,7 +184,11 @@
},
/** purge this HDA and remove the underlying dataset file from the server's fs */
+//TODO: use, override model.destroy, HDA.delete({ purge: true })
purge : function _purge( options ){
+ options = options || {};
+ options.url = galaxy_config.root + 'datasets/' + this.get( 'id' ) + '/purge_async';
+
//TODO: ideally this would be a DELETE call to the api
// using purge async for now
var hda = this,
@@ -337,11 +333,11 @@
initialize : function( models, options ){
options = options || {};
this.historyId = options.historyId;
- this._setUpListeners();
+ //this._setUpListeners();
},
- _setUpListeners : function(){
- },
+ //_setUpListeners : function(){
+ //},
// ........................................................................ common queries
/** Get the ids of every hda in this collection
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/mvc/history/history-model.js
--- a/static/scripts/mvc/history/history-model.js
+++ b/static/scripts/mvc/history/history-model.js
@@ -99,13 +99,6 @@
this.hdas.historyId = newId;
}
}, this );
-
- // debugging events
- //if( this.logger ){
- // this.on( 'all', function( event ){
- // this.log( this + '', arguments );
- // }, this );
- //}
},
//TODO: see base-mvc
@@ -220,7 +213,6 @@
function getHistory( id ){
// get the history data
- //return jQuery.ajax( '/generate_json_error' );
return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyId );
}
function countHdasFromHistory( historyData ){
@@ -240,7 +232,6 @@
}
var data = ( hdaDetailIds.length )?( { details : hdaDetailIds.join( ',' ) } ):( {} );
return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyData.id + '/contents', { data: data });
- //return jQuery.ajax( '/generate_json_error' );
}
// getting these concurrently is 400% slower (sqlite, local, vanilla) - so:
@@ -258,7 +249,6 @@
});
historyXHR.fail( function( xhr, status, message ){
// call reject on the outer deferred to allow it's fail callback to run
- //console.warn( 'getHistoryData.localFailHandler (history)', xhr, status, message );
df.reject( xhr, 'loading the history' );
});
@@ -270,7 +260,6 @@
});
hdaXHR.fail( function( xhr, status, message ){
// call reject on the outer deferred to allow it's fail callback to run
- //console.warn( 'getHistoryData.localFailHandler (hdas)', xhr, status, message );
df.reject( xhr, 'loading the datasets', { history: historyJSON } );
});
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/mvc/history/history-panel.js
--- a/static/scripts/mvc/history/history-panel.js
+++ b/static/scripts/mvc/history/history-panel.js
@@ -14,10 +14,7 @@
use model.save instead of urls
meta:
- css/html class/id 'item' -> hda
- add classes, ids on empty divs
convert function comments to jsDoc style, complete comments
- move inline styles into base.less
feature creep:
lineage
@@ -46,23 +43,28 @@
//logger : console,
/** which class to use for constructing the HDA views */
- //HDAView : HDABaseView,
+ //HDAView : hdaBase.HDABaseView,
HDAView : hdaEdit.HDAEditView,
tagName : 'div',
className : 'history-panel',
/** (in ms) that jquery effects will use */
- fxSpeed : 300,
+ //fxSpeed : 'fast',
+ fxSpeed : 400,
/** event map */
events : {
- 'click #history-tag' : 'loadAndDisplayTags',
+ 'click .icon-button.tags' : 'loadAndDisplayTags',
+//TODO: switch to common close (X) idiom
// allow (error) messages to be clicked away
-//TODO: switch to common close (X) idiom
- 'click #message-container' : 'clearMessages'
+ 'click .message-container' : 'clearMessages'
},
+ datasetsSelector : '.datasets-list',
+ emptyMsgSelector : '.empty-history-message',
+ msgsSelector : '.message-container',
+
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HDACollection events
* @param {Object} attributes
@@ -85,8 +87,6 @@
// ---- set up instance vars
/** map of hda model ids to hda views */
this.hdaViews = {};
- /** map web controller urls for history related actions */
- this.urls = {};
/** loading indicator */
this.indicator = new LoadingIndicator( this.$el );
@@ -114,7 +114,7 @@
this.on( 'loading-history', function(){
// show the loading indicator when loading a new history starts...
- this.showLoadingIndicator();
+ this.showLoadingIndicator( 'loading history...' );
});
this.on( 'loading-done', function(){
// ...hiding it again when loading is done (or there's been an error)
@@ -173,7 +173,7 @@
// otherwise, show an error message inside the panel
} else {
// it's possible to have a triggered error before the message container is rendered - wait for it to show
- if( !this.$el.find( '#message-container' ).is( ':visible' ) ){
+ if( !this.$el.find( this.msgsSelector ).is( ':visible' ) ){
this.once( 'rendered', function(){
this.displayMessage( 'error', parsed.message, parsed.details );
});
@@ -269,6 +269,7 @@
var panel = this,
// will be called to get hda ids that need details from the api
hdaDetailIds = function( historyData ){
+//TODO: non-visible HDAs are getting details loaded... either stop loading them at all or filter ids thru isVisible
return panel.getExpandedHdaIds( historyData.id );
};
return this.loadHistory( historyId, attributes, historyFn, hdaFn, hdaDetailIds );
@@ -285,7 +286,6 @@
hdaFn : hdaFn,
hdaDetailIds : attributes.initiallyExpanded || hdaDetailIds
});
- //console.debug( 'xhr:', xhr );
return this._loadHistoryFromXHR( xhr, attributes )
.fail( function( xhr, where, history ){
// throw an error up for the error handler
@@ -410,7 +410,7 @@
/** clear all stored history panel data */
clearWebStorage : function(){
for( var key in sessionStorage ){
- if( key.indexOf( 'HistoryView.' ) === 0 ){
+ if( key.indexOf( 'history:' ) === 0 ){
sessionStorage.removeItem( key );
}
}
@@ -494,7 +494,7 @@
// create and prepend to current el, if it was empty fadeout the emptyMsg first
$({}).queue([
function fadeOutEmptyMsg( next ){
- var $emptyMsg = panel.$el.find( '#emptyHistoryMessage' );
+ var $emptyMsg = panel.$el.find( panel.emptyMsgSelector );
if( $emptyMsg.is( ':visible' ) ){
$emptyMsg.fadeOut( panel.fxSpeed, next );
} else {
@@ -503,7 +503,7 @@
},
function createAndPrepend( next ){
panel.scrollToTop();
- var $whereTo = panel.$el.find( '#' + panel.model.get( 'id' ) + '-datasets' );
+ var $whereTo = panel.$el.find( panel.datasetsSelector );
panel.createHdaView( hda ).$el.hide().prependTo( $whereTo ).slideDown( panel.fxSpeed );
}
]);
@@ -575,7 +575,9 @@
hdaView.remove();
delete panel.hdaViews[ hdaView.model.id ];
if( _.isEmpty( panel.hdaViews ) ){
- panel.$el.find( '#emptyHistoryMessage' ).fadeIn( panel.fxSpeed );
+ panel.$el.find( panel.emptyMsgSelector ).fadeIn( panel.fxSpeed, function(){
+ panel.trigger( 'empty-history', panel );
+ });
}
});
},
@@ -627,15 +629,18 @@
var $newRender = $( '<div/>' );
// render the main template, tooltips
//NOTE: this is done before the items, since item views should handle theirs themselves
- $newRender.append( HistoryPanel.templates.historyPanel( this.model.toJSON() ) );
+ var templateFn = ( !Galaxy.currUser.isAnonymous() )?( HistoryPanel.templates.historyPanel )
+ :( HistoryPanel.templates.anonHistoryPanel );
+ $newRender.append( templateFn( this.model.toJSON() ) );
+
$newRender.find( '[title]' ).tooltip({ placement: 'bottom' });
// render hda views (if any and any shown (show_deleted/hidden)
//TODO: this seems too elaborate
if( !this.model.hdas.length
- || !this.renderItems( $newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){
+ || !this.renderItems( $newRender.find( this.datasetsSelector ) ) ){
// if history is empty or no hdas would be rendered, show the empty message
- $newRender.find( '#emptyHistoryMessage' ).show();
+ $newRender.find( this.emptyMsgSelector ).show();
}
return $newRender;
},
@@ -644,7 +649,7 @@
renderWithoutModel : function( ){
// we'll always need the message container
var $newRender = $( '<div/>' ),
- $msgContainer = $( '<div/>' ).attr( 'id', 'message-container' )
+ $msgContainer = $( '<div/>' ).addClass( 'message-container' )
.css({ 'margin-left': '4px', 'margin-right': '4px' });
return $newRender.append( $msgContainer );
},
@@ -676,34 +681,48 @@
//TODO: these should be either sub-MVs, or handled by events
_setUpBehaviours : function(){
// anon users shouldn't have access to any of these
- if( !this.model || !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; }
+ if( !this.model || !Galaxy.currUser || Galaxy.currUser.isAnonymous() ){ return; }
// annotation slide down
var panel = this,
- historyAnnotationArea = this.$( '#history-annotation-area' );
- this.$( '#history-annotate' ).click( function() {
- if ( historyAnnotationArea.is( ":hidden" ) ) {
- historyAnnotationArea.slideDown( panel.fxSpeed );
+ // need specific selector ('annotation-display' is used in HDAs, too)
+ $historyAnnotationArea = this.$el.find( '.history-controls .annotation-display' );
+ this.$el.find( '.history-controls .icon-button.annotate' ).click( function() {
+ if( $historyAnnotationArea.is( ":hidden" ) ){
+ $historyAnnotationArea.slideDown( panel.fxSpeed );
} else {
- historyAnnotationArea.slideUp( panel.fxSpeed );
+ $historyAnnotationArea.slideUp( panel.fxSpeed );
}
return false;
});
- // title and annotation editable text
- //NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable
- async_save_text( "history-name-container", "history-name",
- this.model.renameUrl(), "new_name", 18 );
+ this.$el.find( '.history-name' ).make_text_editable({
+ on_finish: function( newName ){
+ panel.$el.find( '.history-name' ).text( newName );
+ panel.model.save({ name: newName })
+ .fail( function(){
+ panel.$el.find( '.history-name' ).text( panel.model.previous( 'name' ) );
+ });
+ }
+ });
- async_save_text( "history-annotation-container", "history-annotation",
- this.model.annotateUrl(), "new_annotation", 18, true, 4 );
+ this.$el.find( '.history-controls .annotation' ).make_text_editable({
+ use_textarea : true,
+ on_finish: function( newAnnotation ){
+ panel.$el.find( '.history-controls .annotation' ).text( newAnnotation );
+ panel.model.save({ annotation: newAnnotation })
+ .fail( function(){
+ panel.$el.find( '.history-controls .annotation' ).text( panel.model.previous( 'annotation' ) );
+ });
+ }
+ });
},
// ------------------------------------------------------------------------ panel events
/** Update the history size display (curr. upper right of panel).
*/
updateHistoryDiskSize : function(){
- this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) );
+ this.$el.find( '.history-size' ).text( this.model.get( 'nice_size' ) );
},
/** Collapse all hda bodies and clear expandedHdas in the storage
@@ -741,39 +760,33 @@
*/
//TODO: into sub-MV
loadAndDisplayTags : function( event ){
- this.log( this + '.loadAndDisplayTags', event );
var panel = this,
- tagArea = this.$el.find( '#history-tag-area' ),
- tagElt = tagArea.find( '.tag-elt' );
- this.log( '\t tagArea', tagArea, ' tagElt', tagElt );
+ $tagArea = this.$el.find( '.history-controls .tags-display' ),
+ $tagElt = $tagArea.find( '.tags' );
// Show or hide tag area; if showing tag area and it's empty, fill it
- if( tagArea.is( ":hidden" ) ){
- if( !jQuery.trim( tagElt.html() ) ){
+ if( $tagArea.is( ":hidden" ) ){
+ if( !jQuery.trim( $tagElt.html() ) ){
// Need to fill tag element.
- $.ajax({
- //TODO: the html from this breaks a couple of times
- url: panel.model.tagUrl(),
- //url: '/generate_json_error',
- error: function( xhr, error, status ) {
- panel.log( 'Error loading tag area html', xhr, error, status );
- panel.trigger( 'error', panel, xhr, null, _l( "Error loading tags" ) );
- },
- success: function(tag_elt_html) {
- //panel.log( panel + ' tag elt html (ajax)', tag_elt_html );
- tagElt.html(tag_elt_html);
- tagElt.find("[title]").tooltip();
- tagArea.slideDown( panel.fxSpeed );
- }
+ var xhr = jQuery.ajax( panel.model.tagUrl() );
+ xhr.fail( function( xhr, status, error ){
+ panel.log( 'Error loading tag area html', xhr, error, status );
+ panel.trigger( 'error', panel, xhr, null, _l( "Error loading tags" ) );
+ });
+ xhr.done( function( html ){
+ //panel.log( panel + ' tag elt html (ajax)', tag_elt_html );
+ $tagElt.html( html );
+ $tagElt.find( "[title]" ).tooltip();
+ $tagArea.slideDown( panel.fxSpeed );
});
} else {
// Tag element already filled: show
- tagArea.slideDown( panel.fxSpeed );
+ $tagArea.slideDown( panel.fxSpeed );
}
} else {
// Currently shown: Hide
- tagArea.slideUp( panel.fxSpeed );
+ $tagArea.slideUp( panel.fxSpeed );
}
return false;
},
@@ -789,7 +802,7 @@
this.indicator.show( 0, callback );
} else {
this.$el.fadeOut( speed );
- this.indicator.show( speed, callback );
+ this.indicator.show( msg, speed, callback );
}
},
@@ -812,7 +825,7 @@
//console.debug( 'displayMessage', type, msg, details );
this.scrollToTop();
- var $msgContainer = this.$el.find( '#message-container' ),
+ var $msgContainer = this.$el.find( this.msgsSelector ),
$msg = $( '<div/>' ).addClass( type + 'message' ).html( msg );
//console.debug( ' ', $msgContainer );
@@ -867,7 +880,7 @@
/** Remove all messages from the panel.
*/
clearMessages : function(){
- var $msgContainer = this.$el.find( '#message-container' );
+ var $msgContainer = this.$el.find( this.msgsSelector );
$msgContainer.empty();
},
@@ -961,7 +974,7 @@
/** Show the over quota message (which happens to be in the history panel).
*/
showQuotaMessage : function(){
- var msg = this.$el.find( '#quota-message-container' );
+ var msg = this.$el.find( '.quota-message' );
//this.log( this + ' showing quota message:', msg, userData );
if( msg.is( ':hidden' ) ){ msg.slideDown( this.fxSpeed ); }
},
@@ -970,7 +983,7 @@
/** Hide the over quota message (which happens to be in the history panel).
*/
hideQuotaMessage : function(){
- var msg = this.$el.find( '#quota-message-container' );
+ var msg = this.$el.find( '.quota-message' );
//this.log( this + ' hiding quota message:', msg, userData );
if( !msg.is( ':hidden' ) ){ msg.slideUp( this.fxSpeed ); }
},
@@ -1000,7 +1013,8 @@
//------------------------------------------------------------------------------ TEMPLATES
HistoryPanel.templates = {
- historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
+ historyPanel : Handlebars.templates[ 'template-history-historyPanel' ],
+ anonHistoryPanel : Handlebars.templates[ 'template-history-historyPanel-anon' ]
};
//==============================================================================
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 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){var l=i.action||i;g.append($("<li></li>").append($("<a>").attr("href",i.url).html(j).click(l)))}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()]={url:f,action:function(){if(!e||confirm(e)){if(h){window.open(f,h)}else{g.click()}}}}}});var c=$(a).find("#"+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}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]")};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((d<a)||(d>c)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.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).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){$(".unified-panel-header [title]").tooltip({placement:"bottom"});$("[title]").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){var l=i.action||i;g.append($("<li></li>").append($("<a>").attr("href",i.url).html(j).click(l)))}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()]={url:f,action:function(){if(!e||confirm(e)){if(h){window.open(f,h)}else{g.click()}}}}}});var c=$(a).find("#"+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}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]")};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((d<a)||(d>c)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.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=("cur_text" in g?g.cur_text: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).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){$(".unified-panel-header [title]").tooltip({placement:"bottom"});$("[title]").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 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/galaxy.upload.js
--- a/static/scripts/packed/galaxy.upload.js
+++ b/static/scripts/packed/galaxy.upload.js
@@ -1,1 +1,1 @@
-define(["galaxy.modal","galaxy.master","utils/galaxy.utils","utils/galaxy.uploadbox","libs/backbone/backbone-relational"],function(b,d,c){var a=Backbone.View.extend({modal:null,button_show:null,uploadbox:null,select_extension:[["Auto-detect","auto"]],select_genome:[["Unspecified (?)","?"]],state:{init:"fa-icon-trash",queued:"fa-icon-spinner fa-icon-spin",running:"__running__",success:"fa-icon-ok",error:"fa-icon-warning-sign"},counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(){if(!Galaxy.currHistoryPanel){var e=this;window.setTimeout(function(){e.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}var e=this;this.button_show=new d.GalaxyMasterIcon({icon:"fa-icon-upload",tooltip:"Upload Files",on_click:function(f){e.event_show(f)},on_unload:function(){if(e.counter.running>0){return"Several uploads are still processing."}},with_number:true});Galaxy.master.prepend(this.button_show);var e=this;c.jsonFromUrl(galaxy_config.root+"api/datatypes",function(f){for(key in f){e.select_extension.push([f[key],f[key]])}});c.jsonFromUrl(galaxy_config.root+"api/genomes",function(f){var g=e.select_genome[0];e.select_genome=[];for(key in f){if(f[key].length>1){if(f[key][1]!==g[1]){e.select_genome.push(f[key])}}}e.select_genome.sort(function(i,h){return i[0]>h[0]?1:i[0]<h[0]?-1:0});e.select_genome.unshift(g)})},event_dragover:function(f){},event_dragleave:function(f){},event_announce:function(i,g,n){var f="#upload-"+i;$(this.el).find("tbody:last").append(this.template_row(f));var h=this.get_upload_item(i);h.fadeIn();h.find("#title").html(g.name);h.find("#size").html(this.size_to_string(g.size));var m=this;h.find("#symbol").on("click",function(){m.event_remove(i)});h.find("#text-content").on("keyup",function(){var o=h.find("#text-content").val().length;h.find("#size").html(m.size_to_string(o))});this.event_progress(i,g,0);this.counter.announce++;this.update_screen();if(g.size==-1){var l=h.find("#text");var j=8;var e=h.width()-2*j;var k=h.height()-j;l.css("width",e+"px");l.css("top",k+"px");h.height(k+l.height()+2*j);l.show()}},event_initialize:function(i,e,n){this.button_show.number(this.counter.announce);var g=this.get_upload_item(i);var k=g.find("#symbol");k.addClass(this.state.running);var j=Galaxy.currHistoryPanel.model.get("id");var f=g.find("#extension").val();var m=g.find("#genome").val();var l=g.find("#text-content").val();var h=g.find("#space_to_tabs").is(":checked");if(!l&&!(e.size>0)){return null}this.uploadbox.configure({url:galaxy_config.root+"api/tools/",paramname:"files_0|file_data"});tool_input={};tool_input.dbkey=m;tool_input.file_type=f;tool_input["files_0|NAME"]=e.name;tool_input["files_0|type"]="upload_dataset";tool_input["files_0|url_paste"]=l;tool_input.space_to_tabs=h;data={};data.history_id=j;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},event_progress:function(f,g,i){var h=this.get_upload_item(f);var e=parseInt(i);h.find(".progress-bar").css({width:e+"%"});if(e!=100){h.find("#percentage").html(e+"%")}else{h.find("#percentage").html("Adding to history...")}},event_success:function(e,f,h){this.event_progress(e,f,100);this.button_show.number("");this.counter.announce--;this.counter.success++;this.update_screen();var g=this.get_upload_item(e);g.addClass("success");g.find("#percentage").html("100%");var i=g.find("#symbol");i.removeClass(this.state.running);i.removeClass(this.state.queued);i.addClass(this.state.success);Galaxy.currHistoryPanel.refreshHdas()},event_error:function(e,f,h){this.event_progress(e,f,0);this.button_show.number("");this.counter.announce--;this.counter.error++;this.update_screen();var g=this.get_upload_item(e);g.addClass("danger");g.find(".progress").remove();g.find("#info").html("<strong>Failed: </strong>"+h).show();var i=g.find("#symbol");i.removeClass(this.state.running);i.removeClass(this.state.queued);i.addClass(this.state.error)},event_start:function(){if(this.counter.announce==0||this.counter.running>0){return}var f=$(this.el).find(".upload-item");var e=this;f.each(function(){var g=$(this).find("#symbol");if(g.hasClass(e.state.init)){g.removeClass(e.state.init);g.addClass(e.state.queued);$(this).find("#text-content").attr("disabled",true);$(this).find("#genome").attr("disabled",true);$(this).find("#extension").attr("disabled",true);$(this).find("#space_to_tabs").attr("disabled",true)}});this.counter.running=this.counter.announce;this.update_screen();this.uploadbox.start()},event_stop:function(){if(this.counter.running==0){return}this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},event_complete:function(){this.counter.running=0;this.update_screen();var f=$(this.el).find(".upload-item");var e=this;f.each(function(){var g=$(this).find("#symbol");if(g.hasClass(e.state.queued)&&!g.hasClass(e.state.running)){g.removeClass(e.state.queued);g.addClass(e.state.init);$(this).find("#text-content").attr("disabled",false);$(this).find("#genome").attr("disabled",false);$(this).find("#extension").attr("disabled",false);$(this).find("#space_to_tabs").attr("disabled",false)}})},event_reset:function(){if(this.counter.running==0){var e=$(this.el).find(".upload-item");$(this.el).find("table").fadeOut({complete:function(){e.remove()}});this.counter.reset();this.update_screen();this.uploadbox.reset()}},event_remove:function(e){var f=this.get_upload_item(e);var g=f.find("#symbol");if(g.hasClass(this.state.init)||g.hasClass(this.state.success)||g.hasClass(this.state.error)){if(f.hasClass("success")){this.counter.success--}else{if(f.hasClass("danger")){this.counter.error--}else{this.counter.announce--}}this.update_screen();this.uploadbox.remove(e);f.remove()}},event_create:function(){this.uploadbox.add([{name:"New File",size:-1}])},event_show:function(g){g.preventDefault();if(!this.modal){var f=this;this.modal=new b.GalaxyModal({title:"Upload files from your local drive",body:this.template("upload-box","upload-info"),buttons:{Select:function(){f.uploadbox.select()},Create:function(){f.event_create()},Upload:function(){f.event_start()},Pause:function(){f.event_stop()},Reset:function(){f.event_reset()},Close:function(){f.modal.hide()},},height:"400",width:"900"});this.setElement("#upload-box");var f=this;this.uploadbox=this.$el.uploadbox({dragover:function(){f.event_dragover()},dragleave:function(){f.event_dragleave()},announce:function(e,h,i){f.event_announce(e,h,i)},initialize:function(e,h,i){return f.event_initialize(e,h,i)},success:function(e,h,i){f.event_success(e,h,i)},progress:function(e,h,i){f.event_progress(e,h,i)},error:function(e,h,i){f.event_error(e,h,i)},complete:function(){f.event_complete()},});this.update_screen()}this.modal.show()},get_upload_item:function(e){return $(this.el).find("#upload-"+e)},size_to_string:function(e){var f="";if(e>=100000000000){e=e/100000000000;f="TB"}else{if(e>=100000000){e=e/100000000;f="GB"}else{if(e>=100000){e=e/100000;f="MB"}else{if(e>=100){e=e/100;f="KB"}else{if(e>0){e=e*10;f="b"}else{return"<strong>-</strong>"}}}}}return"<strong>"+(Math.round(e)/10)+"</strong> "+f},update_screen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="Drag&drop files into this box or click 'Select' to select files!"}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Please upgrade to i.e. Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Upload' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Upload")}else{this.modal.disableButton("Upload")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Select");this.modal.enableButton("Create")}else{this.modal.disableButton("Select");this.modal.disableButton("Create")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("table").show()}else{$(this.el).find("table").hide()}},template:function(f,e){return'<div id="'+f+'" class="upload-box"><table class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Space→Tab</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+e+'" class="upload-info"></h6>'},template_row:function(f){var e='<tr id="'+f.substr(1)+'" class="upload-item"><td><div style="position: relative;"><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You may specify a list of URLs (one per line) or paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td>';e+='<td><select id="extension" class="extension">';for(key in this.select_extension){e+='<option value="'+this.select_extension[key][1]+'">'+this.select_extension[key][0]+"</option>"}e+="</select></td>";e+='<td><select id="genome" class="genome">';for(key in this.select_genome){e+='<option value="'+this.select_genome[key][1]+'">'+this.select_genome[key][0]+"</option>"}e+="</select></td>";e+='<td><input id="space_to_tabs" type="checkbox"></input></td><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="symbol '+this.state.init+'"></div></td></tr>';return e}});return{GalaxyUpload:a}});
\ No newline at end of file
+define(["galaxy.modal","galaxy.master","utils/galaxy.utils","utils/galaxy.uploadbox","libs/backbone/backbone-relational"],function(b,d,c){var a=Backbone.View.extend({modal:null,button_show:null,uploadbox:null,select_extension:[["Auto-detect","auto"]],select_genome:[["Unspecified (?)","?"]],state:{init:"fa-icon-trash",queued:"fa-icon-spinner fa-icon-spin",running:"__running__",success:"fa-icon-ok",error:"fa-icon-warning-sign"},counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(){if(!Galaxy.currHistoryPanel){var e=this;window.setTimeout(function(){e.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}var e=this;this.button_show=new d.GalaxyMasterIcon({icon:"fa-icon-upload",tooltip:"Upload Files",on_click:function(f){e.event_show(f)},on_unload:function(){if(e.counter.running>0){return"Several uploads are still processing."}},with_number:true});Galaxy.master.prepend(this.button_show);var e=this;c.jsonFromUrl(galaxy_config.root+"api/datatypes",function(f){for(key in f){e.select_extension.push([f[key],f[key]])}});c.jsonFromUrl(galaxy_config.root+"api/genomes",function(f){var g=e.select_genome[0];e.select_genome=[];for(key in f){if(f[key].length>1){if(f[key][1]!==g[1]){e.select_genome.push(f[key])}}}e.select_genome.sort(function(i,h){return i[0]>h[0]?1:i[0]<h[0]?-1:0});e.select_genome.unshift(g)})},event_dragover:function(f){},event_dragleave:function(f){},event_announce:function(i,g,n){var f="#upload-"+i;$(this.el).find("tbody:last").append(this.template_row(f));var h=this.get_upload_item(i);h.fadeIn();h.find("#title").html(g.name);h.find("#size").html(this.size_to_string(g.size));var m=this;h.find("#symbol").on("click",function(){m.event_remove(i)});h.find("#text-content").on("keyup",function(){var o=h.find("#text-content").val().length;h.find("#size").html(m.size_to_string(o))});this.event_progress(i,g,0);this.counter.announce++;this.update_screen();if(g.size==-1){var l=h.find("#text");var j=8;var e=h.width()-2*j;var k=h.height()-j;l.css("width",e+"px");l.css("top",k+"px");h.height(k+l.height()+2*j);l.show()}},event_initialize:function(i,e,n){this.button_show.number(this.counter.announce);var g=this.get_upload_item(i);var k=g.find("#symbol");k.addClass(this.state.running);var j=Galaxy.currHistoryPanel.model.get("id");var f=g.find("#extension").val();var m=g.find("#genome").val();var l=g.find("#text-content").val();var h=g.find("#space_to_tabs").is(":checked");if(!l&&!(e.size>0)){return null}this.uploadbox.configure({url:galaxy_config.root+"api/tools",paramname:"files_0|file_data"});tool_input={};tool_input.dbkey=m;tool_input.file_type=f;tool_input["files_0|NAME"]=e.name;tool_input["files_0|type"]="upload_dataset";tool_input["files_0|url_paste"]=l;tool_input.space_to_tabs=h;data={};data.history_id=j;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},event_progress:function(f,g,i){var h=this.get_upload_item(f);var e=parseInt(i);h.find(".progress-bar").css({width:e+"%"});if(e!=100){h.find("#percentage").html(e+"%")}else{h.find("#percentage").html("Adding to history...")}},event_success:function(e,f,h){this.event_progress(e,f,100);this.button_show.number("");this.counter.announce--;this.counter.success++;this.update_screen();var g=this.get_upload_item(e);g.addClass("success");g.find("#percentage").html("100%");var i=g.find("#symbol");i.removeClass(this.state.running);i.removeClass(this.state.queued);i.addClass(this.state.success);Galaxy.currHistoryPanel.refreshHdas()},event_error:function(e,f,h){this.event_progress(e,f,0);this.button_show.number("");this.counter.announce--;this.counter.error++;this.update_screen();var g=this.get_upload_item(e);g.addClass("danger");g.find(".progress").remove();g.find("#info").html("<strong>Failed: </strong>"+h).show();var i=g.find("#symbol");i.removeClass(this.state.running);i.removeClass(this.state.queued);i.addClass(this.state.error)},event_start:function(){if(this.counter.announce==0||this.counter.running>0){return}var f=$(this.el).find(".upload-item");var e=this;f.each(function(){var g=$(this).find("#symbol");if(g.hasClass(e.state.init)){g.removeClass(e.state.init);g.addClass(e.state.queued);$(this).find("#text-content").attr("disabled",true);$(this).find("#genome").attr("disabled",true);$(this).find("#extension").attr("disabled",true);$(this).find("#space_to_tabs").attr("disabled",true)}});this.counter.running=this.counter.announce;this.update_screen();this.uploadbox.start()},event_stop:function(){if(this.counter.running==0){return}this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},event_complete:function(){this.counter.running=0;this.update_screen();var f=$(this.el).find(".upload-item");var e=this;f.each(function(){var g=$(this).find("#symbol");if(g.hasClass(e.state.queued)&&!g.hasClass(e.state.running)){g.removeClass(e.state.queued);g.addClass(e.state.init);$(this).find("#text-content").attr("disabled",false);$(this).find("#genome").attr("disabled",false);$(this).find("#extension").attr("disabled",false);$(this).find("#space_to_tabs").attr("disabled",false)}})},event_reset:function(){if(this.counter.running==0){var e=$(this.el).find(".upload-item");$(this.el).find("table").fadeOut({complete:function(){e.remove()}});this.counter.reset();this.update_screen();this.uploadbox.reset()}},event_remove:function(e){var f=this.get_upload_item(e);var g=f.find("#symbol");if(g.hasClass(this.state.init)||g.hasClass(this.state.success)||g.hasClass(this.state.error)){if(f.hasClass("success")){this.counter.success--}else{if(f.hasClass("danger")){this.counter.error--}else{this.counter.announce--}}this.update_screen();this.uploadbox.remove(e);f.remove()}},event_create:function(){this.uploadbox.add([{name:"New File",size:-1}])},event_show:function(g){g.preventDefault();if(!this.modal){var f=this;this.modal=new b.GalaxyModal({title:"Upload files from your local drive",body:this.template("upload-box","upload-info"),buttons:{Select:function(){f.uploadbox.select()},Create:function(){f.event_create()},Upload:function(){f.event_start()},Pause:function(){f.event_stop()},Reset:function(){f.event_reset()},Close:function(){f.modal.hide()},},height:"400",width:"900"});this.setElement("#upload-box");var f=this;this.uploadbox=this.$el.uploadbox({dragover:function(){f.event_dragover()},dragleave:function(){f.event_dragleave()},announce:function(e,h,i){f.event_announce(e,h,i)},initialize:function(e,h,i){return f.event_initialize(e,h,i)},success:function(e,h,i){f.event_success(e,h,i)},progress:function(e,h,i){f.event_progress(e,h,i)},error:function(e,h,i){f.event_error(e,h,i)},complete:function(){f.event_complete()},});this.update_screen()}this.modal.show()},get_upload_item:function(e){return $(this.el).find("#upload-"+e)},size_to_string:function(e){var f="";if(e>=100000000000){e=e/100000000000;f="TB"}else{if(e>=100000000){e=e/100000000;f="GB"}else{if(e>=100000){e=e/100000;f="MB"}else{if(e>=100){e=e/100;f="KB"}else{if(e>0){e=e*10;f="b"}else{return"<strong>-</strong>"}}}}}return"<strong>"+(Math.round(e)/10)+"</strong> "+f},update_screen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="Drag&drop files into this box or click 'Select' to select files!"}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Please upgrade to i.e. Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Upload' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Upload")}else{this.modal.disableButton("Upload")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Select");this.modal.enableButton("Create")}else{this.modal.disableButton("Select");this.modal.disableButton("Create")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("table").show()}else{$(this.el).find("table").hide()}},template:function(f,e){return'<div id="'+f+'" class="upload-box"><table class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Space→Tab</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+e+'" class="upload-info"></h6>'},template_row:function(f){var e='<tr id="'+f.substr(1)+'" class="upload-item"><td><div style="position: relative;"><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You may specify a list of URLs (one per line) or paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td>';e+='<td><select id="extension" class="extension">';for(key in this.select_extension){e+='<option value="'+this.select_extension[key][1]+'">'+this.select_extension[key][0]+"</option>"}e+="</select></td>";e+='<td><select id="genome" class="genome">';for(key in this.select_genome){e+='<option value="'+this.select_genome[key][1]+'">'+this.select_genome[key][0]+"</option>"}e+="</select></td>";e+='<td><input id="space_to_tabs" type="checkbox"></input></td><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="symbol '+this.state.init+'"></div></td></tr>';return e}});return{GalaxyUpload:a}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/mvc/base-mvc.js
--- a/static/scripts/packed/mvc/base-mvc.js
+++ b/static/scripts/packed/mvc/base-mvc.js
@@ -1,1 +1,1 @@
-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 BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){if(this.model.attributes.hidden){this.$el.hide()}else{this.$el.show()}}});var LoggableMixin={logger:null,log:function(){if(this.logger){var a=this.logger.log;if(typeof this.logger.log==="object"){a=Function.prototype.bind.call(this.logger.log,this.logger)}return a.apply(this.logger,arguments)}return undefined}};var PersistentStorage=function(k,g){if(!k){throw ("PersistentStorage needs storageKey argument")}g=g||{};var i=sessionStorage,c=function j(m){var n=this.getItem(m);return(n!==null)?(JSON.parse(this.getItem(m))):(null)},b=function e(m,n){return this.setItem(m,JSON.stringify(n))},d=function f(m){return this.removeItem(m)};function a(n,m){n=n||{};m=m||null;return{get:function(o){if(o===undefined){return n}else{if(n.hasOwnProperty(o)){return(jQuery.type(n[o])==="object")?(new a(n[o],this)):(n[o])}}return undefined},set:function(o,p){n[o]=p;this._save();return this},deleteKey:function(o){delete n[o];this._save();return this},_save:function(){return m._save()},toString:function(){return("StorageRecursionHelper("+n+")")}}}var l={},h=c.call(i,k);if(h===null||h===undefined){h=jQuery.extend(true,{},g);b.call(i,k,h)}l=new a(h);jQuery.extend(l,{_save:function(m){return b.call(i,k,l.get())},destroy:function(){return d.call(i,k)},toString:function(){return"PersistentStorage("+k+")"}});return l};function LoadingIndicator(a){var c=this,e;function b(){var g=4,f=e.parent().width()||a.width(),h=e.parent().offset()||a.offset();e.outerWidth(f-(g*2));e.css({top:h.top+g+"px",left:h.left+g+"px"})}function d(){var g=$('<span class="fa-icon-spinner fa-icon-spin fa-icon-large"></span>').css({color:"grey","font-size":"16px"});var f=$("<i>loading...</i>").css({color:"grey","margin-left":"8px"});e=$("<div/>").addClass("loading-indicator").css({position:"fixed",padding:"4px","text-align":"center","background-color":"white",opacity:"0.85","border-radius":"3px"}).append(g,f).insertBefore(a);b();return e.hide()}c.show=function(f,g){f=f||"fast";b();e.fadeIn(f,g);return c};c.hide=function(f,g){f=f||"fast";e.fadeOut(f,g);return c};e=d();return c};
\ No newline at end of file
+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 BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){if(this.model.attributes.hidden){this.$el.hide()}else{this.$el.show()}}});var LoggableMixin={logger:null,log:function(){if(this.logger){var a=this.logger.log;if(typeof this.logger.log==="object"){a=Function.prototype.bind.call(this.logger.log,this.logger)}return a.apply(this.logger,arguments)}return undefined}};var PersistentStorage=function(k,g){if(!k){throw ("PersistentStorage needs storageKey argument")}g=g||{};var i=sessionStorage,c=function j(m){var n=this.getItem(m);return(n!==null)?(JSON.parse(this.getItem(m))):(null)},b=function e(m,n){return this.setItem(m,JSON.stringify(n))},d=function f(m){return this.removeItem(m)};function a(n,m){n=n||{};m=m||null;return{get:function(o){if(o===undefined){return n}else{if(n.hasOwnProperty(o)){return(jQuery.type(n[o])==="object")?(new a(n[o],this)):(n[o])}}return undefined},set:function(o,p){n[o]=p;this._save();return this},deleteKey:function(o){delete n[o];this._save();return this},_save:function(){return m._save()},toString:function(){return("StorageRecursionHelper("+n+")")}}}var l={},h=c.call(i,k);if(h===null||h===undefined){h=jQuery.extend(true,{},g);b.call(i,k,h)}l=new a(h);jQuery.extend(l,{_save:function(m){return b.call(i,k,l.get())},destroy:function(){return d.call(i,k)},toString:function(){return"PersistentStorage("+k+")"}});return l};function LoadingIndicator(a,c){c=c||{};var b=this;function d(){var e=['<div class="loading-indicator">','<span class="fa-icon-spinner fa-icon-spin fa-icon-large" style="color: grey"></span>','<span style="margin-left: 8px; color: grey"><i>loading...</i></span>',"</div>"].join("\n");return $(e).css(c.css||{position:"fixed",margin:"6px 0px 0px 10px",opacity:"0.85"}).hide()}b.show=function(f,e,g){f=f||"loading...";e=e||"fast";b.$indicator=d().insertBefore(a);b.message(f);b.$indicator.fadeIn(e,g);return b};b.message=function(e){b.$indicator.find("i").text(e)};b.hide=function(e,f){e=e||"fast";if(b.$indicator&&b.$indicator.size()){b.$indicator.fadeOut(e,function(){b.$indicator.remove();if(f){f()}})}else{if(f){f()}}return b};return b};
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/mvc/dataset/hda-base.js
--- a/static/scripts/packed/mvc/dataset/hda-base.js
+++ b/static/scripts/packed/mvc/dataset/hda-base.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model"],function(b){var a=Backbone.View.extend(LoggableMixin).extend({tagName:"div",className:"historyItemContainer",fxSpeed:"fast",initialize:function(c){if(c.logger){this.logger=this.model.logger=c.logger}this.log(this+".initialize:",c);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton];this.expanded=c.expanded||false;this._setUpListeners()},_setUpListeners:function(){this.model.on("change",function(d,c){if(this.model.changedAttributes().state&&this.model.inReadyState()&&this.expanded&&!this.model.hasDetails()){this.model.fetch()}else{this.render()}},this)},render:function(){var d=this,g=this.model.get("id"),e=this.model.get("state"),c=$("<div/>").attr("id","historyItem-"+g),f=(this.$el.children().size()===0);this.$el.attr("id","historyItemContainer-"+g);this.$el.find("[title]").tooltip("destroy");this.urls=this.model.urls();c.addClass("historyItemWrapper").addClass("historyItem").addClass("historyItem-"+e);c.append(this._render_warnings());c.append(this._render_titleBar());this._setUpBehaviors(c);this.body=$(this._render_body());c.append(this.body);this.$el.fadeOut(this.fxSpeed,function(){d.$el.children().remove();d.$el.append(c).fadeIn(d.fxSpeed,function(){d.log(d+" rendered:",d.$el);var h="rendered";if(f){h+=":initial"}else{if(d.model.inReadyState()){h+=":ready"}}d.trigger(h)})});return this},_setUpBehaviors:function(c){c=c||this.$el;make_popup_menus(c);c.find("[title]").tooltip({placement:"bottom"})},_render_warnings:function(){return $(jQuery.trim(a.templates.messages(this.model.toJSON())))},_render_titleBar:function(){var c=$('<div class="historyItemTitleBar" style="overflow: hidden"></div>');c.append(this._render_titleButtons());c.append('<span class="state-icon"></span>');c.append(this._render_titleLink());return c},_render_titleButtons:function(){var c=$('<div class="historyItemButtons"></div>');c.append(this._render_displayButton());return c},_render_displayButton:function(){if((this.model.get("state")===b.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(this.model.get("state")===b.HistoryDatasetAssociation.STATES.NEW)||(!this.model.get("accessible"))){this.displayButton=null;return null}var d={icon_class:"display",target:"galaxy_main"};if(this.model.get("purged")){d.enabled=false;d.title=_l("Cannot display datasets removed from disk")}else{if(this.model.get("state")===b.HistoryDatasetAssociation.STATES.UPLOAD){d.enabled=false;d.title=_l("This dataset must finish uploading before it can be viewed")}else{d.title=_l("View data");d.href=this.urls.display;var c=this;d.on_click=function(){Galaxy.frame_manager.frame_new({title:"Data Viewer: "+c.model.get("name"),type:"url",location:"center",content:c.urls.display})}}}this.displayButton=new IconButtonView({model:new IconButton(d)});return this.displayButton.render().$el},_render_titleLink:function(){return $(jQuery.trim(a.templates.titleLink(this.model.toJSON())))},_render_hdaSummary:function(){var c=_.extend(this.model.toJSON(),{urls:this.urls});return a.templates.hdaSummary(c)},_render_primaryActionButtons:function(e){var c=this,d=$("<div/>").attr("id","primary-actions-"+this.model.get("id"));_.each(e,function(f){d.append(f.call(c))});return d},_render_downloadButton:function(){if(this.model.get("purged")||!this.model.hasData()){return null}var c=a.templates.downloadLinks(_.extend(this.model.toJSON(),{urls:this.urls}));return $(c.trim())},_render_showParamsButton:function(){this.showParamsButton=new IconButtonView({model:new IconButton({title:_l("View details"),href:this.urls.show_params,target:"galaxy_main",icon_class:"information"})});return this.showParamsButton.render().$el},_render_displayAppArea:function(){return $("<div/>").addClass("display-apps")},_render_displayApps:function(e){e=e||this.$el;var f=e.find("div.display-apps"),c=this.model.get("display_types"),d=this.model.get("display_apps");if((!this.model.hasData())||(!e||!e.length)||(!f.length)){return}f.html(null);if(!_.isEmpty(c)){f.append(a.templates.displayApps({displayApps:c}))}if(!_.isEmpty(d)){f.append(a.templates.displayApps({displayApps:d}))}},_render_peek:function(){var c=this.model.get("peek");if(!c){return null}return $("<div/>").append($("<pre/>").attr("id","peek"+this.model.get("id")).addClass("peek").append(c))},_render_body:function(){var c=$("<div/>").attr("id","info-"+this.model.get("id")).addClass("historyItemBody").attr("style","display: none");if(this.expanded){this._render_body_html(c);c.show()}return c},_render_body_html:function(e){e.empty();var c=this.model.get("state");var f="_render_body_"+c,d=this[f];if(_.isFunction(d)){this[f](e)}else{e.append($('<div>Error: unknown dataset state "'+this.model.get("state")+'".</div>'))}e.append('<div style="clear: both"></div>');this._setUpBehaviors(e)},_render_body_new:function(d){var c=_l("This is a new dataset and not all of its data are available yet");d.append($("<div>"+_l(c)+"</div>"))},_render_body_noPermission:function(c){c.append($("<div>"+_l("You do not have permission to view this dataset")+"</div>"))},_render_body_upload:function(c){c.append($("<div>"+_l("Dataset is uploading")+"</div>"))},_render_body_queued:function(c){c.append($("<div>"+_l("Job is waiting to run")+"</div>"));c.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_paused:function(c){c.append($("<div>"+_l('Job is paused. Use the "Resume Paused Jobs" in the history menu to resume')+"</div>"));c.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_running:function(c){c.append("<div>"+_l("Job is currently running")+"</div>");c.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_error:function(c){if(!this.model.get("purged")){c.append($("<div>"+this.model.get("misc_blurb")+"</div>"))}c.append((_l("An error occurred with this dataset")+": <i>"+$.trim(this.model.get("misc_info"))+"</i>"));c.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers.concat([this._render_downloadButton])))},_render_body_discarded:function(c){c.append("<div>"+_l("The job creating this dataset was cancelled before completion")+".</div>");c.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_setting_metadata:function(c){c.append($("<div>"+_l("Metadata is being auto-detected")+".</div>"))},_render_body_empty:function(c){c.append($("<div>"+_l("No data")+": <i>"+this.model.get("misc_blurb")+"</i></div>"));c.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_failed_metadata:function(c){c.append($(a.templates.failedMetadata(_.extend(this.model.toJSON(),{urls:this.urls}))));this._render_body_ok(c)},_render_body_ok:function(c){c.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){c.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton]));return}c.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton]));c.append('<div class="clear"/>');c.append(this._render_displayAppArea());this._render_displayApps(c);c.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility"},toggleBodyVisibility:function(d,c){c=(c===undefined)?(!this.body.is(":visible")):(c);if(c){this.expandBody()}else{this.collapseBody()}},expandBody:function(){var c=this;function d(){c._render_body_html(c.body);c.body.slideDown(c.fxSpeed,function(){c.expanded=true;c.trigger("body-expanded",c.model.get("id"))})}if(this.model.inReadyState()&&!this.model.hasDetails()){this.model.fetch().done(function(e){d()})}else{d()}},collapseBody:function(){var c=this;this.body.slideUp(c.fxSpeed,function(){c.expanded=false;c.trigger("body-collapsed",c.model.get("id"))})},remove:function(d){var c=this;this.$el.fadeOut(c.fxSpeed,function(){c.$el.remove();c.off();if(d){d()}})},toString:function(){var c=(this.model)?(this.model+""):("(no model)");return"HDABaseView("+c+")"}});a.templates={warningMsg:Handlebars.templates["template-warningmessagesmall"],messages:Handlebars.templates["template-hda-warning-messages"],titleLink:Handlebars.templates["template-hda-titleLink"],hdaSummary:Handlebars.templates["template-hda-hdaSummary"],downloadLinks:Handlebars.templates["template-hda-downloadLinks"],failedMetadata:Handlebars.templates["template-hda-failedMetadata"],displayApps:Handlebars.templates["template-hda-displayApps"]};return{HDABaseView:a}});
\ No newline at end of file
+define(["mvc/dataset/hda-model"],function(b){var a=Backbone.View.extend(LoggableMixin).extend({tagName:"div",className:"dataset hda history-panel-hda",id:function(){return"hda-"+this.model.get("id")},fxSpeed:"fast",initialize:function(c){if(c.logger){this.logger=this.model.logger=c.logger}this.log(this+".initialize:",c);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton];this.expanded=c.expanded||false;this._setUpListeners()},_setUpListeners:function(){this.model.on("change",function(d,c){if(this.model.changedAttributes().state&&this.model.inReadyState()&&this.expanded&&!this.model.hasDetails()){this.model.fetch()}else{this.render()}},this)},render:function(e){e=(e===undefined)?(true):(e);var c=this;this.$el.find("[title]").tooltip("destroy");this.urls=this.model.urls();var d=$(a.templates.skeleton(this.model.toJSON()));d.find(".dataset-primary-actions").append(this._render_titleButtons());d.children(".dataset-body").replaceWith(this._render_body());this._setUpBehaviors(d);if(e){$(c).queue(function(f){this.$el.fadeOut(c.fxSpeed,f)})}$(c).queue(function(f){this.$el.empty().attr("class",c.className).addClass("state-"+c.model.get("state")).append(d.children());f()});if(e){$(c).queue(function(f){this.$el.fadeIn(c.fxSpeed,f)})}$(c).queue(function(f){this.trigger("rendered",c);if(this.model.inReadyState()){this.trigger("rendered:ready",c)}f()});return this},_setUpBehaviors:function(c){c=c||this.$el;make_popup_menus(c);c.find("[title]").tooltip({placement:"bottom"})},_render_titleButtons:function(){return[this._render_displayButton()]},_render_iconButton:function(c){c=c||{};c.classes=["icon-btn"].concat(c.classes||[]);if(c.disabled){c.classes.push("disabled")}var d=['<a class="',c.classes.join(" "),'"',((c.title)?(' title="'+c.title+'"'):("")),((c.target)?(' target="'+c.target+'"'):("")),' href="',((c.href)?(c.href):("javascript:void(0);")),'">','<span class="',c.faIcon,'"></span>',"</a>"].join("");d=$(d);if(_.isFunction(c.onClick)){d.click(c.onClick)}return d},_render_displayButton:function(){if((this.model.get("state")===b.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(this.model.get("state")===b.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===b.HistoryDatasetAssociation.STATES.NEW)||(!this.model.get("accessible"))){this.displayButton=null;return null}var d={icon_class:"display",target:"galaxy_main"};if(this.model.get("purged")){d.enabled=false;d.title=_l("Cannot display datasets removed from disk")}else{if(this.model.get("state")===b.HistoryDatasetAssociation.STATES.UPLOAD){d.enabled=false;d.title=_l("This dataset must finish uploading before it can be viewed")}else{d.title=_l("View data");d.href=this.urls.display;var c=this;d.on_click=function(){Galaxy.frame_manager.frame_new({title:"Data Viewer: "+c.model.get("name"),type:"url",location:"center",content:c.urls.display})}}}this.displayButton=new IconButtonView({model:new IconButton(d)});return this.displayButton.render().$el},_render_downloadButton:function(){if(this.model.get("purged")||!this.model.hasData()){return null}var d=this.urls,e=this.model.get("meta_files");if(_.isEmpty(e)){return $(['<a href="',d.download,'" title="',_l("Download"),'" class="icon-button disk"></a>'].join(""))}var f="dataset-"+this.model.get("id")+"-popup",c=['<div popupmenu="'+f+'">','<a class="action-button" href="'+d.download+'">',_l("Download Dataset"),"</a>","<a>"+_l("Additional Files")+"</a>",_.map(e,function(g){return['<a class="action-button" href="',d.meta_download+g.file_type,'">',_l("Download")," ",g.file_type,"</a>"].join("")}).join("\n"),"</div>",'<div style="float:left;" class="menubutton split popup" id="'+f+'">','<a href="'+d.download+'" title="'+_l("Download")+'" class="icon-button disk"></a>',"</div>"].join("\n");return $(c)},_render_showParamsButton:function(){return new IconButtonView({model:new IconButton({title:_l("View details"),href:this.urls.show_params,target:"galaxy_main",icon_class:"information"})}).render().$el},_render_body:function(){var d=$('<div>Error: unknown dataset state "'+this.model.get("state")+'".</div>'),c=this["_render_body_"+this.model.get("state")];if(_.isFunction(c)){d=c.call(this)}if(this.expanded){d.show()}return d},_render_stateBodyHelper:function(c,f){f=f||[];var d=this,e=$(a.templates.body(_.extend(this.model.toJSON(),{body:c})));e.find(".dataset-actions .left").append(_.map(f,function(g){return g.call(d)}));return e},_render_body_new:function(){return this._render_stateBodyHelper("<div>"+_l("This is a new dataset and not all of its data are available yet")+"</div>")},_render_body_noPermission:function(){return this._render_stateBodyHelper("<div>"+_l("You do not have permission to view this dataset")+"</div>")},_render_body_discarded:function(){return this._render_stateBodyHelper("<div>"+_l("The job creating this dataset was cancelled before completion")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_queued:function(){return this._render_stateBodyHelper("<div>"+_l("This job is waiting to run")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_upload:function(){return this._render_stateBodyHelper("<div>"+_l("This dataset is currently uploading")+"</div>")},_render_body_setting_metadata:function(){return this._render_stateBodyHelper("<div>"+_l("Metadata is being auto-detected")+"</div>")},_render_body_running:function(){return this._render_stateBodyHelper("<div>"+_l("This job is currently running")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_paused:function(){return this._render_stateBodyHelper("<div>"+_l('This job is paused. Use the "Resume Paused Jobs" in the history menu to resume')+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_error:function(){var c=_l("An error occurred with this dataset")+": <i>"+$.trim(this.model.get("misc_info"))+"</i>";if(!this.model.get("purged")){c="<div>"+this.model.get("misc_blurb")+"</div>"+c}return this._render_stateBodyHelper(c,this.defaultPrimaryActionButtonRenderers.concat([this._render_downloadButton]))},_render_body_empty:function(){return this._render_stateBodyHelper("<div>"+_l("No data")+": <i>"+this.model.get("misc_blurb")+"</i></div>",this.defaultPrimaryActionButtonRenderers)},_render_body_failed_metadata:function(){var c=$('<div class="warningmessagesmall"></div>').append($("<strong/>").text(_l("An error occurred setting the metadata for this dataset"))),d=this._render_body_ok();d.prepend(c);return d},_render_body_ok:function(){var c=this,e=$(a.templates.body(this.model.toJSON())),d=[this._render_downloadButton].concat(this.defaultPrimaryActionButtonRenderers);e.find(".dataset-actions .left").append(_.map(d,function(f){return f.call(c)}));if(this.model.isDeletedOrPurged()){return e}return e},events:{"click .dataset-title-bar":"toggleBodyVisibility"},toggleBodyVisibility:function(d,c){var e=this.$el.find(".dataset-body");c=(c===undefined)?(!e.is(":visible")):(c);if(c){this.expandBody()}else{this.collapseBody()}},expandBody:function(){var c=this;function d(){c.render(false).$el.children(".dataset-body").slideDown(c.fxSpeed,function(){c.expanded=true;c.trigger("body-expanded",c.model.get("id"))})}if(this.model.inReadyState()&&!this.model.hasDetails()){this.model.fetch({silent:true}).always(function(e){d()})}else{d()}},collapseBody:function(){var c=this;this.$el.children(".dataset-body").slideUp(c.fxSpeed,function(){c.expanded=false;c.trigger("body-collapsed",c.model.get("id"))})},remove:function(d){var c=this;this.$el.fadeOut(c.fxSpeed,function(){c.$el.remove();c.off();if(d){d()}})},toString:function(){var c=(this.model)?(this.model+""):("(no model)");return"HDABaseView("+c+")"}});a.templates={skeleton:Handlebars.templates["template-hda-skeleton"],body:Handlebars.templates["template-hda-body"]};return{HDABaseView:a}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/mvc/dataset/hda-edit.js
--- a/static/scripts/packed/mvc/dataset/hda-edit.js
+++ b/static/scripts/packed/mvc/dataset/hda-edit.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(g){a.HDABaseView.prototype._setUpBehaviors.call(this,g)},_render_warnings:function(){return $(jQuery.trim(a.HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var g=$('<div class="historyItemButtons"></div>');g.append(this._render_displayButton());g.append(this._render_editButton());g.append(this._render_deleteButton());return g},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(g||i){h.enabled=false;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(h)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var g=this,j="historyItemDeleter-"+g.model.get("id"),h=g.urls["delete"],i={title:_l("Delete"),href:h,id:j,icon_class:"delete",on_click:function(){g.$el.find(".menu-button.delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){i={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(i)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var g=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(g,{dbkey_unknown_and_editable:true})}return a.HDABaseView.templates.hdaSummary(g)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){this.errButton=null;return null}this.errButton=new IconButtonView({model:new IconButton({title:_l("View or report this error"),href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_rerunButton:function(){this.rerunButton=new IconButtonView({model:new IconButton({title:_l("Run this job again"),href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_visualizationsButton:function(){var g=this.model.get("visualizations");if((!this.model.hasData())||(_.isEmpty(g))){this.visualizationsButton=null;return null}if(_.isObject(g[0])){return this._render_visualizationsFrameworkButton(g)}if(!this.urls.visualization){this.visualizationsButton=null;return null}var i=this.model.get("dbkey"),l=this.urls.visualization,j={},m={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(i){m.dbkey=i}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:this.urls.visualization,icon_class:"chart_curve"})});var h=this.visualizationsButton.render().$el;h.addClass("visualize-icon");function k(n){switch(n){case"trackster":return b(l,m,i);case"scatterplot":return e(l,m);default:return function(){Galaxy.frame_manager.frame_new({title:"Visualization",type:"url",content:l+"/"+n+"?"+$.param(m)})}}}if(g.length===1){h.attr("title",g[0]);h.click(k(g[0]))}else{_.each(g,function(o){var n=o.charAt(0).toUpperCase()+o.slice(1);j[_l(n)]=k(o)});make_popupmenu(h,j)}return h},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),icon_class:"chart_curve"})});var i=this.visualizationsButton.render().$el;i.addClass("visualize-icon");if(_.keys(g).length===1){i.attr("title",_.keys(g)[0]);i.attr("href",_.values(g)[0])}else{var j=[];_.each(g,function(k){j.push(k)});var h=new PopupMenu(i,j)}return i},_render_secondaryActionButtons:function(h){var i=$("<div/>"),g=this;i.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(h,function(j){i.append(j.call(g))});return i},_render_tagButton:function(){if(!this.hasUser||!this.urls.tags.get){this.tagButton=null;return null}this.tagButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset tags"),target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!this.hasUser||!this.urls.annotation.get){this.annotateButton=null;return null}this.annotateButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset annotation"),target:"galaxy_main",icon_class:"annotate"})});return this.annotateButton.render().$el},_render_body_error:function(g){a.HDABaseView.prototype._render_body_error.call(this,g);var h=g.find("#primary-actions-"+this.model.get("id"));h.prepend(this._render_errButton())},_render_body_ok:function(g){g.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){g.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}g.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));g.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));g.append('<div class="clear"/>');g.append(this._render_tagArea());g.append(this._render_annotationArea());g.append(this._render_displayAppArea());this._render_displayApps(g);g.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click .historyItemUndelete":function(g){this.model.undelete();return false},"click .historyItemUnhide":function(g){this.model.unhide();return false},"click .historyItemPurge":"confirmPurge","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},confirmPurge:function c(g){this.model.purge({url:this.urls.purge});return false},_render_tagArea:function(){if(!this.hasUser||!this.urls.tags.set){return null}return $(f.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})).trim())},loadAndDisplayTags:function(i){this.log(this+".loadAndDisplayTags",i);var g=this,j=this.$el.find(".tag-area"),h=j.find(".tag-elt");if(j.is(":hidden")){if(!jQuery.trim(h.html())){$.ajax({url:this.urls.tags.get,error:function(m,k,l){g.log("Tagging failed",m,k,l);g.trigger("error",g,m,{},_l("Tagging failed"))},success:function(k){h.html(k);h.find("[title]").tooltip();j.slideDown(g.fxSpeed)}})}else{j.slideDown(g.fxSpeed)}}else{j.slideUp(g.fxSpeed)}return false},_render_annotationArea:function(){if(!this.hasUser||!this.urls.annotation.get){return null}return $(f.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})).trim())},loadAndDisplayAnnotation:function(i){this.log(this+".loadAndDisplayAnnotation",i);var g=this,k=this.$el.find(".annotation-area"),j=k.find(".annotation-elt"),h=this.urls.annotation.set;if(k.is(":hidden")){if(!jQuery.trim(j.html())){$.ajax({url:this.urls.annotation.get,error:function(){g.log("Annotation failed",xhr,status,error);g.trigger("error",g,xhr,{},_l("Annotation failed"))},success:function(l){if(l===""){l="<em>"+_l("Describe or add notes to dataset")+"</em>"}j.html(l);k.find("[title]").tooltip();async_save_text(j.attr("id"),j.attr("id"),h,"new_annotation",18,true,4);k.slideDown(g.fxSpeed)}})}else{k.slideDown(g.fxSpeed)}}else{k.slideUp(g.fxSpeed)}return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});f.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function e(g,h){action=function(){Galaxy.frame_manager.frame_new({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(h),location:"center"});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame_manager.frame_new({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i)})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame_manager.frame_new({title:"Trackster",type:"url",content:m})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(g||i){h.enabled=false;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}return new IconButtonView({model:new IconButton(h)}).render().$el},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var g=this,h=g.urls["delete"],i={title:_l("Delete"),href:h,icon_class:"delete",on_click:function(){g.$el.find(".menu-button.delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){i={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}return new IconButtonView({model:new IconButton(i)}).render().$el},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){this.errButton=null;return null}return new IconButtonView({model:new IconButton({title:_l("View or report this error"),href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})}).render().$el},_render_rerunButton:function(){return new IconButtonView({model:new IconButton({title:_l("Run this job again"),href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})}).render().$el},_render_visualizationsButton:function(){var g=this.model.get("visualizations");if((!this.model.hasData())||(_.isEmpty(g))){this.visualizationsButton=null;return null}if(_.isObject(g[0])){return this._render_visualizationsFrameworkButton(g)}if(!this.urls.visualization){this.visualizationsButton=null;return null}var i=this.model.get("dbkey"),l=this.urls.visualization,j={},m={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(i){m.dbkey=i}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:this.urls.visualization,icon_class:"chart_curve"})});var h=this.visualizationsButton.render().$el;h.addClass("visualize-icon");function k(n){switch(n){case"trackster":return b(l,m,i);case"scatterplot":return e(l,m);default:return function(){Galaxy.frame_manager.frame_new({title:"Visualization",type:"url",content:l+"/"+n+"?"+$.param(m)})}}}if(g.length===1){h.attr("title",g[0]);h.click(k(g[0]))}else{_.each(g,function(o){var n=o.charAt(0).toUpperCase()+o.slice(1);j[_l(n)]=k(o)});make_popupmenu(h,j)}return h},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),icon_class:"chart_curve"})});var i=this.visualizationsButton.render().$el;i.addClass("visualize-icon");if(_.keys(g).length===1){i.attr("title",_.keys(g)[0]);i.attr("href",_.values(g)[0])}else{var j=[];_.each(g,function(k){j.push(k)});var h=new PopupMenu(i,j)}return i},_render_tagButton:function(){if(!this.hasUser||!this.urls.tags.get){this.tagButton=null;return null}return new IconButtonView({model:new IconButton({title:_l("Edit dataset tags"),target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})}).render().$el},_render_annotateButton:function(){if(!this.hasUser||!this.urls.annotation.get){this.annotateButton=null;return null}return new IconButtonView({model:new IconButton({title:_l("Edit dataset annotation"),target:"galaxy_main",icon_class:"annotate"})}).render().$el},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:"galaxy_main"}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);g.find(".dataset-actions .left").append(this._render_visualizationsButton());g.find(".dataset-actions .right").append([this._render_tagButton(),this._render_annotateButton()]);return g},makeDbkeyEditLink:function(g){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){g.find(".dataset-dbkey .value").replaceWith($('<a target="galaxy_main">?</a>').attr("href",this.urls.edit))}},events:{"click .dataset-title-bar":"toggleBodyVisibility","click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},confirmPurge:function c(g){this.model.purge();return false},loadAndDisplayTags:function(i){this.log(this+".loadAndDisplayTags",i);var g=this,h=this.$el.find(".tags-display"),j=h.find(".tags");if(h.is(":hidden")){if(!jQuery.trim(j.html())){var k=$.ajax(this.urls.tags.get);k.fail(function(n,l,m){g.log("Tagging failed",n,l,m);g.trigger("error",g,n,{},_l("Tagging failed"))});k.done(function(l){j.html(l);j.find("[title]").tooltip();h.slideDown(g.fxSpeed)})}else{h.slideDown(g.fxSpeed)}}else{h.slideUp(g.fxSpeed)}return false},loadAndDisplayAnnotation:function(k){this.log(this+".loadAndDisplayAnnotation",k);var i=this,h=this.$el.find(".annotation-display"),g=h.find(".annotation"),j=this.urls.annotation.set;if(h.is(":hidden")){if(!jQuery.trim(g.html())){var l=$.ajax(this.urls.annotation.get);l.fail(function(o,m,n){i.log("Annotation failed",o,m,n);i.trigger("error",i,o,{},_l("Annotation failed"))});l.done(function(m){m=m||"<em>"+_l("Describe or add notes to dataset")+"</em>";g.html(m);h.find("[title]").tooltip();g.make_text_editable({use_textarea:true,on_finish:function(n){g.text(n);i.model.save({annotation:n},{silent:true}).fail(function(){g.text(i.model.previous("annotation"))})}});h.slideDown(i.fxSpeed)})}else{h.slideDown(i.fxSpeed)}}else{h.slideUp(i.fxSpeed)}return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,h){action=function(){Galaxy.frame_manager.frame_new({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(h),location:"center"});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame_manager.frame_new({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i)})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame_manager.frame_new({title:"Trackster",type:"url",content:m})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/mvc/dataset/hda-model.js
--- a/static/scripts/packed/mvc/dataset/hda-model.js
+++ b/static/scripts/packed/mvc/dataset/hda-model.js
@@ -1,1 +1,1 @@
-define([],function(){var d=Backbone.Model.extend(LoggableMixin).extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",hid:0,id:null,name:"(unnamed dataset)",state:"new",deleted:false,visible:true,accessible:true,purged:false,data_type:null,file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:""},urlRoot:"api/histories/",url:function(){return this.urlRoot+this.get("history_id")+"/contents/"+this.get("id")},urls:function(){var j=this.get("id");if(!j){return{}}var h={purge:galaxy_config.root+"datasets/"+j+"/purge_async",display:galaxy_config.root+"datasets/"+j+"/display/?preview=True",download:galaxy_config.root+"datasets/"+j+"/display?to_ext="+this.get("file_ext"),edit:galaxy_config.root+"datasets/"+j+"/edit",report_error:galaxy_config.root+"dataset/errors?id="+j,rerun:galaxy_config.root+"tool_runner/rerun?id="+j,show_params:galaxy_config.root+"datasets/"+j+"/show_params",visualization:galaxy_config.root+"visualization",annotation:{get:galaxy_config.root+"dataset/get_annotation_async?id="+j,set:galaxy_config.root+"dataset/annotate_async?id="+j},tags:{get:galaxy_config.root+"tag/get_tagging_elt_async?item_id="+j+"&item_class=HistoryDatasetAssociation",set:galaxy_config.root+"tag/retag?item_id="+j+"&item_class=HistoryDatasetAssociation"}};var i=this.get("meta_files");if(i){h.meta_download=_.map(i,function(k){return{url:galaxy_config.root+"dataset/get_metadata_file?hda_id="+j+"&metadata_name="+k.file_type,file_type:k.file_type}})}return h},initialize:function(h){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",d.STATES.NOT_VIEWABLE)}this._setUpListeners()},_setUpListeners:function(){this.on("change:state",function(i,h){this.log(this+" has changed state:",i,h);if(this.inReadyState()){this.trigger("state:ready",i,h,this.previous("state"))}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},isVisible:function(i,j){var h=true;if((!i)&&(this.get("deleted")||this.get("purged"))){h=false}if((!j)&&(!this.get("visible"))){h=false}return h},hidden:function(){return !this.get("visible")},inReadyState:function(){var h=_.contains(d.READY_STATES,this.get("state"));return(this.isDeletedOrPurged()||h)},hasDetails:function(){return _.has(this.attributes,"genome_build")},hasData:function(){return(this.get("file_size")>0)},"delete":function c(h){return this.save({deleted:true},h)},undelete:function a(h){return this.save({deleted:false},h)},hide:function b(h){return this.save({visible:false},h)},unhide:function g(h){return this.save({visible:true},h)},purge:function f(h){var i=this,j=jQuery.ajax(h);j.done(function(m,k,l){i.set("purged",true)});j.fail(function(o,k,n){var l=_l("Unable to purge this dataset");var m=("Removal of datasets by users is not allowed in this Galaxy instance");if(o.responseJSON&&o.responseJSON.error){l=o.responseJSON.error}else{if(o.responseText.indexOf(m)!==-1){l=m}}o.responseText=l;i.trigger("error",i,o,h,_l(l),{error:l})})},searchKeys:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],search:function(h){var i=this;h=h.toLowerCase();return _.filter(this.searchKeys,function(k){var j=i.get(k);return(_.isString(j)&&j.toLowerCase().indexOf(h)!==-1)})},matches:function(h){return !!this.search(h).length},toString:function(){var h=this.get("id")||"";if(this.get("name")){h=this.get("hid")+' :"'+this.get("name")+'",'+h}return"HDA("+h+")"}});d.STATES={UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",PAUSED:"paused",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};d.READY_STATES=[d.STATES.NEW,d.STATES.OK,d.STATES.EMPTY,d.STATES.PAUSED,d.STATES.FAILED_METADATA,d.STATES.NOT_VIEWABLE,d.STATES.DISCARDED,d.STATES.ERROR];d.NOT_READY_STATES=[d.STATES.UPLOAD,d.STATES.QUEUED,d.STATES.RUNNING,d.STATES.SETTING_METADATA];var e=Backbone.Collection.extend(LoggableMixin).extend({model:d,urlRoot:galaxy_config.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(i,h){h=h||{};this.historyId=h.historyId;this._setUpListeners()},_setUpListeners:function(){},ids:function(){return this.map(function(h){return h.id})},notReady:function(){return this.filter(function(h){return !h.inReadyState()})},running:function(){var h=[];this.each(function(i){if(!i.inReadyState()){h.push(i.get("id"))}});return h},getByHid:function(h){return _.first(this.filter(function(i){return i.get("hid")===h}))},getVisible:function(h,i){return this.filter(function(j){return j.isVisible(h,i)})},fetchAllDetails:function(){return this.fetch({data:{details:"all"}})},matches:function(h){return this.filter(function(i){return i.matches(h)})},set:function(j,h){var i=this;j=_.map(j,function(l){var m=i.get(l.id);if(!m){return l}var k=m.toJSON();_.extend(k,l);return k});Backbone.Collection.prototype.set.call(this,j,h)},toString:function(){return("HDACollection()")}});return{HistoryDatasetAssociation:d,HDACollection:e}});
\ No newline at end of file
+define([],function(){var d=Backbone.Model.extend(LoggableMixin).extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",hid:0,id:null,name:"(unnamed dataset)",state:"new",deleted:false,visible:true,accessible:true,purged:false,data_type:null,file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:""},urlRoot:"api/histories/",url:function(){return this.urlRoot+this.get("history_id")+"/contents/"+this.get("id")},urls:function(){var i=this.get("id");if(!i){return{}}var h={purge:galaxy_config.root+"datasets/"+i+"/purge_async",display:galaxy_config.root+"datasets/"+i+"/display/?preview=True",edit:galaxy_config.root+"datasets/"+i+"/edit",download:galaxy_config.root+"datasets/"+i+"/display?to_ext="+this.get("file_ext"),report_error:galaxy_config.root+"dataset/errors?id="+i,rerun:galaxy_config.root+"tool_runner/rerun?id="+i,show_params:galaxy_config.root+"datasets/"+i+"/show_params",visualization:galaxy_config.root+"visualization",annotation:{get:galaxy_config.root+"dataset/get_annotation_async?id="+i,set:galaxy_config.root+"dataset/annotate_async?id="+i},tags:{get:galaxy_config.root+"tag/get_tagging_elt_async?item_id="+i+"&item_class=HistoryDatasetAssociation",set:galaxy_config.root+"tag/retag?item_id="+i+"&item_class=HistoryDatasetAssociation"},meta_download:galaxy_config.root+"dataset/get_metadata_file?hda_id="+i+"&metadata_name="};return h},initialize:function(h){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",d.STATES.NOT_VIEWABLE)}this._setUpListeners()},_setUpListeners:function(){this.on("change:state",function(i,h){this.log(this+" has changed state:",i,h);if(this.inReadyState()){this.trigger("state:ready",i,h,this.previous("state"))}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},isVisible:function(i,j){var h=true;if((!i)&&(this.get("deleted")||this.get("purged"))){h=false}if((!j)&&(!this.get("visible"))){h=false}return h},hidden:function(){return !this.get("visible")},inReadyState:function(){var h=_.contains(d.READY_STATES,this.get("state"));return(this.isDeletedOrPurged()||h)},hasDetails:function(){return _.has(this.attributes,"genome_build")},hasData:function(){return(this.get("file_size")>0)},"delete":function c(h){return this.save({deleted:true},h)},undelete:function a(h){return this.save({deleted:false},h)},hide:function b(h){return this.save({visible:false},h)},unhide:function g(h){return this.save({visible:true},h)},purge:function f(h){h=h||{};h.url=galaxy_config.root+"datasets/"+this.get("id")+"/purge_async";var i=this,j=jQuery.ajax(h);j.done(function(m,k,l){i.set("purged",true)});j.fail(function(o,k,n){var l=_l("Unable to purge this dataset");var m=("Removal of datasets by users is not allowed in this Galaxy instance");if(o.responseJSON&&o.responseJSON.error){l=o.responseJSON.error}else{if(o.responseText.indexOf(m)!==-1){l=m}}o.responseText=l;i.trigger("error",i,o,h,_l(l),{error:l})})},searchKeys:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],search:function(h){var i=this;h=h.toLowerCase();return _.filter(this.searchKeys,function(k){var j=i.get(k);return(_.isString(j)&&j.toLowerCase().indexOf(h)!==-1)})},matches:function(h){return !!this.search(h).length},toString:function(){var h=this.get("id")||"";if(this.get("name")){h=this.get("hid")+' :"'+this.get("name")+'",'+h}return"HDA("+h+")"}});d.STATES={UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",PAUSED:"paused",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};d.READY_STATES=[d.STATES.NEW,d.STATES.OK,d.STATES.EMPTY,d.STATES.PAUSED,d.STATES.FAILED_METADATA,d.STATES.NOT_VIEWABLE,d.STATES.DISCARDED,d.STATES.ERROR];d.NOT_READY_STATES=[d.STATES.UPLOAD,d.STATES.QUEUED,d.STATES.RUNNING,d.STATES.SETTING_METADATA];var e=Backbone.Collection.extend(LoggableMixin).extend({model:d,urlRoot:galaxy_config.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(i,h){h=h||{};this.historyId=h.historyId},ids:function(){return this.map(function(h){return h.id})},notReady:function(){return this.filter(function(h){return !h.inReadyState()})},running:function(){var h=[];this.each(function(i){if(!i.inReadyState()){h.push(i.get("id"))}});return h},getByHid:function(h){return _.first(this.filter(function(i){return i.get("hid")===h}))},getVisible:function(h,i){return this.filter(function(j){return j.isVisible(h,i)})},fetchAllDetails:function(){return this.fetch({data:{details:"all"}})},matches:function(h){return this.filter(function(i){return i.matches(h)})},set:function(j,h){var i=this;j=_.map(j,function(l){var m=i.get(l.id);if(!m){return l}var k=m.toJSON();_.extend(k,l);return k});Backbone.Collection.prototype.set.call(this,j,h)},toString:function(){return("HDACollection()")}});return{HistoryDatasetAssociation:d,HDACollection:e}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/mvc/history/history-panel.js
--- a/static/scripts/packed/mvc/history/history-panel.js
+++ b/static/scripts/packed/mvc/history/history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/history/history-model","mvc/dataset/hda-base","mvc/dataset/hda-edit"],function(d,b,a){var c=Backbone.View.extend(LoggableMixin).extend({HDAView:a.HDAEditView,tagName:"div",className:"history-panel",fxSpeed:300,events:{"click #history-tag":"loadAndDisplayTags","click #message-container":"clearMessages"},initialize:function(e){e=e||{};if(e.logger){this.logger=e.logger}this.log(this+".initialize:",e);this._setUpListeners();this.hdaViews={};this.urls={};this.indicator=new LoadingIndicator(this.$el);if(this.model){this._setUpWebStorage(e.initiallyExpanded,e.show_deleted,e.show_hidden);this._setUpModelEventHandlers()}if(e.onready){e.onready.call(this)}},_setUpListeners:function(){this.on("error",function(f,i,e,h,g){this.errorHandler(f,i,e,h,g)});this.on("loading-history",function(){this.showLoadingIndicator()});this.on("loading-done",function(){this.hideLoadingIndicator()});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});this.on("switched-history current-history new-history",function(){if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});if(this.logger){this.on("all",function(e){this.log(this+"",arguments)},this)}},errorHandler:function(g,j,f,i,h){var e=this._parseErrorMessage(g,j,f,i,h);if(j&&j.status===0&&j.readyState===0){}else{if(j&&j.status===502){}else{if(!this.$el.find("#message-container").is(":visible")){this.once("rendered",function(){this.displayMessage("error",e.message,e.details)})}else{this.displayMessage("error",e.message,e.details)}}}},_parseErrorMessage:function(h,l,g,k,j){var f=Galaxy.currUser,e={message:this._bePolite(k),details:{user:(f instanceof User)?(f.toJSON()):(f+""),source:(h instanceof Backbone.Model)?(h.toJSON()):(h+""),xhr:l,options:(l)?(_.omit(g,"xhr")):(g)}};_.extend(e.details,j||{});if(l&&_.isFunction(l.getAllResponseHeaders)){var i=l.getAllResponseHeaders();i=_.compact(i.split("\n"));i=_.map(i,function(m){return m.split(": ")});e.details.xhr.responseHeaders=_.object(i)}return e},_bePolite:function(e){e=e||_l("An error occurred while getting updates from the server");return e+". "+_l("Please contact a Galaxy administrator if the problem persists.")},loadCurrentHistory:function(f){var e=this;return this.loadHistoryWithHDADetails("current",f).then(function(h,g){e.trigger("current-history",e)})},switchToHistory:function(h,g){var e=this,f=function(){return jQuery.post(galaxy_config.root+"api/histories/"+h+"/set_as_current")};return this.loadHistoryWithHDADetails(h,g,f).then(function(j,i){e.trigger("switched-history",e)})},createNewHistory:function(g){var e=this,f=function(){return jQuery.post(galaxy_config.root+"api/histories",{current:true})};return this.loadHistory(undefined,g,f).then(function(i,h){e.trigger("new-history",e)})},loadHistoryWithHDADetails:function(h,g,f,j){var e=this,i=function(k){return e.getExpandedHdaIds(k.id)};return this.loadHistory(h,g,f,j,i)},loadHistory:function(h,g,f,k,i){this.trigger("loading-history",this);g=g||{};var e=this;var j=d.History.getHistoryData(h,{historyFn:f,hdaFn:k,hdaDetailIds:g.initiallyExpanded||i});return this._loadHistoryFromXHR(j,g).fail(function(n,l,m){e.trigger("error",e,n,g,_l("An error was encountered while "+l),{historyId:h,history:m||{}})}).always(function(){e.trigger("loading-done",e)})},_loadHistoryFromXHR:function(g,f){var e=this;g.then(function(h,i){e.setModel(h,i,f)});g.fail(function(i,h){e.render()});return g},setModel:function(g,e,f){f=f||{};if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.hdaViews={};if(Galaxy&&Galaxy.currUser){g.user=Galaxy.currUser.toJSON()}this.model=new d.History(g,e,f);this._setUpWebStorage(f.initiallyExpanded,f.show_deleted,f.show_hidden);this._setUpModelEventHandlers();this.trigger("new-model",this);this.render();return this},refreshHdas:function(f,e){if(this.model){return this.model.refresh(f,e)}return $.when()},_setUpWebStorage:function(f,e,g){this.storage=new PersistentStorage(this._getStorageKey(this.model.get("id")),{expandedHdas:{},show_deleted:false,show_hidden:false});this.log(this+" (prev) storage:",JSON.stringify(this.storage.get(),null,2));if(f){this.storage.set("exandedHdas",f)}if((e===true)||(e===false)){this.storage.set("show_deleted",e)}if((g===true)||(g===false)){this.storage.set("show_hidden",g)}this.show_deleted=this.storage.get("show_deleted");this.show_hidden=this.storage.get("show_hidden");this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get())},_getStorageKey:function(e){if(!e){throw new Error("_getStorageKey needs valid id: "+e)}return("history:"+e)},clearWebStorage:function(){for(var e in sessionStorage){if(e.indexOf("HistoryView.")===0){sessionStorage.removeItem(e)}}},getStoredOptions:function(f){if(!f||f==="current"){return(this.storage)?(this.storage.get()):({})}var e=sessionStorage.getItem(this._getStorageKey(f));return(e===null)?({}):(JSON.parse(e))},getExpandedHdaIds:function(e){var f=this.getStoredOptions(e).expandedHdas;return((_.isEmpty(f))?([]):(_.keys(f)))},_setUpModelEventHandlers:function(){this.model.on("error error:hdas",function(f,h,e,g){this.errorHandler(f,h,e,g)},this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);if(Galaxy&&Galaxy.quotaMeter){this.listenTo(this.model,"change:nice_size",function(){Galaxy.quotaMeter.update()})}this.model.hdas.on("add",this.addHdaView,this);this.model.hdas.on("change:deleted",this.handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this.handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(e){this.model.fetch()},this);this.model.hdas.on("state:ready",function(f,g,e){if((!f.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(this.hdaViews[f.id])}},this)},addHdaView:function(h){this.log("add."+this,h);var f=this;if(!h.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return}$({}).queue([function g(j){var i=f.$el.find("#emptyHistoryMessage");if(i.is(":visible")){i.fadeOut(f.fxSpeed,j)}else{j()}},function e(j){f.scrollToTop();var i=f.$el.find("#"+f.model.get("id")+"-datasets");f.createHdaView(h).$el.hide().prependTo(i).slideDown(f.fxSpeed)}])},createHdaView:function(g){var f=g.get("id"),e=this.storage.get("expandedHdas").get(f),h=new this.HDAView({model:g,expanded:e,hasUser:this.model.hasUser(),logger:this.logger});this._setUpHdaListeners(h);this.hdaViews[f]=h;return h.render()},_setUpHdaListeners:function(f){var e=this;f.on("body-expanded",function(g){e.storage.get("expandedHdas").set(g,true)});f.on("body-collapsed",function(g){e.storage.get("expandedHdas").deleteKey(g)});f.on("error",function(h,j,g,i){e.errorHandler(h,j,g,i)})},handleHdaDeletionChange:function(e){if(e.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[e.id])}},handleHdaVisibleChange:function(e){if(e.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[e.id])}},removeHdaView:function(f){if(!f){return}var e=this;f.$el.fadeOut(e.fxSpeed,function(){f.off();f.remove();delete e.hdaViews[f.model.id];if(_.isEmpty(e.hdaViews)){e.$el.find("#emptyHistoryMessage").fadeIn(e.fxSpeed)}})},render:function(g){var e=this,f;if(this.model){f=this.renderModel()}else{f=this.renderWithoutModel()}$(e).queue("fx",[function(h){if(e.$el.is(":visible")){e.$el.fadeOut(e.fxSpeed,h)}else{h()}},function(h){e.$el.empty();if(f){e.$el.append(f.children())}e.$el.fadeIn(e.fxSpeed,h)},function(h){e._setUpBehaviours();if(g){g.call(this)}e.trigger("rendered",this)}]);return this},renderModel:function(){var e=$("<div/>");e.append(c.templates.historyPanel(this.model.toJSON()));e.find("[title]").tooltip({placement:"bottom"});if(!this.model.hdas.length||!this.renderItems(e.find("#"+this.model.get("id")+"-datasets"))){e.find("#emptyHistoryMessage").show()}return e},renderWithoutModel:function(){var e=$("<div/>"),f=$("<div/>").attr("id","message-container").css({"margin-left":"4px","margin-right":"4px"});return e.append(f)},renderItems:function(f){this.hdaViews={};var e=this,g=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"));_.each(g,function(h){f.prepend(e.createHdaView(h).$el)});return g.length},_setUpBehaviours:function(){if(!this.model||!(this.model.get("user")&&this.model.get("user").email)){return}var e=this,f=this.$("#history-annotation-area");this.$("#history-annotate").click(function(){if(f.is(":hidden")){f.slideDown(e.fxSpeed)}else{f.slideUp(e.fxSpeed)}return false});async_save_text("history-name-container","history-name",this.model.renameUrl(),"new_name",18);async_save_text("history-annotation-container","history-annotation",this.model.annotateUrl(),"new_annotation",18,true,4)},updateHistoryDiskSize:function(){this.$el.find("#history-size").text(this.model.get("nice_size"))},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(e){e.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},toggleShowDeleted:function(){this.storage.set("show_deleted",!this.storage.get("show_deleted"));this.render();return this.storage.get("show_deleted")},toggleShowHidden:function(){this.storage.set("show_hidden",!this.storage.get("show_hidden"));this.render();return this.storage.get("show_hidden")},loadAndDisplayTags:function(g){this.log(this+".loadAndDisplayTags",g);var e=this,h=this.$el.find("#history-tag-area"),f=h.find(".tag-elt");this.log("\t tagArea",h," tagElt",f);if(h.is(":hidden")){if(!jQuery.trim(f.html())){$.ajax({url:e.model.tagUrl(),error:function(k,j,i){e.log("Error loading tag area html",k,j,i);e.trigger("error",e,k,null,_l("Error loading tags"))},success:function(i){f.html(i);f.find("[title]").tooltip();h.slideDown(e.fxSpeed)}})}else{h.slideDown(e.fxSpeed)}}else{h.slideUp(e.fxSpeed)}return false},showLoadingIndicator:function(f,e,g){e=(e!==undefined)?(e):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,g)}else{this.$el.fadeOut(e);this.indicator.show(e,g)}},hideLoadingIndicator:function(e,f){e=(e!==undefined)?(e):(this.fxSpeed);if(this.indicator){this.indicator.hide(e,f)}},displayMessage:function(j,k,i){var g=this;this.scrollToTop();var h=this.$el.find("#message-container"),e=$("<div/>").addClass(j+"message").html(k);if(!_.isEmpty(i)){var f=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(g.messageToModalOptions(j,k,i));return false});e.append(" ",f)}return h.html(e)},messageToModalOptions:function(i,k,h){var e=this,j=$("<div/>"),g={title:"Details"};function f(l){l=_.omit(l,_.functions(l));return["<table>",_.map(l,function(n,m){n=(_.isObject(n))?(f(n)):(n);return'<tr><td style="vertical-align: top; color: grey">'+m+'</td><td style="padding-left: 8px">'+n+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(h)){g.body=j.append(f(h))}else{g.body=j.html(h)}g.buttons={Ok:function(){Galaxy.modal.hide();e.clearMessages()}};return g},clearMessages:function(){var e=this.$el.find("#message-container");e.empty()},scrollPosition:function(){return this.$el.parent().scrollTop()},scrollTo:function(e){this.$el.parent().scrollTop(e)},scrollToTop:function(){this.$el.parent().scrollTop(0);return this},scrollIntoView:function(f,g){if(!g){this.$el.parent().parent().scrollTop(f);return this}var e=window,h=this.$el.parent().parent(),j=$(e).innerHeight(),i=(j/2)-(g/2);$(h).scrollTop(f-i);return this},scrollToId:function(f){if((!f)||(!this.hdaViews[f])){return this}var e=this.hdaViews[f].$el;this.scrollIntoView(e.offset().top,e.outerHeight());return this},scrollToHid:function(e){var f=this.model.hdas.getByHid(e);if(!f){return this}return this.scrollToId(f.id)},connectToQuotaMeter:function(e){if(!e){return this}this.listenTo(e,"quota:over",this.showQuotaMessage);this.listenTo(e,"quota:under",this.hideQuotaMessage);this.on("rendered rendered:initial",function(){if(e&&e.isOverQuota()){this.showQuotaMessage()}});return this},showQuotaMessage:function(){var e=this.$el.find("#quota-message-container");if(e.is(":hidden")){e.slideDown(this.fxSpeed)}},hideQuotaMessage:function(){var e=this.$el.find("#quota-message-container");if(!e.is(":hidden")){e.slideUp(this.fxSpeed)}},connectToOptionsMenu:function(e){if(!e){return this}this.on("new-storage",function(g,f){if(e&&g){e.findItemByHtml(_l("Include Deleted Datasets")).checked=g.get("show_deleted");e.findItemByHtml(_l("Include Hidden Datasets")).checked=g.get("show_hidden")}});return this},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});c.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]};return{HistoryPanel:c}});
\ No newline at end of file
+define(["mvc/history/history-model","mvc/dataset/hda-base","mvc/dataset/hda-edit"],function(d,b,a){var c=Backbone.View.extend(LoggableMixin).extend({HDAView:a.HDAEditView,tagName:"div",className:"history-panel",fxSpeed:400,events:{"click .icon-button.tags":"loadAndDisplayTags","click .message-container":"clearMessages"},datasetsSelector:".datasets-list",emptyMsgSelector:".empty-history-message",msgsSelector:".message-container",initialize:function(e){e=e||{};if(e.logger){this.logger=e.logger}this.log(this+".initialize:",e);this._setUpListeners();this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);if(this.model){this._setUpWebStorage(e.initiallyExpanded,e.show_deleted,e.show_hidden);this._setUpModelEventHandlers()}if(e.onready){e.onready.call(this)}},_setUpListeners:function(){this.on("error",function(f,i,e,h,g){this.errorHandler(f,i,e,h,g)});this.on("loading-history",function(){this.showLoadingIndicator("loading history...")});this.on("loading-done",function(){this.hideLoadingIndicator()});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});this.on("switched-history current-history new-history",function(){if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});if(this.logger){this.on("all",function(e){this.log(this+"",arguments)},this)}},errorHandler:function(g,j,f,i,h){var e=this._parseErrorMessage(g,j,f,i,h);if(j&&j.status===0&&j.readyState===0){}else{if(j&&j.status===502){}else{if(!this.$el.find(this.msgsSelector).is(":visible")){this.once("rendered",function(){this.displayMessage("error",e.message,e.details)})}else{this.displayMessage("error",e.message,e.details)}}}},_parseErrorMessage:function(h,l,g,k,j){var f=Galaxy.currUser,e={message:this._bePolite(k),details:{user:(f instanceof User)?(f.toJSON()):(f+""),source:(h instanceof Backbone.Model)?(h.toJSON()):(h+""),xhr:l,options:(l)?(_.omit(g,"xhr")):(g)}};_.extend(e.details,j||{});if(l&&_.isFunction(l.getAllResponseHeaders)){var i=l.getAllResponseHeaders();i=_.compact(i.split("\n"));i=_.map(i,function(m){return m.split(": ")});e.details.xhr.responseHeaders=_.object(i)}return e},_bePolite:function(e){e=e||_l("An error occurred while getting updates from the server");return e+". "+_l("Please contact a Galaxy administrator if the problem persists.")},loadCurrentHistory:function(f){var e=this;return this.loadHistoryWithHDADetails("current",f).then(function(h,g){e.trigger("current-history",e)})},switchToHistory:function(h,g){var e=this,f=function(){return jQuery.post(galaxy_config.root+"api/histories/"+h+"/set_as_current")};return this.loadHistoryWithHDADetails(h,g,f).then(function(j,i){e.trigger("switched-history",e)})},createNewHistory:function(g){var e=this,f=function(){return jQuery.post(galaxy_config.root+"api/histories",{current:true})};return this.loadHistory(undefined,g,f).then(function(i,h){e.trigger("new-history",e)})},loadHistoryWithHDADetails:function(h,g,f,j){var e=this,i=function(k){return e.getExpandedHdaIds(k.id)};return this.loadHistory(h,g,f,j,i)},loadHistory:function(h,g,f,k,i){this.trigger("loading-history",this);g=g||{};var e=this;var j=d.History.getHistoryData(h,{historyFn:f,hdaFn:k,hdaDetailIds:g.initiallyExpanded||i});return this._loadHistoryFromXHR(j,g).fail(function(n,l,m){e.trigger("error",e,n,g,_l("An error was encountered while "+l),{historyId:h,history:m||{}})}).always(function(){e.trigger("loading-done",e)})},_loadHistoryFromXHR:function(g,f){var e=this;g.then(function(h,i){e.setModel(h,i,f)});g.fail(function(i,h){e.render()});return g},setModel:function(g,e,f){f=f||{};if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.hdaViews={};if(Galaxy&&Galaxy.currUser){g.user=Galaxy.currUser.toJSON()}this.model=new d.History(g,e,f);this._setUpWebStorage(f.initiallyExpanded,f.show_deleted,f.show_hidden);this._setUpModelEventHandlers();this.trigger("new-model",this);this.render();return this},refreshHdas:function(f,e){if(this.model){return this.model.refresh(f,e)}return $.when()},_setUpWebStorage:function(f,e,g){this.storage=new PersistentStorage(this._getStorageKey(this.model.get("id")),{expandedHdas:{},show_deleted:false,show_hidden:false});this.log(this+" (prev) storage:",JSON.stringify(this.storage.get(),null,2));if(f){this.storage.set("exandedHdas",f)}if((e===true)||(e===false)){this.storage.set("show_deleted",e)}if((g===true)||(g===false)){this.storage.set("show_hidden",g)}this.show_deleted=this.storage.get("show_deleted");this.show_hidden=this.storage.get("show_hidden");this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get())},_getStorageKey:function(e){if(!e){throw new Error("_getStorageKey needs valid id: "+e)}return("history:"+e)},clearWebStorage:function(){for(var e in sessionStorage){if(e.indexOf("history:")===0){sessionStorage.removeItem(e)}}},getStoredOptions:function(f){if(!f||f==="current"){return(this.storage)?(this.storage.get()):({})}var e=sessionStorage.getItem(this._getStorageKey(f));return(e===null)?({}):(JSON.parse(e))},getExpandedHdaIds:function(e){var f=this.getStoredOptions(e).expandedHdas;return((_.isEmpty(f))?([]):(_.keys(f)))},_setUpModelEventHandlers:function(){this.model.on("error error:hdas",function(f,h,e,g){this.errorHandler(f,h,e,g)},this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);if(Galaxy&&Galaxy.quotaMeter){this.listenTo(this.model,"change:nice_size",function(){Galaxy.quotaMeter.update()})}this.model.hdas.on("add",this.addHdaView,this);this.model.hdas.on("change:deleted",this.handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this.handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(e){this.model.fetch()},this);this.model.hdas.on("state:ready",function(f,g,e){if((!f.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(this.hdaViews[f.id])}},this)},addHdaView:function(h){this.log("add."+this,h);var f=this;if(!h.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return}$({}).queue([function g(j){var i=f.$el.find(f.emptyMsgSelector);if(i.is(":visible")){i.fadeOut(f.fxSpeed,j)}else{j()}},function e(j){f.scrollToTop();var i=f.$el.find(f.datasetsSelector);f.createHdaView(h).$el.hide().prependTo(i).slideDown(f.fxSpeed)}])},createHdaView:function(g){var f=g.get("id"),e=this.storage.get("expandedHdas").get(f),h=new this.HDAView({model:g,expanded:e,hasUser:this.model.hasUser(),logger:this.logger});this._setUpHdaListeners(h);this.hdaViews[f]=h;return h.render()},_setUpHdaListeners:function(f){var e=this;f.on("body-expanded",function(g){e.storage.get("expandedHdas").set(g,true)});f.on("body-collapsed",function(g){e.storage.get("expandedHdas").deleteKey(g)});f.on("error",function(h,j,g,i){e.errorHandler(h,j,g,i)})},handleHdaDeletionChange:function(e){if(e.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[e.id])}},handleHdaVisibleChange:function(e){if(e.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[e.id])}},removeHdaView:function(f){if(!f){return}var e=this;f.$el.fadeOut(e.fxSpeed,function(){f.off();f.remove();delete e.hdaViews[f.model.id];if(_.isEmpty(e.hdaViews)){e.$el.find(e.emptyMsgSelector).fadeIn(e.fxSpeed,function(){e.trigger("empty-history",e)})}})},render:function(g){var e=this,f;if(this.model){f=this.renderModel()}else{f=this.renderWithoutModel()}$(e).queue("fx",[function(h){if(e.$el.is(":visible")){e.$el.fadeOut(e.fxSpeed,h)}else{h()}},function(h){e.$el.empty();if(f){e.$el.append(f.children())}e.$el.fadeIn(e.fxSpeed,h)},function(h){e._setUpBehaviours();if(g){g.call(this)}e.trigger("rendered",this)}]);return this},renderModel:function(){var e=$("<div/>");var f=(!Galaxy.currUser.isAnonymous())?(c.templates.historyPanel):(c.templates.anonHistoryPanel);e.append(f(this.model.toJSON()));e.find("[title]").tooltip({placement:"bottom"});if(!this.model.hdas.length||!this.renderItems(e.find(this.datasetsSelector))){e.find(this.emptyMsgSelector).show()}return e},renderWithoutModel:function(){var e=$("<div/>"),f=$("<div/>").addClass("message-container").css({"margin-left":"4px","margin-right":"4px"});return e.append(f)},renderItems:function(f){this.hdaViews={};var e=this,g=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"));_.each(g,function(h){f.prepend(e.createHdaView(h).$el)});return g.length},_setUpBehaviours:function(){if(!this.model||!Galaxy.currUser||Galaxy.currUser.isAnonymous()){return}var e=this,f=this.$el.find(".history-controls .annotation-display");this.$el.find(".history-controls .icon-button.annotate").click(function(){if(f.is(":hidden")){f.slideDown(e.fxSpeed)}else{f.slideUp(e.fxSpeed)}return false});this.$el.find(".history-name").make_text_editable({on_finish:function(g){e.$el.find(".history-name").text(g);e.model.save({name:g}).fail(function(){e.$el.find(".history-name").text(e.model.previous("name"))})}});this.$el.find(".history-controls .annotation").make_text_editable({use_textarea:true,on_finish:function(g){e.$el.find(".history-controls .annotation").text(g);e.model.save({annotation:g}).fail(function(){e.$el.find(".history-controls .annotation").text(e.model.previous("annotation"))})}})},updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(e){e.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},toggleShowDeleted:function(){this.storage.set("show_deleted",!this.storage.get("show_deleted"));this.render();return this.storage.get("show_deleted")},toggleShowHidden:function(){this.storage.set("show_hidden",!this.storage.get("show_hidden"));this.render();return this.storage.get("show_hidden")},loadAndDisplayTags:function(g){var e=this,f=this.$el.find(".history-controls .tags-display"),h=f.find(".tags");if(f.is(":hidden")){if(!jQuery.trim(h.html())){var i=jQuery.ajax(e.model.tagUrl());i.fail(function(l,j,k){e.log("Error loading tag area html",l,k,j);e.trigger("error",e,l,null,_l("Error loading tags"))});i.done(function(j){h.html(j);h.find("[title]").tooltip();f.slideDown(e.fxSpeed)})}else{f.slideDown(e.fxSpeed)}}else{f.slideUp(e.fxSpeed)}return false},showLoadingIndicator:function(f,e,g){e=(e!==undefined)?(e):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,g)}else{this.$el.fadeOut(e);this.indicator.show(f,e,g)}},hideLoadingIndicator:function(e,f){e=(e!==undefined)?(e):(this.fxSpeed);if(this.indicator){this.indicator.hide(e,f)}},displayMessage:function(j,k,i){var g=this;this.scrollToTop();var h=this.$el.find(this.msgsSelector),e=$("<div/>").addClass(j+"message").html(k);if(!_.isEmpty(i)){var f=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(g.messageToModalOptions(j,k,i));return false});e.append(" ",f)}return h.html(e)},messageToModalOptions:function(i,k,h){var e=this,j=$("<div/>"),g={title:"Details"};function f(l){l=_.omit(l,_.functions(l));return["<table>",_.map(l,function(n,m){n=(_.isObject(n))?(f(n)):(n);return'<tr><td style="vertical-align: top; color: grey">'+m+'</td><td style="padding-left: 8px">'+n+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(h)){g.body=j.append(f(h))}else{g.body=j.html(h)}g.buttons={Ok:function(){Galaxy.modal.hide();e.clearMessages()}};return g},clearMessages:function(){var e=this.$el.find(this.msgsSelector);e.empty()},scrollPosition:function(){return this.$el.parent().scrollTop()},scrollTo:function(e){this.$el.parent().scrollTop(e)},scrollToTop:function(){this.$el.parent().scrollTop(0);return this},scrollIntoView:function(f,g){if(!g){this.$el.parent().parent().scrollTop(f);return this}var e=window,h=this.$el.parent().parent(),j=$(e).innerHeight(),i=(j/2)-(g/2);$(h).scrollTop(f-i);return this},scrollToId:function(f){if((!f)||(!this.hdaViews[f])){return this}var e=this.hdaViews[f].$el;this.scrollIntoView(e.offset().top,e.outerHeight());return this},scrollToHid:function(e){var f=this.model.hdas.getByHid(e);if(!f){return this}return this.scrollToId(f.id)},connectToQuotaMeter:function(e){if(!e){return this}this.listenTo(e,"quota:over",this.showQuotaMessage);this.listenTo(e,"quota:under",this.hideQuotaMessage);this.on("rendered rendered:initial",function(){if(e&&e.isOverQuota()){this.showQuotaMessage()}});return this},showQuotaMessage:function(){var e=this.$el.find(".quota-message");if(e.is(":hidden")){e.slideDown(this.fxSpeed)}},hideQuotaMessage:function(){var e=this.$el.find(".quota-message");if(!e.is(":hidden")){e.slideUp(this.fxSpeed)}},connectToOptionsMenu:function(e){if(!e){return this}this.on("new-storage",function(g,f){if(e&&g){e.findItemByHtml(_l("Include Deleted Datasets")).checked=g.get("show_deleted");e.findItemByHtml(_l("Include Hidden Datasets")).checked=g.get("show_hidden")}});return this},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});c.templates={historyPanel:Handlebars.templates["template-history-historyPanel"],anonHistoryPanel:Handlebars.templates["template-history-historyPanel-anon"]};return{HistoryPanel:c}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/mvc/tools.js
--- a/static/scripts/packed/mvc/tools.js
+++ b/static/scripts/packed/mvc/tools.js
@@ -1,1 +1,1 @@
-define(["libs/underscore","viz/trackster/util","mvc/data","libs/backbone/backbone-relational"],function(x,a,y){var g={hidden:false,show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},toggle:function(){this.set("hidden",!this.get("hidden"))},is_visible:function(){return !this.attributes.hidden}};var e=Backbone.RelationalModel.extend({defaults:{name:null,label:null,type:null,value:null,html:null,num_samples:5},subModelTypes:{integer:"IntegerToolParameter","float":"FloatToolParameter",data:"DataToolParameter",select:"SelectToolParameter"},initialize:function(){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new e(this.toJSON())},set_value:function(z){this.set("value",z||"")}});var o={};Backbone.Relational.store.addModelScope(o);var j=o.DataToolParameter=e.extend({});var d=o.IntegerToolParameter=e.extend({set_value:function(z){this.set("value",parseInt(z,10))},get_samples:function(){return d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}});var f=o.FloatToolParameter=d.extend({set_value:function(z){this.set("value",parseFloat(z))}});var t=o.SelectToolParameter=e.extend({get_samples:function(){return x.map(this.get("options"),function(z){return z[0]})}});var i=Backbone.RelationalModel.extend({defaults:{id:null,name:null,description:null,target:null,inputs:[],outputs:[]},relations:[{type:Backbone.HasMany,key:"inputs",relatedModel:e}],urlRoot:galaxy_config.root+"api/tools",remove_inputs:function(A){var z=this,B=z.get("inputs").filter(function(C){return(A.indexOf(C.get("type"))!==-1)});z.get("inputs").remove(B)},copy:function(A){var B=new i(this.toJSON());if(A){var z=new Backbone.Collection();B.get("inputs").each(function(C){if(C.get_samples()){z.push(C)}});B.set("inputs",z)}return B},apply_search_results:function(z){(x.indexOf(z,this.attributes.id)!==-1?this.show():this.hide());return this.is_visible()},set_input_value:function(z,A){this.get("inputs").find(function(B){return B.get("name")===z}).set("value",A)},set_input_values:function(A){var z=this;x.each(x.keys(A),function(B){z.set_input_value(B,A[B])})},run:function(){return this._run()},rerun:function(A,z){return this._run({action:"rerun",target_dataset_id:A.id,regions:z})},get_inputs_dict:function(){var z={};this.get("inputs").each(function(A){z[A.get("name")]=A.get("value")});return z},_run:function(B){var C=x.extend({tool_id:this.id,inputs:this.get_inputs_dict()},B);var A=$.Deferred(),z=new a.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(C),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(D){return D!=="pending"}});$.when(z.go()).then(function(D){A.resolve(new y.DatasetCollection().reset(D))});return A}});x.extend(i.prototype,g);var q=Backbone.View.extend({});var m=Backbone.Collection.extend({model:i});var v=Backbone.Model.extend(g);var k=Backbone.Model.extend({defaults:{elems:[],open:false},clear_search_results:function(){x.each(this.attributes.elems,function(z){z.show()});this.show();this.set("open",false)},apply_search_results:function(A){var B=true,z;x.each(this.attributes.elems,function(C){if(C instanceof v){z=C;z.hide()}else{if(C instanceof i){if(C.apply_search_results(A)){B=false;if(z){z.show()}}}}});if(B){this.hide()}else{this.show();this.set("open",true)}}});x.extend(k.prototype,g);var c=Backbone.Model.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 B=this.attributes.query;if(B.length<this.attributes.min_chars_for_search){this.set("results",null);return}var A=B+"*";if(this.timer){clearTimeout(this.timer)}$("#search-clear-btn").hide();$("#search-spinner").show();var z=this;this.timer=setTimeout(function(){$.get(z.attributes.search_url,{query:A},function(C){z.set("results",C);$("#search-spinner").hide();$("#search-clear-btn").show()},"json")},200)},clear_search:function(){this.set("query","");this.set("results",null)}});x.extend(c.prototype,g);var n=Backbone.Model.extend({initialize:function(z){this.attributes.tool_search=z.tool_search;this.attributes.tool_search.on("change:results",this.apply_search_results,this);this.attributes.tools=z.tools;this.attributes.layout=new Backbone.Collection(this.parse(z.layout))},parse:function(A){var z=this,B=function(E){var D=E.model_class;if(D.indexOf("Tool")===D.length-4){return z.attributes.tools.get(E.id)}else{if(D==="ToolSection"){var C=x.map(E.elems,B);E.elems=C;return new k(E)}else{if(D==="ToolSectionLabel"){return new v(E)}}}};return x.map(A,B)},clear_search_results:function(){this.get("layout").each(function(z){if(z instanceof k){z.clear_search_results()}else{z.show()}})},apply_search_results:function(){var A=this.get("tool_search").get("results");if(A===null){this.clear_search_results();return}var z=null;this.get("layout").each(function(B){if(B instanceof v){z=B;z.hide()}else{if(B instanceof i){if(B.apply_search_results(A)){if(z){z.show()}}}else{z=null;B.apply_search_results(A)}}})}});var s=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 l=s.extend({tagName:"div",template:Handlebars.templates.tool_link,render:function(){this.$el.append(this.template(this.model.toJSON()));return this}});var b=s.extend({tagName:"div",className:"toolPanelLabel",render:function(){this.$el.append($("<span/>").text(this.model.attributes.text));return this}});var r=s.extend({tagName:"div",className:"toolSectionWrapper",template:Handlebars.templates.panel_section,initialize:function(){s.prototype.initialize.call(this);this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(this.template(this.model.toJSON()));var z=this.$el.find(".toolSectionBody");x.each(this.model.attributes.elems,function(A){if(A instanceof i){var B=new l({model:A,className:"toolTitle"});B.render();z.append(B.$el)}else{if(A instanceof v){var C=new b({model:A});C.render();z.append(C.$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 p=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()}this.$el.find("[title]").tooltip();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(z){if((this.model.attributes.clear_key)&&(this.model.attributes.clear_key===z.which)){this.clear();return false}this.model.set("query",this.$el.find(":input").val())}});var w=Backbone.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.model.get("tool_search").on("change:results",this.handle_search_results,this)},render:function(){var z=this;var A=new p({model:this.model.get("tool_search")});A.render();z.$el.append(A.$el);this.model.get("layout").each(function(C){if(C instanceof k){var B=new r({model:C});B.render();z.$el.append(B.$el)}else{if(C instanceof i){var D=new l({model:C,className:"toolTitleNoSection"});D.render();z.$el.append(D.$el)}else{if(C instanceof v){var E=new b({model:C});E.render();z.$el.append(E.$el)}}}});z.$el.find("a.tool-link").click(function(D){var C=$(this).attr("class").split(/\s+/)[0],B=z.model.get("tools").get(C);z.trigger("tool_link_click",D,B)});return this},handle_search_results:function(){var z=this.model.get("tool_search").get("results");if(z&&z.length===0){$("#search-no-results").show()}else{$("#search-no-results").hide()}}});var u=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 h=Backbone.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new w({collection:this.collection});this.tool_form_view=new u()},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 z=this;this.tool_panel_view.on("tool_link_click",function(B,A){B.preventDefault();z.show_tool(A)})},show_tool:function(A){var z=this;A.fetch().done(function(){z.tool_form_view.model=A;z.tool_form_view.render();z.tool_form_view.$el.show();$("#left").width("650px")})}});return{ToolParameter:e,IntegerToolParameter:d,SelectToolParameter:t,Tool:i,ToolCollection:m,ToolSearch:c,ToolPanel:n,ToolPanelView:w,ToolFormView:u}});
\ No newline at end of file
+define(["libs/underscore","viz/trackster/util","mvc/data","libs/backbone/backbone-relational"],function(x,a,y){var g={hidden:false,show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},toggle:function(){this.set("hidden",!this.get("hidden"))},is_visible:function(){return !this.attributes.hidden}};var e=Backbone.RelationalModel.extend({defaults:{name:null,label:null,type:null,value:null,html:null,num_samples:5},subModelTypes:{integer:"IntegerToolParameter","float":"FloatToolParameter",data:"DataToolParameter",select:"SelectToolParameter"},initialize:function(){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new e(this.toJSON())},set_value:function(z){this.set("value",z||"")}});var o={};Backbone.Relational.store.addModelScope(o);var j=o.DataToolParameter=e.extend({});var d=o.IntegerToolParameter=e.extend({set_value:function(z){this.set("value",parseInt(z,10))},get_samples:function(){return d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}});var f=o.FloatToolParameter=d.extend({set_value:function(z){this.set("value",parseFloat(z))}});var t=o.SelectToolParameter=e.extend({get_samples:function(){return x.map(this.get("options"),function(z){return z[0]})}});var i=Backbone.RelationalModel.extend({defaults:{id:null,name:null,description:null,target:null,inputs:[],outputs:[]},relations:[{type:Backbone.HasMany,key:"inputs",relatedModel:e}],urlRoot:galaxy_config.root+"api/tools",remove_inputs:function(A){var z=this,B=z.get("inputs").filter(function(C){return(A.indexOf(C.get("type"))!==-1)});z.get("inputs").remove(B)},copy:function(A){var B=new i(this.toJSON());if(A){var z=new Backbone.Collection();B.get("inputs").each(function(C){if(C.get_samples()){z.push(C)}});B.set("inputs",z)}return B},apply_search_results:function(z){(x.indexOf(z,this.attributes.id)!==-1?this.show():this.hide());return this.is_visible()},set_input_value:function(z,A){this.get("inputs").find(function(B){return B.get("name")===z}).set("value",A)},set_input_values:function(A){var z=this;x.each(x.keys(A),function(B){z.set_input_value(B,A[B])})},run:function(){return this._run()},rerun:function(A,z){return this._run({action:"rerun",target_dataset_id:A.id,regions:z})},get_inputs_dict:function(){var z={};this.get("inputs").each(function(A){z[A.get("name")]=A.get("value")});return z},_run:function(B){var C=x.extend({tool_id:this.id,inputs:this.get_inputs_dict()},B);var A=$.Deferred(),z=new a.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(C),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(D){return D!=="pending"}});$.when(z.go()).then(function(D){A.resolve(new y.DatasetCollection().reset(D))});return A}});x.extend(i.prototype,g);var q=Backbone.View.extend({});var m=Backbone.Collection.extend({model:i});var v=Backbone.Model.extend(g);var k=Backbone.Model.extend({defaults:{elems:[],open:false},clear_search_results:function(){x.each(this.attributes.elems,function(z){z.show()});this.show();this.set("open",false)},apply_search_results:function(A){var B=true,z;x.each(this.attributes.elems,function(C){if(C instanceof v){z=C;z.hide()}else{if(C instanceof i){if(C.apply_search_results(A)){B=false;if(z){z.show()}}}}});if(B){this.hide()}else{this.show();this.set("open",true)}}});x.extend(k.prototype,g);var c=Backbone.Model.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 B=this.attributes.query;if(B.length<this.attributes.min_chars_for_search){this.set("results",null);return}var A=B+"*";if(this.timer){clearTimeout(this.timer)}$("#search-clear-btn").hide();$("#search-spinner").show();var z=this;this.timer=setTimeout(function(){$.get(z.attributes.search_url,{query:A},function(C){z.set("results",C);$("#search-spinner").hide();$("#search-clear-btn").show()},"json")},200)},clear_search:function(){this.set("query","");this.set("results",null)}});x.extend(c.prototype,g);var n=Backbone.Model.extend({initialize:function(z){this.attributes.tool_search=z.tool_search;this.attributes.tool_search.on("change:results",this.apply_search_results,this);this.attributes.tools=z.tools;this.attributes.layout=new Backbone.Collection(this.parse(z.layout))},parse:function(A){var z=this,B=function(E){var D=E.model_class;if(D.indexOf("Tool")===D.length-4){return z.attributes.tools.get(E.id)}else{if(D==="ToolSection"){var C=x.map(E.elems,B);E.elems=C;return new k(E)}else{if(D==="ToolSectionLabel"){return new v(E)}}}};return x.map(A,B)},clear_search_results:function(){this.get("layout").each(function(z){if(z instanceof k){z.clear_search_results()}else{z.show()}})},apply_search_results:function(){var A=this.get("tool_search").get("results");if(A===null){this.clear_search_results();return}var z=null;this.get("layout").each(function(B){if(B instanceof v){z=B;z.hide()}else{if(B instanceof i){if(B.apply_search_results(A)){if(z){z.show()}}}else{z=null;B.apply_search_results(A)}}})}});var s=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 l=s.extend({tagName:"div",render:function(){this.$el.append(Handlebars.templates.tool_link(this.model.toJSON()));return this}});var b=s.extend({tagName:"div",className:"toolPanelLabel",render:function(){this.$el.append($("<span/>").text(this.model.attributes.text));return this}});var r=s.extend({tagName:"div",className:"toolSectionWrapper",initialize:function(){s.prototype.initialize.call(this);this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(Handlebars.templates.panel_section(this.model.toJSON()));var z=this.$el.find(".toolSectionBody");x.each(this.model.attributes.elems,function(A){if(A instanceof i){var B=new l({model:A,className:"toolTitle"});B.render();z.append(B.$el)}else{if(A instanceof v){var C=new b({model:A});C.render();z.append(C.$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 p=Backbone.View.extend({tagName:"div",id:"tool-search",className:"bar",events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){this.$el.append(Handlebars.templates.tool_search(this.model.toJSON()));if(!this.model.is_visible()){this.$el.hide()}this.$el.find("[title]").tooltip();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(z){if((this.model.attributes.clear_key)&&(this.model.attributes.clear_key===z.which)){this.clear();return false}this.model.set("query",this.$el.find(":input").val())}});var w=Backbone.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.model.get("tool_search").on("change:results",this.handle_search_results,this)},render:function(){var z=this;var A=new p({model:this.model.get("tool_search")});A.render();z.$el.append(A.$el);this.model.get("layout").each(function(C){if(C instanceof k){var B=new r({model:C});B.render();z.$el.append(B.$el)}else{if(C instanceof i){var D=new l({model:C,className:"toolTitleNoSection"});D.render();z.$el.append(D.$el)}else{if(C instanceof v){var E=new b({model:C});E.render();z.$el.append(E.$el)}}}});z.$el.find("a.tool-link").click(function(D){var C=$(this).attr("class").split(/\s+/)[0],B=z.model.get("tools").get(C);z.trigger("tool_link_click",D,B)});return this},handle_search_results:function(){var z=this.model.get("tool_search").get("results");if(z&&z.length===0){$("#search-no-results").show()}else{$("#search-no-results").hide()}}});var u=Backbone.View.extend({className:"toolForm",render:function(){this.$el.children().remove();this.$el.append(Handlebars.templates.tool_form(this.model.toJSON()))}});var h=Backbone.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new w({collection:this.collection});this.tool_form_view=new u()},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 z=this;this.tool_panel_view.on("tool_link_click",function(B,A){B.preventDefault();z.show_tool(A)})},show_tool:function(A){var z=this;A.fetch().done(function(){z.tool_form_view.model=A;z.tool_form_view.render();z.tool_form_view.$el.show();$("#left").width("650px")})}});return{ToolParameter:e,IntegerToolParameter:d,SelectToolParameter:t,Tool:i,ToolCollection:m,ToolSearch:c,ToolPanel:n,ToolPanelView:w,ToolFormView:u}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 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.registerHelper("warningmessagesmall",function(a){return'<div class="warningmessagesmall"><strong>'+a.fn(this)+"</strong></div>"});Handlebars.registerHelper("local",function(a){return _l(a.fn(this))});
\ No newline at end of file
+Handlebars.registerHelper("local",function(a){return _l(a.fn(this))});Handlebars.registerHelper("n2br",function(a){return a.fn(this).replace(/\n/g,"<br/>")});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/templates/compiled/history-templates.js
--- a/static/scripts/packed/templates/compiled/history-templates.js
+++ b/static/scripts/packed/templates/compiled/history-templates.js
@@ -1,1 +1,1 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-historyPanel"]=b(function(m,C,A,s,J){this.compilerInfo=[4,">= 1.0.0"];A=this.merge(A,m.helpers);J=J||{};var B="",p,l,h,x=this,e="function",c=A.blockHelperMissing,d=this.escapeExpression;function v(O,N){var K="",M,L;K+='\n <div id="history-name" class="editable-text"\n title="';L={hash:{},inverse:x.noop,fn:x.program(2,u,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='">';if(M=A.name){M=M.call(O,{hash:{},data:N})}else{M=O.name;M=typeof M===e?M.apply(O):M}K+=d(M)+"</div>\n ";return K}function u(L,K){return"Click to rename history"}function t(O,N){var K="",M,L;K+='\n <div id="history-name"\n title="';L={hash:{},inverse:x.noop,fn:x.program(5,r,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='">';if(M=A.name){M=M.call(O,{hash:{},data:N})}else{M=O.name;M=typeof M===e?M.apply(O):M}K+=d(M)+"</div>\n ";return K}function r(L,K){return"You must be logged in to edit your history name"}function q(O,N){var K="",M,L;K+='\n <a id="history-tag" title="';L={hash:{},inverse:x.noop,fn:x.program(8,o,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='"\n class="icon-button tags" target="galaxy_main" href="javascript:void(0)"></a>\n <a id="history-annotate" title="';L={hash:{},inverse:x.noop,fn:x.program(10,I,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='"\n class="icon-button annotate" target="galaxy_main" href="javascript:void(0)"></a>\n ';return K}function o(L,K){return"Edit history tags"}function I(L,K){return"Edit history annotation"}function H(O,N){var K="",M,L;K+='\n <div id="history-tag-annotation">\n\n <div id="history-tag-area" style="display: none">\n <strong>';L={hash:{},inverse:x.noop,fn:x.program(13,G,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+=':</strong>\n <div class="tag-elt"></div>\n </div>\n\n <div id="history-annotation-area" style="display: none">\n <strong>';L={hash:{},inverse:x.noop,fn:x.program(15,F,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+=':</strong>\n <div id="history-annotation-container">\n <div id="history-annotation" class="editable-text"\n title="';L={hash:{},inverse:x.noop,fn:x.program(17,E,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='">\n ';M=A["if"].call(O,O.annotation,{hash:{},inverse:x.program(21,n,N),fn:x.program(19,D,N),data:N});if(M||M===0){K+=M}K+="\n </div>\n </div>\n </div>\n </div>\n ";return K}function G(L,K){return"Tags"}function F(L,K){return"Annotation"}function E(L,K){return"Click to edit annotation"}function D(N,M){var K="",L;K+="\n ";if(L=A.annotation){L=L.call(N,{hash:{},data:M})}else{L=N.annotation;L=typeof L===e?L.apply(N):L}K+=d(L)+"\n ";return K}function n(O,N){var K="",M,L;K+="\n <em>";L={hash:{},inverse:x.noop,fn:x.program(22,k,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+="</em>\n ";return K}function k(L,K){return"Describe or add notes to history"}function j(O,N){var K="",M,L;K+="\n ";L={hash:{},inverse:x.noop,fn:x.program(25,i,N),data:N};if(M=A.warningmessagesmall){M=M.call(O,L)}else{M=O.warningmessagesmall;M=typeof M===e?M.apply(O):M}if(!A.warningmessagesmall){M=c.call(O,M,L)}if(M||M===0){K+=M}K+="\n ";return K}function i(N,M){var L,K;K={hash:{},inverse:x.noop,fn:x.program(26,g,M),data:M};if(L=A.local){L=L.call(N,K)}else{L=N.local;L=typeof L===e?L.apply(N):L}if(!A.local){L=c.call(N,L,K)}if(L||L===0){return L}else{return""}}function g(L,K){return"You are currently viewing a deleted history!"}function f(N,M){var K="",L;K+='\n <div class="';if(L=A.status){L=L.call(N,{hash:{},data:M})}else{L=N.status;L=typeof L===e?L.apply(N):L}K+=d(L)+'message">';if(L=A.message){L=L.call(N,{hash:{},data:M})}else{L=N.message;L=typeof L===e?L.apply(N):L}K+=d(L)+"</div>\n ";return K}function z(L,K){return"You are over your disk quota"}function y(L,K){return"Tool execution is on hold until your disk usage drops below your allocated quota"}function w(L,K){return"Your history is empty. Click 'Get Data' on the left pane to start"}B+='<div id="history-controls">\n\n <div id="history-title-area" class="historyLinks">\n \n <div id="history-name-container">\n \n ';l=A["if"].call(C,((p=C.user),p==null||p===false?p:p.email),{hash:{},inverse:x.program(4,t,J),fn:x.program(1,v,J),data:J});if(l||l===0){B+=l}B+='\n </div>\n </div>\n\n <div id="history-subtitle-area">\n <div id="history-size" style="float:left;">';if(l=A.nice_size){l=l.call(C,{hash:{},data:J})}else{l=C.nice_size;l=typeof l===e?l.apply(C):l}B+=d(l)+'</div>\n\n <div id="history-secondary-links" style="float: right;">\n ';l=A["if"].call(C,((p=C.user),p==null||p===false?p:p.email),{hash:{},inverse:x.noop,fn:x.program(7,q,J),data:J});if(l||l===0){B+=l}B+='\n </div>\n <div style="clear: both;"></div>\n </div>\n\n \n \n ';l=A["if"].call(C,((p=C.user),p==null||p===false?p:p.email),{hash:{},inverse:x.noop,fn:x.program(12,H,J),data:J});if(l||l===0){B+=l}B+="\n\n ";l=A["if"].call(C,C.deleted,{hash:{},inverse:x.noop,fn:x.program(24,j,J),data:J});if(l||l===0){B+=l}B+='\n\n <div id="message-container">\n ';l=A["if"].call(C,C.message,{hash:{},inverse:x.noop,fn:x.program(28,f,J),data:J});if(l||l===0){B+=l}B+='\n </div>\n\n <div id="quota-message-container" style="display: none">\n <div id="quota-message" class="errormessage">\n ';h={hash:{},inverse:x.noop,fn:x.program(30,z,J),data:J};if(l=A.local){l=l.call(C,h)}else{l=C.local;l=typeof l===e?l.apply(C):l}if(!A.local){l=c.call(C,l,h)}if(l||l===0){B+=l}B+=".\n ";h={hash:{},inverse:x.noop,fn:x.program(32,y,J),data:J};if(l=A.local){l=l.call(C,h)}else{l=C.local;l=typeof l===e?l.apply(C):l}if(!A.local){l=c.call(C,l,h)}if(l||l===0){B+=l}B+='.\n </div>\n </div>\n</div>\n\n<div id="';if(l=A.id){l=l.call(C,{hash:{},data:J})}else{l=C.id;l=typeof l===e?l.apply(C):l}B+=d(l)+'-datasets" class="history-datasets-list"></div>\n\n<div class="infomessagesmall" id="emptyHistoryMessage" style="display: none;">\n ';h={hash:{},inverse:x.noop,fn:x.program(34,w,J),data:J};if(l=A.local){l=l.call(C,h)}else{l=C.local;l=typeof l===e?l.apply(C):l}if(!A.local){l=c.call(C,l,h)}if(l||l===0){B+=l}B+="\n</div>";return B})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-annotationArea"]=b(function(g,m,f,l,k){this.compilerInfo=[4,">= 1.0.0"];f=this.merge(f,g.helpers);k=k||{};var i="",d,p,h="function",j=this.escapeExpression,o=this,n=f.blockHelperMissing;function e(r,q){return"Annotation"}function c(r,q){return"Edit dataset annotation"}i+='\n<div id="';if(d=f.id){d=d.call(m,{hash:{},data:k})}else{d=m.id;d=typeof d===h?d.apply(m):d}i+=j(d)+'-annotation-area" class="annotation-area" style="display: none;">\n <strong>';p={hash:{},inverse:o.noop,fn:o.program(1,e,k),data:k};if(d=f.local){d=d.call(m,p)}else{d=m.local;d=typeof d===h?d.apply(m):d}if(!f.local){d=n.call(m,d,p)}if(d||d===0){i+=d}i+=':</strong>\n <div id="';if(d=f.id){d=d.call(m,{hash:{},data:k})}else{d=m.id;d=typeof d===h?d.apply(m):d}i+=j(d)+'-anotation-elt" class="annotation-elt editable-text"\n style="margin: 1px 0px 1px 0px" title="';p={hash:{},inverse:o.noop,fn:o.program(3,c,k),data:k};if(d=f.local){d=d.call(m,p)}else{d=m.local;d=typeof d===h?d.apply(m):d}if(!f.local){d=n.call(m,d,p)}if(d||d===0){i+=d}i+='">\n </div>\n</div>';return i})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-displayApps"]=b(function(h,m,g,l,k){this.compilerInfo=[4,">= 1.0.0"];g=this.merge(g,h.helpers);k=k||{};var d,i="function",j=this.escapeExpression,o=this,n=g.blockHelperMissing;function f(s,r){var p="",q;p+="\n ";if(q=g.label){q=q.call(s,{hash:{},data:r})}else{q=s.label;q=typeof q===i?q.apply(s):q}p+=j(q)+"\n ";q=g.each.call(s,s.links,{hash:{},inverse:o.noop,fn:o.program(2,e,r),data:r});if(q||q===0){p+=q}p+="\n <br />\n";return p}function e(t,s){var p="",r,q;p+='\n <a target="';if(r=g.target){r=r.call(t,{hash:{},data:s})}else{r=t.target;r=typeof r===i?r.apply(t):r}p+=j(r)+'" href="';if(r=g.href){r=r.call(t,{hash:{},data:s})}else{r=t.href;r=typeof r===i?r.apply(t):r}p+=j(r)+'">';q={hash:{},inverse:o.noop,fn:o.program(3,c,s),data:s};if(r=g.local){r=r.call(t,q)}else{r=t.local;r=typeof r===i?r.apply(t):r}if(!g.local){r=n.call(t,r,q)}if(r||r===0){p+=r}p+="</a>\n ";return p}function c(r,q){var p;if(p=g.text){p=p.call(r,{hash:{},data:q})}else{p=r.text;p=typeof p===i?p.apply(r):p}return j(p)}d=g.each.call(m,m.displayApps,{hash:{},inverse:o.noop,fn:o.program(1,f,k),data:k});if(d||d===0){return d}else{return""}})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-downloadLinks"]=b(function(g,l,f,k,j){this.compilerInfo=[4,">= 1.0.0"];f=this.merge(f,g.helpers);j=j||{};var c,s,h="function",i=this.escapeExpression,q=this,m=f.blockHelperMissing;function e(y,x){var t="",w,v,u;t+='\n<div popupmenu="dataset-';if(w=f.id){w=w.call(y,{hash:{},data:x})}else{w=y.id;w=typeof w===h?w.apply(y):w}t+=i(w)+'-popup">\n <a class="action-button" href="'+i(((w=((w=y.urls),w==null||w===false?w:w.download)),typeof w===h?w.apply(y):w))+'">';u={hash:{},inverse:q.noop,fn:q.program(2,d,x),data:x};if(v=f.local){v=v.call(y,u)}else{v=y.local;v=typeof v===h?v.apply(y):v}if(!f.local){v=m.call(y,v,u)}if(v||v===0){t+=v}t+="</a>\n <a>";u={hash:{},inverse:q.noop,fn:q.program(4,r,x),data:x};if(v=f.local){v=v.call(y,u)}else{v=y.local;v=typeof v===h?v.apply(y):v}if(!f.local){v=m.call(y,v,u)}if(v||v===0){t+=v}t+="</a>\n ";v=f.each.call(y,((w=y.urls),w==null||w===false?w:w.meta_download),{hash:{},inverse:q.noop,fn:q.program(6,p,x),data:x});if(v||v===0){t+=v}t+='\n</div>\n<div style="float:left;" class="menubutton split popup" id="dataset-';if(v=f.id){v=v.call(y,{hash:{},data:x})}else{v=y.id;v=typeof v===h?v.apply(y):v}t+=i(v)+'-popup">\n <a href="'+i(((w=((w=y.urls),w==null||w===false?w:w.download)),typeof w===h?w.apply(y):w))+'" title="';u={hash:{},inverse:q.noop,fn:q.program(7,o,x),data:x};if(v=f.local){v=v.call(y,u)}else{v=y.local;v=typeof v===h?v.apply(y):v}if(!f.local){v=m.call(y,v,u)}if(v||v===0){t+=v}t+='" class="icon-button disk"></a>\n</div>\n';return t}function d(u,t){return"Download Dataset"}function r(u,t){return"Additional Files"}function p(x,w){var t="",v,u;t+='\n <a class="action-button" href="';if(v=f.url){v=v.call(x,{hash:{},data:w})}else{v=x.url;v=typeof v===h?v.apply(x):v}t+=i(v)+'">';u={hash:{},inverse:q.noop,fn:q.program(7,o,w),data:w};if(v=f.local){v=v.call(x,u)}else{v=x.local;v=typeof v===h?v.apply(x):v}if(!f.local){v=m.call(x,v,u)}if(v||v===0){t+=v}t+=" ";if(v=f.file_type){v=v.call(x,{hash:{},data:w})}else{v=x.file_type;v=typeof v===h?v.apply(x):v}t+=i(v)+"</a>\n ";return t}function o(u,t){return"Download"}function n(y,x){var t="",w,v,u;t+='\n\n<a href="'+i(((w=((w=y.urls),w==null||w===false?w:w.download)),typeof w===h?w.apply(y):w))+'" title="';u={hash:{},inverse:q.noop,fn:q.program(7,o,x),data:x};if(v=f.local){v=v.call(y,u)}else{v=y.local;v=typeof v===h?v.apply(y):v}if(!f.local){v=m.call(y,v,u)}if(v||v===0){t+=v}t+='" class="icon-button disk"></a>\n';return t}s=f["if"].call(l,((c=l.urls),c==null||c===false?c:c.meta_download),{hash:{},inverse:q.program(9,n,j),fn:q.program(1,e,j),data:j});if(s||s===0){return s}else{return""}})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-failedMetadata"]=b(function(g,l,f,k,j){this.compilerInfo=[4,">= 1.0.0"];f=this.merge(f,g.helpers);j=j||{};var c,o,n=this,h="function",m=f.blockHelperMissing,i=this.escapeExpression;function e(t,s){var p="",r,q;p+="\n";q={hash:{},inverse:n.noop,fn:n.program(2,d,s),data:s};if(r=f.local){r=r.call(t,q)}else{r=t.local;r=typeof r===h?r.apply(t):r}if(!f.local){r=m.call(t,r,q)}if(r||r===0){p+=r}p+='\nYou may be able to <a href="'+i(((r=((r=t.urls),r==null||r===false?r:r.edit)),typeof r===h?r.apply(t):r))+'" target="galaxy_main">set it manually or retry auto-detection</a>.\n';return p}function d(q,p){return"An error occurred setting the metadata for this dataset."}o={hash:{},inverse:n.noop,fn:n.program(1,e,j),data:j};if(c=f.warningmessagesmall){c=c.call(l,o)}else{c=l.warningmessagesmall;c=typeof c===h?c.apply(l):c}if(!f.warningmessagesmall){c=m.call(l,c,o)}if(c||c===0){return c}else{return""}})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-hdaSummary"]=b(function(g,r,p,k,u){this.compilerInfo=[4,">= 1.0.0"];p=this.merge(p,g.helpers);u=u||{};var q="",h,e="function",d=this.escapeExpression,o=this,c=p.blockHelperMissing;function n(y,x){var v="",w;v+="\n ";if(w=p.misc_blurb){w=w.call(y,{hash:{},data:x})}else{w=y.misc_blurb;w=typeof w===e?w.apply(y):w}v+=d(w)+"<br />\n ";return v}function m(z,y){var v="",x,w;v+="\n ";w={hash:{},inverse:o.noop,fn:o.program(4,l,y),data:y};if(x=p.local){x=x.call(z,w)}else{x=z.local;x=typeof x===e?x.apply(z):x}if(!p.local){x=c.call(z,x,w)}if(x||x===0){v+=x}v+='<span class="';if(x=p.data_type){x=x.call(z,{hash:{},data:y})}else{x=z.data_type;x=typeof x===e?x.apply(z):x}v+=d(x)+'">';if(x=p.data_type){x=x.call(z,{hash:{},data:y})}else{x=z.data_type;x=typeof x===e?x.apply(z):x}v+=d(x)+"</span>,\n ";return v}function l(w,v){return"format: "}function j(z,y){var v="",x,w;v+="\n ";w={hash:{},inverse:o.noop,fn:o.program(7,i,y),data:y};if(x=p.local){x=x.call(z,w)}else{x=z.local;x=typeof x===e?x.apply(z):x}if(!p.local){x=c.call(z,x,w)}if(x||x===0){v+=x}v+="\n ";x=p["if"].call(z,z.dbkey_unknown_and_editable,{hash:{},inverse:o.program(11,t,y),fn:o.program(9,f,y),data:y});if(x||x===0){v+=x}v+="\n ";return v}function i(w,v){return"database: "}function f(z,y){var v="",x,w;v+='\n <a class="metadata-dbkey" href="'+d(((x=((x=z.urls),x==null||x===false?x:x.edit)),typeof x===e?x.apply(z):x))+'" target="galaxy_main">';if(w=p.metadata_dbkey){w=w.call(z,{hash:{},data:y})}else{w=z.metadata_dbkey;w=typeof w===e?w.apply(z):w}v+=d(w)+"</a>\n ";return v}function t(y,x){var v="",w;v+='\n <span class="metadata-dbkey ';if(w=p.metadata_dbkey){w=w.call(y,{hash:{},data:x})}else{w=y.metadata_dbkey;w=typeof w===e?w.apply(y):w}v+=d(w)+'">';if(w=p.metadata_dbkey){w=w.call(y,{hash:{},data:x})}else{w=y.metadata_dbkey;w=typeof w===e?w.apply(y):w}v+=d(w)+"</span>\n ";return v}function s(y,x){var v="",w;v+='\n<div class="hda-info"> ';if(w=p.misc_info){w=w.call(y,{hash:{},data:x})}else{w=y.misc_info;w=typeof w===e?w.apply(y):w}v+=d(w)+" </div>\n";return v}q+='<div class="hda-summary">\n ';h=p["if"].call(r,r.misc_blurb,{hash:{},inverse:o.noop,fn:o.program(1,n,u),data:u});if(h||h===0){q+=h}q+="\n ";h=p["if"].call(r,r.data_type,{hash:{},inverse:o.noop,fn:o.program(3,m,u),data:u});if(h||h===0){q+=h}q+="\n ";h=p["if"].call(r,r.metadata_dbkey,{hash:{},inverse:o.noop,fn:o.program(6,j,u),data:u});if(h||h===0){q+=h}q+="\n</div>\n";h=p["if"].call(r,r.misc_info,{hash:{},inverse:o.noop,fn:o.program(13,s,u),data:u});if(h||h===0){q+=h}return q})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-tagArea"]=b(function(f,k,e,j,i){this.compilerInfo=[4,">= 1.0.0"];e=this.merge(e,f.helpers);i=i||{};var h="",c,n,m=this,g="function",l=e.blockHelperMissing;function d(p,o){return"Tags"}h+='\n<div class="tag-area" style="display: none;">\n <strong>';n={hash:{},inverse:m.noop,fn:m.program(1,d,i),data:i};if(c=e.local){c=c.call(k,n)}else{c=k.local;c=typeof c===g?c.apply(k):c}if(!e.local){c=l.call(k,c,n)}if(c||c===0){h+=c}h+=':</strong>\n <div class="tag-elt">\n </div>\n</div>';return h})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-titleLink"]=b(function(e,k,d,j,i){this.compilerInfo=[4,">= 1.0.0"];d=this.merge(d,e.helpers);i=i||{};var g="",c,f="function",h=this.escapeExpression;g+='<span class="historyItemTitle">';if(c=d.hid){c=c.call(k,{hash:{},data:i})}else{c=k.hid;c=typeof c===f?c.apply(k):c}g+=h(c)+": ";if(c=d.name){c=c.call(k,{hash:{},data:i})}else{c=k.name;c=typeof c===f?c.apply(k):c}g+=h(c)+"</span>";return g})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-warning-messages"]=b(function(h,u,s,n,C){this.compilerInfo=[4,">= 1.0.0"];s=this.merge(s,h.helpers);C=C||{};var t="",j,e="function",d=this.escapeExpression,r=this,c=s.blockHelperMissing;function q(H,G){var D="",F,E;D+='\n<div class="errormessagesmall">\n ';E={hash:{},inverse:r.noop,fn:r.program(2,p,G),data:G};if(F=s.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!s.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+=":\n ";E={hash:{},inverse:r.noop,fn:r.program(4,o,G),data:G};if(F=s.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!s.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+="\n</div>\n";return D}function p(E,D){return"There was an error getting the data for this dataset"}function o(F,E){var D;if(D=s.error){D=D.call(F,{hash:{},data:E})}else{D=F.error;D=typeof D===e?D.apply(F):D}return d(D)}function m(F,E){var D;D=s.unless.call(F,F.purged,{hash:{},inverse:r.noop,fn:r.program(7,l,E),data:E});if(D||D===0){return D}else{return""}}function l(H,G){var D="",F,E;D+="\n";E={hash:{},inverse:r.noop,fn:r.program(8,i,G),data:G};if(F=s.warningmessagesmall){F=F.call(H,E)}else{F=H.warningmessagesmall;F=typeof F===e?F.apply(H):F}if(!s.warningmessagesmall){F=c.call(H,F,E)}if(F||F===0){D+=F}D+="\n";return D}function i(I,H){var D="",G,F,E;D+="\n ";E={hash:{},inverse:r.noop,fn:r.program(9,g,H),data:H};if(G=s.local){G=G.call(I,E)}else{G=I.local;G=typeof G===e?G.apply(I):G}if(!s.local){G=c.call(I,G,E)}if(G||G===0){D+=G}D+="\n ";F=s["if"].call(I,((G=I.urls),G==null||G===false?G:G.undelete),{hash:{},inverse:r.noop,fn:r.program(11,B,H),data:H});if(F||F===0){D+=F}D+="\n";return D}function g(E,D){return"This dataset has been deleted."}function B(H,G){var D="",F,E;D+='\n \n Click <a href="'+d(((F=((F=H.urls),F==null||F===false?F:F.undelete)),typeof F===e?F.apply(H):F))+'" class="historyItemUndelete" id="historyItemUndeleter-';if(E=s.id){E=E.call(H,{hash:{},data:G})}else{E=H.id;E=typeof E===e?E.apply(H):E}D+=d(E)+'"\n target="galaxy_history">here</a> to undelete it\n ';E=s["if"].call(H,((F=H.urls),F==null||F===false?F:F.purge),{hash:{},inverse:r.noop,fn:r.program(12,A,G),data:G});if(E||E===0){D+=E}D+="\n ";return D}function A(H,G){var D="",F,E;D+='\n or <a href="'+d(((F=((F=H.urls),F==null||F===false?F:F.purge)),typeof F===e?F.apply(H):F))+'" class="historyItemPurge" id="historyItemPurger-';if(E=s.id){E=E.call(H,{hash:{},data:G})}else{E=H.id;E=typeof E===e?E.apply(H):E}D+=d(E)+'"\n target="galaxy_history">here</a> to immediately remove it from disk\n ';return D}function z(G,F){var E,D;D={hash:{},inverse:r.noop,fn:r.program(15,y,F),data:F};if(E=s.warningmessagesmall){E=E.call(G,D)}else{E=G.warningmessagesmall;E=typeof E===e?E.apply(G):E}if(!s.warningmessagesmall){E=c.call(G,E,D)}if(E||E===0){return E}else{return""}}function y(H,G){var D="",F,E;D+="\n ";E={hash:{},inverse:r.noop,fn:r.program(16,x,G),data:G};if(F=s.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!s.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+="\n";return D}function x(E,D){return"This dataset has been deleted and removed from disk."}function w(G,F){var E,D;D={hash:{},inverse:r.noop,fn:r.program(19,v,F),data:F};if(E=s.warningmessagesmall){E=E.call(G,D)}else{E=G.warningmessagesmall;E=typeof E===e?E.apply(G):E}if(!s.warningmessagesmall){E=c.call(G,E,D)}if(E||E===0){return E}else{return""}}function v(I,H){var D="",G,F,E;D+="\n ";E={hash:{},inverse:r.noop,fn:r.program(20,k,H),data:H};if(G=s.local){G=G.call(I,E)}else{G=I.local;G=typeof G===e?G.apply(I):G}if(!s.local){G=c.call(I,G,E)}if(G||G===0){D+=G}D+="\n ";F=s["if"].call(I,((G=I.urls),G==null||G===false?G:G.unhide),{hash:{},inverse:r.noop,fn:r.program(22,f,H),data:H});if(F||F===0){D+=F}D+="\n";return D}function k(E,D){return"This dataset has been hidden."}function f(H,G){var D="",F,E;D+='\n Click <a href="'+d(((F=((F=H.urls),F==null||F===false?F:F.unhide)),typeof F===e?F.apply(H):F))+'" class="historyItemUnhide" id="historyItemUnhider-';if(E=s.id){E=E.call(H,{hash:{},data:G})}else{E=H.id;E=typeof E===e?E.apply(H):E}D+=d(E)+'"\n target="galaxy_history">here</a> to unhide it\n ';return D}j=s["if"].call(u,u.error,{hash:{},inverse:r.noop,fn:r.program(1,q,C),data:C});if(j||j===0){t+=j}t+="\n\n";j=s["if"].call(u,u.deleted,{hash:{},inverse:r.noop,fn:r.program(6,m,C),data:C});if(j||j===0){t+=j}t+="\n\n";j=s["if"].call(u,u.purged,{hash:{},inverse:r.noop,fn:r.program(14,z,C),data:C});if(j||j===0){t+=j}t+="\n\n";j=s.unless.call(u,u.visible,{hash:{},inverse:r.noop,fn:r.program(18,w,C),data:C});if(j||j===0){t+=j}return t})})();
\ No newline at end of file
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-body"]=b(function(j,v,t,o,C){this.compilerInfo=[4,">= 1.0.0"];t=this.merge(t,j.helpers);C=C||{};var u="",l,e="function",d=this.escapeExpression,s=this,c=t.blockHelperMissing;function r(G,F){var D="",E;D+="\n ";if(E=t.body){E=E.call(G,{hash:{},data:F})}else{E=G.body;E=typeof E===e?E.apply(G):E}if(E||E===0){D+=E}D+='\n <div class="dataset-actions clear">\n <div class="left"></div>\n <div class="right"></div>\n </div>\n\n ';return D}function q(G,F){var D="",E;D+='\n <div class="dataset-summary">\n ';E=t["if"].call(G,G.misc_blurb,{hash:{},inverse:s.noop,fn:s.program(4,p,F),data:F});if(E||E===0){D+=E}D+="\n\n ";E=t["if"].call(G,G.data_type,{hash:{},inverse:s.noop,fn:s.program(6,n,F),data:F});if(E||E===0){D+=E}D+="\n\n ";E=t["if"].call(G,G.metadata_dbkey,{hash:{},inverse:s.noop,fn:s.program(9,i,F),data:F});if(E||E===0){D+=E}D+="\n\n ";E=t["if"].call(G,G.misc_info,{hash:{},inverse:s.noop,fn:s.program(12,A,F),data:F});if(E||E===0){D+=E}D+='\n </div>\n\n <div class="dataset-actions clear">\n <div class="left"></div>\n <div class="right"></div>\n </div>\n\n ';E=t.unless.call(G,G.deleted,{hash:{},inverse:s.noop,fn:s.program(14,z,F),data:F});if(E||E===0){D+=E}D+="\n\n ";return D}function p(G,F){var D="",E;D+='\n <div class="dataset-blurb">\n <span class="value">';if(E=t.misc_blurb){E=E.call(G,{hash:{},data:F})}else{E=G.misc_blurb;E=typeof E===e?E.apply(G):E}D+=d(E)+"</span>\n </div>\n ";return D}function n(H,G){var D="",F,E;D+='\n <div class="dataset-datatype">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(7,m,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <span class="value">';if(F=t.data_type){F=F.call(H,{hash:{},data:G})}else{F=H.data_type;F=typeof F===e?F.apply(H):F}D+=d(F)+"</span>\n </div>\n ";return D}function m(E,D){return"format"}function i(H,G){var D="",F,E;D+='\n <div class="dataset-dbkey">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(10,B,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <span class="value">\n ';if(F=t.metadata_dbkey){F=F.call(H,{hash:{},data:G})}else{F=H.metadata_dbkey;F=typeof F===e?F.apply(H):F}D+=d(F)+"\n </span>\n </div>\n ";return D}function B(E,D){return"database"}function A(G,F){var D="",E;D+='\n <div class="dataset-info">\n <span class="value">';if(E=t.misc_info){E=E.call(G,{hash:{},data:F})}else{E=G.misc_info;E=typeof E===e?E.apply(G):E}D+=d(E)+"</span>\n </div>\n ";return D}function z(H,G){var D="",F,E;D+='\n \n <div class="tags-display">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(15,y,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <div class="tags"></div>\n </div>\n\n \n <div class="annotation-display">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(17,x,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <div id="dataset-';if(F=t.id){F=F.call(H,{hash:{},data:G})}else{F=H.id;F=typeof F===e?F.apply(H):F}D+=d(F)+'-annotation" class="annotation editable-text"\n title="';E={hash:{},inverse:s.noop,fn:s.program(19,w,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='"></div>\n </div>\n\n <div class="dataset-display-applications">\n ';F=t.each.call(H,H.display_apps,{hash:{},inverse:s.noop,fn:s.program(21,k,G),data:G});if(F||F===0){D+=F}D+="\n\n ";F=t.each.call(H,H.display_types,{hash:{},inverse:s.noop,fn:s.program(21,k,G),data:G});if(F||F===0){D+=F}D+='\n </div>\n\n <div class="dataset-peek">\n ';F=t["if"].call(H,H.peek,{hash:{},inverse:s.noop,fn:s.program(25,f,G),data:G});if(F||F===0){D+=F}D+="\n </div>\n\n ";return D}function y(E,D){return"Tags"}function x(E,D){return"Annotation"}function w(E,D){return"Edit dataset annotation"}function k(G,F){var D="",E;D+='\n <div class="display-application">\n <span class="display-application-location">';if(E=t.label){E=E.call(G,{hash:{},data:F})}else{E=G.label;E=typeof E===e?E.apply(G):E}D+=d(E)+'</span>\n <span class="display-application-links">\n ';E=t.each.call(G,G.links,{hash:{},inverse:s.noop,fn:s.program(22,h,F),data:F});if(E||E===0){D+=E}D+="\n </span>\n </div>\n ";return D}function h(H,G){var D="",F,E;D+='\n <a target="';if(F=t.target){F=F.call(H,{hash:{},data:G})}else{F=H.target;F=typeof F===e?F.apply(H):F}D+=d(F)+'" href="';if(F=t.href){F=F.call(H,{hash:{},data:G})}else{F=H.href;F=typeof F===e?F.apply(H):F}D+=d(F)+'">';E={hash:{},inverse:s.noop,fn:s.program(23,g,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+="</a>\n ";return D}function g(F,E){var D;if(D=t.text){D=D.call(F,{hash:{},data:E})}else{D=F.text;D=typeof D===e?D.apply(F):D}return d(D)}function f(G,F){var D="",E;D+='\n <pre class="peek">';if(E=t.peek){E=E.call(G,{hash:{},data:F})}else{E=G.peek;E=typeof E===e?E.apply(G):E}if(E||E===0){D+=E}D+="</pre>\n ";return D}u+='<div class="dataset-body">\n ';l=t["if"].call(v,v.body,{hash:{},inverse:s.program(3,q,C),fn:s.program(1,r,C),data:C});if(l||l===0){u+=l}u+="\n</div>";return u})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-skeleton"]=b(function(f,r,p,k,w){this.compilerInfo=[4,">= 1.0.0"];p=this.merge(p,f.helpers);w=w||{};var q="",h,e="function",d=this.escapeExpression,o=this,c=p.blockHelperMissing;function n(B,A){var x="",z,y;x+='\n <div class="errormessagesmall">\n ';y={hash:{},inverse:o.noop,fn:o.program(2,m,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+=":\n ";y={hash:{},inverse:o.noop,fn:o.program(4,l,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+="\n </div>\n ";return x}function m(y,x){return"There was an error getting the data for this dataset"}function l(z,y){var x;if(x=p.error){x=x.call(z,{hash:{},data:y})}else{x=z.error;x=typeof x===e?x.apply(z):x}return d(x)}function j(A,z){var x="",y;x+="\n ";y=p["if"].call(A,A.purged,{hash:{},inverse:o.program(10,v,z),fn:o.program(7,i,z),data:z});if(y||y===0){x+=y}x+="\n ";return x}function i(B,A){var x="",z,y;x+='\n <div class="warningmessagesmall"><strong>\n ';y={hash:{},inverse:o.noop,fn:o.program(8,g,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+="\n </strong></div>\n\n ";return x}function g(y,x){return"This dataset has been deleted and removed from disk."}function v(B,A){var x="",z,y;x+='\n <div class="warningmessagesmall"><strong>\n ';y={hash:{},inverse:o.noop,fn:o.program(11,u,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+='\n \n \n Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it\n or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk\n </strong></div>\n ';return x}function u(y,x){return"This dataset has been deleted."}function t(B,A){var x="",z,y;x+='\n <div class="warningmessagesmall"><strong>\n ';y={hash:{},inverse:o.noop,fn:o.program(14,s,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+='\n \n Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it\n </strong></div>\n ';return x}function s(y,x){return"This dataset has been hidden."}q+='<div class="dataset hda">\n <div class="dataset-warnings">\n ';h=p["if"].call(r,r.error,{hash:{},inverse:o.noop,fn:o.program(1,n,w),data:w});if(h||h===0){q+=h}q+="\n\n ";h=p["if"].call(r,r.deleted,{hash:{},inverse:o.noop,fn:o.program(6,j,w),data:w});if(h||h===0){q+=h}q+="\n\n ";h=p.unless.call(r,r.visible,{hash:{},inverse:o.noop,fn:o.program(13,t,w),data:w});if(h||h===0){q+=h}q+='\n </div>\n\n <div class="dataset-primary-actions"></div>\n\n <div class="dataset-title-bar">\n <span class="dataset-state-icon state-icon"></span>\n <div class="dataset-title">\n <span class="hda-hid">';if(h=p.hid){h=h.call(r,{hash:{},data:w})}else{h=r.hid;h=typeof h===e?h.apply(r):h}q+=d(h)+'</span>\n <span class="dataset-name">';if(h=p.name){h=h.call(r,{hash:{},data:w})}else{h=r.name;h=typeof h===e?h.apply(r):h}q+=d(h)+'</span>\n </div>\n </div>\n <div class="clear"></div>\n\n <div class="dataset-body"></div>\n</div>';return q})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-historyPanel-anon"]=b(function(g,r,p,k,u){this.compilerInfo=[4,">= 1.0.0"];p=this.merge(p,g.helpers);u=u||{};var q="",i,f,o=this,e="function",c=p.blockHelperMissing,d=this.escapeExpression;function n(z,y){var v="",x,w;v+='\n <div class="history-name" title="';w={hash:{},inverse:o.noop,fn:o.program(2,m,y),data:y};if(x=p.local){x=x.call(z,w)}else{x=z.local;x=typeof x===e?x.apply(z):x}if(!p.local){x=c.call(z,x,w)}if(x||x===0){v+=x}v+='">\n ';if(x=p.name){x=x.call(z,{hash:{},data:y})}else{x=z.name;x=typeof x===e?x.apply(z):x}v+=d(x)+"\n </div>\n ";return v}function m(w,v){return"You must be logged in to edit your history name"}function l(y,x){var v="",w;v+='\n <div class="history-size">';if(w=p.nice_size){w=w.call(y,{hash:{},data:x})}else{w=y.nice_size;w=typeof w===e?w.apply(y):w}v+=d(w)+"</div>\n ";return v}function j(y,x){var v="",w;v+='\n \n <div class="';if(w=p.status){w=w.call(y,{hash:{},data:x})}else{w=y.status;w=typeof w===e?w.apply(y):w}v+=d(w)+'message">';if(w=p.message){w=w.call(y,{hash:{},data:x})}else{w=y.message;w=typeof w===e?w.apply(y):w}v+=d(w)+"</div>\n ";return v}function h(w,v){return"You are over your disk quota"}function t(w,v){return"Tool execution is on hold until your disk usage drops below your allocated quota"}function s(w,v){return"Your history is empty. Click 'Get Data' on the left pane to start"}q+='<div class="history-controls">\n\n <div class="history-title">\n \n ';i=p["if"].call(r,r.name,{hash:{},inverse:o.noop,fn:o.program(1,n,u),data:u});if(i||i===0){q+=i}q+='\n </div>\n\n <div class="history-subtitle clear">\n ';i=p["if"].call(r,r.nice_size,{hash:{},inverse:o.noop,fn:o.program(4,l,u),data:u});if(i||i===0){q+=i}q+='\n </div>\n\n <div class="message-container">\n ';i=p["if"].call(r,r.message,{hash:{},inverse:o.noop,fn:o.program(6,j,u),data:u});if(i||i===0){q+=i}q+='\n </div>\n\n <div class="quota-message errormessage">\n ';f={hash:{},inverse:o.noop,fn:o.program(8,h,u),data:u};if(i=p.local){i=i.call(r,f)}else{i=r.local;i=typeof i===e?i.apply(r):i}if(!p.local){i=c.call(r,i,f)}if(i||i===0){q+=i}q+=".\n ";f={hash:{},inverse:o.noop,fn:o.program(10,t,u),data:u};if(i=p.local){i=i.call(r,f)}else{i=r.local;i=typeof i===e?i.apply(r):i}if(!p.local){i=c.call(r,i,f)}if(i||i===0){q+=i}q+='.\n </div>\n\n </div>\n\n \n <div class="datasets-list"></div>\n\n <div class="empty-history-message infomessagesmall">\n ';f={hash:{},inverse:o.noop,fn:o.program(12,s,u),data:u};if(i=p.local){i=i.call(r,f)}else{i=r.local;i=typeof i===e?i.apply(r):i}if(!p.local){i=c.call(r,i,f)}if(i||i===0){q+=i}q+="\n </div>";return q})})();(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-historyPanel"]=b(function(k,x,v,p,E){this.compilerInfo=[4,">= 1.0.0"];v=this.merge(v,k.helpers);E=E||{};var w="",n,h,t=this,e="function",c=v.blockHelperMissing,d=this.escapeExpression;function s(J,I){var F="",H,G;F+='\n <div class="history-name editable-text" title="';G={hash:{},inverse:t.noop,fn:t.program(2,r,I),data:I};if(H=v.local){H=H.call(J,G)}else{H=J.local;H=typeof H===e?H.apply(J):H}if(!v.local){H=c.call(J,H,G)}if(H||H===0){F+=H}F+='">\n ';if(H=v.name){H=H.call(J,{hash:{},data:I})}else{H=J.name;H=typeof H===e?H.apply(J):H}F+=d(H)+"\n </div>\n ";return F}function r(G,F){return"Click to rename history"}function q(I,H){var F="",G;F+='\n <div class="history-size">';if(G=v.nice_size){G=G.call(I,{hash:{},data:H})}else{G=I.nice_size;G=typeof G===e?G.apply(I):G}F+=d(G)+"</div>\n ";return F}function o(G,F){return"Edit history tags"}function m(G,F){return"Edit history annotation"}function D(J,I){var F="",H,G;F+='\n <div class="warningmessagesmall"><strong>\n ';G={hash:{},inverse:t.noop,fn:t.program(11,C,I),data:I};if(H=v.local){H=H.call(J,G)}else{H=J.local;H=typeof H===e?H.apply(J):H}if(!v.local){H=c.call(J,H,G)}if(H||H===0){F+=H}F+="\n </strong></div>\n ";return F}function C(G,F){return"You are currently viewing a deleted history!"}function B(I,H){var F="",G;F+='\n \n <div class="';if(G=v.status){G=G.call(I,{hash:{},data:H})}else{G=I.status;G=typeof G===e?G.apply(I):G}F+=d(G)+'message">';if(G=v.message){G=G.call(I,{hash:{},data:H})}else{G=I.message;G=typeof G===e?G.apply(I):G}F+=d(G)+"</div>\n ";return F}function A(G,F){return"You are over your disk quota"}function z(G,F){return"Tool execution is on hold until your disk usage drops below your allocated quota"}function y(G,F){return"Tags"}function l(G,F){return"Annotation"}function j(G,F){return"Click to edit annotation"}function i(I,H){var F="",G;F+="\n ";if(G=v.annotation){G=G.call(I,{hash:{},data:H})}else{G=I.annotation;G=typeof G===e?G.apply(I):G}F+=d(G)+"\n ";return F}function g(J,I){var F="",H,G;F+="\n <em>";G={hash:{},inverse:t.noop,fn:t.program(28,f,I),data:I};if(H=v.local){H=H.call(J,G)}else{H=J.local;H=typeof H===e?H.apply(J):H}if(!v.local){H=c.call(J,H,G)}if(H||H===0){F+=H}F+="</em>\n ";return F}function f(G,F){return"Describe or add notes to history"}function u(G,F){return"Your history is empty. Click 'Get Data' on the left pane to start"}w+='<div class="history-controls">\n\n <div class="history-title">\n ';n=v["if"].call(x,x.name,{hash:{},inverse:t.noop,fn:t.program(1,s,E),data:E});if(n||n===0){w+=n}w+='\n </div>\n\n <div class="history-subtitle clear">\n ';n=v["if"].call(x,x.nice_size,{hash:{},inverse:t.noop,fn:t.program(4,q,E),data:E});if(n||n===0){w+=n}w+='\n\n <div class="history-secondary-actions">\n <a title="';h={hash:{},inverse:t.noop,fn:t.program(6,o,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='"\n class="icon-button tags" href="javascript:void(0)"></a>\n <a title="';h={hash:{},inverse:t.noop,fn:t.program(8,m,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='"\n class="icon-button annotate" href="javascript:void(0)"></a>\n </div>\n </div>\n\n ';n=v["if"].call(x,x.deleted,{hash:{},inverse:t.noop,fn:t.program(10,D,E),data:E});if(n||n===0){w+=n}w+='\n\n <div class="message-container">\n ';n=v["if"].call(x,x.message,{hash:{},inverse:t.noop,fn:t.program(13,B,E),data:E});if(n||n===0){w+=n}w+='\n </div>\n\n <div class="quota-message errormessage">\n ';h={hash:{},inverse:t.noop,fn:t.program(15,A,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+=".\n ";h={hash:{},inverse:t.noop,fn:t.program(17,z,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='.\n </div>\n \n \n \n <div class="tags-display">\n <label class="prompt">';h={hash:{},inverse:t.noop,fn:t.program(19,y,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='</label>\n <div class="tags"></div>\n </div>\n\n \n <div class="annotation-display">\n <label class="prompt">';h={hash:{},inverse:t.noop,fn:t.program(21,l,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='</label>\n <div class="annotation editable-text" title="';h={hash:{},inverse:t.noop,fn:t.program(23,j,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='">\n ';n=v["if"].call(x,x.annotation,{hash:{},inverse:t.program(27,g,E),fn:t.program(25,i,E),data:E});if(n||n===0){w+=n}w+='\n </div>\n </div>\n\n </div>\n\n \n <div class="datasets-list"></div>\n\n <div class="empty-history-message infomessagesmall">\n ';h={hash:{},inverse:t.noop,fn:t.program(30,u,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+="\n </div>";return w})})();
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/templates/compiled/template-hda-body.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-hda-body.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-body"]=b(function(j,v,t,o,C){this.compilerInfo=[4,">= 1.0.0"];t=this.merge(t,j.helpers);C=C||{};var u="",l,e="function",d=this.escapeExpression,s=this,c=t.blockHelperMissing;function r(G,F){var D="",E;D+="\n ";if(E=t.body){E=E.call(G,{hash:{},data:F})}else{E=G.body;E=typeof E===e?E.apply(G):E}if(E||E===0){D+=E}D+='\n <div class="dataset-actions clear">\n <div class="left"></div>\n <div class="right"></div>\n </div>\n\n ';return D}function q(G,F){var D="",E;D+='\n <div class="dataset-summary">\n ';E=t["if"].call(G,G.misc_blurb,{hash:{},inverse:s.noop,fn:s.program(4,p,F),data:F});if(E||E===0){D+=E}D+="\n\n ";E=t["if"].call(G,G.data_type,{hash:{},inverse:s.noop,fn:s.program(6,n,F),data:F});if(E||E===0){D+=E}D+="\n\n ";E=t["if"].call(G,G.metadata_dbkey,{hash:{},inverse:s.noop,fn:s.program(9,i,F),data:F});if(E||E===0){D+=E}D+="\n\n ";E=t["if"].call(G,G.misc_info,{hash:{},inverse:s.noop,fn:s.program(12,A,F),data:F});if(E||E===0){D+=E}D+='\n </div>\n\n <div class="dataset-actions clear">\n <div class="left"></div>\n <div class="right"></div>\n </div>\n\n ';E=t.unless.call(G,G.deleted,{hash:{},inverse:s.noop,fn:s.program(14,z,F),data:F});if(E||E===0){D+=E}D+="\n\n ";return D}function p(G,F){var D="",E;D+='\n <div class="dataset-blurb">\n <span class="value">';if(E=t.misc_blurb){E=E.call(G,{hash:{},data:F})}else{E=G.misc_blurb;E=typeof E===e?E.apply(G):E}D+=d(E)+"</span>\n </div>\n ";return D}function n(H,G){var D="",F,E;D+='\n <div class="dataset-datatype">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(7,m,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <span class="value">';if(F=t.data_type){F=F.call(H,{hash:{},data:G})}else{F=H.data_type;F=typeof F===e?F.apply(H):F}D+=d(F)+"</span>\n </div>\n ";return D}function m(E,D){return"format"}function i(H,G){var D="",F,E;D+='\n <div class="dataset-dbkey">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(10,B,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <span class="value">\n ';if(F=t.metadata_dbkey){F=F.call(H,{hash:{},data:G})}else{F=H.metadata_dbkey;F=typeof F===e?F.apply(H):F}D+=d(F)+"\n </span>\n </div>\n ";return D}function B(E,D){return"database"}function A(G,F){var D="",E;D+='\n <div class="dataset-info">\n <span class="value">';if(E=t.misc_info){E=E.call(G,{hash:{},data:F})}else{E=G.misc_info;E=typeof E===e?E.apply(G):E}D+=d(E)+"</span>\n </div>\n ";return D}function z(H,G){var D="",F,E;D+='\n \n <div class="tags-display">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(15,y,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <div class="tags"></div>\n </div>\n\n \n <div class="annotation-display">\n <label class="prompt">';E={hash:{},inverse:s.noop,fn:s.program(17,x,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='</label>\n <div id="dataset-';if(F=t.id){F=F.call(H,{hash:{},data:G})}else{F=H.id;F=typeof F===e?F.apply(H):F}D+=d(F)+'-annotation" class="annotation editable-text"\n title="';E={hash:{},inverse:s.noop,fn:s.program(19,w,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+='"></div>\n </div>\n\n <div class="dataset-display-applications">\n ';F=t.each.call(H,H.display_apps,{hash:{},inverse:s.noop,fn:s.program(21,k,G),data:G});if(F||F===0){D+=F}D+="\n\n ";F=t.each.call(H,H.display_types,{hash:{},inverse:s.noop,fn:s.program(21,k,G),data:G});if(F||F===0){D+=F}D+='\n </div>\n\n <div class="dataset-peek">\n ';F=t["if"].call(H,H.peek,{hash:{},inverse:s.noop,fn:s.program(25,f,G),data:G});if(F||F===0){D+=F}D+="\n </div>\n\n ";return D}function y(E,D){return"Tags"}function x(E,D){return"Annotation"}function w(E,D){return"Edit dataset annotation"}function k(G,F){var D="",E;D+='\n <div class="display-application">\n <span class="display-application-location">';if(E=t.label){E=E.call(G,{hash:{},data:F})}else{E=G.label;E=typeof E===e?E.apply(G):E}D+=d(E)+'</span>\n <span class="display-application-links">\n ';E=t.each.call(G,G.links,{hash:{},inverse:s.noop,fn:s.program(22,h,F),data:F});if(E||E===0){D+=E}D+="\n </span>\n </div>\n ";return D}function h(H,G){var D="",F,E;D+='\n <a target="';if(F=t.target){F=F.call(H,{hash:{},data:G})}else{F=H.target;F=typeof F===e?F.apply(H):F}D+=d(F)+'" href="';if(F=t.href){F=F.call(H,{hash:{},data:G})}else{F=H.href;F=typeof F===e?F.apply(H):F}D+=d(F)+'">';E={hash:{},inverse:s.noop,fn:s.program(23,g,G),data:G};if(F=t.local){F=F.call(H,E)}else{F=H.local;F=typeof F===e?F.apply(H):F}if(!t.local){F=c.call(H,F,E)}if(F||F===0){D+=F}D+="</a>\n ";return D}function g(F,E){var D;if(D=t.text){D=D.call(F,{hash:{},data:E})}else{D=F.text;D=typeof D===e?D.apply(F):D}return d(D)}function f(G,F){var D="",E;D+='\n <pre class="peek">';if(E=t.peek){E=E.call(G,{hash:{},data:F})}else{E=G.peek;E=typeof E===e?E.apply(G):E}if(E||E===0){D+=E}D+="</pre>\n ";return D}u+='<div class="dataset-body">\n ';l=t["if"].call(v,v.body,{hash:{},inverse:s.program(3,q,C),fn:s.program(1,r,C),data:C});if(l||l===0){u+=l}u+="\n</div>";return u})})();
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/templates/compiled/template-hda-skeleton.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-hda-skeleton.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-hda-skeleton"]=b(function(f,r,p,k,w){this.compilerInfo=[4,">= 1.0.0"];p=this.merge(p,f.helpers);w=w||{};var q="",h,e="function",d=this.escapeExpression,o=this,c=p.blockHelperMissing;function n(B,A){var x="",z,y;x+='\n <div class="errormessagesmall">\n ';y={hash:{},inverse:o.noop,fn:o.program(2,m,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+=":\n ";y={hash:{},inverse:o.noop,fn:o.program(4,l,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+="\n </div>\n ";return x}function m(y,x){return"There was an error getting the data for this dataset"}function l(z,y){var x;if(x=p.error){x=x.call(z,{hash:{},data:y})}else{x=z.error;x=typeof x===e?x.apply(z):x}return d(x)}function j(A,z){var x="",y;x+="\n ";y=p["if"].call(A,A.purged,{hash:{},inverse:o.program(10,v,z),fn:o.program(7,i,z),data:z});if(y||y===0){x+=y}x+="\n ";return x}function i(B,A){var x="",z,y;x+='\n <div class="warningmessagesmall"><strong>\n ';y={hash:{},inverse:o.noop,fn:o.program(8,g,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+="\n </strong></div>\n\n ";return x}function g(y,x){return"This dataset has been deleted and removed from disk."}function v(B,A){var x="",z,y;x+='\n <div class="warningmessagesmall"><strong>\n ';y={hash:{},inverse:o.noop,fn:o.program(11,u,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+='\n \n \n Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it\n or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk\n </strong></div>\n ';return x}function u(y,x){return"This dataset has been deleted."}function t(B,A){var x="",z,y;x+='\n <div class="warningmessagesmall"><strong>\n ';y={hash:{},inverse:o.noop,fn:o.program(14,s,A),data:A};if(z=p.local){z=z.call(B,y)}else{z=B.local;z=typeof z===e?z.apply(B):z}if(!p.local){z=c.call(B,z,y)}if(z||z===0){x+=z}x+='\n \n Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it\n </strong></div>\n ';return x}function s(y,x){return"This dataset has been hidden."}q+='<div class="dataset hda">\n <div class="dataset-warnings">\n ';h=p["if"].call(r,r.error,{hash:{},inverse:o.noop,fn:o.program(1,n,w),data:w});if(h||h===0){q+=h}q+="\n\n ";h=p["if"].call(r,r.deleted,{hash:{},inverse:o.noop,fn:o.program(6,j,w),data:w});if(h||h===0){q+=h}q+="\n\n ";h=p.unless.call(r,r.visible,{hash:{},inverse:o.noop,fn:o.program(13,t,w),data:w});if(h||h===0){q+=h}q+='\n </div>\n\n <div class="dataset-primary-actions"></div>\n\n <div class="dataset-title-bar">\n <span class="dataset-state-icon state-icon"></span>\n <div class="dataset-title">\n <span class="hda-hid">';if(h=p.hid){h=h.call(r,{hash:{},data:w})}else{h=r.hid;h=typeof h===e?h.apply(r):h}q+=d(h)+'</span>\n <span class="dataset-name">';if(h=p.name){h=h.call(r,{hash:{},data:w})}else{h=r.name;h=typeof h===e?h.apply(r):h}q+=d(h)+'</span>\n </div>\n </div>\n <div class="clear"></div>\n\n <div class="dataset-body"></div>\n</div>';return q})})();
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/templates/compiled/template-history-historyPanel-anon.js
--- /dev/null
+++ b/static/scripts/packed/templates/compiled/template-history-historyPanel-anon.js
@@ -0,0 +1,1 @@
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-historyPanel-anon"]=b(function(g,r,p,k,u){this.compilerInfo=[4,">= 1.0.0"];p=this.merge(p,g.helpers);u=u||{};var q="",i,f,o=this,e="function",c=p.blockHelperMissing,d=this.escapeExpression;function n(z,y){var v="",x,w;v+='\n <div class="history-name" title="';w={hash:{},inverse:o.noop,fn:o.program(2,m,y),data:y};if(x=p.local){x=x.call(z,w)}else{x=z.local;x=typeof x===e?x.apply(z):x}if(!p.local){x=c.call(z,x,w)}if(x||x===0){v+=x}v+='">\n ';if(x=p.name){x=x.call(z,{hash:{},data:y})}else{x=z.name;x=typeof x===e?x.apply(z):x}v+=d(x)+"\n </div>\n ";return v}function m(w,v){return"You must be logged in to edit your history name"}function l(y,x){var v="",w;v+='\n <div class="history-size">';if(w=p.nice_size){w=w.call(y,{hash:{},data:x})}else{w=y.nice_size;w=typeof w===e?w.apply(y):w}v+=d(w)+"</div>\n ";return v}function j(y,x){var v="",w;v+='\n \n <div class="';if(w=p.status){w=w.call(y,{hash:{},data:x})}else{w=y.status;w=typeof w===e?w.apply(y):w}v+=d(w)+'message">';if(w=p.message){w=w.call(y,{hash:{},data:x})}else{w=y.message;w=typeof w===e?w.apply(y):w}v+=d(w)+"</div>\n ";return v}function h(w,v){return"You are over your disk quota"}function t(w,v){return"Tool execution is on hold until your disk usage drops below your allocated quota"}function s(w,v){return"Your history is empty. Click 'Get Data' on the left pane to start"}q+='<div class="history-controls">\n\n <div class="history-title">\n \n ';i=p["if"].call(r,r.name,{hash:{},inverse:o.noop,fn:o.program(1,n,u),data:u});if(i||i===0){q+=i}q+='\n </div>\n\n <div class="history-subtitle clear">\n ';i=p["if"].call(r,r.nice_size,{hash:{},inverse:o.noop,fn:o.program(4,l,u),data:u});if(i||i===0){q+=i}q+='\n </div>\n\n <div class="message-container">\n ';i=p["if"].call(r,r.message,{hash:{},inverse:o.noop,fn:o.program(6,j,u),data:u});if(i||i===0){q+=i}q+='\n </div>\n\n <div class="quota-message errormessage">\n ';f={hash:{},inverse:o.noop,fn:o.program(8,h,u),data:u};if(i=p.local){i=i.call(r,f)}else{i=r.local;i=typeof i===e?i.apply(r):i}if(!p.local){i=c.call(r,i,f)}if(i||i===0){q+=i}q+=".\n ";f={hash:{},inverse:o.noop,fn:o.program(10,t,u),data:u};if(i=p.local){i=i.call(r,f)}else{i=r.local;i=typeof i===e?i.apply(r):i}if(!p.local){i=c.call(r,i,f)}if(i||i===0){q+=i}q+='.\n </div>\n\n </div>\n\n \n <div class="datasets-list"></div>\n\n <div class="empty-history-message infomessagesmall">\n ';f={hash:{},inverse:o.noop,fn:o.program(12,s,u),data:u};if(i=p.local){i=i.call(r,f)}else{i=r.local;i=typeof i===e?i.apply(r):i}if(!p.local){i=c.call(r,i,f)}if(i||i===0){q+=i}q+="\n </div>";return q})})();
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/packed/templates/compiled/template-history-historyPanel.js
--- a/static/scripts/packed/templates/compiled/template-history-historyPanel.js
+++ b/static/scripts/packed/templates/compiled/template-history-historyPanel.js
@@ -1,1 +1,1 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-historyPanel"]=b(function(m,C,A,s,J){this.compilerInfo=[4,">= 1.0.0"];A=this.merge(A,m.helpers);J=J||{};var B="",p,l,h,x=this,e="function",c=A.blockHelperMissing,d=this.escapeExpression;function v(O,N){var K="",M,L;K+='\n <div id="history-name" class="editable-text"\n title="';L={hash:{},inverse:x.noop,fn:x.program(2,u,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='">';if(M=A.name){M=M.call(O,{hash:{},data:N})}else{M=O.name;M=typeof M===e?M.apply(O):M}K+=d(M)+"</div>\n ";return K}function u(L,K){return"Click to rename history"}function t(O,N){var K="",M,L;K+='\n <div id="history-name"\n title="';L={hash:{},inverse:x.noop,fn:x.program(5,r,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='">';if(M=A.name){M=M.call(O,{hash:{},data:N})}else{M=O.name;M=typeof M===e?M.apply(O):M}K+=d(M)+"</div>\n ";return K}function r(L,K){return"You must be logged in to edit your history name"}function q(O,N){var K="",M,L;K+='\n <a id="history-tag" title="';L={hash:{},inverse:x.noop,fn:x.program(8,o,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='"\n class="icon-button tags" target="galaxy_main" href="javascript:void(0)"></a>\n <a id="history-annotate" title="';L={hash:{},inverse:x.noop,fn:x.program(10,I,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='"\n class="icon-button annotate" target="galaxy_main" href="javascript:void(0)"></a>\n ';return K}function o(L,K){return"Edit history tags"}function I(L,K){return"Edit history annotation"}function H(O,N){var K="",M,L;K+='\n <div id="history-tag-annotation">\n\n <div id="history-tag-area" style="display: none">\n <strong>';L={hash:{},inverse:x.noop,fn:x.program(13,G,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+=':</strong>\n <div class="tag-elt"></div>\n </div>\n\n <div id="history-annotation-area" style="display: none">\n <strong>';L={hash:{},inverse:x.noop,fn:x.program(15,F,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+=':</strong>\n <div id="history-annotation-container">\n <div id="history-annotation" class="editable-text"\n title="';L={hash:{},inverse:x.noop,fn:x.program(17,E,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+='">\n ';M=A["if"].call(O,O.annotation,{hash:{},inverse:x.program(21,n,N),fn:x.program(19,D,N),data:N});if(M||M===0){K+=M}K+="\n </div>\n </div>\n </div>\n </div>\n ";return K}function G(L,K){return"Tags"}function F(L,K){return"Annotation"}function E(L,K){return"Click to edit annotation"}function D(N,M){var K="",L;K+="\n ";if(L=A.annotation){L=L.call(N,{hash:{},data:M})}else{L=N.annotation;L=typeof L===e?L.apply(N):L}K+=d(L)+"\n ";return K}function n(O,N){var K="",M,L;K+="\n <em>";L={hash:{},inverse:x.noop,fn:x.program(22,k,N),data:N};if(M=A.local){M=M.call(O,L)}else{M=O.local;M=typeof M===e?M.apply(O):M}if(!A.local){M=c.call(O,M,L)}if(M||M===0){K+=M}K+="</em>\n ";return K}function k(L,K){return"Describe or add notes to history"}function j(O,N){var K="",M,L;K+="\n ";L={hash:{},inverse:x.noop,fn:x.program(25,i,N),data:N};if(M=A.warningmessagesmall){M=M.call(O,L)}else{M=O.warningmessagesmall;M=typeof M===e?M.apply(O):M}if(!A.warningmessagesmall){M=c.call(O,M,L)}if(M||M===0){K+=M}K+="\n ";return K}function i(N,M){var L,K;K={hash:{},inverse:x.noop,fn:x.program(26,g,M),data:M};if(L=A.local){L=L.call(N,K)}else{L=N.local;L=typeof L===e?L.apply(N):L}if(!A.local){L=c.call(N,L,K)}if(L||L===0){return L}else{return""}}function g(L,K){return"You are currently viewing a deleted history!"}function f(N,M){var K="",L;K+='\n <div class="';if(L=A.status){L=L.call(N,{hash:{},data:M})}else{L=N.status;L=typeof L===e?L.apply(N):L}K+=d(L)+'message">';if(L=A.message){L=L.call(N,{hash:{},data:M})}else{L=N.message;L=typeof L===e?L.apply(N):L}K+=d(L)+"</div>\n ";return K}function z(L,K){return"You are over your disk quota"}function y(L,K){return"Tool execution is on hold until your disk usage drops below your allocated quota"}function w(L,K){return"Your history is empty. Click 'Get Data' on the left pane to start"}B+='<div id="history-controls">\n\n <div id="history-title-area" class="historyLinks">\n \n <div id="history-name-container">\n \n ';l=A["if"].call(C,((p=C.user),p==null||p===false?p:p.email),{hash:{},inverse:x.program(4,t,J),fn:x.program(1,v,J),data:J});if(l||l===0){B+=l}B+='\n </div>\n </div>\n\n <div id="history-subtitle-area">\n <div id="history-size" style="float:left;">';if(l=A.nice_size){l=l.call(C,{hash:{},data:J})}else{l=C.nice_size;l=typeof l===e?l.apply(C):l}B+=d(l)+'</div>\n\n <div id="history-secondary-links" style="float: right;">\n ';l=A["if"].call(C,((p=C.user),p==null||p===false?p:p.email),{hash:{},inverse:x.noop,fn:x.program(7,q,J),data:J});if(l||l===0){B+=l}B+='\n </div>\n <div style="clear: both;"></div>\n </div>\n\n \n \n ';l=A["if"].call(C,((p=C.user),p==null||p===false?p:p.email),{hash:{},inverse:x.noop,fn:x.program(12,H,J),data:J});if(l||l===0){B+=l}B+="\n\n ";l=A["if"].call(C,C.deleted,{hash:{},inverse:x.noop,fn:x.program(24,j,J),data:J});if(l||l===0){B+=l}B+='\n\n <div id="message-container">\n ';l=A["if"].call(C,C.message,{hash:{},inverse:x.noop,fn:x.program(28,f,J),data:J});if(l||l===0){B+=l}B+='\n </div>\n\n <div id="quota-message-container" style="display: none">\n <div id="quota-message" class="errormessage">\n ';h={hash:{},inverse:x.noop,fn:x.program(30,z,J),data:J};if(l=A.local){l=l.call(C,h)}else{l=C.local;l=typeof l===e?l.apply(C):l}if(!A.local){l=c.call(C,l,h)}if(l||l===0){B+=l}B+=".\n ";h={hash:{},inverse:x.noop,fn:x.program(32,y,J),data:J};if(l=A.local){l=l.call(C,h)}else{l=C.local;l=typeof l===e?l.apply(C):l}if(!A.local){l=c.call(C,l,h)}if(l||l===0){B+=l}B+='.\n </div>\n </div>\n</div>\n\n<div id="';if(l=A.id){l=l.call(C,{hash:{},data:J})}else{l=C.id;l=typeof l===e?l.apply(C):l}B+=d(l)+'-datasets" class="history-datasets-list"></div>\n\n<div class="infomessagesmall" id="emptyHistoryMessage" style="display: none;">\n ';h={hash:{},inverse:x.noop,fn:x.program(34,w,J),data:J};if(l=A.local){l=l.call(C,h)}else{l=C.local;l=typeof l===e?l.apply(C):l}if(!A.local){l=c.call(C,l,h)}if(l||l===0){B+=l}B+="\n</div>";return B})})();
\ No newline at end of file
+(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-history-historyPanel"]=b(function(k,x,v,p,E){this.compilerInfo=[4,">= 1.0.0"];v=this.merge(v,k.helpers);E=E||{};var w="",n,h,t=this,e="function",c=v.blockHelperMissing,d=this.escapeExpression;function s(J,I){var F="",H,G;F+='\n <div class="history-name editable-text" title="';G={hash:{},inverse:t.noop,fn:t.program(2,r,I),data:I};if(H=v.local){H=H.call(J,G)}else{H=J.local;H=typeof H===e?H.apply(J):H}if(!v.local){H=c.call(J,H,G)}if(H||H===0){F+=H}F+='">\n ';if(H=v.name){H=H.call(J,{hash:{},data:I})}else{H=J.name;H=typeof H===e?H.apply(J):H}F+=d(H)+"\n </div>\n ";return F}function r(G,F){return"Click to rename history"}function q(I,H){var F="",G;F+='\n <div class="history-size">';if(G=v.nice_size){G=G.call(I,{hash:{},data:H})}else{G=I.nice_size;G=typeof G===e?G.apply(I):G}F+=d(G)+"</div>\n ";return F}function o(G,F){return"Edit history tags"}function m(G,F){return"Edit history annotation"}function D(J,I){var F="",H,G;F+='\n <div class="warningmessagesmall"><strong>\n ';G={hash:{},inverse:t.noop,fn:t.program(11,C,I),data:I};if(H=v.local){H=H.call(J,G)}else{H=J.local;H=typeof H===e?H.apply(J):H}if(!v.local){H=c.call(J,H,G)}if(H||H===0){F+=H}F+="\n </strong></div>\n ";return F}function C(G,F){return"You are currently viewing a deleted history!"}function B(I,H){var F="",G;F+='\n \n <div class="';if(G=v.status){G=G.call(I,{hash:{},data:H})}else{G=I.status;G=typeof G===e?G.apply(I):G}F+=d(G)+'message">';if(G=v.message){G=G.call(I,{hash:{},data:H})}else{G=I.message;G=typeof G===e?G.apply(I):G}F+=d(G)+"</div>\n ";return F}function A(G,F){return"You are over your disk quota"}function z(G,F){return"Tool execution is on hold until your disk usage drops below your allocated quota"}function y(G,F){return"Tags"}function l(G,F){return"Annotation"}function j(G,F){return"Click to edit annotation"}function i(I,H){var F="",G;F+="\n ";if(G=v.annotation){G=G.call(I,{hash:{},data:H})}else{G=I.annotation;G=typeof G===e?G.apply(I):G}F+=d(G)+"\n ";return F}function g(J,I){var F="",H,G;F+="\n <em>";G={hash:{},inverse:t.noop,fn:t.program(28,f,I),data:I};if(H=v.local){H=H.call(J,G)}else{H=J.local;H=typeof H===e?H.apply(J):H}if(!v.local){H=c.call(J,H,G)}if(H||H===0){F+=H}F+="</em>\n ";return F}function f(G,F){return"Describe or add notes to history"}function u(G,F){return"Your history is empty. Click 'Get Data' on the left pane to start"}w+='<div class="history-controls">\n\n <div class="history-title">\n ';n=v["if"].call(x,x.name,{hash:{},inverse:t.noop,fn:t.program(1,s,E),data:E});if(n||n===0){w+=n}w+='\n </div>\n\n <div class="history-subtitle clear">\n ';n=v["if"].call(x,x.nice_size,{hash:{},inverse:t.noop,fn:t.program(4,q,E),data:E});if(n||n===0){w+=n}w+='\n\n <div class="history-secondary-actions">\n <a title="';h={hash:{},inverse:t.noop,fn:t.program(6,o,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='"\n class="icon-button tags" href="javascript:void(0)"></a>\n <a title="';h={hash:{},inverse:t.noop,fn:t.program(8,m,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='"\n class="icon-button annotate" href="javascript:void(0)"></a>\n </div>\n </div>\n\n ';n=v["if"].call(x,x.deleted,{hash:{},inverse:t.noop,fn:t.program(10,D,E),data:E});if(n||n===0){w+=n}w+='\n\n <div class="message-container">\n ';n=v["if"].call(x,x.message,{hash:{},inverse:t.noop,fn:t.program(13,B,E),data:E});if(n||n===0){w+=n}w+='\n </div>\n\n <div class="quota-message errormessage">\n ';h={hash:{},inverse:t.noop,fn:t.program(15,A,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+=".\n ";h={hash:{},inverse:t.noop,fn:t.program(17,z,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='.\n </div>\n \n \n \n <div class="tags-display">\n <label class="prompt">';h={hash:{},inverse:t.noop,fn:t.program(19,y,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='</label>\n <div class="tags"></div>\n </div>\n\n \n <div class="annotation-display">\n <label class="prompt">';h={hash:{},inverse:t.noop,fn:t.program(21,l,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='</label>\n <div class="annotation editable-text" title="';h={hash:{},inverse:t.noop,fn:t.program(23,j,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+='">\n ';n=v["if"].call(x,x.annotation,{hash:{},inverse:t.program(27,g,E),fn:t.program(25,i,E),data:E});if(n||n===0){w+=n}w+='\n </div>\n </div>\n\n </div>\n\n \n <div class="datasets-list"></div>\n\n <div class="empty-history-message infomessagesmall">\n ';h={hash:{},inverse:t.noop,fn:t.program(30,u,E),data:E};if(n=v.local){n=n.call(x,h)}else{n=x.local;n=typeof n===e?n.apply(x):n}if(!v.local){n=c.call(x,n,h)}if(n||n===0){w+=n}w+="\n </div>";return w})})();
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 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","viz/trackster/filters","mvc/data","mvc/tools"],function(ab,y,l,u,L,i,X,R){var q=ab.extend;var n={};var k=function(ac,ad){n[ac.attr("id")]=ad};var m=function(ac,ae,ag,af){ag=".group";var ad={};n[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=n[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();var ar=n[ai.attr("id")];if(ap.offsetY<aq){$(this).insertBefore(ai);ar.remove_drawable(am);ar.container.add_drawable_before(am,ar);return}else{if(ap.offsetY>ah){$(this).insertAfter(ai);ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al<ak.length;al++){aj=$(ak.get(al));aq=aj.position().top;ah=aq+aj.outerHeight();if(aj.is(ag)&&this!==aj.get(0)&&ap.offsetY>=aq&&ap.offsetY<=ah){if(ap.offsetY-aq<ah-ap.offsetY){aj.find(".content-div").prepend(this)}else{aj.find(".content-div").append(this)}if(am.container){am.container.remove_drawable(am)}n[aj.attr("id")].add_drawable(am);return}}for(al=0;al<ak.length;al++){aj=$(ak.get(al));if(ap.offsetY<aj.position().top&&!(aj.hasClass("reference-track")||aj.hasClass("intro"))){break}}if(al===ak.length){if(this!==ak.get(al-1)){at.append(this);n[at.attr("id")].move_drawable(am,al)}}else{if(this!==ak.get(al)){$(this).insertBefore(ak.get(al));n[at.attr("id")].move_drawable(am,(ap.deltaY>0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var F=20,B=100,I=12000,S=400,K=5000,x=100,o="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",G="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: ",b="Loading data...",U="Ready for display",Q=10,H=20,C=["Histogram","Line","Filled","Intensity"];function V(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var r=function(ad,ac,af){if(!r.id_counter){r.id_counter=0}this.id=r.id_counter++;this.view=ad;this.container=ac;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.config=new j({params:this.config_params,onchange:this.config_onchange,saved_values:af.prefs});this.prefs=this.config.get("values");if(!this.prefs.name){this.prefs.name=af.name}if(this.config_onchange){this.config.on("change:values",this.config_onchange,this)}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(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("<div style='clear: both'/>").appendTo(this.container_div)}};r.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.prefs.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.prefs.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.prefs.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var ac=new a({model:ad.config});ac.render()}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".tooltip").remove();ac.remove()}}];q(r.prototype,{config_params:[{key:"name",label:"Name",type:"text",default_value:""},{key:"content_visible",type:"bool",default_value:true,hidden:true}],config_onchange:function(){this.track.set_name(this.track.config.get("values").name)},init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.prefs.content_visible){return true}return false},request_draw:function(){},_draw:function(ac){},to_dict:function(){},set_name:function(ac){this.old_name=this.prefs.name;this.prefs.name=ac;this.name_div.text(this.prefs.name)},revert_name:function(){if(this.old_name){this.prefs.name=this.old_name;this.name_div.text(this.prefs.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("<a/>").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad<ac.length;ad++){ae=ac[ad];this.add_action_icon(ae.name,ae.title,ae.css_class,ae.on_click_fn,ae.prepend,ae.hide)}},update_icons:function(){},hide_contents:function(){},show_contents:function(){},get_drawables:function(){}});var A=function(ad,ac,ae){r.call(this,ad,ac,ae);this.obj_type=ae.obj_type;this.drawables=[]};q(A.prototype,r.prototype,{unpack_drawables:function(ae){this.drawables=[];var ad;for(var ac=0;ac<ae.length;ac++){ad=p(ae[ac],this.view,this);this.add_drawable(ad)}},init:function(){for(var ac=0;ac<this.drawables.length;ac++){this.drawables[ac].init()}},_draw:function(ac){for(var ad=0;ad<this.drawables.length;ad++){this.drawables[ad]._draw(ac)}},to_dict:function(){var ad=[];for(var ac=0;ac<this.drawables.length;ac++){ad.push(this.drawables[ac].to_dict())}return{prefs:this.prefs,obj_type:this.obj_type,drawables:ad}},add_drawable:function(ac){this.drawables.push(ac);ac.container=this;this.changed()},add_drawable_before:function(ae,ac){this.changed();var ad=this.drawables.indexOf(ac);if(ad!==-1){this.drawables.splice(ad,0,ae);return true}return false},replace_drawable:function(ae,ac,ad){var af=this.drawables.indexOf(ae);if(af!==-1){this.drawables[af]=ac;if(ad){ae.container_div.replaceWith(ac.container_div)}this.changed()}return af},remove_drawable:function(ad){var ac=this.drawables.indexOf(ad);if(ac!==-1){this.drawables.splice(ac,1);ad.container=null;this.changed();return true}return false},move_drawable:function(ad,ae){var ac=this.drawables.indexOf(ad);if(ac!==-1){this.drawables.splice(ac,1);this.drawables.splice(ae,0,ad);this.changed();return true}return false},get_drawables:function(){return this.drawables},get_tracks:function(af){var ac=this.drawables.slice(0),ad=[],ae;while(ac.length!==0){ae=ac.shift();if(ae instanceof af){ad.push(ae)}else{if(ae.drawables){ac=ac.concat(ae.drawables)}}}return ad}});var P=function(ad,ac,af){q(af,{obj_type:"DrawableGroup",drag_handle_class:"group-handle"});A.call(this,ad,ac,af);this.content_div=$("<div/>").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);k(this.container_div,this);k(this.content_div,this);m(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};q(P.prototype,r.prototype,A.prototype,{action_icons_def:[r.prototype.action_icons_def[0],r.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw({clear_tile_cache:true})}ac.filters_manager.toggle()}},r.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("<div/>").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("<div/>").addClass("track-header");ac.append($("<div/>").addClass(this.drag_handle_class));this.name_div=$("<div/>").addClass("track-name").text(this.prefs.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al<ae;al++){ai=this.drawables[al];if(ai.get_type()!==ag){can_composite=false;break}if(ai instanceof d){ac++}}if(ao||ac===1){this.action_icons.composite_icon.show()}else{this.action_icons.composite_icon.hide();$(".tooltip").remove()}if(ac>1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak<ai.filters_manager.filters.length;ak++){ad=ai.filters_manager.filters[ak];ap[ad.name]=[ad]}for(al=1;al<this.drawables.length;al++){ai=this.drawables[al];for(ak=0;ak<ai.filters_manager.filters.length;ak++){ad=ai.filters_manager.filters[ak];if(ad.name in ap){ap[ad.name].push(ad)}}}this.filters_manager.remove_all();var af,ah,aj,am;for(var an in ap){af=ap[an];if(af.length===ac){ah=new i.NumberFilter({name:af[0].name,index:af[0].index});this.filters_manager.add_filter(ah)}}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 ac=0;ac<this.drawables.length;ac++){this.drawables[ac].filters_manager=this.saved_filters_managers[ac]}this.saved_filters_managers=[]},setup_multitrack_filtering:function(){if(this.filters_manager.filters.length>0){this.saved_filters_managers=[];for(var ac=0;ac<this.drawables.length;ac++){drawable=this.drawables[ac];this.saved_filters_managers.push(drawable.filters_manager);drawable.filters_manager=this.filters_manager}}this.filters_manager.init_filters()},show_composite_track:function(){var ad=new f(this.view,this.view,{name:this.prefs.name,drawables:this.drawables});var ac=this.container.replace_drawable(this,ad,true);ad.request_draw()},add_drawable:function(ac){A.prototype.add_drawable.call(this,ac);this.update_icons()},remove_drawable:function(ac){A.prototype.remove_drawable.call(this,ac);this.update_icons()},to_dict:function(){if(this.filters_manager.visible()){this._restore_filter_managers()}var ac=q(A.prototype.to_dict.call(this),{filters:this.filters_manager.to_dict()});if(this.filters_manager.visible()){this.setup_multitrack_filtering()}return ac},request_draw:function(ac){ab.each(this.drawables,function(ad){ad.request_draw(ac)})}});var Y=Backbone.View.extend({initialize:function(ac){q(ac,{obj_type:"View"});A.call(this,"View",ac.container,ac);this.chrom=null;this.vis_id=ac.vis_id;this.dbkey=ac.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.render();this.canvas_manager=new y.CanvasManager(this.container.get(0).ownerDocument);this.reset();this.config=new j({track:this,params:[{key:"a_color",label:"A Color",type:"color",default_value:"#FF0000"},{key:"c_color",label:"C Color",type:"color",default_value:"#00FF00"},{key:"g_color",label:"G Color",type:"color",default_value:"#0000FF"},{key:"t_color",label:"T Color",type:"color",default_value:"#FF00FF"},{key:"n_color",label:"N Color",type:"color",default_value:"#AAAAAA"}],saved_values:ac.prefs,onchange:function(){track.request_redraw({clear_tile_cache:true})}})},render:function(){this.requested_redraw=false;var ae=this.container,ac=this;this.top_container=$("<div/>").addClass("top-container").appendTo(ae);this.browser_content_div=$("<div/>").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(ae);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;k(this.viewport_container,ac);this.intro_div=$("<div/>").addClass("intro").appendTo(this.viewport_container).hide();var af=$("<div/>").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){y.select_datasets(galaxy_config.root+"visualization/list_current_history_datasets",galaxy_config.root+"api/datasets",{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(p(ah,ac,ac))})})});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("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 ad=function(ag){if(ag.type==="focusout"||(ag.keyCode||ag.which)===13||(ag.keyCode||ag.which)===27){if((ag.keyCode||ag.which)!==27){ac.go_to($(this).val())}$(this).hide();$(this).val("");ac.location_span.show();ac.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",ad).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(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_tracks(d),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[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(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a/>").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("<div />").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new W(this,{content_div:this.top_labeltrack}));this.add_label_track(new W(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.get("values")[ac.toLowerCase()+"_color"]||this.config.get("values").n_color}});q(Y.prototype,A.prototype,{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(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=x;var ac=this,ad=$.Deferred();$.ajax({url:galaxy_config.root+"api/genomes/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){var ah=new D(ac);ac.add_label_track(ah);ac.reference_track=ah}ac.chrom_data=ag.chrom_info;var ak='<option value="">Select Chrom/Contig</option>';for(var aj=0,af=ac.chrom_data.length;aj<af;aj++){var ai=ac.chrom_data[aj].chrom;ak+='<option value="'+ai+'">'+ai+"</option>"}if(ag.prev_chroms){ak+='<option value="previous">Previous '+x+"</option>"}if(ag.next_chroms){ak+='<option value="next">Next '+x+"</option>"}ac.chrom_select.html(ak);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-x});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+x});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();for(var ag=0,ac=ae.drawables.length;ag<ac;ag++){var af=ae.drawables[ag];if(af.init){af.init()}}if(ae.reference_track){ae.reference_track.init()}}if(ad&&aj){ae.low=Math.max(ad,0);ae.high=Math.min(aj,ae.max_high)}else{ae.low=0;ae.high=ae.max_high}ae.reset_overview();ae.request_redraw()}},go_to:function(ag){ag=ag.replace(/,/g,"");ag=ag.replace(/:|\-/g," ");var ad=ag.split(/\s+/),af=ad[0],ae=(ad[1]?parseInt(ad[1],10):null),ac=(ad[2]?parseInt(ad[2],10):null);if(!ac){ae=ae-15;ac=ae+15}this.change_chrom(af,ae,ac)},move_fraction:function(ae){var ac=this;var ad=ac.high-ac.low;this.move_delta(ae*ad)},move_delta:function(af){var ac=this;var ae=ac.high-ac.low;if(ac.low-af<ac.max_low){ac.low=ac.max_low;ac.high=ac.max_low+ae}else{if(ac.high-af>ac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw({data_fetch:false});if(this.redraw_on_move_fn){clearTimeout(this.redraw_on_move_fn)}this.redraw_on_move_fn=setTimeout(function(){ac.request_redraw()},200);var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){A.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){A.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ad,ae){var ac=this,af=(ae?[ae]:ac.drawables);ab.each(af,function(ag){var ah=ab.find(ac.tracks_to_be_redrawn,function(ai){return ai[0]===ag});if(ah){ah[1]=ad}else{ac.tracks_to_be_redrawn.push([ag,ad])}});if(!this.requested_redraw){requestAnimationFrame(function(){ac._redraw()});this.requested_redraw=true}},_redraw:function(){this.requested_redraw=false;var ac=this.low,ag=this.high;if(ac<this.max_low){ac=this.max_low}if(ag>this.max_high){ag=this.max_high}var ad=this.high-this.low;if(this.high!==0&&ad<this.min_separation){ag=ac+this.min_separation}this.low=Math.floor(ac);this.high=Math.ceil(ag);this.update_location(this.low,this.high);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 ah=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var ae=13;this.overview_box.css({left:af,width:Math.max(ae,ah)}).show();if(ah<ae){this.overview_box.css("left",af-(ae-ah)/2)}if(this.overview_highlight){this.overview_highlight.css({left:af,width:ah})}ab.each(this.tracks_to_be_redrawn,function(ak){var ai=ak[0],aj=ak[1];if(ai){ai._draw(aj)}});this.tracks_to_be_redrawn=[];ab.each(this.label_tracks,function(ai){ai._draw()})},zoom_in:function(ad,ae){if(this.max_high===0||this.high-this.low<=this.min_separation){return}var af=this.high-this.low,ag=af/2+this.low,ac=(af/this.zoom_factor)/2;if(ad){ag=ad/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(ag-ac);this.high=Math.round(ag+ac);this.changed();this.request_redraw()},zoom_out:function(){if(this.max_high===0){return}var ad=this.high-this.low,ae=ad/2+this.low,ac=(ad*this.zoom_factor)/2;this.low=Math.round(ae-ac);this.high=Math.round(ae+ac);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(ae){if(this.overview_drawable){if(this.overview_drawable.dataset.id===ae.dataset.id){return}this.overview_viewport.find(".track").remove()}var ad=ae.copy({content_div:this.overview_viewport}),ac=this;ad.header_div.hide();ad.is_overview=true;ac.overview_drawable=ad;this.overview_drawable.postdraw_actions=function(){ac.overview_highlight.show().height(ac.overview_drawable.content_div.height());ac.overview_viewport.height(ac.overview_drawable.content_div.height()+ac.overview_box.outerHeight());ac.overview_close.show();ac.resize_window()};ac.overview_drawable.request_draw();this.changed()},reset_overview:function(){$(".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 s=R.Tool.extend({defaults:{track:null},initialize:function(ac){if(ac.tool_state!==undefined){this.set("hidden",ac.tool_state.hidden)}this.remove_inputs(["data","hidden_data","conditional"])},state_dict:function(ac){return ab.extend(this.get_inputs_dict(),{hidden:!this.is_visible()})}});var v=Backbone.View.extend({events:{"change input":"update_value"},render:function(){var ae=this.$el.addClass("param-row"),af=this.model;var ac=$("<div>").addClass("param-label").text(af.get("label")).appendTo(ae);var ad=$("<div/>").addClass("param-input").html(af.get("html")).appendTo(ae);ad.find(":input").val(af.get("value"));$("<div style='clear: both;'/>").appendTo(ae)},update_value:function(ac){this.model.set_value($(ac.target).val())}});var aa=Backbone.View.extend({initialize:function(ac){this.model.on("change:hidden",this.set_visible,this)},render:function(){var ad=this;tool=this.model,parent_div=this.$el.addClass("dynamic-tool").hide();parent_div.bind("drag",function(ah){ah.stopPropagation()}).click(function(ah){ah.stopPropagation()}).bind("dblclick",function(ah){ah.stopPropagation()}).keydown(function(ah){ah.stopPropagation()});var ae=$("<div class='tool-name'>").appendTo(parent_div).text(tool.get("name"));tool.get("inputs").each(function(ai){var ah=new v({model:ai});ah.render();parent_div.append(ah.$el)});parent_div.find("input").click(function(){$(this).select()});var af=$("<div>").addClass("param-row").appendTo(parent_div);var ag=$("<input type='submit'>").attr("value","Run on complete dataset").appendTo(af);var ac=$("<input type='submit'>").attr("value","Run on visible region").css("margin-left","3em").appendTo(af);ac.click(function(){ad.run_on_region()});ag.click(function(){ad.run_on_dataset()});if(tool.is_visible()){this.$el.show()}},set_visible:function(){if(this.model.is_visible()){this.$el.show()}else{this.$el.hide()}},update_params:function(){for(var ac=0;ac<this.params.length;ac++){this.params[ac].update_value()}},run_on_dataset:function(){var ac=this.model;this.run({target_dataset_id:this.model.get("track").dataset.id,action:"rerun",tool_id:ac.id},null,function(ad){Galaxy.modal.show({title:ac.get("name")+" is Running",body:ac.get("name")+" is running on the complete dataset. Tool outputs are in dataset's history.",buttons:{Close:function(){Galaxy.modal.hide()}}})})},run_on_region:function(){var ad=this.model.get("track"),ag=this.model,ai=new y.GenomeRegion({chrom:ad.view.chrom,start:ad.view.low,end:ad.view.high}),aj={target_dataset_id:ad.dataset.id,action:"rerun",tool_id:ag.id,regions:[ai.toJSON()]},ah=ad,al=aj.tool_id+ah.tool_region_and_parameters_str(ai),ac;if(ah.container===view){var ak=new P(view,view,{name:this.prefs.name});var af=ah.container.replace_drawable(ah,ak,false);ak.container_div.insertBefore(ah.view.content_div.children()[af]);ak.add_drawable(ah);ah.container_div.appendTo(ak.content_div);ac=ak}else{ac=ah.container}var ae=new ah.constructor(view,ac,{name:al,hda_ldda:"hda"});ae.init_for_tool_data();ae.change_mode(ah.mode);ae.set_filters_manager(ah.filters_manager.copy(ae));ae.update_icons();ac.add_drawable(ae);ae.tiles_div.text("Starting job.");this.run(aj,ae,function(am){ae.set_dataset(new X.Dataset(am));ae.tiles_div.text("Running job.");ae.init()})},run:function(ac,ae,af){ac.inputs=this.model.get_inputs_dict();var ad=new l.ServerStateDeferred({ajax_settings:{url:galaxy_config.root+"api/tools",data:JSON.stringify(ac),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(ag){return ag!=="pending"}});$.when(ad.go()).then(function(ag){if(ag==="no converter"){ae.container_div.addClass("error");ae.content_div.text(J)}else{if(ag.error){ae.container_div.addClass("error");ae.content_div.text(z+ag.message)}else{af(ag)}}})}});var E=function(ac,ad){L.Scaler.call(this,ad);this.filter=ac};E.prototype.gen_val=function(ac){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(ac[this.filter.index])-this.filter.low)/(this.filter.high-this.filter.low))};var j=Backbone.Model.extend({initialize:function(ad){var ac={};ab.each(ad.params,function(ae){ac[ae.key]=ae.default_value});if(ad.saved_values){ab.each(this.get("params"),function(ae){if(ae.key in ad.saved_values){ac[ae.key]=ad.saved_values[ae.key]}})}this.set("values",ac)},set_param_default_value:function(ad,ac){var ae=ab.find(this.get("params"),function(af){return af.key===ad});if(ae){ae.default_value=ac}},set_param_value:function(ac,ad){var ae=ab.find(this.get("params"),function(af){return af.key===ac});if(ae){if(typeof ad==="string"||ad instanceof String){if(ad.trim()===""){ad=ae.default_value}else{if(ae.type==="float"){ad=parseFloat(ad)}else{if(ae.type==="int"){ad=parseInt(ad,10)}}}}}if(this.get("values")[ac]!==ad){this.get("values")[ac]=ad;this.trigger("change:values");return true}else{return false}}});var a=Backbone.View.extend({render:function(){var aj=this.model;var ac=$("<div/>").keydown(function(ak){ak.stopPropagation()});var ai;function ah(ao,ak){for(var at=0;at<ao.length;at++){ai=ao[at];if(ai.hidden){continue}var am="param_"+at;var ax=aj.get("values")[ai.key];var az=$("<div class='form-row' />").appendTo(ak);az.append($("<label />").attr("for",am).text(ai.label+":"));if(ai.type==="bool"){az.append($('<input type="checkbox" />').attr("id",am).attr("name",am).attr("checked",ax))}else{if(ai.type==="text"){az.append($('<input type="text"/>').attr("id",am).val(ax).click(function(){$(this).select()}))}else{if(ai.type==="select"){var av=$("<select />").attr("id",am);for(var aq=0;aq<ai.options.length;aq++){$("<option/>").text(ai.options[aq].label).attr("value",ai.options[aq].value).appendTo(av)}av.val(ax);az.append(av)}else{if(ai.type==="color"){var ay=$("<div/>").appendTo(az),au=$("<input />").attr("id",am).attr("name",am).val(ax).css("float","left").appendTo(ay).click(function(aB){$(".tooltip").removeClass("in");var aA=$(this).siblings(".tooltip").addClass("in");aA.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top+Galaxy.modal.scrollTop()-($(aA).height()/2)+($(this).height()/2)}).show();aA.click(function(aC){aC.stopPropagation()});$(document).bind("click.color-picker",function(){aA.hide();$(document).unbind("click.color-picker")});aB.stopPropagation()}),ar=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(ay).attr("title","Set new random color").tooltip(),aw=$("<div class='tooltip right' style='position: absolute;' />").appendTo(ay).hide(),an=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(aw),al=$("<div class='tooltip-arrow'></div>").appendTo(aw),ap=$.farbtastic(an,{width:100,height:100,callback:au,color:ax});ay.append($("<div/>").css("clear","both"));(function(aA){ar.click(function(){aA.setColor(l.get_random_color())})})(ap)}else{az.append($("<input />").attr("id",am).attr("name",am).val(ax))}}}}if(ai.help){az.append($("<div class='help'/>").text(ai.help))}}}ah(aj.get("params"),ac);var ad=this,ag=function(){Galaxy.modal.hide();$(window).unbind("keypress.check_enter_esc")},ae=function(){ad.update_from_form($(Galaxy.modal.el));Galaxy.modal.hide();$(window).unbind("keypress.check_enter_esc")},af=function(ak){if((ak.keyCode||ak.which)===27){ag()}else{if((ak.keyCode||ak.which)===13){ae()}}};$(window).bind("keypress.check_enter_esc",af);Galaxy.modal.show({title:"Configure",body:ac,buttons:{Cancel:ag,Ok:ae}})},update_from_form:function(ac){var ad=this.model;var ae=false;ab.each(ad.get("params"),function(ah,af){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}ae=ad.set_param_value(ah.key,ag)||ae}})}});var c=function(ac,af,ag,ad,ae){this.track=ac;this.region=af;this.low=af.get("start");this.high=af.get("end");this.w_scale=ag;this.canvas=ad;this.html_elt=$("<div class='track-tile'/>").append(ad);this.data=ae;this.stale=false};c.prototype.predisplay_actions=function(){};var M=function(ac,af,ag,ad,ae){c.call(this,ac,af,ag,ad,ae)};M.prototype.predisplay_actions=function(){};var O=function(af,am,ao,ae,ah,ai,ap,ad,al){c.call(this,af,am,ao,ae,ah);this.mode=ai;this.all_slotted=ad;this.feature_mapper=al;this.has_icons=false;if(ap){this.has_icons=true;var aj=this;ae=this.html_elt.children()[0],message_div=$("<div/>").addClass("tile-message").css({height:F,width:ae.width}).prependTo(this.html_elt);var ak=new y.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),an=ah.length,ag=$("<a/>").addClass("icon more-down").attr("title","For speed, only the first "+an+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("<a/>").addClass("icon more-across").attr("title","For speed, only the first "+an+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ag.click(function(){aj.stale=true;af.data_manager.get_more_data(ak,af.mode,1/aj.w_scale,{},af.data_manager.DEEP_DATA_REQ);$(".tooltip").hide();af.request_draw()}).dblclick(function(aq){aq.stopPropagation()});ac.click(function(){aj.stale=true;af.data_manager.get_more_data(ak,af.mode,1/aj.w_scale,{},af.data_manager.BROAD_DATA_REQ);$(".tooltip").hide();af.request_draw()}).dblclick(function(aq){aq.stopPropagation()})}};q(O.prototype,c.prototype);O.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.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(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al<ai.length;al++){ah=ai[al];ap[ah.name]=at[ah.index]}af=$("<div/>").attr("id",ak).addClass("feature-popup");var au=$("<table/>"),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("<tr/>").appendTo(au);$("<th/>").appendTo(av).text(ar);$("<td/>").attr("align","left").appendTo(av).text(typeof(aq)==="number"?V(aq,2):aq)}af.append($("<div class='feature-popup-inner'>").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"),10)-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"),10)+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){q(ae,{drag_handle_class:"draghandle"});r.call(this,ad,ac,ae);this.dataset=null;if(ae.dataset){this.dataset=(ae.dataset instanceof Backbone.Model?ae.dataset:X.Dataset.findOrCreate(ae.dataset))}this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new y.GenomeDataManager({dataset:this.dataset,genome:new y.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),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=this.prefs.height;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 ae)||ae.resize){this.add_resize_handle()}}};q(g.prototype,r.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},r.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},r.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.tool.toggle();if(ac.tool.is_visible()){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='<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>',ae=ab.template(af,{track:ac});var ah=function(){Galaxy.modal.hide();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new y.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new y.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}Galaxy.modal.hide();window.location.href=galaxy_config.root+"visualization/sweepster?"+$.param({dataset_id:ac.dataset.id,hda_ldda:ac.dataset.get("hda_ldda"),regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};Galaxy.modal.show({title:"Visualize tool parameter space and output from different parameter settings?",body:ae,buttons:{No:ah,Yes:ad}})}},r.prototype.action_icons_def[2]],can_draw:function(){return this.dataset&&r.prototype.can_draw.call(this)},build_container_div:function(){return $("<div/>").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("<div class='track-header'/>");if(this.view.editor){this.drag_div=$("<div/>").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("<div/>").addClass("track-name").appendTo(ac).text(this.prefs.name).attr("id",this.prefs.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},set_dataset:function(ac){this.dataset=ac;this.data_manager.set("dataset",ac)},on_resize:function(){this.request_draw({clear_tile_cache:true})},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("<div class='track-resize'>");$(ac.container_div).hover(function(){if(ac.prefs.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.get("values").height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.get("values").mode?this.config.get("values").mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae<ac;ae++){var ah=ad.display_modes[ae];ag[ah]=function(aj){return function(){ad.change_mode(aj);ad.icons_div.show();ad.container_div.mouseleave(function(){ad.icons_div.hide()})}}(ah)}make_popupmenu(this.action_icons.mode_icon,ag)},build_action_icons:function(){r.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 W){return"LabelTrack"}else{if(this instanceof D){return"ReferenceTrack"}else{if(this instanceof h){return"LineTrack"}else{if(this instanceof T){return"ReadTrack"}else{if(this instanceof Z){return"VariantTrack"}else{if(this instanceof f){return"CompositeTrack"}else{if(this instanceof d){return"FeatureTrack"}}}}}}}return""},init:function(ae){var ad=this;ad.enabled=false;ad.tile_cache.clear();ad.data_manager.clear();ad.tiles_div.css("height","auto");ad.tiles_div.text("").children().remove();ad.container_div.removeClass("nodata error pending");if(!ad.dataset.id){return}var ac=$.Deferred(),af={hda_ldda:ad.dataset.get("hda_ldda"),data_type:this.dataset_check_type,chrom:ad.view.chrom,retry:ae};$.getJSON(this.dataset.url(),af,function(ag){if(!ag||ag==="error"||ag.kind==="error"){ad.container_div.addClass("error");ad.tiles_div.text(o);if(ag.message){ad.tiles_div.append($("<a href='javascript:void(0);'></a>").text("View error").click(function(){Galaxy.modal.show({title:"Trackster Error",body:"<pre>"+ag.message+"</pre>",buttons:{Close:function(){Galaxy.modal.hide()}}})}));ad.tiles_div.append($("<span/>").text(" "));ad.tiles_div.append($("<a href='javascript:void(0);'></a>").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(G)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(w);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(U);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when.apply($,ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){ac.container_div.addClass("line-track");var af=ad.data;if(af&&af.min&&af.max){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.config.set_param_default_value("min_value",ae);ac.config.set_param_default_value("max_value",ag);ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},get_drawables:function(){return this}});var N=function(ae,ad,ag){g.call(this,ae,ad,ag);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in ag?ag.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=(ag.tool?new s(ab.extend(ag.tool,{track:this,tool_state:ag.tool_state})):null);this.tile_cache=new y.Cache(Q);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){var af=new aa({model:this.tool});af.render();this.dynamic_tool_div=af.$el;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("<div/>").addClass("tiles").appendTo(this.content_div);if(!this.prefs.content_visible){this.tiles_div.hide()}this.overlay_div=$("<div/>").addClass("overlay").appendTo(this.content_div);if(ag.mode){this.change_mode(ag.mode)}};q(N.prototype,r.prototype,g.prototype,{action_icons_def:g.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(ac){$(".tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw({clear_tile_cache:true})},hide:true}]),copy:function(ac){var ad=this.to_dict();q(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),dataset:{id:this.dataset.id,hda_ldda:this.dataset.get("hda_ldda")},prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},set_min_max:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){var af=ad.data;if(isNaN(parseFloat(ac.prefs.min_value))||isNaN(parseFloat(ac.prefs.max_value))){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.get("values").mode=ad;if(ad==="Auto"){this.data_manager.clear()}ac.request_draw({clear_tile_cache:true});this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ac){if(ac&&ac.clear_tile_cache){this.tile_cache.clear()}this.view.request_redraw(ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(aq){if(!this.can_draw()){return}var an=aq&&aq.clear_after,al=this.view.low,ag=this.view.high,aj=ag-al,ad=this.view.container.width(),ap=this.view.resolution_px_b,af=1/ap;if(this.is_overview){al=this.view.max_low;ag=this.view.max_high;ap=ad/(view.max_high-view.max_low);af=1/ap}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(af*S)),ak,am,ah,ai=[],ao=[];while((ac*S*af)<ag){ak=Math.floor(ac*S*af);am=new y.GenomeRegion({chrom:this.view.chrom,start:ak,end:Math.min(ak+Math.ceil(S*af),this.view.max_high)});ah=this.draw_helper(am,ap,aq);ai.push(ah);$.when(ah).then(function(ar){ao.push(ar)});ac+=1}if(!an){this.tiles_div.children(".remove").removeClass("remove").remove()}var ae=this;$.when.apply($,ai).then(function(){ae.tiles_div.children(".remove").remove();ao=ab.filter(ao,function(ar){return ar!==null});if(ao.length!==0){ae.postdraw_actions(ao,ad,ap,an)}})},_add_yaxis_label:function(af,ah){var ad=this,ag=(af==="max"?"top":"bottom"),ai=(af==="max"?"max":"min"),ac=(af==="max"?"max_value":"min_value"),ae=this.container_div.find(".yaxislabel."+ag);ah=ah||function(){ad.request_draw({clear_tile_cache:true})};if(ae.length!==0){ae.text(ad.prefs[ac])}else{ae=$("<div/>").text(ad.prefs[ac]).make_text_editable({num_cols:12,on_finish:function(aj){$(".tooltip").remove();ad.config.set_param_value(ac,aj);ah()},help_text:"Set "+ai+" value"}).addClass("yaxislabel "+ag).css("color",this.prefs.label_color);this.container_div.prepend(ae)}},postdraw_actions:function(af,ag,ai,ac){var ae=ab.filter(af,function(aj){return(aj instanceof M)});if(ae.length>0){this.max_height_px=0;var ad=this;ab.each(af,function(aj){if(!(aj instanceof M)){aj.html_elt.remove();ad.draw_helper(aj.region,ai,{force:true,mode:"Coverage"})}});ad._add_yaxis_label("max")}else{this.container_div.find(".yaxislabel").remove();var ah=ab.find(af,function(aj){return aj.has_icons});if(ah){ab.each(af,function(aj){if(!aj.has_icons){aj.html_elt.css("padding-top",F)}})}}},get_mode:function(ac){return this.mode},update_auto_mode:function(ac){},_get_drawables:function(){return[this]},draw_helper:function(al,an,ao){if(!ao){ao={}}var ad=ao.force,ai=ao.mode||this.mode,af=1/an,ae=this,ag=this._get_drawables(),am=this._gen_tile_cache_key(an,al),ah=function(ap){return(ap&&"track" in ap)};var aj=(ad?undefined:ae.tile_cache.get_elt(am));if(aj){if(ah(aj)){ae.show_tile(aj,an)}return aj}if(ao.data_fetch===false){return null}var ak=function(){var ap=(ab.find(C,function(ar){return ar===ai})?"Coverage":ai);var aq=ab.map(ag,function(ar){return ar.data_manager.get_data(al,ap,af,ae.data_url_extra_params)});if(view.reference_track){aq.push(view.reference_track.data_manager.get_data(al,ai,af,view.reference_track.data_url_extra_params))}return aq};var ac=$.Deferred();ae.tile_cache.set_elt(am,ac);$.when.apply($,ak()).then(function(){var ap=ak(),av=ap,aB;if(view.reference_track){aB=view.reference_track.data_manager.subset_entry(ap.pop(),al)}var aw=[],at=[];ab.each(ag,function(aG,aD){var aF=aG.mode,aE=av[aD];if(aF==="Auto"){aF=aG.get_mode(aE);aG.update_auto_mode(aF)}aw.push(aF);at.push(aG.get_canvas_height(aE,aF,an,aq))});var au=ae.view.canvas_manager.new_canvas(),ax=al.get("start"),aC=al.get("end"),ar=0,aq=Math.ceil((aC-ax)*an)+ae.left_offset,az=ab.max(at),ay;au.width=aq;au.height=(ao.height||az);var aA=au.getContext("2d");aA.translate(ae.left_offset,0);if(ag.length>1){aA.globalAlpha=0.5;aA.globalCompositeOperation="source-over"}ab.each(ag,function(aE,aD){ay=aE.draw_tile(av[aD],aA,aw[aD],al,an,aB)});if(ay!==undefined){ae.tile_cache.set_elt(am,ay);ae.show_tile(ay,an)}ac.resolve(ay)});return ac},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_line_track_tile:function(ac,ae,ah,ag,ai){var af=ae.canvas,ad=new L.LinePainter(ac.data,ag.get("start"),ag.get("end"),this.prefs,ah);ad.draw(ae,af.width,af.height,ai);return new M(this,ag,ai,af,ac.data)},draw_tile:function(ac,ad,ag,af,ah,ae){},show_tile:function(ae,ah){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ah;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{this.tiles_div.append(ac)}ac.css("height","auto");this.max_height_px=Math.max(this.max_height_px,ac.height());ac.parent().children().css("height",this.max_height_px+"px");var af=this.max_height_px;if(this.visible_height_px!==0){af=Math.min(this.max_height_px,this.visible_height_px)}this.tiles_div.css("height",af+"px")},tool_region_and_parameters_str:function(af){var ac=this,ae=(af!==undefined?af.toString():"all"),ad=ab.values(ac.tool.get_inputs_dict()).join(", ");return" - region=["+ae+"], parameters=["+ad+"]"},data_and_mode_compatible:function(ac,ad){if(ad==="Auto"){return true}else{if(ad==="Coverage"){return ac.dataset_type==="bigwig"}else{if(ac.dataset_type==="bigwig"||ac.extra_info==="no_detail"){return false}else{return true}}}},can_subset:function(ac){if(ac.message||ac.extra_info==="no_detail"){return false}else{if(ac.dataset_type==="bigwig"){return(ac.data[1][0]-ac.data[0][0]===1)}}return true},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state"}});var W=function(ad,ac){var ae={resize:false};g.call(this,ad,ac,ae);this.container_div.addClass("label-track")};q(W.prototype,g.prototype,{build_header_div:function(){},init:function(){this.enabled=true},predraw_init:function(){},_draw:function(ag){var ae=this.view,af=ae.high-ae.low,aj=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/aj)*aj,ah=this.view.container.width(),ad=$("<div style='position: relative; height: 1.3em;'></div>");while(ac<ae.high){var ai=(ac-ae.low)/af*ah;ad.append($("<div class='label'>"+commatize(ac)+"</div>").css({position:"absolute",left:ai-1}));ac+=aj}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){N.call(this,ad,ac,ag);this.drawables=[];if("drawables" in ag){var af;for(var ae=0;ae<ag.drawables.length;ae++){af=ag.drawables[ae];this.drawables[ae]=p(af,ad,null);if(af.left_offset>this.left_offset){this.left_offset=af.left_offset}}this.enabled=true}ab.each(this.drawables,function(ah){if(ah instanceof d||ah instanceof T){ah.change_mode("Coverage")}});this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,N.prototype,{display_modes:C,action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".tooltip").remove();ac.show_group()}}].concat(N.prototype.action_icons_def),to_dict:A.prototype.to_dict,add_drawable:A.prototype.add_drawable,unpack_drawables:A.prototype.unpack_drawables,change_mode:function(ac){N.prototype.change_mode.call(this,ac);for(var ad=0;ad<this.drawables.length;ad++){this.drawables[ad].change_mode(ac)}},init:function(){var ae=[];for(var ad=0;ad<this.drawables.length;ad++){ae.push(this.drawables[ad].init())}var ac=this;$.when.apply($,ae).then(function(){ac.enabled=true;ac.request_draw()})},update_icons:function(){this.action_icons.filters_icon.hide();this.action_icons.tools_icon.hide();this.action_icons.param_space_viz_icon.hide()},can_draw:r.prototype.can_draw,_get_drawables:function(){return this.drawables},show_group:function(){var af=new P(this.view,this.container,{name:this.prefs.name}),ac;for(var ae=0;ae<this.drawables.length;ae++){ac=this.drawables[ae];ac.update_icons();af.add_drawable(ac);ac.container=af;af.content_div.append(ac.container_div)}var ad=this.container.replace_drawable(this,af,true);af.request_draw({clear_tile_cache:true})},before_draw:function(){N.prototype.before_draw.call(this);var ad=ab.min(ab.map(this.drawables,function(ae){return ae.prefs.min_value})),ac=ab.max(ab.map(this.drawables,function(ae){return ae.prefs.max_value}));this.prefs.min_value=ad;this.prefs.max_value=ac;ab.each(this.drawables,function(ae){ae.prefs.min_value=ad;ae.prefs.max_value=ac})},update_all_min_max:function(){var ac=this;ab.each(this.drawables,function(ad){ad.prefs.min_value=ac.prefs.min_value;ad.prefs.max_value=ac.prefs.max_value});this.request_draw({clear_tile_cache:true})},postdraw_actions:function(ai,ac,al,ah){N.prototype.postdraw_actions.call(this,ai,ac,al,ah);var ag=-1,ae;for(ae=0;ae<ai.length;ae++){var aj=ai[ae].html_elt.find("canvas").height();if(aj>ag){ag=aj}}for(ae=0;ae<ai.length;ae++){var af=ai[ae];if(af.html_elt.find("canvas").height()!==ag){this.draw_helper(af.region,al,{force:true,height:ag});af.html_elt.remove()}}var ad=this,ak=function(){ad.update_all_min_max()};this._add_yaxis_label("min",ak);this._add_yaxis_label("max",ak)}});var D=function(ac){N.call(this,ac,{content_div:ac.top_labeltrack},{resize:false});this.left_offset=ac.canvas_manager.char_width_px;this.container_div.addClass("reference-track");this.data_url=galaxy_config.root+"api/genomes/"+this.view.dbkey;this.data_url_extra_params={reference:true};this.data_manager=new y.GenomeReferenceDataManager({data_url:this.data_url,can_subset:this.can_subset});this.hide_contents()};q(D.prototype,r.prototype,N.prototype,{config_params:ab.union(r.prototype.config_params,[{key:"height",type:"int",default_value:13,hidden:true}]),build_header_div:function(){},init:function(){this.data_manager.clear();this.enabled=true},predraw_init:function(){},can_draw:r.prototype.can_draw,draw_helper:function(ad,ae,ac){if(ae>this.view.canvas_manager.char_width_px){this.tiles_div.show();return N.prototype.draw_helper.call(this,ad,ae,ac)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(ae,ak,af,ah,al){var ad=this.data_manager.subset_entry(ae,ah),aj=ad.data;var ac=ak.canvas;ak.font=ak.canvas.manager.default_font;ak.textAlign="center";for(var ag=0,ai=aj.length;ag<ai;ag++){ak.fillStyle=this.view.get_base_color(aj[ag]);ak.fillText(aj[ag],Math.floor(ag*al),10)}return new c(this,ah,al,ac,ad)}});var h=function(ad,ac,ae){this.mode="Histogram";N.call(this,ad,ac,ae)};q(h.prototype,r.prototype,N.prototype,{display_modes:C,config_params:ab.union(r.prototype.config_params,[{key:"color",label:"Color",type:"color",default_value:l.get_random_color()},{key:"min_value",label:"Min Value",type:"float",default_value:undefined},{key:"max_value",label:"Max Value",type:"float",default_value:undefined},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:30,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})},before_draw:function(){},draw_tile:function(ac,ad,af,ae,ag){return this._draw_line_track_tile(ac,ad,af,ae,ag)},can_subset:function(ac){return(ac.data[1][0]-ac.data[0][0]===1)},postdraw_actions:function(ad,ae,af,ac){this._add_yaxis_label("max");this._add_yaxis_label("min")}});var t=function(ad,ac,ae){this.mode="Heatmap";N.call(this,ad,ac,ae)};q(t.prototype,r.prototype,N.prototype,{display_modes:["Heatmap"],config_params:ab.union(r.prototype.config_params,[{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"min_value",label:"Min Value",type:"float",default_value:-1},{key:"max_value",label:"Max Value",type:"float",default_value:1},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:500,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})},draw_tile:function(ac,ae,ah,ag,ai){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ag.get("start"),ag.get("end"),this.prefs,ah);ad.draw(ae,af.width,af.height,ai);return new c(this,ag,ai,af,ac.data)}});var d=function(ad,ac,ae){N.call(this,ad,ac,ae);this.container_div.addClass("feature-track");this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};q(d.prototype,r.prototype,N.prototype,{display_modes:["Auto","Coverage","Dense","Squish","Pack"],config_params:ab.union(r.prototype.config_params,[{key:"block_color",label:"Block color",type:"color",default_value:l.get_random_color()},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:l.get_random_color()},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true,help:"Show the number of items in each bin when drawing summary histogram"},{key:"min_value",label:"Histogram minimum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"max_value",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"connector_style",label:"Connector style",type:"select",default_value:"fishbones",options:[{label:"Line with arrows",value:"fishbone"},{label:"Arcs",value:"arcs"}]},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:0,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.set_painter_from_config();this.request_draw({clear_tile_cache:true})},set_painter_from_config:function(){if(this.config.get("values").connector_style==="arcs"){this.painter=L.ArcLinkedFeaturePainter}else{this.painter=L.LinkedFeaturePainter}},postdraw_actions:function(am,ae,an,al){N.prototype.postdraw_actions.call(this,am,ae,an,al);var ag=this,ah;if(ag.filters_manager){var af=ag.filters_manager.filters,aj;for(aj=0;aj<af.length;aj++){af[aj].update_ui_elt()}var ai=false,ak,ad;for(ah=0;ah<am.length;ah++){if(am[ah].data.length){ak=am[ah].data[0];for(aj=0;aj<af.length;aj++){ad=af[aj];if(ad.applies_to(ak)&&ad.min!==ad.max){ai=true;break}}}}if(ag.filters_available!==ai){ag.filters_available=ai;if(!ag.filters_available){ag.filters_manager.hide()}ag.update_icons()}}if(am[0] instanceof O){var ac=true;for(ah=0;ah<am.length;ah++){if(!am[ah].all_slotted){ac=false;break}}if(!ac){this.action_icons.show_more_rows_icon.show()}else{this.action_icons.show_more_rows_icon.hide()}}else{this.action_icons.show_more_rows_icon.hide()}},update_auto_mode:function(ac){if(this.mode==="Auto"){if(ac==="no_detail"){ac="feature spans"}this.action_icons.mode_icon.attr("title","Set display mode (now: Auto/"+ac+")")}},incremental_slots:function(ag,ac,af){var ad=this.view.canvas_manager.dummy_context,ae=this.slotters[ag];if(!ae||(ae.mode!==af)){ae=new (u.FeatureSlotter)(ag,af,B,function(ah){return ad.measureText(ah)});this.slotters[ag]=ae}return ae.slot_features(ac)},get_mode:function(ac){if(ac.extra_info==="no_detail"||this.is_overview){mode="no_detail"}else{if(this.view.high-this.view.low>I){mode="Squish"}else{mode="Pack"}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="Coverage"||ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(this.min_height_px,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,af,aj,ae){var ap=this,ad=aq.canvas,ax=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(am.dataset_type==="bigwig"){return this._draw_line_track_tile(am,aq,ao,af,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var ar=0,au=am.data.length;ar<au;ar++){var ah=am.data[ar];var at=false;var al;for(var aw=0,aB=ak.length;aw<aB;aw++){al=ak[aw];al.update_attrs(ah);if(!al.keep(ah)){at=true;break}}if(!at){ai.push(ah);if(!(ah[0] in an)){all_slotted=false}}}}var aA=(this.filters_manager.alpha_filter?new E(this.filters_manager.alpha_filter):null),ay=(this.filters_manager.height_filter?new E(this.filters_manager.height_filter):null),az=new (this.painter)(ai,ax,ac,this.prefs,ao,aA,ay,ae,function(aC){return ap.view.get_base_color(aC)});var av=null;aq.fillStyle=this.prefs.block_color;aq.font=aq.canvas.manager.default_font;aq.textAlign="right";if(am.data){av=az.draw(aq,ad.width,ad.height,aj,an);av.translation=-ag}return new O(ap,af,aj,ad,am.data,ao,am.message,all_slotted,av)}});var Z=function(ad,ac,ae){N.call(this,ad,ac,ae);this.painter=L.VariantPainter;this.summary_draw_height=30;this.left_offset=30};q(Z.prototype,r.prototype,N.prototype,{display_modes:["Auto","Coverage","Dense","Squish","Pack"],config_params:ab.union(r.prototype.config_params,[{key:"color",label:"Histogram color",type:"color",default_value:l.get_random_color()},{key:"show_sample_data",label:"Show sample data",type:"bool",default_value:true},{key:"show_labels",label:"Show summary and sample labels",type:"bool",default_value:true},{key:"summary_height",label:"Locus summary height",type:"float",default_value:20},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:0,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})},draw_tile:function(ac,af,ah,ag,ai){if(ac.dataset_type==="bigwig"){return this._draw_line_track_tile(ac,af,"Histogram",ag,ai)}else{var ae=this.view,ad=new (this.painter)(ac.data,ag.get("start"),ag.get("end"),this.prefs,ah,function(aj){return ae.get_base_color(aj)});ad.draw(af,af.canvas.width,af.canvas.height,ai);return new c(this,ag,ai,af.canvas,ac.data)}},get_canvas_height:function(ac,ag,ah,ae){if(ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var ad=(this.dataset.get_metadata("sample_names")?this.dataset.get_metadata("sample_names").length:0);if(ad===0&&ac.data.length!==0){ad=ac.data[0][7].match(/,/g);if(ad===null){ad=1}else{ad=ad.length+1}}var af=new (this.painter)(null,null,null,this.prefs,ag);return af.get_required_height(ad)}},predraw_init:function(){var ac=[g.prototype.predraw_init.call(this)];if(!this.dataset.get_metadata("sample_names")){ac.push(this.dataset.fetch())}return ac},postdraw_actions:function(ag,ah,aj,ad){N.prototype.postdraw_actions.call(this,ag,ah,aj,ad);var af=ab.filter(ag,function(ak){return(ak instanceof M)});var ae=this.dataset.get_metadata("sample_names");if(af.length===0&&this.prefs.show_labels&&ae){var ac;if(this.container_div.find(".yaxislabel.variant").length===0){ac=this.prefs.summary_height/2;this.tiles_div.prepend($("<div/>").text("Summary").addClass("yaxislabel variant top").css({"font-size":ac+"px",top:(this.prefs.summary_height-ac)/2+"px"}));if(this.prefs.show_sample_data){var ai=ae.join("<br/>");this.tiles_div.prepend($("<div/>").html(ai).addClass("yaxislabel variant top sample").css({top:this.prefs.summary_height+2,}))}}ac=(this.mode==="Squish"?5:10)+"px";$(this.tiles_div).find(".sample").css({"font-size":ac,"line-height":ac});$(this.tiles_div).find(".yaxislabel").css("color",this.prefs.label_color)}else{this.container_div.find(".yaxislabel.variant").remove()}}});var T=function(ad,ac,ae){d.call(this,ad,ac,ae);this.painter=(ad.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};q(T.prototype,r.prototype,N.prototype,d.prototype,{config_params:ab.union(r.prototype.config_params,[{key:"block_color",label:"Block and sense strand color",type:"color",default_value:l.get_random_color()},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:l.get_random_color()},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"min_value",label:"Histogram minimum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"max_value",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"height",type:"int",default_value:0,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})}});var e={CompositeTrack:f,DrawableGroup:P,DiagonalHeatmapTrack:t,FeatureTrack:d,LineTrack:h,ReadTrack:T,VariantTrack:Z,VcfTrack:Z};var p=function(ae,ad,ac){if("copy" in ae){return ae.copy(ac)}else{var af=ae.obj_type;if(!af){af=ae.track_type}return new e[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:P,LineTrack:h,FeatureTrack:d,DiagonalHeatmapTrack:t,ReadTrack:T,VariantTrack:Z,CompositeTrack:f,object_from_template:p}});
\ No newline at end of file
+define(["libs/underscore","viz/visualization","viz/trackster/util","viz/trackster/slotting","viz/trackster/painters","viz/trackster/filters","mvc/data","mvc/tools"],function(ab,y,l,u,L,i,X,R){var q=ab.extend;var n={};var k=function(ac,ad){n[ac.attr("id")]=ad};var m=function(ac,ae,ag,af){ag=".group";var ad={};n[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=n[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();var ar=n[ai.attr("id")];if(ap.offsetY<aq){$(this).insertBefore(ai);ar.remove_drawable(am);ar.container.add_drawable_before(am,ar);return}else{if(ap.offsetY>ah){$(this).insertAfter(ai);ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al<ak.length;al++){aj=$(ak.get(al));aq=aj.position().top;ah=aq+aj.outerHeight();if(aj.is(ag)&&this!==aj.get(0)&&ap.offsetY>=aq&&ap.offsetY<=ah){if(ap.offsetY-aq<ah-ap.offsetY){aj.find(".content-div").prepend(this)}else{aj.find(".content-div").append(this)}if(am.container){am.container.remove_drawable(am)}n[aj.attr("id")].add_drawable(am);return}}for(al=0;al<ak.length;al++){aj=$(ak.get(al));if(ap.offsetY<aj.position().top&&!(aj.hasClass("reference-track")||aj.hasClass("intro"))){break}}if(al===ak.length){if(this!==ak.get(al-1)){at.append(this);n[at.attr("id")].move_drawable(am,al)}}else{if(this!==ak.get(al)){$(this).insertBefore(ak.get(al));n[at.attr("id")].move_drawable(am,(ap.deltaY>0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var F=20,B=100,I=12000,S=400,K=5000,x=100,o="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",G="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: ",b="Loading data...",U="Ready for display",Q=10,H=20,C=["Histogram","Line","Filled","Intensity"];function V(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var r=function(ad,ac,af){if(!r.id_counter){r.id_counter=0}this.id=r.id_counter++;this.view=ad;this.container=ac;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.config=new j({params:this.config_params,onchange:this.config_onchange,saved_values:af.prefs});this.prefs=this.config.get("values");if(!this.prefs.name){this.prefs.name=af.name}if(this.config_onchange){this.config.on("change:values",this.config_onchange,this)}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(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("<div style='clear: both'/>").appendTo(this.container_div)}};r.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.prefs.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.prefs.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.prefs.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var ac=new a({model:ad.config});ac.render()}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".tooltip").remove();ac.remove()}}];q(r.prototype,{config_params:[{key:"name",label:"Name",type:"text",default_value:""},{key:"content_visible",type:"bool",default_value:true,hidden:true}],config_onchange:function(){this.track.set_name(this.track.config.get("values").name)},init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.prefs.content_visible){return true}return false},request_draw:function(){},_draw:function(ac){},to_dict:function(){},set_name:function(ac){this.old_name=this.prefs.name;this.prefs.name=ac;this.name_div.text(this.prefs.name)},revert_name:function(){if(this.old_name){this.prefs.name=this.old_name;this.name_div.text(this.prefs.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("<a/>").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad<ac.length;ad++){ae=ac[ad];this.add_action_icon(ae.name,ae.title,ae.css_class,ae.on_click_fn,ae.prepend,ae.hide)}},update_icons:function(){},hide_contents:function(){},show_contents:function(){},get_drawables:function(){}});var A=function(ad,ac,ae){r.call(this,ad,ac,ae);this.obj_type=ae.obj_type;this.drawables=[]};q(A.prototype,r.prototype,{unpack_drawables:function(ae){this.drawables=[];var ad;for(var ac=0;ac<ae.length;ac++){ad=p(ae[ac],this.view,this);this.add_drawable(ad)}},init:function(){for(var ac=0;ac<this.drawables.length;ac++){this.drawables[ac].init()}},_draw:function(ac){for(var ad=0;ad<this.drawables.length;ad++){this.drawables[ad]._draw(ac)}},to_dict:function(){var ad=[];for(var ac=0;ac<this.drawables.length;ac++){ad.push(this.drawables[ac].to_dict())}return{prefs:this.prefs,obj_type:this.obj_type,drawables:ad}},add_drawable:function(ac){this.drawables.push(ac);ac.container=this;this.changed()},add_drawable_before:function(ae,ac){this.changed();var ad=this.drawables.indexOf(ac);if(ad!==-1){this.drawables.splice(ad,0,ae);return true}return false},replace_drawable:function(ae,ac,ad){var af=this.drawables.indexOf(ae);if(af!==-1){this.drawables[af]=ac;if(ad){ae.container_div.replaceWith(ac.container_div)}this.changed()}return af},remove_drawable:function(ad){var ac=this.drawables.indexOf(ad);if(ac!==-1){this.drawables.splice(ac,1);ad.container=null;this.changed();return true}return false},move_drawable:function(ad,ae){var ac=this.drawables.indexOf(ad);if(ac!==-1){this.drawables.splice(ac,1);this.drawables.splice(ae,0,ad);this.changed();return true}return false},get_drawables:function(){return this.drawables},get_tracks:function(af){var ac=this.drawables.slice(0),ad=[],ae;while(ac.length!==0){ae=ac.shift();if(ae instanceof af){ad.push(ae)}else{if(ae.drawables){ac=ac.concat(ae.drawables)}}}return ad}});var P=function(ad,ac,af){q(af,{obj_type:"DrawableGroup",drag_handle_class:"group-handle"});A.call(this,ad,ac,af);this.content_div=$("<div/>").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);k(this.container_div,this);k(this.content_div,this);m(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};q(P.prototype,r.prototype,A.prototype,{action_icons_def:[r.prototype.action_icons_def[0],r.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw({clear_tile_cache:true})}ac.filters_manager.toggle()}},r.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("<div/>").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("<div/>").addClass("track-header");ac.append($("<div/>").addClass(this.drag_handle_class));this.name_div=$("<div/>").addClass("track-name").text(this.prefs.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al<ae;al++){ai=this.drawables[al];if(ai.get_type()!==ag){can_composite=false;break}if(ai instanceof d){ac++}}if(ao||ac===1){this.action_icons.composite_icon.show()}else{this.action_icons.composite_icon.hide();$(".tooltip").remove()}if(ac>1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak<ai.filters_manager.filters.length;ak++){ad=ai.filters_manager.filters[ak];ap[ad.name]=[ad]}for(al=1;al<this.drawables.length;al++){ai=this.drawables[al];for(ak=0;ak<ai.filters_manager.filters.length;ak++){ad=ai.filters_manager.filters[ak];if(ad.name in ap){ap[ad.name].push(ad)}}}this.filters_manager.remove_all();var af,ah,aj,am;for(var an in ap){af=ap[an];if(af.length===ac){ah=new i.NumberFilter({name:af[0].name,index:af[0].index});this.filters_manager.add_filter(ah)}}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 ac=0;ac<this.drawables.length;ac++){this.drawables[ac].filters_manager=this.saved_filters_managers[ac]}this.saved_filters_managers=[]},setup_multitrack_filtering:function(){if(this.filters_manager.filters.length>0){this.saved_filters_managers=[];for(var ac=0;ac<this.drawables.length;ac++){drawable=this.drawables[ac];this.saved_filters_managers.push(drawable.filters_manager);drawable.filters_manager=this.filters_manager}}this.filters_manager.init_filters()},show_composite_track:function(){var ad=new f(this.view,this.view,{name:this.prefs.name,drawables:this.drawables});var ac=this.container.replace_drawable(this,ad,true);ad.request_draw()},add_drawable:function(ac){A.prototype.add_drawable.call(this,ac);this.update_icons()},remove_drawable:function(ac){A.prototype.remove_drawable.call(this,ac);this.update_icons()},to_dict:function(){if(this.filters_manager.visible()){this._restore_filter_managers()}var ac=q(A.prototype.to_dict.call(this),{filters:this.filters_manager.to_dict()});if(this.filters_manager.visible()){this.setup_multitrack_filtering()}return ac},request_draw:function(ac){ab.each(this.drawables,function(ad){ad.request_draw(ac)})}});var Y=Backbone.View.extend({initialize:function(ac){q(ac,{obj_type:"View"});A.call(this,"View",ac.container,ac);this.chrom=null;this.vis_id=ac.vis_id;this.dbkey=ac.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.render();this.canvas_manager=new y.CanvasManager(this.container.get(0).ownerDocument);this.reset();this.config=new j({track:this,params:[{key:"a_color",label:"A Color",type:"color",default_value:"#FF0000"},{key:"c_color",label:"C Color",type:"color",default_value:"#00FF00"},{key:"g_color",label:"G Color",type:"color",default_value:"#0000FF"},{key:"t_color",label:"T Color",type:"color",default_value:"#FF00FF"},{key:"n_color",label:"N Color",type:"color",default_value:"#AAAAAA"}],saved_values:ac.prefs,onchange:function(){track.request_redraw({clear_tile_cache:true})}})},render:function(){this.requested_redraw=false;var ae=this.container,ac=this;this.top_container=$("<div/>").addClass("top-container").appendTo(ae);this.browser_content_div=$("<div/>").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(ae);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;k(this.viewport_container,ac);this.intro_div=$("<div/>").addClass("intro").appendTo(this.viewport_container).hide();var af=$("<div/>").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){y.select_datasets(galaxy_config.root+"visualization/list_current_history_datasets",galaxy_config.root+"api/datasets",{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(p(ah,ac,ac))})})});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("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 ad=function(ag){if(ag.type==="focusout"||(ag.keyCode||ag.which)===13||(ag.keyCode||ag.which)===27){if((ag.keyCode||ag.which)!==27){ac.go_to($(this).val())}$(this).hide();$(this).val("");ac.location_span.show();ac.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",ad).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(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_tracks(d),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[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(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a/>").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("<div />").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new W(this,{content_div:this.top_labeltrack}));this.add_label_track(new W(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.get("values")[ac.toLowerCase()+"_color"]||this.config.get("values").n_color}});q(Y.prototype,A.prototype,{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(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=x;var ac=this,ad=$.Deferred();$.ajax({url:galaxy_config.root+"api/genomes/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){var ah=new D(ac);ac.add_label_track(ah);ac.reference_track=ah}ac.chrom_data=ag.chrom_info;var ak='<option value="">Select Chrom/Contig</option>';for(var aj=0,af=ac.chrom_data.length;aj<af;aj++){var ai=ac.chrom_data[aj].chrom;ak+='<option value="'+ai+'">'+ai+"</option>"}if(ag.prev_chroms){ak+='<option value="previous">Previous '+x+"</option>"}if(ag.next_chroms){ak+='<option value="next">Next '+x+"</option>"}ac.chrom_select.html(ak);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-x});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+x});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();for(var ag=0,ac=ae.drawables.length;ag<ac;ag++){var af=ae.drawables[ag];if(af.init){af.init()}}if(ae.reference_track){ae.reference_track.init()}}if(ad&&aj){ae.low=Math.max(ad,0);ae.high=Math.min(aj,ae.max_high)}else{ae.low=0;ae.high=ae.max_high}ae.reset_overview();ae.request_redraw()}},go_to:function(ag){ag=ag.replace(/,/g,"");ag=ag.replace(/:|\-/g," ");var ad=ag.split(/\s+/),af=ad[0],ae=(ad[1]?parseInt(ad[1],10):null),ac=(ad[2]?parseInt(ad[2],10):null);if(!ac){ae=ae-15;ac=ae+15}this.change_chrom(af,ae,ac)},move_fraction:function(ae){var ac=this;var ad=ac.high-ac.low;this.move_delta(ae*ad)},move_delta:function(af){var ac=this;var ae=ac.high-ac.low;if(ac.low-af<ac.max_low){ac.low=ac.max_low;ac.high=ac.max_low+ae}else{if(ac.high-af>ac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw({data_fetch:false});if(this.redraw_on_move_fn){clearTimeout(this.redraw_on_move_fn)}this.redraw_on_move_fn=setTimeout(function(){ac.request_redraw()},200);var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){A.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){A.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ad,ae){var ac=this,af=(ae?[ae]:ac.drawables);ab.each(af,function(ag){var ah=ab.find(ac.tracks_to_be_redrawn,function(ai){return ai[0]===ag});if(ah){ah[1]=ad}else{ac.tracks_to_be_redrawn.push([ag,ad])}});if(!this.requested_redraw){requestAnimationFrame(function(){ac._redraw()});this.requested_redraw=true}},_redraw:function(){this.requested_redraw=false;var ac=this.low,ag=this.high;if(ac<this.max_low){ac=this.max_low}if(ag>this.max_high){ag=this.max_high}var ad=this.high-this.low;if(this.high!==0&&ad<this.min_separation){ag=ac+this.min_separation}this.low=Math.floor(ac);this.high=Math.ceil(ag);this.update_location(this.low,this.high);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 ah=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var ae=13;this.overview_box.css({left:af,width:Math.max(ae,ah)}).show();if(ah<ae){this.overview_box.css("left",af-(ae-ah)/2)}if(this.overview_highlight){this.overview_highlight.css({left:af,width:ah})}ab.each(this.tracks_to_be_redrawn,function(ak){var ai=ak[0],aj=ak[1];if(ai){ai._draw(aj)}});this.tracks_to_be_redrawn=[];ab.each(this.label_tracks,function(ai){ai._draw()})},zoom_in:function(ad,ae){if(this.max_high===0||this.high-this.low<=this.min_separation){return}var af=this.high-this.low,ag=af/2+this.low,ac=(af/this.zoom_factor)/2;if(ad){ag=ad/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(ag-ac);this.high=Math.round(ag+ac);this.changed();this.request_redraw()},zoom_out:function(){if(this.max_high===0){return}var ad=this.high-this.low,ae=ad/2+this.low,ac=(ad*this.zoom_factor)/2;this.low=Math.round(ae-ac);this.high=Math.round(ae+ac);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(ae){if(this.overview_drawable){if(this.overview_drawable.dataset.id===ae.dataset.id){return}this.overview_viewport.find(".track").remove()}var ad=ae.copy({content_div:this.overview_viewport}),ac=this;ad.header_div.hide();ad.is_overview=true;ac.overview_drawable=ad;this.overview_drawable.postdraw_actions=function(){ac.overview_highlight.show().height(ac.overview_drawable.content_div.height());ac.overview_viewport.height(ac.overview_drawable.content_div.height()+ac.overview_box.outerHeight());ac.overview_close.show();ac.resize_window()};ac.overview_drawable.request_draw();this.changed()},reset_overview:function(){$(".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 s=R.Tool.extend({defaults:{track:null},initialize:function(ac){if(ac.tool_state!==undefined){this.set("hidden",ac.tool_state.hidden)}this.remove_inputs(["data","hidden_data","conditional"])},state_dict:function(ac){return ab.extend(this.get_inputs_dict(),{hidden:!this.is_visible()})}});var v=Backbone.View.extend({events:{"change input":"update_value"},render:function(){var ae=this.$el.addClass("param-row"),af=this.model;var ac=$("<div>").addClass("param-label").text(af.get("label")).appendTo(ae);var ad=$("<div/>").addClass("param-input").html(af.get("html")).appendTo(ae);ad.find(":input").val(af.get("value"));$("<div style='clear: both;'/>").appendTo(ae)},update_value:function(ac){this.model.set_value($(ac.target).val())}});var aa=Backbone.View.extend({initialize:function(ac){this.model.on("change:hidden",this.set_visible,this)},render:function(){var ad=this;tool=this.model,parent_div=this.$el.addClass("dynamic-tool").hide();parent_div.bind("drag",function(ah){ah.stopPropagation()}).click(function(ah){ah.stopPropagation()}).bind("dblclick",function(ah){ah.stopPropagation()}).keydown(function(ah){ah.stopPropagation()});var ae=$("<div class='tool-name'>").appendTo(parent_div).text(tool.get("name"));tool.get("inputs").each(function(ai){var ah=new v({model:ai});ah.render();parent_div.append(ah.$el)});parent_div.find("input").click(function(){$(this).select()});var af=$("<div>").addClass("param-row").appendTo(parent_div);var ag=$("<input type='submit'>").attr("value","Run on complete dataset").appendTo(af);var ac=$("<input type='submit'>").attr("value","Run on visible region").css("margin-left","3em").appendTo(af);ac.click(function(){ad.run_on_region()});ag.click(function(){ad.run_on_dataset()});if(tool.is_visible()){this.$el.show()}},set_visible:function(){if(this.model.is_visible()){this.$el.show()}else{this.$el.hide()}},update_params:function(){for(var ac=0;ac<this.params.length;ac++){this.params[ac].update_value()}},run_on_dataset:function(){var ac=this.model;this.run({target_dataset_id:this.model.get("track").dataset.id,action:"rerun",tool_id:ac.id},null,function(ad){Galaxy.modal.show({title:ac.get("name")+" is Running",body:ac.get("name")+" is running on the complete dataset. Tool outputs are in dataset's history.",buttons:{Close:function(){Galaxy.modal.hide()}}})})},run_on_region:function(){var ad=this.model.get("track"),ag=this.model,ai=new y.GenomeRegion({chrom:ad.view.chrom,start:ad.view.low,end:ad.view.high}),aj={target_dataset_id:ad.dataset.id,action:"rerun",tool_id:ag.id,regions:[ai.toJSON()]},ah=ad,al=aj.tool_id+ah.tool_region_and_parameters_str(ai),ac;if(ah.container===view){var ak=new P(view,view,{name:this.prefs.name});var af=ah.container.replace_drawable(ah,ak,false);ak.container_div.insertBefore(ah.view.content_div.children()[af]);ak.add_drawable(ah);ah.container_div.appendTo(ak.content_div);ac=ak}else{ac=ah.container}var ae=new ah.constructor(view,ac,{name:al,hda_ldda:"hda"});ae.init_for_tool_data();ae.change_mode(ah.mode);ae.set_filters_manager(ah.filters_manager.copy(ae));ae.update_icons();ac.add_drawable(ae);ae.tiles_div.text("Starting job.");this.run(aj,ae,function(am){ae.set_dataset(new X.Dataset(am));ae.tiles_div.text("Running job.");ae.init()})},run:function(ac,ae,af){ac.inputs=this.model.get_inputs_dict();var ad=new l.ServerStateDeferred({ajax_settings:{url:galaxy_config.root+"api/tools",data:JSON.stringify(ac),dataType:"json",contentType:"application/json",type:"POST"},interval:2000,success_fn:function(ag){return ag!=="pending"}});$.when(ad.go()).then(function(ag){if(ag==="no converter"){ae.container_div.addClass("error");ae.content_div.text(J)}else{if(ag.error){ae.container_div.addClass("error");ae.content_div.text(z+ag.message)}else{af(ag)}}})}});var E=function(ac,ad){L.Scaler.call(this,ad);this.filter=ac};E.prototype.gen_val=function(ac){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(ac[this.filter.index])-this.filter.low)/(this.filter.high-this.filter.low))};var j=Backbone.Model.extend({initialize:function(ad){var ac={};ab.each(ad.params,function(ae){ac[ae.key]=ae.default_value});if(ad.saved_values){ab.each(this.get("params"),function(ae){if(ae.key in ad.saved_values){ac[ae.key]=ad.saved_values[ae.key]}})}this.set("values",ac)},set_param_default_value:function(ad,ac){var ae=ab.find(this.get("params"),function(af){return af.key===ad});if(ae){ae.default_value=ac}},set_param_value:function(ac,ad){var ae=ab.find(this.get("params"),function(af){return af.key===ac});if(ae){if(typeof ad==="string"||ad instanceof String){if(ad.trim()===""){ad=ae.default_value}else{if(ae.type==="float"){ad=parseFloat(ad)}else{if(ae.type==="int"){ad=parseInt(ad,10)}}}}}if(this.get("values")[ac]!==ad){this.get("values")[ac]=ad;this.trigger("change:values");return true}else{return false}}});var a=Backbone.View.extend({render:function(){var aj=this.model;var ac=$("<div/>").keydown(function(ak){ak.stopPropagation()});var ai;function ah(ao,ak){for(var at=0;at<ao.length;at++){ai=ao[at];if(ai.hidden){continue}var am="param_"+at;var ax=aj.get("values")[ai.key];var az=$("<div class='form-row' />").appendTo(ak);az.append($("<label />").attr("for",am).text(ai.label+":"));if(ai.type==="bool"){az.append($('<input type="checkbox" />').attr("id",am).attr("name",am).attr("checked",ax))}else{if(ai.type==="text"){az.append($('<input type="text"/>').attr("id",am).val(ax).click(function(){$(this).select()}))}else{if(ai.type==="select"){var av=$("<select />").attr("id",am);for(var aq=0;aq<ai.options.length;aq++){$("<option/>").text(ai.options[aq].label).attr("value",ai.options[aq].value).appendTo(av)}av.val(ax);az.append(av)}else{if(ai.type==="color"){var ay=$("<div/>").appendTo(az),au=$("<input />").attr("id",am).attr("name",am).val(ax).css("float","left").appendTo(ay).click(function(aB){$(".tooltip").removeClass("in");var aA=$(this).siblings(".tooltip").addClass("in");aA.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top+Galaxy.modal.scrollTop()-($(aA).height()/2)+($(this).height()/2)}).show();aA.click(function(aC){aC.stopPropagation()});$(document).bind("click.color-picker",function(){aA.hide();$(document).unbind("click.color-picker")});aB.stopPropagation()}),ar=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(ay).attr("title","Set new random color").tooltip(),aw=$("<div class='tooltip right' style='position: absolute;' />").appendTo(ay).hide(),an=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(aw),al=$("<div class='tooltip-arrow'></div>").appendTo(aw),ap=$.farbtastic(an,{width:100,height:100,callback:au,color:ax});ay.append($("<div/>").css("clear","both"));(function(aA){ar.click(function(){aA.setColor(l.get_random_color())})})(ap)}else{az.append($("<input />").attr("id",am).attr("name",am).val(ax))}}}}if(ai.help){az.append($("<div class='help'/>").text(ai.help))}}}ah(aj.get("params"),ac);var ad=this,ag=function(){Galaxy.modal.hide();$(window).unbind("keypress.check_enter_esc")},ae=function(){ad.update_from_form($(Galaxy.modal.el));Galaxy.modal.hide();$(window).unbind("keypress.check_enter_esc")},af=function(ak){if((ak.keyCode||ak.which)===27){ag()}else{if((ak.keyCode||ak.which)===13){ae()}}};$(window).bind("keypress.check_enter_esc",af);Galaxy.modal.show({title:"Configure",body:ac,buttons:{Cancel:ag,Ok:ae}})},update_from_form:function(ac){var ad=this.model;var ae=false;ab.each(ad.get("params"),function(ah,af){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}ae=ad.set_param_value(ah.key,ag)||ae}})}});var c=function(ac,af,ag,ad,ae){this.track=ac;this.region=af;this.low=af.get("start");this.high=af.get("end");this.w_scale=ag;this.canvas=ad;this.html_elt=$("<div class='track-tile'/>").append(ad);this.data=ae;this.stale=false};c.prototype.predisplay_actions=function(){};var M=function(ac,af,ag,ad,ae){c.call(this,ac,af,ag,ad,ae)};M.prototype.predisplay_actions=function(){};var O=function(af,am,ao,ae,ah,ai,ap,ad,al){c.call(this,af,am,ao,ae,ah);this.mode=ai;this.all_slotted=ad;this.feature_mapper=al;this.has_icons=false;if(ap){this.has_icons=true;var aj=this;ae=this.html_elt.children()[0],message_div=$("<div/>").addClass("tile-message").css({height:F,width:ae.width}).prependTo(this.html_elt);var ak=new y.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),an=ah.length,ag=$("<a/>").addClass("icon more-down").attr("title","For speed, only the first "+an+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("<a/>").addClass("icon more-across").attr("title","For speed, only the first "+an+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ag.click(function(){aj.stale=true;af.data_manager.get_more_data(ak,af.mode,1/aj.w_scale,{},af.data_manager.DEEP_DATA_REQ);$(".tooltip").hide();af.request_draw()}).dblclick(function(aq){aq.stopPropagation()});ac.click(function(){aj.stale=true;af.data_manager.get_more_data(ak,af.mode,1/aj.w_scale,{},af.data_manager.BROAD_DATA_REQ);$(".tooltip").hide();af.request_draw()}).dblclick(function(aq){aq.stopPropagation()})}};q(O.prototype,c.prototype);O.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.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(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al<ai.length;al++){ah=ai[al];ap[ah.name]=at[ah.index]}af=$("<div/>").attr("id",ak).addClass("feature-popup");var au=$("<table/>"),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("<tr/>").appendTo(au);$("<th/>").appendTo(av).text(ar);$("<td/>").attr("align","left").appendTo(av).text(typeof(aq)==="number"?V(aq,2):aq)}af.append($("<div class='feature-popup-inner'>").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"),10)-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"),10)+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){q(ae,{drag_handle_class:"draghandle"});r.call(this,ad,ac,ae);this.dataset=null;if(ae.dataset){this.dataset=(ae.dataset instanceof Backbone.Model?ae.dataset:X.Dataset.findOrCreate(ae.dataset))}this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new y.GenomeDataManager({dataset:this.dataset,genome:new y.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),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=this.prefs.height;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 ae)||ae.resize){this.add_resize_handle()}}};q(g.prototype,r.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},r.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},r.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.tool.toggle();if(ac.tool.is_visible()){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='<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>',ae=ab.template(af,{track:ac});var ah=function(){Galaxy.modal.hide();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new y.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new y.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}Galaxy.modal.hide();window.location.href=galaxy_config.root+"visualization/sweepster?"+$.param({dataset_id:ac.dataset.id,hda_ldda:ac.dataset.get("hda_ldda"),regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};Galaxy.modal.show({title:"Visualize tool parameter space and output from different parameter settings?",body:ae,buttons:{No:ah,Yes:ad}})}},r.prototype.action_icons_def[2]],can_draw:function(){return this.dataset&&r.prototype.can_draw.call(this)},build_container_div:function(){return $("<div/>").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("<div class='track-header'/>");if(this.view.editor){this.drag_div=$("<div/>").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("<div/>").addClass("track-name").appendTo(ac).text(this.prefs.name).attr("id",this.prefs.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},set_dataset:function(ac){this.dataset=ac;this.data_manager.set("dataset",ac)},on_resize:function(){this.request_draw({clear_tile_cache:true})},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("<div class='track-resize'>");$(ac.container_div).hover(function(){if(ac.prefs.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.get("values").height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.get("values").mode?this.config.get("values").mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae<ac;ae++){var ah=ad.display_modes[ae];ag[ah]=function(aj){return function(){ad.change_mode(aj);ad.icons_div.show();ad.container_div.mouseleave(function(){ad.icons_div.hide()})}}(ah)}make_popupmenu(this.action_icons.mode_icon,ag)},build_action_icons:function(){r.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 W){return"LabelTrack"}else{if(this instanceof D){return"ReferenceTrack"}else{if(this instanceof h){return"LineTrack"}else{if(this instanceof T){return"ReadTrack"}else{if(this instanceof Z){return"VariantTrack"}else{if(this instanceof f){return"CompositeTrack"}else{if(this instanceof d){return"FeatureTrack"}}}}}}}return""},init:function(ae){var ad=this;ad.enabled=false;ad.tile_cache.clear();ad.data_manager.clear();ad.tiles_div.css("height","auto");ad.tiles_div.text("").children().remove();ad.container_div.removeClass("nodata error pending");if(!ad.dataset.id){return}var ac=$.Deferred(),af={hda_ldda:ad.dataset.get("hda_ldda"),data_type:this.dataset_check_type,chrom:ad.view.chrom,retry:ae};$.getJSON(this.dataset.url(),af,function(ag){if(!ag||ag==="error"||ag.kind==="error"){ad.container_div.addClass("error");ad.tiles_div.text(o);if(ag.message){ad.tiles_div.append($("<a href='javascript:void(0);'></a>").text("View error").click(function(){Galaxy.modal.show({title:"Trackster Error",body:"<pre>"+ag.message+"</pre>",buttons:{Close:function(){Galaxy.modal.hide()}}})}));ad.tiles_div.append($("<span/>").text(" "));ad.tiles_div.append($("<a href='javascript:void(0);'></a>").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(G)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(w);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(U);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when.apply($,ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){ac.container_div.addClass("line-track");var af=ad.data;if(af&&af.min&&af.max){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.config.set_param_default_value("min_value",ae);ac.config.set_param_default_value("max_value",ag);ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},get_drawables:function(){return this}});var N=function(ae,ad,ag){g.call(this,ae,ad,ag);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in ag?ag.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=(ag.tool?new s(ab.extend(ag.tool,{track:this,tool_state:ag.tool_state})):null);this.tile_cache=new y.Cache(Q);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){var af=new aa({model:this.tool});af.render();this.dynamic_tool_div=af.$el;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("<div/>").addClass("tiles").appendTo(this.content_div);if(!this.prefs.content_visible){this.tiles_div.hide()}this.overlay_div=$("<div/>").addClass("overlay").appendTo(this.content_div);if(ag.mode){this.change_mode(ag.mode)}};q(N.prototype,r.prototype,g.prototype,{action_icons_def:g.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(ac){$(".tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw({clear_tile_cache:true})},hide:true}]),copy:function(ac){var ad=this.to_dict();q(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),dataset:{id:this.dataset.id,hda_ldda:this.dataset.get("hda_ldda")},prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},set_min_max:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){var af=ad.data;if(isNaN(parseFloat(ac.prefs.min_value))||isNaN(parseFloat(ac.prefs.max_value))){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.get("values").mode=ad;if(ad==="Auto"){this.data_manager.clear()}ac.request_draw({clear_tile_cache:true});this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ac){if(ac&&ac.clear_tile_cache){this.tile_cache.clear()}this.view.request_redraw(ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(aq){if(!this.can_draw()){return}var an=aq&&aq.clear_after,al=this.view.low,ag=this.view.high,aj=ag-al,ad=this.view.container.width(),ap=this.view.resolution_px_b,af=1/ap;if(this.is_overview){al=this.view.max_low;ag=this.view.max_high;ap=ad/(view.max_high-view.max_low);af=1/ap}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(af*S)),ak,am,ah,ai=[],ao=[];while((ac*S*af)<ag){ak=Math.floor(ac*S*af);am=new y.GenomeRegion({chrom:this.view.chrom,start:ak,end:Math.min(ak+Math.ceil(S*af),this.view.max_high)});ah=this.draw_helper(am,ap,aq);ai.push(ah);$.when(ah).then(function(ar){ao.push(ar)});ac+=1}if(!an){this.tiles_div.children(".remove").removeClass("remove").remove()}var ae=this;$.when.apply($,ai).then(function(){ae.tiles_div.children(".remove").remove();ao=ab.filter(ao,function(ar){return ar!==null});if(ao.length!==0){ae.postdraw_actions(ao,ad,ap,an)}})},_add_yaxis_label:function(af,ah){var ad=this,ag=(af==="max"?"top":"bottom"),ai=(af==="max"?"max":"min"),ac=(af==="max"?"max_value":"min_value"),ae=this.container_div.find(".yaxislabel."+ag);ah=ah||function(){ad.request_draw({clear_tile_cache:true})};if(ae.length!==0){ae.text(ad.prefs[ac])}else{ae=$("<div/>").text(ad.prefs[ac]).make_text_editable({num_cols:12,on_finish:function(aj){$(".tooltip").remove();ad.config.set_param_value(ac,aj);ah()},help_text:"Set "+ai+" value"}).addClass("yaxislabel "+ag).css("color",this.prefs.label_color);this.container_div.prepend(ae)}},postdraw_actions:function(af,ag,ai,ac){var ae=ab.filter(af,function(aj){return(aj instanceof M)});if(ae.length>0){this.max_height_px=0;var ad=this;ab.each(af,function(aj){if(!(aj instanceof M)){aj.html_elt.remove();ad.draw_helper(aj.region,ai,{force:true,mode:"Coverage"})}});ad._add_yaxis_label("max")}else{this.container_div.find(".yaxislabel").remove();var ah=ab.find(af,function(aj){return aj.has_icons});if(ah){ab.each(af,function(aj){if(!aj.has_icons){aj.html_elt.css("padding-top",F)}})}}},get_mode:function(ac){return this.mode},update_auto_mode:function(ac){},_get_drawables:function(){return[this]},draw_helper:function(al,an,ao){if(!ao){ao={}}var ad=ao.force,ai=ao.mode||this.mode,af=1/an,ae=this,ag=this._get_drawables(),am=this._gen_tile_cache_key(an,al),ah=function(ap){return(ap&&"track" in ap)};var aj=(ad?undefined:ae.tile_cache.get_elt(am));if(aj){if(ah(aj)){ae.show_tile(aj,an)}return aj}if(ao.data_fetch===false){return null}var ak=function(){var ap=(ab.find(C,function(ar){return ar===ai})?"Coverage":ai);var aq=ab.map(ag,function(ar){return ar.data_manager.get_data(al,ap,af,ae.data_url_extra_params)});if(view.reference_track){aq.push(view.reference_track.data_manager.get_data(al,ai,af,view.reference_track.data_url_extra_params))}return aq};var ac=$.Deferred();ae.tile_cache.set_elt(am,ac);$.when.apply($,ak()).then(function(){var ap=ak(),av=ap,aB;if(view.reference_track){aB=view.reference_track.data_manager.subset_entry(ap.pop(),al)}var aw=[],at=[];ab.each(ag,function(aG,aD){var aF=aG.mode,aE=av[aD];if(aF==="Auto"){aF=aG.get_mode(aE);aG.update_auto_mode(aF)}aw.push(aF);at.push(aG.get_canvas_height(aE,aF,an,aq))});var au=ae.view.canvas_manager.new_canvas(),ax=al.get("start"),aC=al.get("end"),ar=0,aq=Math.ceil((aC-ax)*an)+ae.left_offset,az=ab.max(at),ay;au.width=aq;au.height=(ao.height||az);var aA=au.getContext("2d");aA.translate(ae.left_offset,0);if(ag.length>1){aA.globalAlpha=0.5;aA.globalCompositeOperation="source-over"}ab.each(ag,function(aE,aD){ay=aE.draw_tile(av[aD],aA,aw[aD],al,an,aB)});if(ay!==undefined){ae.tile_cache.set_elt(am,ay);ae.show_tile(ay,an)}ac.resolve(ay)});return ac},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_line_track_tile:function(ac,ae,ah,ag,ai){var af=ae.canvas,ad=new L.LinePainter(ac.data,ag.get("start"),ag.get("end"),this.prefs,ah);ad.draw(ae,af.width,af.height,ai);return new M(this,ag,ai,af,ac.data)},draw_tile:function(ac,ad,ag,af,ah,ae){},show_tile:function(ae,ah){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ah;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{this.tiles_div.append(ac)}ac.css("height","auto");this.max_height_px=Math.max(this.max_height_px,ac.height());ac.parent().children().css("height",this.max_height_px+"px");var af=this.max_height_px;if(this.visible_height_px!==0){af=Math.min(this.max_height_px,this.visible_height_px)}this.tiles_div.css("height",af+"px")},tool_region_and_parameters_str:function(af){var ac=this,ae=(af!==undefined?af.toString():"all"),ad=ab.values(ac.tool.get_inputs_dict()).join(", ");return" - region=["+ae+"], parameters=["+ad+"]"},data_and_mode_compatible:function(ac,ad){if(ad==="Auto"){return true}else{if(ad==="Coverage"){return ac.dataset_type==="bigwig"}else{if(ac.dataset_type==="bigwig"||ac.extra_info==="no_detail"){return false}else{return true}}}},can_subset:function(ac){if(ac.message||ac.extra_info==="no_detail"){return false}else{if(ac.dataset_type==="bigwig"){return(ac.data[1][0]-ac.data[0][0]===1)}}return true},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state"}});var W=function(ad,ac){var ae={resize:false};g.call(this,ad,ac,ae);this.container_div.addClass("label-track")};q(W.prototype,g.prototype,{build_header_div:function(){},init:function(){this.enabled=true},predraw_init:function(){},_draw:function(ag){var ae=this.view,af=ae.high-ae.low,aj=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/aj)*aj,ah=this.view.container.width(),ad=$("<div style='position: relative; height: 1.3em;'></div>");while(ac<ae.high){var ai=(ac-ae.low)/af*ah;ad.append($("<div class='label'>"+commatize(ac)+"</div>").css({position:"absolute",left:ai-1}));ac+=aj}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){N.call(this,ad,ac,ag);this.drawables=[];if("drawables" in ag){var af;for(var ae=0;ae<ag.drawables.length;ae++){af=ag.drawables[ae];this.drawables[ae]=p(af,ad,null);if(af.left_offset>this.left_offset){this.left_offset=af.left_offset}}this.enabled=true}ab.each(this.drawables,function(ah){if(ah instanceof d||ah instanceof T){ah.change_mode("Coverage")}});this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,N.prototype,{display_modes:C,action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".tooltip").remove();ac.show_group()}}].concat(N.prototype.action_icons_def),to_dict:A.prototype.to_dict,add_drawable:A.prototype.add_drawable,unpack_drawables:A.prototype.unpack_drawables,change_mode:function(ac){N.prototype.change_mode.call(this,ac);for(var ad=0;ad<this.drawables.length;ad++){this.drawables[ad].change_mode(ac)}},init:function(){var ae=[];for(var ad=0;ad<this.drawables.length;ad++){ae.push(this.drawables[ad].init())}var ac=this;$.when.apply($,ae).then(function(){ac.enabled=true;ac.request_draw()})},update_icons:function(){this.action_icons.filters_icon.hide();this.action_icons.tools_icon.hide();this.action_icons.param_space_viz_icon.hide()},can_draw:r.prototype.can_draw,_get_drawables:function(){return this.drawables},show_group:function(){var af=new P(this.view,this.container,{name:this.prefs.name}),ac;for(var ae=0;ae<this.drawables.length;ae++){ac=this.drawables[ae];ac.update_icons();af.add_drawable(ac);ac.container=af;af.content_div.append(ac.container_div)}var ad=this.container.replace_drawable(this,af,true);af.request_draw({clear_tile_cache:true})},before_draw:function(){N.prototype.before_draw.call(this);var ad=ab.min(ab.map(this.drawables,function(ae){return ae.prefs.min_value})),ac=ab.max(ab.map(this.drawables,function(ae){return ae.prefs.max_value}));this.prefs.min_value=ad;this.prefs.max_value=ac;ab.each(this.drawables,function(ae){ae.prefs.min_value=ad;ae.prefs.max_value=ac})},update_all_min_max:function(){var ac=this;ab.each(this.drawables,function(ad){ad.prefs.min_value=ac.prefs.min_value;ad.prefs.max_value=ac.prefs.max_value});this.request_draw({clear_tile_cache:true})},postdraw_actions:function(ai,ac,al,ah){N.prototype.postdraw_actions.call(this,ai,ac,al,ah);var ag=-1,ae;for(ae=0;ae<ai.length;ae++){var aj=ai[ae].html_elt.find("canvas").height();if(aj>ag){ag=aj}}for(ae=0;ae<ai.length;ae++){var af=ai[ae];if(af.html_elt.find("canvas").height()!==ag){this.draw_helper(af.region,al,{force:true,height:ag});af.html_elt.remove()}}var ad=this,ak=function(){ad.update_all_min_max()};this._add_yaxis_label("min",ak);this._add_yaxis_label("max",ak)}});var D=function(ac){N.call(this,ac,{content_div:ac.top_labeltrack},{resize:false});this.left_offset=ac.canvas_manager.char_width_px;this.container_div.addClass("reference-track");this.data_url=galaxy_config.root+"api/genomes/"+this.view.dbkey;this.data_url_extra_params={reference:true};this.data_manager=new y.GenomeReferenceDataManager({data_url:this.data_url,can_subset:this.can_subset});this.hide_contents()};q(D.prototype,r.prototype,N.prototype,{config_params:ab.union(r.prototype.config_params,[{key:"height",type:"int",default_value:13,hidden:true}]),build_header_div:function(){},init:function(){this.data_manager.clear();this.enabled=true},predraw_init:function(){},can_draw:r.prototype.can_draw,draw_helper:function(ad,ae,ac){if(ae>this.view.canvas_manager.char_width_px){this.tiles_div.show();return N.prototype.draw_helper.call(this,ad,ae,ac)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(ae,ak,af,ah,al){var ad=this.data_manager.subset_entry(ae,ah),aj=ad.data;var ac=ak.canvas;ak.font=ak.canvas.manager.default_font;ak.textAlign="center";for(var ag=0,ai=aj.length;ag<ai;ag++){ak.fillStyle=this.view.get_base_color(aj[ag]);ak.fillText(aj[ag],Math.floor(ag*al),10)}return new c(this,ah,al,ac,ad)}});var h=function(ad,ac,ae){this.mode="Histogram";N.call(this,ad,ac,ae)};q(h.prototype,r.prototype,N.prototype,{display_modes:C,config_params:ab.union(r.prototype.config_params,[{key:"color",label:"Color",type:"color",default_value:l.get_random_color()},{key:"min_value",label:"Min Value",type:"float",default_value:undefined},{key:"max_value",label:"Max Value",type:"float",default_value:undefined},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:30,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})},before_draw:function(){},draw_tile:function(ac,ad,af,ae,ag){return this._draw_line_track_tile(ac,ad,af,ae,ag)},can_subset:function(ac){return(ac.data[1][0]-ac.data[0][0]===1)},postdraw_actions:function(ad,ae,af,ac){this._add_yaxis_label("max");this._add_yaxis_label("min")}});var t=function(ad,ac,ae){this.mode="Heatmap";N.call(this,ad,ac,ae)};q(t.prototype,r.prototype,N.prototype,{display_modes:["Heatmap"],config_params:ab.union(r.prototype.config_params,[{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"min_value",label:"Min Value",type:"float",default_value:-1},{key:"max_value",label:"Max Value",type:"float",default_value:1},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:500,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})},draw_tile:function(ac,ae,ah,ag,ai){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ag.get("start"),ag.get("end"),this.prefs,ah);ad.draw(ae,af.width,af.height,ai);return new c(this,ag,ai,af,ac.data)}});var d=function(ad,ac,ae){N.call(this,ad,ac,ae);this.container_div.addClass("feature-track");this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};q(d.prototype,r.prototype,N.prototype,{display_modes:["Auto","Coverage","Dense","Squish","Pack"],config_params:ab.union(r.prototype.config_params,[{key:"block_color",label:"Block color",type:"color",default_value:l.get_random_color()},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:l.get_random_color()},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true,help:"Show the number of items in each bin when drawing summary histogram"},{key:"min_value",label:"Histogram minimum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"max_value",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"connector_style",label:"Connector style",type:"select",default_value:"fishbones",options:[{label:"Line with arrows",value:"fishbone"},{label:"Arcs",value:"arcs"}]},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:0,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.set_painter_from_config();this.request_draw({clear_tile_cache:true})},set_painter_from_config:function(){if(this.config.get("values").connector_style==="arcs"){this.painter=L.ArcLinkedFeaturePainter}else{this.painter=L.LinkedFeaturePainter}},postdraw_actions:function(am,ae,an,al){N.prototype.postdraw_actions.call(this,am,ae,an,al);var ag=this,ah;if(ag.filters_manager){var af=ag.filters_manager.filters,aj;for(aj=0;aj<af.length;aj++){af[aj].update_ui_elt()}var ai=false,ak,ad;for(ah=0;ah<am.length;ah++){if(am[ah].data.length){ak=am[ah].data[0];for(aj=0;aj<af.length;aj++){ad=af[aj];if(ad.applies_to(ak)&&ad.min!==ad.max){ai=true;break}}}}if(ag.filters_available!==ai){ag.filters_available=ai;if(!ag.filters_available){ag.filters_manager.hide()}ag.update_icons()}}if(am[0] instanceof O){var ac=true;for(ah=0;ah<am.length;ah++){if(!am[ah].all_slotted){ac=false;break}}if(!ac){this.action_icons.show_more_rows_icon.show()}else{this.action_icons.show_more_rows_icon.hide()}}else{this.action_icons.show_more_rows_icon.hide()}},update_auto_mode:function(ac){if(this.mode==="Auto"){if(ac==="no_detail"){ac="feature spans"}this.action_icons.mode_icon.attr("title","Set display mode (now: Auto/"+ac+")")}},incremental_slots:function(ag,ac,af){var ad=this.view.canvas_manager.dummy_context,ae=this.slotters[ag];if(!ae||(ae.mode!==af)){ae=new (u.FeatureSlotter)(ag,af,B,function(ah){return ad.measureText(ah)});this.slotters[ag]=ae}return ae.slot_features(ac)},get_mode:function(ac){if(ac.extra_info==="no_detail"||this.is_overview){mode="no_detail"}else{if(this.view.high-this.view.low>I){mode="Squish"}else{mode="Pack"}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="Coverage"||ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(this.min_height_px,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,af,aj,ae){var ap=this,ad=aq.canvas,ax=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(am.dataset_type==="bigwig"){return this._draw_line_track_tile(am,aq,ao,af,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var ar=0,au=am.data.length;ar<au;ar++){var ah=am.data[ar];var at=false;var al;for(var aw=0,aB=ak.length;aw<aB;aw++){al=ak[aw];al.update_attrs(ah);if(!al.keep(ah)){at=true;break}}if(!at){ai.push(ah);if(!(ah[0] in an)){all_slotted=false}}}}var aA=(this.filters_manager.alpha_filter?new E(this.filters_manager.alpha_filter):null),ay=(this.filters_manager.height_filter?new E(this.filters_manager.height_filter):null),az=new (this.painter)(ai,ax,ac,this.prefs,ao,aA,ay,ae,function(aC){return ap.view.get_base_color(aC)});var av=null;aq.fillStyle=this.prefs.block_color;aq.font=aq.canvas.manager.default_font;aq.textAlign="right";if(am.data){av=az.draw(aq,ad.width,ad.height,aj,an);av.translation=-ag}return new O(ap,af,aj,ad,am.data,ao,am.message,all_slotted,av)}});var Z=function(ad,ac,ae){N.call(this,ad,ac,ae);this.painter=L.VariantPainter;this.summary_draw_height=30;this.left_offset=30};q(Z.prototype,r.prototype,N.prototype,{display_modes:["Auto","Coverage","Dense","Squish","Pack"],config_params:ab.union(r.prototype.config_params,[{key:"color",label:"Histogram color",type:"color",default_value:l.get_random_color()},{key:"show_sample_data",label:"Show sample data",type:"bool",default_value:true},{key:"show_labels",label:"Show summary and sample labels",type:"bool",default_value:true},{key:"summary_height",label:"Locus summary height",type:"float",default_value:20},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:0,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})},draw_tile:function(ac,af,ah,ag,ai){if(ac.dataset_type==="bigwig"){return this._draw_line_track_tile(ac,af,"Histogram",ag,ai)}else{var ae=this.view,ad=new (this.painter)(ac.data,ag.get("start"),ag.get("end"),this.prefs,ah,function(aj){return ae.get_base_color(aj)});ad.draw(af,af.canvas.width,af.canvas.height,ai);return new c(this,ag,ai,af.canvas,ac.data)}},get_canvas_height:function(ac,ag,ah,ae){if(ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var ad=(this.dataset.get_metadata("sample_names")?this.dataset.get_metadata("sample_names").length:0);if(ad===0&&ac.data.length!==0){ad=ac.data[0][7].match(/,/g);if(ad===null){ad=1}else{ad=ad.length+1}}var af=new (this.painter)(null,null,null,this.prefs,ag);return af.get_required_height(ad)}},predraw_init:function(){var ac=[g.prototype.predraw_init.call(this)];if(!this.dataset.get_metadata("sample_names")){ac.push(this.dataset.fetch())}return ac},postdraw_actions:function(ag,ah,aj,ad){N.prototype.postdraw_actions.call(this,ag,ah,aj,ad);var af=ab.filter(ag,function(ak){return(ak instanceof M)});var ae=this.dataset.get_metadata("sample_names");if(af.length===0&&this.prefs.show_labels&&ae&&ae.length>1){var ac;if(this.container_div.find(".yaxislabel.variant").length===0){ac=this.prefs.summary_height/2;this.tiles_div.prepend($("<div/>").text("Summary").addClass("yaxislabel variant top").css({"font-size":ac+"px",top:(this.prefs.summary_height-ac)/2+"px"}));if(this.prefs.show_sample_data){var ai=ae.join("<br/>");this.tiles_div.prepend($("<div/>").html(ai).addClass("yaxislabel variant top sample").css({top:this.prefs.summary_height+2,}))}}ac=(this.mode==="Squish"?5:10)+"px";$(this.tiles_div).find(".sample").css({"font-size":ac,"line-height":ac});$(this.tiles_div).find(".yaxislabel").css("color",this.prefs.label_color)}else{this.container_div.find(".yaxislabel.variant").remove()}}});var T=function(ad,ac,ae){d.call(this,ad,ac,ae);this.painter=(ad.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};q(T.prototype,r.prototype,N.prototype,d.prototype,{config_params:ab.union(r.prototype.config_params,[{key:"block_color",label:"Block and sense strand color",type:"color",default_value:l.get_random_color()},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:l.get_random_color()},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"min_value",label:"Histogram minimum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"max_value",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"height",type:"int",default_value:0,hidden:true}]),config_onchange:function(){this.set_name(this.prefs.name);this.request_draw({clear_tile_cache:true})}});var e={CompositeTrack:f,DrawableGroup:P,DiagonalHeatmapTrack:t,FeatureTrack:d,LineTrack:h,ReadTrack:T,VariantTrack:Z,VcfTrack:Z};var p=function(ae,ad,ac){if("copy" in ae){return ae.copy(ac)}else{var af=ae.obj_type;if(!af){af=ae.track_type}return new e[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:P,LineTrack:h,FeatureTrack:d,DiagonalHeatmapTrack:t,ReadTrack:T,VariantTrack:Z,CompositeTrack:f,object_from_template:p}});
\ No newline at end of file
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/templates/common-templates.html
--- a/static/scripts/templates/common-templates.html
+++ b/static/scripts/templates/common-templates.html
@@ -1,19 +1,3 @@
-<script type="text/javascript" class="helper-common" id="helper-clearFloatDiv">
-/** Hack div to clear re-enclose float'd elements above it in the parent div
- */
-Handlebars.registerPartial( 'clearFloatDiv', function( options ){
- return '<div class="clear"></div>';
-});
-</script>
-
-<script type="text/javascript" class="helper-common" id="helper-warningmessagesmall">
-/** Renders a warning in a (mostly css) highlighted, iconned warning box
- */
-Handlebars.registerHelper( 'warningmessagesmall', function( options ){
- return '<div class="warningmessagesmall"><strong>' + options.fn( this ) + '</strong></div>'
-});
-</script>
-
<script type="text/javascript" class="helper-common" id="helper-local">
/** Calls _l (from base-mvc.js) to localize strings (if poosible)
* This helper is specifically for localization within templates
@@ -23,12 +7,9 @@
});
</script>
-<script type="text/template" class="template-common" id="template-warningmessagesmall">
-{{! renders a warning in a (mostly css) highlighted, iconned warning box }}
- <div class="warningmessagesmall"><strong>{{{ warning }}}</strong></div>
+<script type="text/javascript" class="helper-common" id="helper-n2br">
+/** replace newlines with breaks */
+Handlebars.registerHelper( 'n2br', function( options ){
+ return options.fn( this ).replace( /\n/g, '<br/>' );
+});
</script>
-
-<script type="text/template" class="template-common" id="template-iconButton">
-{{! alternate template-based icon-button }}
-{{> iconButton this}}
-</script>
diff -r 284e273fcae00a9d605f0b35c73351d1de5c179f -r 0c559492c88a85a4b81e005276858974df7f8076 static/scripts/templates/compiled/helpers-common-templates.js
--- a/static/scripts/templates/compiled/helpers-common-templates.js
+++ b/static/scripts/templates/compiled/helpers-common-templates.js
@@ -1,16 +1,10 @@
-/** Hack div to clear re-enclose float'd elements above it in the parent div
- */
-Handlebars.registerPartial( 'clearFloatDiv', function( options ){
- return '<div class="clear"></div>';
-});
-/** Renders a warning in a (mostly css) highlighted, iconned warning box
- */
-Handlebars.registerHelper( 'warningmessagesmall', function( options ){
- return '<div class="warningmessagesmall"><strong>' + options.fn( this ) + '</strong></div>'
-});
/** Calls _l (from base-mvc.js) to localize strings (if poosible)
* This helper is specifically for localization within templates
*/
Handlebars.registerHelper( 'local', function( options ){
return _l( options.fn( this ) );
+});
+/** replace newlines with breaks */
+Handlebars.registerHelper( 'n2br', function( options ){
+ return options.fn( this ).replace( /\n/g, '<br/>' );
});
\ No newline at end of file
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/a1e670fa596f/
Changeset: a1e670fa596f
User: kellrott
Date: 2013-11-12 00:26:14
Summary: Merged galaxy/galaxy-central into default
Affected #: 182 files
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 .hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -4,3 +4,4 @@
2cc8d10988e03257dc7b97f8bb332c7df745d1dd security_2013.04.08
524f246ca85395082719ae7a6ff72260d7ad5612 release_2013.06.03
1ae95b3aa98d1ccf15b243ac3ce6a895eb7efc53 release_2013.08.12
+26f58e05aa1068761660681583821e21e6cbf7ab release_2013.11.04
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/Gruntfile.js
--- a/config/plugins/visualizations/scatterplot/Gruntfile.js
+++ b/config/plugins/visualizations/scatterplot/Gruntfile.js
@@ -6,6 +6,7 @@
pkg: grunt.file.readJSON( 'package.json' ),
handlebars: {
+ // compile all hb templates into a single file in the build dir
compile: {
options: {
namespace: 'Templates',
@@ -20,6 +21,7 @@
},
concat: {
+ // concat the template file and any js files in the src dir into a single file in the build dir
options: {
separator: ';\n'
},
@@ -31,16 +33,19 @@
},
uglify: {
+ // uglify the concat single file directly into the static dir
options: {
+ //mangle : false,
+ //beautify : true
},
dist: {
src : 'build/scatterplot-concat.js',
- // uglify directly into static dir
- dest: 'static/scatterplot.js'
+ dest: 'static/scatterplot-edit.js'
}
},
watch: {
+ // watch for changes in the src dir
files: [ 'src/**.js', 'src/handlebars/*.handlebars' ],
tasks: [ 'default' ]
}
@@ -52,5 +57,6 @@
grunt.loadNpmTasks( 'grunt-contrib-watch' );
grunt.registerTask( 'default', [ 'handlebars', 'concat', 'uglify' ]);
- grunt.registerTask( 'watch', [ 'handlebars', 'concat', 'uglify', 'watch' ]);
+ // you can run grunt watch directly:
+ // grunt watch
};
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/package.json
--- a/config/plugins/visualizations/scatterplot/package.json
+++ b/config/plugins/visualizations/scatterplot/package.json
@@ -19,6 +19,6 @@
"grunt-contrib-handlebars": "~0.5.10",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-uglify": "~0.2.2",
- "grunt-contrib-watch": "~0.5.1"
+ "grunt-contrib-watch": "~0.5.3"
}
}
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars
+++ /dev/null
@@ -1,56 +0,0 @@
-<p class="help-text">
- Use the following controls to how the chart is displayed.
- The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
- Move the focus between controls by using the tab or shift+tab keys on your keyboard.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- <div id="datapointSize" class="form-input numeric-slider-input">
- <label for="datapointSize">Size of data point: </label>
- <div class="slider-output">{{datapointSize}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- Size of the graphic representation of each data point
- </p>
- </div>
-
- <div id="animDuration" class="form-input checkbox-input">
- <label for="animate-chart">Animate chart transitions?: </label>
- <input type="checkbox" id="animate-chart"
- class="checkbox control"{{#if animDuration}} checked="true"{{/if}} />
- <p class="form-help help-text-small">
- Uncheck this to disable the animations used on the chart
- </p>
- </div>
-
- <div id="width" class="form-input numeric-slider-input">
- <label for="width">Chart width: </label>
- <div class="slider-output">{{width}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="height" class="form-input numeric-slider-input">
- <label for="height">Chart height: </label>
- <div class="slider-output">{{height}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="X-axis-label"class="text-input form-input">
- <label for="X-axis-label">Re-label the X axis: </label>
- <input type="text" name="X-axis-label" id="X-axis-label" value="{{xLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <div id="Y-axis-label" class="text-input form-input">
- <label for="Y-axis-label">Re-label the Y axis: </label>
- <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{yLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
\ No newline at end of file
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars
+++ /dev/null
@@ -1,1 +0,0 @@
-<svg width="{{width}}" height="{{height}}"></svg>
\ No newline at end of file
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
@@ -0,0 +1,47 @@
+<p class="help-text">
+ Use the following controls to how the chart is displayed.
+ The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
+ Move the focus between controls by using the tab or shift+tab keys on your keyboard.
+ Use the 'Draw' button to render (or re-render) the chart with the current settings.
+</p>
+
+<div data-config-key="datapointSize" class="form-input numeric-slider-input">
+ <label for="datapointSize">Size of data point: </label>
+ <div class="slider-output">{{datapointSize}}</div>
+ <div class="slider"></div>
+ <p class="form-help help-text-small">
+ Size of the graphic representation of each data point
+ </p>
+</div>
+
+<div data-config-key="width" class="form-input numeric-slider-input">
+ <label for="width">Chart width: </label>
+ <div class="slider-output">{{width}}</div>
+ <div class="slider"></div>
+ <p class="form-help help-text-small">
+ (not including chart margins and axes)
+ </p>
+</div>
+
+<div data-config-key="height" class="form-input numeric-slider-input">
+ <label for="height">Chart height: </label>
+ <div class="slider-output">{{height}}</div>
+ <div class="slider"></div>
+ <p class="form-help help-text-small">
+ (not including chart margins and axes)
+ </p>
+</div>
+
+<div data-config-key="X-axis-label"class="text-input form-input">
+ <label for="X-axis-label">Re-label the X axis: </label>
+ <input type="text" name="X-axis-label" id="X-axis-label" value="{{x.label}}" />
+ <p class="form-help help-text-small"></p>
+</div>
+
+<div data-config-key="Y-axis-label" class="text-input form-input">
+ <label for="Y-axis-label">Re-label the Y axis: </label>
+ <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{y.label}}" />
+ <p class="form-help help-text-small"></p>
+</div>
+
+<button class="render-button btn btn-primary active">Draw</button>
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars
+++ /dev/null
@@ -1,56 +0,0 @@
-<p class="help-text">
- Use the following controls to change the data used by the chart.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- {{! column selector containers }}
- <div class="column-select">
- <label for="X-select">Data column for X: </label>
- <select name="X" id="X-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- <div class="column-select">
- <label for="Y-select">Data column for Y: </label>
- <select name="Y" id="Y-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! optional id column }}
- <div id="include-id">
- <label for="include-id-checkbox">Include a third column as data point IDs?</label>
- <input type="checkbox" name="include-id" id="include-id-checkbox" />
- <p class="help-text-small">
- These will be displayed (along with the x and y values) when you hover over
- a data point.
- </p>
- </div>
- <div class="column-select" style="display: none">
- <label for="ID-select">Data column for IDs: </label>
- <select name="ID" id="ID-select">
- {{#each allColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
- <div id="first-line-header" style="display: none;">
- <p>Possible headers: {{ possibleHeaders }}
- </p>
- <label for="first-line-header-checkbox">Use the above as column headers?</label>
- <input type="checkbox" name="include-id" id="first-line-header-checkbox"
- {{#if usePossibleHeaders }}checked="true"{{/if}}/>
- <p class="help-text-small">
- It looks like Galaxy couldn't get proper column headers for this data.
- Would you like to use the column headers above as column names to select columns?
- </p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
- <div class="clear"></div>
\ No newline at end of file
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
@@ -0,0 +1,55 @@
+<p class="help-text">
+ Use the following controls to change the data used by the chart.
+ Use the 'Draw' button to render (or re-render) the chart with the current settings.
+</p>
+
+{{! column selector containers }}
+<div class="column-select">
+ <label>Data column for X: </label>
+ <select name="xColumn">
+ {{#each numericColumns}}
+ <option value="{{index}}">{{name}}</option>
+ {{/each}}
+ </select>
+</div>
+<div class="column-select">
+ <label>Data column for Y: </label>
+ <select name="yColumn">
+ {{#each numericColumns}}
+ <option value="{{index}}">{{name}}</option>
+ {{/each}}
+ </select>
+</div>
+
+{{! optional id column }}
+<div id="include-id">
+ <label for="include-id-checkbox">Include a third column as data point IDs?</label>
+ <input type="checkbox" name="include-id" id="include-id-checkbox" />
+ <p class="help-text-small">
+ These will be displayed (along with the x and y values) when you hover over
+ a data point.
+ </p>
+</div>
+<div class="column-select" style="display: none">
+ <label for="ID-select">Data column for IDs: </label>
+ <select name="idColumn">
+ {{#each allColumns}}
+ <option value="{{index}}">{{name}}</option>
+ {{/each}}
+ </select>
+</div>
+
+{{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
+<div id="first-line-header" style="display: none;">
+ <p>Possible headers: {{ possibleHeaders }}
+ </p>
+ <label for="first-line-header-checkbox">Use the above as column headers?</label>
+ <input type="checkbox" name="include-id" id="first-line-header-checkbox"
+ {{#if usePossibleHeaders }}checked="true"{{/if}}/>
+ <p class="help-text-small">
+ It looks like Galaxy couldn't get proper column headers for this data.
+ Would you like to use the column headers above as column names to select columns?
+ </p>
+</div>
+
+<button class="render-button btn btn-primary active">Draw</button>
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars
@@ -0,0 +1,36 @@
+<div class="scatterplot-editor tabbable tabs-left">
+ {{! tab buttons/headers using Bootstrap }}
+ <ul class="nav nav-tabs">
+ {{! start with the data controls as the displayed tab }}
+ <li class="active">
+ <a title="Use this tab to change which data are used"
+ href="#data-control" data-toggle="tab">Data Controls</a>
+ </li>
+ <li>
+ <a title="Use this tab to change how the chart is drawn"
+ href="#chart-control" data-toggle="tab" >Chart Controls</a>
+ </li>
+ {{! both stats and chart start as disabled since there's no info yet }}
+ <li class="disabled">
+ <a title="This tab will display the chart"
+ href="#chart-display" data-toggle="tab">Chart</a>
+ </li>
+ </ul>
+
+ {{! data form, chart config form, stats, and chart all get their own tab }}
+ <div class="tab-content">
+ {{! ---------------------------- tab for data settings form }}
+ <div id="data-control" class="scatterplot-config-control tab-pane active">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for chart graphics control form }}
+ <div id="chart-control" class="scatterplot-config-control tab-pane">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for actual chart }}
+ <div id="chart-display" class="scatterplot-display tab-pane"></div>
+
+ </div>{{! end .tab-content }}
+</div>{{! end .chart-control }}
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars
+++ /dev/null
@@ -1,46 +0,0 @@
-{{! main layout }}
-
-<div class="scatterplot-container chart-container tabbable tabs-left">
- {{! tab buttons/headers using Bootstrap }}
- <ul class="nav nav-tabs">
- {{! start with the data controls as the displayed tab }}
- <li class="active"><a href="#data-control" data-toggle="tab" class="tooltip"
- title="Use this tab to change which data are used">Data Controls</a></li>
- <li><a href="#chart-control" data-toggle="tab" class="tooltip"
- title="Use this tab to change how the chart is drawn">Chart Controls</a></li>
- <li><a href="#stats-display" data-toggle="tab" class="tooltip"
- title="This tab will display overall statistics for your data">Statistics</a></li>
- <li><a href="#chart-display" data-toggle="tab" class="tooltip"
- title="This tab will display the chart">Chart</a>
- {{! loading indicator - initially hidden }}
- <div id="loading-indicator" style="display: none;">
- <img class="loading-img" src="{{loadingIndicatorImagePath}}" />
- <span class="loading-message">{{message}}</span>
- </div>
- </li>
- </ul>
-
- {{! data form, chart config form, stats, and chart all get their own tab }}
- <div class="tab-content">
- {{! ---------------------------- tab for data settings form }}
- <div id="data-control" class="tab-pane active">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for chart graphics control form }}
- <div id="chart-control" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for data statistics }}
- <div id="stats-display" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for actual chart }}
- <div id="chart-display" class="tab-pane">
- {{! chart rendered separately }}
- </div>
-
- </div>{{! end .tab-content }}
-</div>{{! end .chart-control }}
\ No newline at end of file
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars
+++ /dev/null
@@ -1,8 +0,0 @@
-<p class="help-text">By column:</p>
- <table id="chart-stats-table">
- <thead><th></th><th>X</th><th>Y</th></thead>
- {{#each stats}}
- <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
- </tr>
- {{/each}}
- </table>
\ No newline at end of file
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -0,0 +1,237 @@
+/* =============================================================================
+todo:
+ Remove 'chart' names
+ Make this (the config control/editor) and the ScatterplotView (in scatterplot.js) both
+ views onto a visualization/revision model
+ Move margins into wid/hi calcs (so final svg dims are w/h)
+ Better separation of AJAX in scatterplot.js (maybe pass in function?)
+ Labels should auto fill in chart control when dataset has column_names
+ Allow column selection/config using the peek output as a base for UI
+ Allow setting perPage of config
+ Auto render if given data and/or config
+ Allow option to auto set width/height based on screen real estate avail.
+ Use d3.nest to allow grouping, pagination/filtration by group (e.g. chromCol)
+ Semantic HTML (figure, caption)
+ Save as visualization, load from visualization
+ Save as SVG/png
+ Does it work w/ Galaxy.Frame?
+ Embedding
+ Small multiples
+ Drag & Drop other splots onto current (redraw with new axis and differentiate the datasets)
+
+ Subclass on specific datatypes? (vcf, cuffdiff, etc.)
+ What can be common/useful to other visualizations?
+
+============================================================================= */
+/**
+ * Scatterplot config control UI as a backbone view
+ * handles:
+ * configuring which data will be used
+ * configuring the plot display
+ */
+var ScatterplotConfigEditor = BaseView.extend( LoggableMixin ).extend({
+ //TODO: !should be a view on a visualization model
+ //logger : console,
+ className : 'scatterplot-control-form',
+
+ /** initialize requires a configuration Object containing a dataset Object */
+ initialize : function( attributes ){
+ //console.log( this + '.initialize, attributes:', attributes );
+ if( !attributes || !attributes.config || !attributes.config.dataset ){
+ throw new Error( "ScatterplotView requires a configuration and dataset" );
+ }
+ this.dataset = attributes.config.dataset;
+ //console.log( 'dataset:', this.dataset );
+
+ this.plotView = new ScatterplotView({
+ config : attributes.config
+ });
+ },
+
+ // ------------------------------------------------------------------------- CONTROLS RENDERING
+ render : function(){
+ //console.log( this + '.render' );
+
+ // render the tab controls, areas and loading indicator
+ this.$el.append( ScatterplotConfigEditor.templates.mainLayout({
+ }));
+
+ // render the tab content
+ this.$el.find( '#data-control' ).append( this._render_dataControl() );
+ this._render_chartControls( this.$el.find( '#chart-control' ) );
+ //this.$statsDisplay = this.$el.find( '.tab-pane#stats-display' );
+ this._render_chartDisplay();
+
+ //TODO: auto render if given both x, y column choices in query for page
+
+ // set up behaviours
+ this.$el.find( '[title]' ).tooltip();
+
+ // uncomment any of the following to have that tab show on initial load (for testing)
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#data-control"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-control"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#stats-display"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ return this;
+ },
+
+ _render_dataControl : function(){
+ // controls for which columns are used to plot datapoints (and ids/additional info to attach if desired)
+ var dataset = this.dataset;
+ //console.log( 'metadata_column_types:', this.dataset.metadata_column_types );
+ //console.log( 'metadata_column_names:', this.dataset.metadata_column_names );
+
+ var allColumns = _.map( dataset.metadata_column_types, function( type, i ){
+ var column = { index: i, type: type, name: ( 'column ' + ( i + 1 ) ) };
+ if( dataset.metadata_column_names && dataset.metadata_column_names[ i ] ){
+ column.name = dataset.metadata_column_names[ i ];
+ }
+ return column;
+ });
+ var numericColumns = _.filter( allColumns, function( column, i ){
+ return ( ( column.type === 'int' ) || ( column.type === 'float' ) );
+ });
+ if( numericColumns < 2 ){
+ numericColumns = allColumns;
+ }
+ //console.log( 'allColumns:', allColumns );
+ //console.log( 'numericColumns:', numericColumns );
+
+ // render the html
+ var $dataControl = this.$el.find( '.tab-pane#data-control' );
+ $dataControl.html( ScatterplotConfigEditor.templates.dataControl({
+ allColumns : allColumns,
+ numericColumns : numericColumns
+ }));
+
+ // preset to column selectors if they were passed in the config in the query string
+ $dataControl.find( '[name="xColumn"]' ).val( this.plotView.config.xColumn || numericColumns[0].index );
+ $dataControl.find( '[name="yColumn"]' ).val( this.plotView.config.yColumn || numericColumns[1].index );
+ if( this.plotView.config.idColumn !== undefined ){
+ $dataControl.find( '#include-id-checkbox' ).prop( 'checked', true ).trigger( 'change' );
+ $dataControl.find( 'select[name="idColumn"]' ).val( this.plotView.config.idColumn );
+ }
+
+ return $dataControl;
+ },
+
+ _render_chartControls : function( $chartControls ){
+ // tab content to control how the chart is rendered (data glyph size, chart size, etc.)
+ $chartControls.html( ScatterplotConfigEditor.templates.chartControl( this.plotView.config ) );
+ //console.debug( '$chartControl:', $chartControls );
+
+ // set up behaviours, js on sliders
+ //console.debug( 'numeric sliders:', $chartControls.find( '.numeric-slider-input' ) );
+ // what to do when the slider changes: update display and update chartConfig
+ var view = this,
+ // limits for controls (by control/chartConfig id)
+ //TODO: move into TwoVarScatterplot
+ controlRanges = {
+ 'datapointSize' : { min: 2, max: 10, step: 1 },
+ 'width' : { min: 200, max: 800, step: 20 },
+ 'height' : { min: 200, max: 800, step: 20 }
+ };
+
+ function onSliderChange(){
+ var $this = $( this );
+ $this.siblings( '.slider-output' ).text( $this.slider( 'value' ) );
+ }
+ $chartControls.find( '.numeric-slider-input' ).each( function(){
+ var $this = $( this ),
+ configKey = $this.attr( 'data-config-key' ),
+ sliderSettings = _.extend( controlRanges[ configKey ], {
+ value : view.plotView.config[ configKey ],
+ change : onSliderChange,
+ slide : onSliderChange
+ });
+ //console.debug( configKey + ' slider settings:', sliderSettings );
+ $this.find( '.slider' ).slider( sliderSettings );
+ });
+//TODO: to more common area (like render)?
+ // set label inputs to current x, y metadata_column_names (if any)
+ if( this.dataset.metadata_column_names ){
+ //var colNames = this.dataset.metadata_column_names;
+ //$chartControls.find( 'input[name="X-axis-label"]' ).val( colNames );
+ //$chartControls.find( 'input[name="Y-axis-label"]' ).val( colNames );
+//TODO: on change of x, y data controls
+ }
+
+ //console.debug( '$chartControls:', $chartControls );
+ return $chartControls;
+ },
+
+ _render_chartDisplay : function(){
+ // render the tab content where the chart is displayed (but not the chart itself)
+ var $chartDisplay = this.$el.find( '.tab-pane#chart-display' );
+ this.plotView.setElement( $chartDisplay );
+ this.plotView.render();
+ return $chartDisplay;
+ },
+
+ // ------------------------------------------------------------------------- EVENTS
+ events : {
+ 'change #include-id-checkbox' : 'toggleThirdColumnSelector',
+ 'click #data-control .render-button' : 'renderChart',
+ 'click #chart-control .render-button' : 'renderChart'
+ },
+
+ toggleThirdColumnSelector : function(){
+ // show/hide the id selector on the data settings panel
+ this.$el.find( 'select[name="idColumn"]' ).parent().toggle();
+ },
+
+ // ------------------------------------------------------------------------- CHART/STATS RENDERING
+ renderChart : function(){
+ //console.log( this + '.renderChart' );
+ // fetch the data, (re-)render the chart
+ this.$el.find( '.nav li.disabled' ).removeClass( 'disabled' );
+ this.updateConfigWithDataSettings();
+ this.updateConfigWithChartSettings();
+ this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ this.plotView.fetchData();
+ //console.debug( this.plotView.$el );
+ },
+
+ // ------------------------------------------------------------------------- GET DATA/CHART SETTINGS
+ updateConfigWithDataSettings : function(){
+ // parse the column values for both indeces (for the data fetch) and names (for the chart)
+ var $dataControls = this.$el.find( '#data-control' );
+ var settings = {
+ xColumn : $dataControls.find( '[name="xColumn"]' ).val(),
+ yColumn : $dataControls.find( '[name="yColumn"]' ).val()
+ };
+ if( $dataControls.find( '#include-id-checkbox' ).prop( 'checked' ) ){
+ settings.idColumn = $dataControls.find( '[name="idColumn"]' ).val();
+ }
+ //console.log( '\t data settings:', settings );
+ return _.extend( this.plotView.config, settings );
+ },
+
+ updateConfigWithChartSettings : function(){
+ // gets the user-selected chartConfig from the chart settings panel
+ var plotView = this.plotView,
+ $chartControls = this.$el.find( '#chart-control' );
+ // use a loop of config keys to get the form values for these sliders
+ [ 'datapointSize', 'width', 'height' ].forEach( function( v, i ){
+ plotView.config[ v ] = $chartControls.find( '.numeric-slider-input[data-config-key="' + v + '"]' )
+ .find( '.slider' ).slider( 'value' );
+ });
+ // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName
+ plotView.config.x.label = $chartControls.find( 'input[name="X-axis-label"]' ).val();
+ plotView.config.y.label = $chartControls.find( 'input[name="Y-axis-label"]' ).val();
+ //console.log( '\t chartSettings:', settings );
+ return plotView.config;
+ },
+
+ toString : function(){
+ return 'ScatterplotConfigEditor(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')';
+ }
+});
+
+ScatterplotConfigEditor.templates = {
+ mainLayout : Templates.editor,
+ dataControl : Templates.datacontrol,
+ chartControl : Templates.chartcontrol
+};
+
+//==============================================================================
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/scatterplot-display.js
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
@@ -0,0 +1,288 @@
+// =============================================================================
+/**
+ * Scatterplot display control UI as a backbone view
+ * handles:
+ * fetching the data (if needed)
+ * computing and displaying data stats
+ * controls for pagination of data (if needed)
+ */
+var ScatterplotView = Backbone.View.extend({
+ //TODO: should be a view on visualization(revision) model
+
+ defaults : {
+ dataset : {
+ },
+ metadata : {
+ dataLines : undefined
+ },
+
+ ajaxFn : null,
+
+ pagination : {
+ currPage : 0,
+ perPage : 3000
+ },
+
+ width : 400,
+ height : 400,
+
+ margin : {
+ top : 16,
+ right : 16,
+ bottom : 40,
+ left : 54
+ },
+
+ x : {
+ ticks : 10,
+ label : 'X'
+ },
+ y : {
+ ticks : 10,
+ label : 'Y'
+ },
+
+ datapointSize : 4,
+ animDuration : 500
+ },
+
+ initialize : function( attributes ){
+ this.config = _.extend( _.clone( this.defaults ), attributes.config || {});
+ //console.debug( this + '.config:', this.config );
+ },
+
+ updateConfig : function( newConfig ){
+ //console.log( this + '.updateConfig:', newConfig );
+ this.config = this.config || {};
+ //TODO: validate here
+ _.extend( this.config, newConfig );
+ //TODO: implement rerender flag
+ },
+
+ fetchData : function(){
+//TODO: doesn't work bc it's rendered in render()...
+ this.showLoadingIndicator( 'getting data' );
+ //console.debug( 'currPage', this.config.pagination.currPage );
+ var view = this;
+//TODO: very tied to datasets - should be generalized eventually
+ xhr = jQuery.getJSON( '/api/datasets/' + this.config.dataset.id, {
+ data_type : 'raw_data',
+ provider : 'dataset-column',
+ limit : this.config.pagination.perPage,
+ offset : ( this.config.pagination.currPage * this.config.pagination.perPage )
+ });
+ xhr.done( function( data ){
+ view.renderData( data.data );
+ });
+ xhr.fail( function( xhr, status, message ){
+ alert( 'Error loading data:\n' + xhr.responseText );
+ console.error( xhr, status, message );
+ });
+ xhr.always( function(){
+ view.hideLoadingIndicator();
+ });
+ return xhr;
+ },
+
+ render : function( data ){
+ this.$el.addClass( 'scatterplot-display' ).html([
+ '<div class="controls clear"></div>',
+ '<div class="loading-indicator">',
+ '<span class="fa fa-spinner fa-spin"></span>',
+ '<span class="loading-indicator-message"></span>',
+ '</div>',
+ '<svg/>', //TODO: id
+ '<div class="stats-display"></div>'
+ ].join( '' ));
+ this.$el.children().hide();
+
+ if( data ){
+ this.renderData( data );
+ }
+ return this;
+ },
+
+ showLoadingIndicator : function( message, speed ){
+ // display the loading indicator over the tab panels if hidden, update message (if passed)
+//TODO: move loading indicator into data-info-text
+ message = message || '';
+ speed = speed || 'fast';
+ var $indicator = this.$el.find( '.loading-indicator' );
+
+ if( message ){ $indicator.find( '.loading-indicator-message' ).text( message ); }
+ if( !$indicator.is( ':visible' ) ){
+ this.toggleStats( false );
+ $indicator.css({ left: ( this.config.width / 2 ), top: this.config.height / 2 }).show();
+ }
+ },
+
+ hideLoadingIndicator : function( speed ){
+ speed = speed || 'fast';
+ this.$el.find( '.loading-indicator' ).hide();
+ },
+
+ renderData : function( data ){
+ this.$el.find( '.controls' ).empty().append( this.renderControls( data ) ).show();
+ this.renderPlot( data );
+ this.getStats( data );
+ },
+
+ renderControls : function( data ){
+ var view = this;
+ var $left = $( '<div class="left"></div>' ),
+ $right = $( '<div class="right"></div>' );
+
+ $left.append([
+ this.renderPrevNext( data ),
+ this.renderPagination( data )
+ ]);
+ $right.append([
+ this.renderLineInfo( data ),
+ $( '<button>Stats</button>' ).addClass( 'stats-toggle-btn' )
+ .click( function(){
+ view.toggleStats();
+ }),
+ $( '<button>Redraw</button>' ).addClass( 'rerender-btn' )
+ .click( function(){
+ view.renderPlot( data );
+ })
+ ]);
+ return [ $left, $right ];
+ },
+
+ renderLineInfo : function( data ){
+ var totalLines = this.config.dataset.metadata_data_lines || 'an unknown number of',
+ lineStart = ( this.config.pagination.currPage * this.config.pagination.perPage ),
+ lineEnd = lineStart + data.length;
+ return $( '<p/>' ).addClass( 'scatterplot-data-info' )
+ .text([ 'Displaying lines', lineStart + 1, 'to', lineEnd, 'of', totalLines, 'lines' ].join( ' ' ));
+ },
+
+ renderPrevNext : function( data ){
+ // this is cra-zazy
+ if( !data
+ || ( this.config.pagination.currPage === 0 && data.length < this.config.pagination.perPage ) ){ return null; }
+
+ function makePage$Li( text ){
+ return $([ '<li><a href="javascript:void(0);">', text, '</a></li>' ].join( '' ));
+ }
+//TODO: cache numPages/numLines in config
+ var view = this,
+ dataLines = this.config.dataset.metadata_data_lines,
+ numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined );
+ //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages );
+
+ // prev next buttons
+ var $prev = makePage$Li( 'Prev' ).click( function(){
+ if( view.config.pagination.currPage > 0 ){
+ view.config.pagination.currPage -= 1;
+ view.fetchData();
+ }
+ }),
+ $next = makePage$Li( 'Next' ).click( function(){
+ if( !numPages || view.config.pagination.currPage < ( numPages - 1 ) ){
+ view.config.pagination.currPage += 1;
+ view.fetchData();
+ }
+ }),
+ $prevNextList = $( '<ul/>' ).addClass( 'pagination data-prev-next' )
+ .append([ $prev, $next ]);
+
+ if( view.config.pagination.currPage === 0 ){
+ $prev.addClass( 'disabled' );
+ }
+ if( numPages && view.config.pagination.currPage === ( numPages - 1 ) ){
+ $next.addClass( 'disabled' );
+ }
+ return $prevNextList;
+ },
+
+ renderPagination : function( data ){
+ // this is cra-zazy
+ if( !data
+ || ( this.config.pagination.currPage === 0 && data.length < this.config.pagination.perPage ) ){ return null; }
+
+ function makePage$Li( text ){
+ return $([ '<li><a href="javascript:void(0);">', text, '</a></li>' ].join( '' ));
+ }
+//TODO: cache numPages/numLines in config
+ var view = this,
+ dataLines = this.config.dataset.metadata_data_lines,
+ numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined );
+ //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages );
+
+ // page numbers (as separate control)
+ //var $paginationContainer = $( '<div/>' ).addClass( 'pagination-container' ),
+ var $pagesList = $( '<ul/>' ).addClass( 'pagination data-pages' );
+ function pageNumClick( ev ){
+ view.config.pagination.currPage = $( this ).data( 'page' );
+ view.fetchData();
+ }
+ for( var i=0; i<numPages; i+=1 ){
+ // add page data for later event handling
+ var $pageLi = makePage$Li( i + 1 ).attr( 'data-page', i ).click( pageNumClick );
+ if( i === this.config.pagination.currPage ){
+ $pageLi.addClass( 'active' );
+ }
+ $pagesList.append( $pageLi );
+ }
+ // placing the pages list in an extra container allows us to set a max-width and scroll if overflow
+ //$paginationContainer.append( $pagesList );
+ //return $paginationContainer;
+ return $pagesList;
+ },
+
+ renderPlot : function( data ){
+ this.toggleStats( false );
+ var $svg = this.$el.find( 'svg' );
+ $svg.off().empty().show();
+ scatterplot( $svg.get( 0 ), this.config, data );
+ },
+
+ getStats : function( data ){
+ var view = this;
+ meanWorker = new Worker( '/plugins/visualizations/scatterplot/static/worker-stats.js' );
+ meanWorker.postMessage({
+ data : data,
+ keys : [ this.config.xColumn, this.config.yColumn ]
+ });
+ meanWorker.onerror = function( event ){
+ meanWorker.terminate();
+ };
+ meanWorker.onmessage = function( event ){
+ view.renderStats( event.data );
+ };
+ },
+
+ renderStats : function( stats, error ){
+ //console.debug( 'renderStats:', stats, error );
+ //console.debug( JSON.stringify( stats, null, ' ' ) );
+ var $statsTable = this.$el.find( '.stats-display' );
+
+ var xLabel = this.config.x.label, yLabel = this.config.y.label,
+ $table = $( '<table/>' ).addClass( 'table' )
+ .append([ '<thead><th></th><th>', xLabel, '</th><th>', yLabel, '</th></thead>' ].join( '' ))
+ .append( _.map( stats, function( stat, key ){
+ return $([ '<tr><td>', key, '</td><td>', stat[0], '</td><td>', stat[1], '</td></tr>' ].join( '' ));
+ }));
+ $statsTable.empty().append( $table );
+ },
+
+ toggleStats : function( showStats ){
+ var $statsDisplay = this.$el.find( '.stats-display' );
+ showStats = ( showStats === undefined )?( $statsDisplay.is( ':hidden' ) ):( showStats );
+ if( showStats ){
+ this.$el.find( 'svg' ).hide();
+ $statsDisplay.show();
+ this.$el.find( '.controls .stats-toggle-btn' ).text( 'Plot' );
+ } else {
+ $statsDisplay.hide();
+ this.$el.find( 'svg' ).show();
+ this.$el.find( '.controls .stats-toggle-btn' ).text( 'Stats' );
+ }
+ },
+
+ toString : function(){
+ return 'ScatterplotView()';
+ }
+});
diff -r 0c559492c88a85a4b81e005276858974df7f8076 -r a1e670fa596f733defef37746ea2e7aae31b6c87 config/plugins/visualizations/scatterplot/src/scatterplot.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot.js
@@ -1,488 +1,278 @@
-/* =============================================================================
-todo:
- outside this:
- BUG: setting width, height in plot controls doesn't re-interpolate data locations!!
- BUG?: get metadata_column_names (from datatype if necessary)
- BUG: single vis in popupmenu should have tooltip with that name NOT 'Visualizations'
-
- wire label setters, anim setter
-
- TwoVarScatterplot:
- ??: maybe better to do this with a canvas...
- save as visualization
- to seperate file?
- remove underscore dependencies
- add interface to change values (seperate)?
- download svg -> base64 encode
- incorporate glyphs, glyph state renderers
-
- ScatterplotSettingsForm:
- some css bug that lowers the width of settings form when plot-controls tab is open
- causes chart to shift
- what can be abstracted/reused for other graphs?
- avoid direct manipulation of this.plot
- allow option to put plot into seperate tab of interface (for small multiples)
-
- provide callback in view to load data incrementally - for large sets
- paginate
- handle rerender
- use endpoint (here and on the server (fileptr))
- fetch (new?) data
- handle rerender
- use d3.TSV?
- render warning on long data (> maxDataPoints)
- adjust endpoint
-
- selectable list of preset column comparisons (rnaseq etc.)
- how to know what sort of Tabular the data is?
- smarter about headers
- validate columns selection (here or server)
-
- set stats column names by selected columns
- move chart into tabbed area...
-
- Scatterplot.mako:
- multiple plots on one page (small multiples)
- ?? ensure svg styles thru d3 or css?
- d3: configable (easily)
- css: standard - better maintenance
- ? override at config
-
-============================================================================= */
+// =============================================================================
/**
* Two Variable scatterplot visualization using d3
* Uses semi transparent circles to show density of data in x, y grid
* usage :
- * var plot = new TwoVarScatterplot({ containerSelector : 'div#my-plot', ... })
- * plot.render( xColumnData, yColumnData );
- *
- * depends on: d3, underscore
+ * var plot = new scatterplot( $( 'svg' ).get(0), config, data )
*/
-function TwoVarScatterplot( config ){
- var TICK_LINE_AND_PADDING = 10,
- GUESS_AT_SVG_CHAR_WIDTH = 7,
- GUESS_AT_SVG_CHAR_HEIGHT = 10,
- PADDING = 8,
- X_LABEL_TOO_LONG_AT = 5;
-
- // set up logging
- //this.debugging = true;
- this.log = function(){
- if( this.debugging && console && console.debug ){
- var args = Array.prototype.slice.call( arguments );
- args.unshift( this.toString() );
- console.debug.apply( console, args );
- }
- };
- this.log( 'new TwoVarScatterplot:', config );
-
- // ........................................................ set up chart config
- // config will default to these values when not passed in
- //NOTE: called on new
- this.defaults = {
- id : 'TwoVarScatterplot',
- containerSelector : 'body',
- //TODO??: needed?
- maxDataPoints : 30000,
- datapointSize : 4,
- animDuration : 500,
- //TODO: variable effect (not always exactly # of ticks set to)
- xNumTicks : 10,
- yNumTicks : 10,
- xAxisLabelBumpY : 40,
- yAxisLabelBumpX : -40,
- width : 400,
- height : 400,
- //TODO: anyway to make this a sub-obj?
- 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, config );
- this.log( 'intial config:', this.config );
-
- this.updateConfig = function( newConfig, rerender ){
- // setter for chart config
- //TODO: validate here
- _.extend( this.config, newConfig );
- this.log( this + '.updateConfig:', this.config );
- //TODO: implement rerender flag
- };
-
- // ........................................................ helpers
- this.toString = function(){
- return this.config.id;
- };
- // conv. methods for svg transforms
- this.translateStr = function( x, y ){
- return 'translate(' + x + ',' + y + ')';
- };
- this.rotateStr = function( d, x, y ){
- return 'rotate(' + d + ',' + x + ',' + y + ')';
+function scatterplot( renderTo, config, data ){
+ //console.log( 'scatterplot', config );
+
+ var translateStr = function( x, y ){
+ return 'translate(' + x + ',' + y + ')';
+ },
+ rotateStr = function( d, x, y ){
+ return 'rotate(' + d + ',' + x + ',' + y + ')';
+ },
+ getX = function( d, i ){
+ //console.debug( d[ config.xColumn ] );
+ return d[ config.xColumn ];
+ },
+ getY = function( d, i ){
+ //console.debug( d[ config.yColumn ] );
+ return d[ config.yColumn ];
+ };
+
+ // .................................................................... scales
+ var stats = {
+ x : { extent: d3.extent( data, getX ) },
+ y : { extent: d3.extent( data, getY ) }
+ };
+
+ //TODO: set pan/zoom limits
+ // from http://stackoverflow.com/questions/10422738/limiting-domain-when-zooming-or…
+ //self.x.domain([Math.max(self.x.domain()[0], self.options.xmin), Math.min(self.x.domain()[1], self.options.xmax)]);
+ //self.y.domain([Math.max(self.y.domain()[0], self.options.ymin), Math.min(self.y.domain()[1], self.options.ymax)]);
+ var interpolaterFns = {
+ x : d3.scale.linear()
+ .domain( stats.x.extent )
+ .range([ 0, config.width ]),
+ y : d3.scale.linear()
+ .domain( stats.y.extent )
+ .range([ config.height, 0 ])
};
- // ........................................................ initial element creation
- this.adjustChartDimensions = function( top, right, bottom, left ){
- //this.log( this + '.adjustChartDimensions', arguments );
- top = top || 0;
- right = right || 0;
- bottom = bottom || 0;
- left = left || 0;
- this.svg
- .attr( "width", this.config.width + ( this.config.marginRight + right )
- + ( this.config.marginLeft + left ) )
- .attr( "height", this.config.height + ( this.config.marginTop + top )
- + ( this.config.marginBottom + bottom ) )
- // initial is hidden - show it
- .style( 'display', 'block' );
-
- // move content group away from margins
- //TODO: allow top, right axis
- this.content = this.svg.select( "g.content" )
- .attr( "transform", this.translateStr( this.config.marginLeft + left, this.config.marginTop + top ) );
- };
-
- // ........................................................ data and scales
- this.preprocessData = function( data, min, max ){
- //this.log( this + '.preprocessData', arguments );
- //TODO: filter by min, max if set
-
- // set a cap on the data, limit to first n points
- return ( data.length > this.config.maxDataPoints )? ( data.slice( 0, this.config.maxDataPoints ) ): ( data );
- };
-
- this.findMinMaxes = function( xCol, yCol, meta ){
- //this.log( this + '.findMinMaxes', arguments );
- // configuration takes priority, otherwise meta (from the server) if passed, last-resort: compute it here
- this.xMin = this.config.xMin || ( meta )?( meta[0].min ):( d3.min( xCol ) );
- this.xMax = this.config.xMax || ( meta )?( meta[0].max ):( d3.max( xCol ) );
- this.yMin = this.config.yMin || ( meta )?( meta[1].min ):( d3.min( yCol ) );
- this.yMax = this.config.yMax || ( meta )?( meta[1].max ):( d3.max( yCol ) );
- };
-
- this.setUpScales = function(){
- //this.log( this + '.setUpScales', arguments );
- // Interpolation for x, y based on data domains
- 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 ]);
- };
-
- // ........................................................ axis and ticks
- this.setUpXAxis = function(){
- //this.log( this + '.setUpXAxis', arguments );
- // origin: bottom, left
- //TODO: incoporate top, right
- this.xAxisFn = d3.svg.axis()
- .scale( this.xScale )
- .ticks( this.config.xNumTicks )
- .orient( 'bottom' );
- this.xAxis// = content.select( 'g#x-axis' )
- .attr( 'transform', this.translateStr( 0, this.config.height ) )
- .call( this.xAxisFn );
- //this.log( 'xAxis:', this.xAxis );
-
- //TODO: adjust ticks when tick labels are long - move odds down and extend tick line
- // (for now) hide them
- var xLongestTickLabel = d3.max( _.map( [ this.xMin, this.xMax ],
- function( number ){ return ( String( number ) ).length; } ) );
- //this.log( 'xLongestTickLabel:', xLongestTickLabel );
- if( xLongestTickLabel >= X_LABEL_TOO_LONG_AT ){
- this.xAxis.selectAll( 'g' ).filter( ':nth-child(odd)' ).style( 'display', 'none' );
- }
-
- this.log( 'this.config.xLabel:', this.config.xLabel );
- this.xAxisLabel// = xAxis.select( 'text#x-axis-label' )
- .attr( 'x', this.config.width / 2 )
- .attr( 'y', this.config.xAxisLabelBumpY )
- .attr( 'text-anchor', 'middle' )
- .text( this.config.xLabel );
- this.log( 'xAxisLabel:', this.xAxisLabel );
+ // .................................................................... main components
+ var zoom = d3.behavior.zoom()
+ .x( interpolaterFns.x )
+ .y( interpolaterFns.y )
+ .scaleExtent([ 1, 10 ]);
+//TODO: you can prog. set the zoom and pan with zoom.scale( val ) and zoom.translate([ x, y ])...
+
+ //console.debug( renderTo );
+ var svg = d3.select( renderTo )
+ .attr( "class", "scatterplot" )
+ //.attr( "width", config.width + ( config.margin.right + config.margin.left ) )
+ .attr( "width", '100%' )
+ .attr( "height", config.height + ( config.margin.top + config.margin.bottom ) );
+
+ var content = svg.append( "g" )
+ .attr( "class", "content" )
+ .attr( "transform", translateStr( config.margin.left, config.margin.top ) )
+ .call( zoom );
+
+ // a BIG gotcha - zoom (or any mouse/touch event in SVG?) requires the pointer to be over an object
+ // create a transparent rect to be that object here
+ content.append( 'rect' )
+ .attr( "class", "zoom-rect" )
+ .attr( "width", config.width ).attr( "height", config.height )
+ .style( "fill", "transparent" );
+
+ //console.log( 'svg:', svg, 'content:', content );
+
+ // .................................................................... axes
+ var axis = { x : {}, y : {} };
+ //console.log( 'x.ticks:', config.x.ticks );
+ //console.log( 'y.ticks:', config.y.ticks );
+ axis.x.fn = d3.svg.axis()
+ .orient( 'bottom' )
+ .scale( interpolaterFns.x )
+ .ticks( config.x.ticks )
+ // this will convert thousands -> k, millions -> M, etc.
+ .tickFormat( d3.format( 's' ) );
+
+ axis.y.fn = d3.svg.axis()
+ .orient( 'left' )
+ .scale( interpolaterFns.y )
+ .ticks( config.y.ticks )
+ .tickFormat( d3.format( 's' ) );
+
+ axis.x.g = content.append( 'g' )
+ .attr( 'class', 'x axis' )
+ .attr( 'transform', translateStr( 0, config.height ) )
+ .call( axis.x.fn );
+ //console.log( 'axis.x.g:', axis.x.g );
+
+ axis.y.g = content.append( 'g' )
+ .attr( 'class', 'y axis' )
+ .call( axis.y.fn );
+ //console.log( 'axis.y.g:', axis.y.g );
+
+ // ................................ axis labels
+ var padding = 4;
+ // x-axis label
+ axis.x.label = svg.append( 'text' )
+ .attr( 'class', 'axis-label' )
+ .text( config.x.label )
+ // align to the top-middle
+ .attr( 'text-anchor', 'middle' )
+ .attr( 'dominant-baseline', 'text-after-edge' )
+ .attr( 'x', ( config.width / 2 ) + config.margin.left )
+ // place 4 pixels below the axis bounds
+ .attr( 'y', ( config.height + config.margin.bottom + config.margin.top ) - padding );
+ //console.log( 'axis.x.label:', axis.x.label );
+
+//TODO: anchor to left of x margin/graph
+ // y-axis label
+ // place 4 pixels left of the axis.y.g left edge
+ axis.y.label = svg.append( 'text' )
+ .attr( 'class', 'axis-label' )
+ .text( config.y.label )
+ // align to bottom-middle
+ .attr( 'text-anchor', 'middle' )
+ .attr( 'dominant-baseline', 'text-before-edge' )
+ .attr( 'x', padding )
+ .attr( 'y', config.height / 2 )
+ // rotate around the alignment point
+ .attr( 'transform', rotateStr( -90, padding, config.height / 2 ) );
+ //console.log( 'axis.y.label:', axis.y.label );
+
+ axis.redraw = function _redrawAxis(){
+ svg.select( ".x.axis" ).call( axis.x.fn );
+ svg.select( ".y.axis" ).call( axis.y.fn );
};
- this.setUpYAxis = function(){
- //this.log( this + '.setUpYAxis', arguments );
- this.yAxisFn = d3.svg.axis()
- .scale( this.yScale )
- .ticks( this.config.yNumTicks )
- .orient( 'left' );
- this.yAxis// = content.select( 'g#y-axis' )
- .call( this.yAxisFn );
- //this.log( 'yAxis:', this.yAxis );
-
- // a too complicated section for increasing the left margin when tick labels are long
- // get the tick labels for the y axis
- var yTickLabels = this.yAxis.selectAll( 'text' ).filter( function( e, i ){ return i !== 0; } );
- this.log( 'yTickLabels:', yTickLabels );
-
- // get the longest label length (or 0 if no labels)
- this.yLongestLabel = d3.max(
- //NOTE: d3 returns an nested array - use the plain array inside ([0])
- yTickLabels[0].map( function( e, i ){
- return ( d3.select( e ).text() ).length;
- })
- ) || 0;
- //this.log( 'yLongestLabel:', this.yLongestLabel );
- //TODO: lose the guessing if possible
- var neededY = TICK_LINE_AND_PADDING + ( this.yLongestLabel * GUESS_AT_SVG_CHAR_WIDTH )
- + PADDING + GUESS_AT_SVG_CHAR_HEIGHT;
- //this.log( 'neededY:', neededY );
-
- // increase width for yLongerStr, increase margin for y
- //TODO??: (or transform each number: 2k)
- this.config.yAxisLabelBumpX = -( neededY - GUESS_AT_SVG_CHAR_HEIGHT );
- if( this.config.marginLeft < neededY ){
- var adjusting = ( neededY ) - this.config.marginLeft;
- adjusting = ( adjusting < 0 )?( 0 ):( adjusting );
- //this.log( 'adjusting:', adjusting );
-
- // update dimensions, translations
- this.adjustChartDimensions( 0, 0, 0, adjusting );
+ // .................................................................... grid
+ function renderGrid(){
+ var grid = { v : {}, h: {} };
+ // vertical
+ grid.v.lines = content.selectAll( 'line.v-grid-line' )
+ // data are the axis ticks; enter, update, exit
+ .data( interpolaterFns.x.ticks( axis.x.fn.ticks()[0] ) );
+ // enter: append any extra lines needed (more ticks)
+ grid.v.lines.enter()
+ .append( 'svg:line' )
+ .classed( 'grid-line v-grid-line', true );
+ // update: set coords
+ grid.v.lines
+ .attr( 'x1', interpolaterFns.x )
+ .attr( 'x2', interpolaterFns.x )
+ .attr( 'y1', 0 )
+ .attr( 'y2', config.height );
+ // exit: just remove them
+ grid.v.lines.exit().remove();
+ //console.log( 'grid.v.lines:', grid.v.lines );
+
+ // horizontal
+ grid.h.lines = content.selectAll( 'line.h-grid-line' )
+ .data( interpolaterFns.y.ticks( axis.y.fn.ticks()[0] ) );
+ grid.h.lines.enter()
+ .append( 'svg:line' )
+ .classed( 'grid-line h-grid-line', true );
+ grid.h.lines
+ .attr( 'x1', 0 )
+ .attr( 'x2', config.width )
+ .attr( 'y1', interpolaterFns.y )
+ .attr( 'y2', interpolaterFns.y );
+ grid.h.lines.exit().remove();
+ //console.log( 'grid.h.lines:', grid.h.lines );
+ return grid;
+ }
+ var grid = renderGrid();
+
+ //// .................................................................... datapoints
+ var datapoints = content.selectAll( '.glyph' ).data( data )
+ // enter - NEW data to be added as glyphs
+ .enter().append( 'svg:circle' )
+ .classed( "glyph", true )
+ .attr( "cx", function( d, i ){ return interpolaterFns.x( getX( d, i ) ); })
+ // give them a 'entry' position and style
+ .attr( "cy", config.height )
+ .attr( "r", 0 );
+
+ // for all EXISTING glyphs and those that need to be added: transition anim to final state
+ datapoints.transition().duration( config.animDuration )
+ .attr( "cy", function( d, i ){ return interpolaterFns.y( getY( d, i ) ); })
+ .attr( "r", config.datapointSize );
+ //console.log( 'datapoints:', datapoints );
+
+ function _redrawDatapointsClipped(){
+ return datapoints
+ .attr( "cx", function( d, i ){ return interpolaterFns.x( getX( d, i ) ); })
+ .attr( "cy", function( d, i ){ return interpolaterFns.y( getY( d, i ) ); })
+ .style( 'display', 'block' )
+ // filter out points now outside the graph content area and hide them
+ .filter( function( d, i ){
+ var cx = d3.select( this ).attr( "cx" ),
+ cy = d3.select( this ).attr( "cy" );
+ if( cx < 0 || cx > config.width ){ return true; }
+ if( cy < 0 || cy > config.height ){ return true; }
+ return false;
+ }).style( 'display', 'none' );
+ }
+
+ // .................................................................... behaviors
+ function zoomed( scale, translateX, translateY ){
+ //console.debug( 'zoom', this, scale, translateX, translateY, arguments );
+ // re-render axis, grid, and datapoints
+ axis.redraw();
+ _redrawDatapointsClipped();
+ grid = renderGrid();
+ $( '.chart-info-box' ).remove();
+ $( svg.node() ).trigger( 'zoom.scatterplot', [] );
+ }
+ //TODO: programmatically set zoom/pan and save in config
+ //TODO: set pan/zoom limits
+ zoom.on( "zoom", zoomed );
+
+
+ function infoBox( top, left, d ){
+ // create an abs pos. element containing datapoint data (d) near the point (top, left)
+ // with added padding to clear the mouse pointer
+ left += 8;
+ return $([
+ '<div class="chart-info-box" style="position: absolute">',
+ (( config.idColumn )?( '<div>' + d[ config.idColumn ] + '</div>' ):( '' )),
+ '<div>', getX( d ), '</div>',
+ '<div>', getY( d ), '</div>',
+ '</div>'
+ ].join( '' ) ).css({ top: top, left: left, 'z-index': 2 });
+ }
+
+ datapoints.on( 'mouseover', function( d, i ){
+ var datapoint = d3.select( this );
+ datapoint
+ .style( 'fill', 'red' )
+ .style( 'fill-opacity', 1 );
+
+ // create horiz line to axis
+ content.append( 'line' )
+ .attr( 'stroke', 'red' )
+ .attr( 'stroke-width', 1 )
+ // start not at center, but at the edge of the circle - to prevent mouseover thrashing
+ .attr( 'x1', datapoint.attr( 'cx' ) - config.datapointSize )
+ .attr( 'y1', datapoint.attr( 'cy' ) )
+ .attr( 'x2', 0 )
+ .attr( 'y2', datapoint.attr( 'cy' ) )
+ .classed( 'hoverline', true );
+
+ // create vertical line to axis - if not on the x axis
+ if( datapoint.attr( 'cy' ) < config.height ){
+ content.append( 'line' )
+ .attr( 'stroke', 'red' )
+ .attr( 'stroke-width', 1 )
+ .attr( 'x1', datapoint.attr( 'cx' ) )
+ // attributes are strings so, (accrd. to js) '3' - 1 = 2 but '3' + 1 = '31': coerce
+ .attr( 'y1', +datapoint.attr( 'cy' ) + config.datapointSize )
+ .attr( 'x2', datapoint.attr( 'cx' ) )
+ .attr( 'y2', config.height )
+ .classed( 'hoverline', true );
}
- //this.log( 'this.config.yAxisLableBumpx, this.config.marginLeft:',
- // this.config.yAxisLabelBumpX, this.config.marginLeft );
-
- this.yAxisLabel// = yAxis.select( 'text#y-axis-label' )
- .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 );
- };
-
- // ........................................................ grid lines
- this.renderGrid = function(){
- //this.log( this + '.renderGrid', arguments );
- // VERTICAL
- // select existing
- this.vGridLines = this.content.selectAll( 'line.v-grid-line' )
- .data( this.xScale.ticks( this.xAxisFn.ticks()[0] ) );
-
- // append any extra lines needed (more ticks)
- this.vGridLines.enter().append( 'svg:line' )
- .classed( 'grid-line v-grid-line', true );
-
- // update the attributes of existing and appended
- this.vGridLines
- .attr( 'x1', this.xScale )
- .attr( 'y1', 0 )
- .attr( 'x2', this.xScale )
- .attr( 'y2', this.config.height );
-
- // remove unneeded (less ticks)
- this.vGridLines.exit().remove();
- //this.log( 'vGridLines:', this.vGridLines );
- // HORIZONTAL
- 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 );
- };
-
- // ........................................................ data points
- this.renderDatapoints = function( xCol, yCol, ids ){
- this.log( this + '.renderDatapoints', arguments );
- var count = 0,
- plot = this,
- xPosFn = function( d, i ){
- //if( d ){ this.log( 'x.data:', newXCol[ i ], 'plotted:', plot.xScale( newXCol[ i ] ) ); }
- return plot.xScale( xCol[ i ] );
- },
- yPosFn = function( d, i ){
- //if( d ){ this.log( 'y.data:', newYCol[ i ], 'plotted:', plot.yScale( newYCol[ i ] ) ); }
- return plot.yScale( yCol[ i ] );
- };
+ // show the info box and trigger an event
+ var bbox = this.getBoundingClientRect();
+ $( 'body' ).append( infoBox( bbox.top, bbox.right, d ) );
+ $( svg.node() ).trigger( 'mouseover-datapoint.scatterplot', [ this, d, i ] );
+ });
- //this.datapoints = this.addDatapoints( xCol, yCol, ids, ".glyph" );
- var datapoints = this.content.selectAll( '.glyph' ).data( xCol );
-
- // enter - NEW data to be added as glyphs: give them a 'entry' position and style
- count = 0;
- datapoints.enter()
- .append( 'svg:circle' )
- .each( function(){ count += 1; } )
- .classed( "glyph", true )
- .attr( "cx", 0 )
- .attr( "cy", this.config.height )
- // start all bubbles small...
- .attr( "r", 0 );
- this.log( count, ' new glyphs created' );
-
- // for all EXISTING glyphs and those that need to be added: transition anim to final state
- count = 0;
- datapoints
- // ...animate to final position
- .transition().duration( this.config.animDuration )
- .each( function(){ count += 1; } )
- .attr( "cx", xPosFn )
- .attr( "cy", yPosFn )
- .attr( "r", plot.config.datapointSize );
- this.log( count, ' existing glyphs transitioned' );
-
- // events
- // glyphs that need to be removed: transition to from normal state to 'exit' state, remove from DOM
- datapoints.exit()
- .each( function(){ count += 1; } )
- .transition().duration( this.config.animDuration )
- .attr( "cy", this.config.height )
- .attr( "r", 0 )
- .remove();
- this.log( count, ' glyphs removed' );
-
- this._addDatapointEventhandlers( datapoints, xCol, yCol, ids );
- };
-
- this._addDatapointEventhandlers = function( datapoints, xCol, yCol, ids ){
- var plot = this;
- datapoints
- //TODO: remove magic numbers
- .on( 'mouseover', function( d, i ){
- var datapoint = d3.select( this );
- datapoint
- .style( 'fill', 'red' )
- .style( 'fill-opacity', 1 );
-
- // create horiz, vert lines to axis
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- // start not at center, but at the edge of the circle - to prevent mouseover thrashing
- .attr( 'x1', datapoint.attr( 'cx' ) - plot.config.datapointSize )
- .attr( 'y1', datapoint.attr( 'cy' ) )
- .attr( 'x2', 0 )
- .attr( 'y2', datapoint.attr( 'cy' ) )
- .classed( 'hoverline', true );
-
- // if the vertical hoverline
- if( datapoint.attr( 'cy' ) < plot.config.height ){
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- .attr( 'x1', datapoint.attr( 'cx' ) )
- .attr( 'y1', datapoint.attr( 'cy' ) + plot.config.datapointSize )
- .attr( 'x2', datapoint.attr( 'cx' ) )
- .attr( 'y2', plot.config.height )
- .classed( 'hoverline', true );
- }
-
- var datapointWindowPos = $( this ).offset();
- plot.datapointInfoBox = plot.infoBox(
- datapointWindowPos.top, datapointWindowPos.left,
- plot.infoHtml( xCol[ i ], yCol[ i ], ( ids )?( ids[ i ] ):( undefined ) )
- );
- $( 'body' ).append( plot.datapointInfoBox );
- })
- .on( 'mouseout', function(){
- d3.select( this )
- .style( 'fill', 'black' )
- .style( 'fill-opacity', 0.2 );
- plot.content.selectAll( '.hoverline' ).remove();
- if( plot.datapointInfoBox ){
- plot.datapointInfoBox.remove();
- }
- });
- },
-
- this.render = function( columnData, meta ){
- this.log( this + '.render', arguments );
- this.log( '\t config:', this.config );
-
- // prepare the data
- //pre: columns passed are numeric
- //pre: at least two columns are passed
- //assume: first column is x, second column is y, any remaining aren't used
- var xCol = columnData[0],
- yCol = columnData[1],
- ids = ( columnData.length > 2 )?( columnData[2] ):( undefined );
- //this.log( this + '.render', xCol.length, yCol.length, this.config );
-
- //pre: xCol.len == yCol.len
- xCol = this.preprocessData( xCol );
- yCol = this.preprocessData( yCol );
- this.log( 'xCol len', xCol.length, 'yCol len', yCol.length );
-
- this.findMinMaxes( xCol, yCol, meta );
- //this.log( 'xMin, xMax, yMin, yMax:', this.xMin, this.xMax, this.yMin, this.yMax );
- this.setUpScales();
-
- // find (or build if it doesn't exist) the svg dom infrastructure
- if( !this.svg ){ this.svg = d3.select( 'svg' ).attr( "class", "chart" ); }
- if( !this.content ){
- this.content = this.svg.append( "svg:g" ).attr( "class", "content" ).attr( 'id', this.config.id );
- }
- //this.log( 'svg:', this.svg );
- //this.log( 'content:', this.content );
-
- this.adjustChartDimensions();
-
- if( !this.xAxis ){ this.xAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'x-axis' ); }
- if( !this.xAxisLabel ){
- this.xAxisLabel = this.xAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'x-axis-label' );
- }
- //this.log( 'xAxis:', this.xAxis, 'xAxisLabel:', this.xAxisLabel );
-
- if( !this.yAxis ){ this.yAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'y-axis' ); }
- if( !this.yAxisLabel ){
- this.yAxisLabel = this.yAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'y-axis-label' );
- }
- //this.log( 'yAxis:', this.yAxis, 'yAxisLabel:', this.yAxisLabel );
-
- this.setUpXAxis();
- this.setUpYAxis();
-
- this.renderGrid();
- this.renderDatapoints( xCol, yCol, ids );
- };
-
- this.infoHtml = function( x, y, id ){
- var retDiv = $( '<div/>' );
- if( id ){
- $( '<div/>' ).text( id ).css( 'font-weight', 'bold' ).appendTo( retDiv );
- }
- $( '<div/>' ).text( x ).appendTo( retDiv );
- $( '<div/>' ).text( y ).appendTo( retDiv );
- return retDiv.html();
- };
-
- //TODO: html for now
- this.infoBox = function( top, left, html, adjTop, adjLeft ){
- adjTop = adjTop || 0;
- adjLeft = adjLeft || 20;
- var infoBox = $( '<div />' )
- .addClass( 'chart-info-box' )
- .css({
- 'position' : 'absolute',
- 'top' : top + adjTop,
- 'left' : left + adjLeft
- });
- infoBox.html( html );
- return infoBox;
- };
-
+ datapoints.on( 'mouseout', function(){
+ // return the point to normal, remove hoverlines and info box
+ d3.select( this )
+ .style( 'fill', 'black' )
+ .style( 'fill-opacity', 0.2 );
+ content.selectAll( '.hoverline' ).remove();
+ $( '.chart-info-box' ).remove();
+ });
}
//==============================================================================
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/da0672e25186/
Changeset: da0672e25186
User: kellrott
Date: 2013-11-13 19:06:12
Summary: Merged galaxy/galaxy-central into default
Affected #: 20 files
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -1,16 +1,38 @@
"""
Provides mapping between extensions and datatypes, mime-types, etc.
"""
-import os, sys, tempfile, threading, logging, imp
-import data, tabular, interval, images, sequence, qualityscore, genetics, xml, coverage, tracks, chrominfo, binary, assembly, ngsindex, graph
+import os
+import sys
+import tempfile
+import threading
+import logging
+import imp
+import data
+import tabular
+import interval
+import images
+import sequence
+import qualityscore
+import genetics
+import xml
+import coverage
+import tracks
+import chrominfo
+import binary
+import assembly
+import ngsindex
+import graph
import galaxy.util
from galaxy.util.odict import odict
from display_applications.application import DisplayApplication
+
class ConfigurationError( Exception ):
pass
+
class Registry( object ):
+
def __init__( self ):
self.log = logging.getLogger(__name__)
self.log.addHandler( logging.NullHandler() )
@@ -47,18 +69,21 @@
self.datatype_elems = []
self.sniffer_elems = []
self.xml_filename = None
+
def load_datatypes( self, root_dir=None, config=None, deactivate=False, override=True ):
"""
- Parse a datatypes XML file located at root_dir/config. If deactivate is True, an installed tool shed
- repository that includes proprietary datatypes is being deactivated, so appropriate loaded datatypes
- will be removed from the registry. The value of override will be False when a tool shed repository is
- being installed. Since installation is occurring after the datatypes registry has been initialized, its
- contents cannot be overridden by new introduced conflicting data types.
+ Parse a datatypes XML file located at root_dir/config (if processing the Galaxy distributed config) or contained within
+ an installed Tool Shed repository. If deactivate is True, an installed Tool Shed repository that includes custom datatypes
+ is being deactivated or uninstalled, so appropriate loaded datatypes will be removed from the registry. The value of
+ override will be False when a Tool Shed repository is being installed. Since installation is occurring after the datatypes
+ registry has been initialized at server startup, it's contents cannot be overridden by newly introduced conflicting data types.
"""
+
def __import_module( full_path, datatype_module, datatype_class_name ):
open_file_obj, file_name, description = imp.find_module( datatype_module, [ full_path ] )
imported_module = imp.load_module( datatype_class_name, open_file_obj, file_name, description )
return imported_module
+
if root_dir and config:
handling_proprietary_datatypes = False
# Parse datatypes_conf.xml
@@ -82,308 +107,275 @@
# Proprietary datatype's <registration> tag may have special attributes, proprietary_converter_path and proprietary_display_path.
proprietary_converter_path = registration.get( 'proprietary_converter_path', None )
proprietary_display_path = registration.get( 'proprietary_display_path', None )
- if proprietary_converter_path or proprietary_display_path and not handling_proprietary_datatypes:
+ if proprietary_converter_path is not None or proprietary_display_path is not None and not handling_proprietary_datatypes:
handling_proprietary_datatypes = True
for elem in registration.findall( 'datatype' ):
- try:
- extension = elem.get( 'extension', None )
- dtype = elem.get( 'type', None )
- type_extension = elem.get( 'type_extension', None )
- mimetype = elem.get( 'mimetype', None )
- display_in_upload = galaxy.util.string_as_bool( elem.get( 'display_in_upload', False ) )
- make_subclass = galaxy.util.string_as_bool( elem.get( 'subclass', False ) )
- # Proprietary datatypes included in installed tool shed repositories will include two special attributes
- # (proprietary_path and proprietary_datatype_module) if they depend on proprietary datatypes classes.
- proprietary_path = elem.get( 'proprietary_path', None )
- proprietary_datatype_module = elem.get( 'proprietary_datatype_module', None )
- if proprietary_path or proprietary_datatype_module and not handling_proprietary_datatypes:
- handling_proprietary_datatypes = True
- if deactivate:
- # We are deactivating an installed tool shed repository, so eliminate the
- # datatype elem from the in-memory list of datatype elems.
- for in_memory_elem in self.datatype_elems:
- in_memory_extension = in_memory_elem.get( 'extension', None )
- if in_memory_extension == extension:
- in_memory_dtype = elem.get( 'type', None )
- in_memory_type_extension = elem.get( 'type_extension', None )
- in_memory_mimetype = elem.get( 'mimetype', None )
- in_memory_display_in_upload = galaxy.util.string_as_bool( elem.get( 'display_in_upload', False ) )
- in_memory_make_subclass = galaxy.util.string_as_bool( elem.get( 'subclass', False ) )
- if in_memory_dtype == dtype and in_memory_type_extension == type_extension and in_memory_mimetype == mimetype \
- and in_memory_display_in_upload == display_in_upload and in_memory_make_subclass == make_subclass:
- self.datatype_elems.remove( in_memory_elem )
- else:
- # Keep an in-memory list of datatype elems to enable persistence.
- if extension not in self.datatypes_by_extension:
- self.datatype_elems.append( elem )
- if extension and extension in self.datatypes_by_extension and deactivate:
- # We are deactivating an installed tool shed repository, so eliminate the datatype from the registry.
- # TODO: Handle deactivating datatype converters, etc before removing from self.datatypes_by_extension.
- self.log.debug( "Removing datatype with extension '%s' from the registry." % extension )
+ # Keep a status of the process steps to enable stopping the process of handling the datatype if necessary.
+ ok = True
+ extension = elem.get( 'extension', None )
+ dtype = elem.get( 'type', None )
+ type_extension = elem.get( 'type_extension', None )
+ mimetype = elem.get( 'mimetype', None )
+ display_in_upload = galaxy.util.string_as_bool( elem.get( 'display_in_upload', False ) )
+ make_subclass = galaxy.util.string_as_bool( elem.get( 'subclass', False ) )
+ # Proprietary datatypes included in installed tool shed repositories will include two special attributes
+ # (proprietary_path and proprietary_datatype_module) if they depend on proprietary datatypes classes.
+ proprietary_path = elem.get( 'proprietary_path', None )
+ proprietary_datatype_module = elem.get( 'proprietary_datatype_module', None )
+ if proprietary_path is not None or proprietary_datatype_module is not None and not handling_proprietary_datatypes:
+ handling_proprietary_datatypes = True
+ if deactivate:
+ # We are deactivating or uninstalling an installed tool shed repository, so eliminate the datatype
+ # elem from the in-memory list of datatype elems.
+ for in_memory_elem in self.datatype_elems:
+ in_memory_extension = in_memory_elem.get( 'extension', None )
+ if in_memory_extension == extension:
+ in_memory_dtype = elem.get( 'type', None )
+ in_memory_type_extension = elem.get( 'type_extension', None )
+ in_memory_mimetype = elem.get( 'mimetype', None )
+ in_memory_display_in_upload = galaxy.util.string_as_bool( elem.get( 'display_in_upload', False ) )
+ in_memory_make_subclass = galaxy.util.string_as_bool( elem.get( 'subclass', False ) )
+ if in_memory_dtype == dtype and \
+ in_memory_type_extension == type_extension and \
+ in_memory_mimetype == mimetype and \
+ in_memory_display_in_upload == display_in_upload and \
+ in_memory_make_subclass == make_subclass:
+ self.datatype_elems.remove( in_memory_elem )
+ if extension is not None and extension in self.datatypes_by_extension:
+ # We are deactivating or uninstalling an installed tool shed repository, so eliminate the datatype
+ # from the registry. TODO: Handle deactivating datatype converters, etc before removing from
+ # self.datatypes_by_extension.
del self.datatypes_by_extension[ extension ]
if extension in self.upload_file_formats:
self.upload_file_formats.remove( extension )
- can_process_datatype = False
+ self.log.debug( "Removed datatype with extension '%s' from the registry." % extension )
+ else:
+ # We are loading new datatype, so we'll make sure it is correctly defined before proceeding.
+ can_process_datatype = False
+ if extension is not None:
+ if dtype is not None or type_extension is not None:
+ if override or extension not in self.datatypes_by_extension:
+ can_process_datatype = True
+ if can_process_datatype:
+ if dtype is not None:
+ try:
+ fields = dtype.split( ':' )
+ datatype_module = fields[ 0 ]
+ datatype_class_name = fields[ 1 ]
+ except Exception, e:
+ self.log.exception( 'Error parsing datatype definition for dtype %s: %s' % ( str( dtype ), str( e ) ) )
+ ok = False
+ if ok:
+ datatype_class = None
+ if proprietary_path and proprietary_datatype_module and datatype_class_name:
+ # We need to change the value of sys.path, so do it in a way that is thread-safe.
+ lock = threading.Lock()
+ lock.acquire( True )
+ try:
+ imported_module = __import_module( proprietary_path,
+ proprietary_datatype_module,
+ datatype_class_name )
+ if imported_module not in self.imported_modules:
+ self.imported_modules.append( imported_module )
+ if hasattr( imported_module, datatype_class_name ):
+ datatype_class = getattr( imported_module, datatype_class_name )
+ except Exception, e:
+ full_path = os.path.join( proprietary_path, proprietary_datatype_module )
+ self.log.debug( "Exception importing proprietary code file %s: %s" % ( str( full_path ), str( e ) ) )
+ ok = False
+ finally:
+ lock.release()
+ if ok:
+ if datatype_class is None:
+ try:
+ # The datatype class name must be contained in one of the datatype modules in the Galaxy distribution.
+ fields = datatype_module.split( '.' )
+ module = __import__( fields.pop( 0 ) )
+ for mod in fields:
+ module = getattr( module, mod )
+ datatype_class = getattr( module, datatype_class_name )
+ except Exception, e:
+ self.log.exception( 'Error importing datatype module %s: %s' % ( str( datatype_module ), str( e ) ) )
+ ok = False
+ elif type_extension is not None:
+ try:
+ datatype_class = self.datatypes_by_extension[ type_extension ].__class__
+ except Exception, e:
+ self.log.exception( 'Error determining datatype_class for type_extension %s: %s' % ( str( type_extension ), str( e ) ) )
+ ok = False
+ if ok:
+ if not deactivate:
+ # A new tool shed repository that contains custom datatypes is being installed, and since installation is
+ # occurring after the datatypes registry has been initialized at server startup, its contents cannot be
+ # overridden by new introduced conflicting data types unless the value of override is True.
+ if extension in self.datatypes_by_extension:
+ # Because of the way that the value of can_process_datatype was set above, we know that the value of
+ # override is True.
+ self.log.warning( "Overriding conflicting datatype with extension '%s', using datatype from %s." % \
+ ( str( extension ), str( config ) ) )
+ if make_subclass:
+ datatype_class = type( datatype_class_name, ( datatype_class, ), {} )
+ self.datatypes_by_extension[ extension ] = datatype_class()
+ if mimetype is None:
+ # Use default mimetype per datatype specification.
+ mimetype = self.datatypes_by_extension[ extension ].get_mime()
+ self.mimetypes_by_extension[ extension ] = mimetype
+ if datatype_class.track_type:
+ self.available_tracks.append( extension )
+ if display_in_upload and extension not in self.upload_file_formats:
+ self.upload_file_formats.append( extension )
+ # Max file size cut off for setting optional metadata.
+ self.datatypes_by_extension[ extension ].max_optional_metadata_filesize = elem.get( 'max_optional_metadata_filesize', None )
+ for converter in elem.findall( 'converter' ):
+ # Build the list of datatype converters which will later be loaded into the calling app's toolbox.
+ converter_config = converter.get( 'file', None )
+ target_datatype = converter.get( 'target_datatype', None )
+ depends_on = converter.get( 'depends_on', None )
+ if depends_on is not None and target_datatype is not None:
+ if extension not in self.converter_deps:
+ self.converter_deps[ extension ] = {}
+ self.converter_deps[ extension ][ target_datatype ] = depends_on.split( ',' )
+ if converter_config and target_datatype:
+ if proprietary_converter_path:
+ self.proprietary_converters.append( ( converter_config, extension, target_datatype ) )
+ else:
+ self.converters.append( ( converter_config, extension, target_datatype ) )
+ # Add composite files.
+ for composite_file in elem.findall( 'composite_file' ):
+ name = composite_file.get( 'name', None )
+ if name is None:
+ self.log.warning( "You must provide a name for your composite_file (%s)." % composite_file )
+ optional = composite_file.get( 'optional', False )
+ mimetype = composite_file.get( 'mimetype', None )
+ self.datatypes_by_extension[ extension ].add_composite_file( name, optional=optional, mimetype=mimetype )
+ for display_app in elem.findall( 'display' ):
+ if proprietary_display_path:
+ if elem not in self.proprietary_display_app_containers:
+ self.proprietary_display_app_containers.append( elem )
+ else:
+ if elem not in self.display_app_containers:
+ self.display_app_containers.append( elem )
+ # Processing the new datatype elem is now complete, so make sure the element defining it is retained by appending
+ # the new datatype to the in-memory list of datatype elems to enable persistence.
+ self.datatype_elems.append( elem )
else:
- can_process_datatype = ( extension and ( dtype or type_extension ) ) and ( extension not in self.datatypes_by_extension or override )
- if can_process_datatype:
- if dtype:
- fields = dtype.split( ':' )
- datatype_module = fields[0]
- datatype_class_name = fields[1]
- datatype_class = None
- if proprietary_path and proprietary_datatype_module and datatype_class_name:
- # We need to change the value of sys.path, so do it in a way that is thread-safe.
- lock = threading.Lock()
- lock.acquire( True )
- try:
- imported_module = __import_module( proprietary_path, proprietary_datatype_module, datatype_class_name )
- if imported_module not in self.imported_modules:
- self.imported_modules.append( imported_module )
- if hasattr( imported_module, datatype_class_name ):
- datatype_class = getattr( imported_module, datatype_class_name )
- except Exception, e:
- full_path = os.path.join( proprietary_path, proprietary_datatype_module )
- self.log.debug( "Exception importing proprietary code file %s: %s" % ( str( full_path ), str( e ) ) )
- finally:
- lock.release()
- if datatype_class is None:
- # The datatype class name must be contained in one of the datatype modules in the Galaxy distribution.
- fields = datatype_module.split( '.' )
- module = __import__( fields.pop(0) )
- for mod in fields:
- module = getattr( module, mod )
- datatype_class = getattr( module, datatype_class_name )
- elif type_extension:
- datatype_class = self.datatypes_by_extension[type_extension].__class__
- if make_subclass:
- datatype_class = type( datatype_class_name, (datatype_class,), {} )
- if extension in self.datatypes_by_extension:
- self.log.warning( "Overriding conflicting datatype with extension '%s', using datatype from %s." % ( extension, config ) )
- self.datatypes_by_extension[ extension ] = datatype_class()
- if mimetype is None:
- # Use default mime type as per datatype spec
- mimetype = self.datatypes_by_extension[ extension ].get_mime()
- self.mimetypes_by_extension[ extension ] = mimetype
- if datatype_class.track_type:
- self.available_tracks.append( extension )
- if display_in_upload and extension not in self.upload_file_formats:
- self.upload_file_formats.append( extension )
- # Max file size cut off for setting optional metadata
- self.datatypes_by_extension[ extension ].max_optional_metadata_filesize = elem.get( 'max_optional_metadata_filesize', None )
- for converter in elem.findall( 'converter' ):
- # Build the list of datatype converters which will later be loaded into the calling app's toolbox.
- converter_config = converter.get( 'file', None )
- target_datatype = converter.get( 'target_datatype', None )
- depends_on = converter.get( 'depends_on', None )
- if depends_on and target_datatype:
- if extension not in self.converter_deps:
- self.converter_deps[extension] = {}
- self.converter_deps[extension][target_datatype] = depends_on.split(',')
- if converter_config and target_datatype:
- #if imported_modules:
- if proprietary_converter_path:
- self.proprietary_converters.append( ( converter_config, extension, target_datatype ) )
- else:
- self.converters.append( ( converter_config, extension, target_datatype ) )
- for composite_file in elem.findall( 'composite_file' ):
- # add composite files
- name = composite_file.get( 'name', None )
- if name is None:
- self.log.warning( "You must provide a name for your composite_file (%s)." % composite_file )
- optional = composite_file.get( 'optional', False )
- mimetype = composite_file.get( 'mimetype', None )
- self.datatypes_by_extension[extension].add_composite_file( name, optional=optional, mimetype=mimetype )
- for display_app in elem.findall( 'display' ):
- #if imported_modules:
- if proprietary_display_path:
- if elem not in self.proprietary_display_app_containers:
- self.proprietary_display_app_containers.append( elem )
- else:
- if elem not in self.display_app_containers:
- self.display_app_containers.append( elem )
- elif not deactivate:
- # A new tool shed repository that contains proprietary datatypes is being installed, and since installation
- # is occurring after the datatypes registry has been initialized, its contents cannot be overridden by new
- # introduced conflicting data types.
- self.log.warning( "Ignoring conflicting datatype with extension '%s' from %s." % ( extension, config ) )
- except Exception, e:
- if deactivate:
- self.log.warning( "Error deactivating datatype with extension '%s': %s" % ( extension, str( e ) ) )
- else:
- self.log.warning( "Error loading datatype with extension '%s': %s" % ( extension, str( e ) ) )
- # Load datatype sniffers from the config
- sniffers = root.find( 'sniffers' )
- if sniffers:
- for elem in sniffers.findall( 'sniffer' ):
- # Keep an in-memory list of sniffer elems to enable persistence.
- if elem not in self.sniffer_elems:
- self.sniffer_elems.append( elem )
- dtype = elem.get( 'type', None )
- if dtype:
- try:
- fields = dtype.split( ":" )
- datatype_module = fields[0]
- datatype_class_name = fields[1]
- module = None
- #if imported_modules:
- if handling_proprietary_datatypes:
- # See if one of the imported modules contains the datatype class name.
- for imported_module in self.imported_modules:
- if hasattr( imported_module, datatype_class_name ):
- module = imported_module
- break
- if module is None:
+ if extension is not None:
+ if dtype is not None or type_extension is not None:
+ if extension in self.datatypes_by_extension:
+ if not override:
+ # Do not load the datatype since it conflicts with an existing datatype which we are not supposed
+ # to override.
+ self.log.warning( "Ignoring conflicting datatype with extension '%s' from %s." % ( extension, config ) )
+ # Load datatype sniffers from the config - we'll do this even if one or more datatypes were not properly processed in the config
+ # since sniffers are not tightly coupled with datatypes.
+ self.load_datatype_sniffers( root,
+ deactivate=deactivate,
+ handling_proprietary_datatypes=handling_proprietary_datatypes,
+ override=override )
+ self.upload_file_formats.sort()
+ # Persist the xml form of the registry into a temporary file so that it can be loaded from the command line by tools and
+ # set_metadata processing.
+ self.to_xml_file()
+ self.set_default_values()
+
+ def append_to_sniff_order():
+ # Just in case any supported data types are not included in the config's sniff_order section.
+ for ext in self.datatypes_by_extension:
+ datatype = self.datatypes_by_extension[ ext ]
+ included = False
+ for atype in self.sniff_order:
+ if isinstance( atype, datatype.__class__ ):
+ included = True
+ break
+ if not included:
+ self.sniff_order.append( datatype )
+ append_to_sniff_order()
+
+ def load_datatype_sniffers( self, root, deactivate=False, handling_proprietary_datatypes=False, override=False ):
+ """
+ Process the sniffers element from a parsed a datatypes XML file located at root_dir/config (if processing the Galaxy
+ distributed config) or contained within an installed Tool Shed repository. If deactivate is True, an installed Tool
+ Shed repository that includes custom sniffers is being deactivated or uninstalled, so appropriate loaded sniffers will
+ be removed from the registry. The value of override will be False when a Tool Shed repository is being installed.
+ Since installation is occurring after the datatypes registry has been initialized at server startup, it's contents
+ cannot be overridden by newly introduced conflicting sniffers.
+ """
+ sniffer_elem_classes = [ e.attrib[ 'type' ] for e in self.sniffer_elems ]
+ sniffers = root.find( 'sniffers' )
+ if sniffers:
+ for elem in sniffers.findall( 'sniffer' ):
+ # Keep a status of the process steps to enable stopping the process of handling the sniffer if necessary.
+ ok = True
+ dtype = elem.get( 'type', None )
+ if dtype is not None:
+ try:
+ fields = dtype.split( ":" )
+ datatype_module = fields[ 0 ]
+ datatype_class_name = fields[ 1 ]
+ module = None
+ except Exception, e:
+ self.log.exception( 'Error determining datatype class or module for dtype %s: %s' % ( str( dtype ), str( e ) ) )
+ ok = False
+ if ok:
+ if handling_proprietary_datatypes:
+ # See if one of the imported modules contains the datatype class name.
+ for imported_module in self.imported_modules:
+ if hasattr( imported_module, datatype_class_name ):
+ module = imported_module
+ break
+ if module is None:
+ try:
# The datatype class name must be contained in one of the datatype modules in the Galaxy distribution.
module = __import__( datatype_module )
for comp in datatype_module.split( '.' )[ 1: ]:
module = getattr( module, comp )
- aclass = getattr( module, datatype_class_name )()
- if deactivate:
- if elem in self.sniffer_elems:
- self.sniffer_elems.remove( elem )
- for sniffer_class in self.sniff_order:
- if sniffer_class.__class__ == aclass.__class__:
- self.sniff_order.remove( sniffer_class )
- break
- self.log.debug( "Deactivated sniffer for datatype '%s'" % dtype )
- else:
- # See if we have a conflicting sniffer already loaded.
- conflict = False
- for conflict_loc, sniffer_class in enumerate( self.sniff_order ):
- if sniffer_class.__class__ == aclass.__class__:
- # We have a conflicting sniffer, so replace the one previously loaded.
- conflict = True
+ except Exception, e:
+ self.log.exception( "Error importing datatype class for '%s': %s" % ( str( dtype ), str( e ) ) )
+ ok = False
+ if ok:
+ try:
+ aclass = getattr( module, datatype_class_name )()
+ except Exception, e:
+ self.log.exception( 'Error calling method %s from class %s: %s' ( str( datatype_class_name ), str( module ), str( e ) ) )
+ ok = False
+ if ok:
+ if deactivate:
+ # We are deactivating or uninstalling an installed Tool Shed repository, so eliminate the appropriate sniffers.
+ sniffer_class = elem.get( 'type', None )
+ if sniffer_class is not None:
+ for index, s_e_c in enumerate( sniffer_elem_classes ):
+ if sniffer_class == s_e_c:
+ del self.sniffer_elems[ index ]
+ self.log.debug( "Removed sniffer element for datatype '%s'" % str( dtype ) )
+ break
+ for sniffer_class in self.sniff_order:
+ if sniffer_class.__class__ == aclass.__class__:
+ self.sniff_order.remove( sniffer_class )
+ self.log.debug( "Removed sniffer class for datatype '%s' from sniff order" % str( dtype ) )
+ break
+ else:
+ # We are loading new sniffer, so see if we have a conflicting sniffer already loaded.
+ conflict = False
+ for conflict_loc, sniffer_class in enumerate( self.sniff_order ):
+ if sniffer_class.__class__ == aclass.__class__:
+ # We have a conflicting sniffer, so replace the one previously loaded.
+ conflict = True
+ if override:
+ del self.sniff_order[ conflict_loc ]
+ self.log.debug( "Removed conflicting sniffer for datatype '%s'" % dtype )
+ break
+ if conflict:
if override:
- del self.sniff_order[ conflict_loc ]
- self.log.debug( "Replaced conflicting sniffer for datatype '%s'" % dtype )
- break
- if conflict:
- if override:
+ self.sniff_order.append( aclass )
+ self.log.debug( "Loaded sniffer for datatype '%s'" % dtype )
+ else:
self.sniff_order.append( aclass )
self.log.debug( "Loaded sniffer for datatype '%s'" % dtype )
- else:
- self.sniff_order.append( aclass )
- self.log.debug( "Loaded sniffer for datatype '%s'" % dtype )
- except Exception, exc:
- if deactivate:
- self.log.warning( "Error deactivating sniffer for datatype '%s': %s" % ( dtype, str( exc ) ) )
- else:
- self.log.warning( "Error appending sniffer for datatype '%s' to sniff_order: %s" % ( dtype, str( exc ) ) )
- self.upload_file_formats.sort()
- # Persist the xml form of the registry into a temporary file so that it
- # can be loaded from the command line by tools and set_metadata processing.
- self.to_xml_file()
- # Default values.
- if not self.datatypes_by_extension:
- self.datatypes_by_extension = {
- 'ab1' : binary.Ab1(),
- 'axt' : sequence.Axt(),
- 'bam' : binary.Bam(),
- 'bed' : interval.Bed(),
- 'coverage' : coverage.LastzCoverage(),
- 'customtrack' : interval.CustomTrack(),
- 'csfasta' : sequence.csFasta(),
- 'fasta' : sequence.Fasta(),
- 'eland' : tabular.Eland(),
- 'fastq' : sequence.Fastq(),
- 'fastqsanger' : sequence.FastqSanger(),
- 'gtf' : interval.Gtf(),
- 'gff' : interval.Gff(),
- 'gff3' : interval.Gff3(),
- 'genetrack' : tracks.GeneTrack(),
- 'interval' : interval.Interval(),
- 'laj' : images.Laj(),
- 'lav' : sequence.Lav(),
- 'maf' : sequence.Maf(),
- 'pileup' : tabular.Pileup(),
- 'qualsolid' : qualityscore.QualityScoreSOLiD(),
- 'qualsolexa' : qualityscore.QualityScoreSolexa(),
- 'qual454' : qualityscore.QualityScore454(),
- 'sam' : tabular.Sam(),
- 'scf' : binary.Scf(),
- 'sff' : binary.Sff(),
- 'tabular' : tabular.Tabular(),
- 'taxonomy' : tabular.Taxonomy(),
- 'txt' : data.Text(),
- 'wig' : interval.Wiggle(),
- 'xml' : xml.GenericXml(),
- }
- self.mimetypes_by_extension = {
- 'ab1' : 'application/octet-stream',
- 'axt' : 'text/plain',
- 'bam' : 'application/octet-stream',
- 'bed' : 'text/plain',
- 'customtrack' : 'text/plain',
- 'csfasta' : 'text/plain',
- 'eland' : 'application/octet-stream',
- 'fasta' : 'text/plain',
- 'fastq' : 'text/plain',
- 'fastqsanger' : 'text/plain',
- 'gtf' : 'text/plain',
- 'gff' : 'text/plain',
- 'gff3' : 'text/plain',
- 'interval' : 'text/plain',
- 'laj' : 'text/plain',
- 'lav' : 'text/plain',
- 'maf' : 'text/plain',
- 'memexml' : 'application/xml',
- 'pileup' : 'text/plain',
- 'qualsolid' : 'text/plain',
- 'qualsolexa' : 'text/plain',
- 'qual454' : 'text/plain',
- 'sam' : 'text/plain',
- 'scf' : 'application/octet-stream',
- 'sff' : 'application/octet-stream',
- 'tabular' : 'text/plain',
- 'taxonomy' : 'text/plain',
- 'txt' : 'text/plain',
- 'wig' : 'text/plain',
- 'xml' : 'application/xml',
- }
- # super supertype fix for input steps in workflows.
- if 'data' not in self.datatypes_by_extension:
- self.datatypes_by_extension['data'] = data.Data()
- self.mimetypes_by_extension['data'] = 'application/octet-stream'
- # Default values - the order in which we attempt to determine data types is critical
- # because some formats are much more flexibly defined than others.
- if len(self.sniff_order) < 1:
- self.sniff_order = [
- binary.Bam(),
- binary.Sff(),
- xml.GenericXml(),
- sequence.Maf(),
- sequence.Lav(),
- sequence.csFasta(),
- qualityscore.QualityScoreSOLiD(),
- qualityscore.QualityScore454(),
- sequence.Fasta(),
- sequence.Fastq(),
- interval.Wiggle(),
- images.Html(),
- sequence.Axt(),
- interval.Bed(),
- interval.CustomTrack(),
- interval.Gtf(),
- interval.Gff(),
- interval.Gff3(),
- tabular.Pileup(),
- interval.Interval(),
- tabular.Sam(),
- tabular.Eland()
- ]
- def append_to_sniff_order():
- # Just in case any supported data types are not included in the config's sniff_order section.
- for ext in self.datatypes_by_extension:
- datatype = self.datatypes_by_extension[ext]
- included = False
- for atype in self.sniff_order:
- if isinstance(atype, datatype.__class__):
- included = True
- break
- if not included:
- self.sniff_order.append(datatype)
- append_to_sniff_order()
+ # Processing the new sniffer elem is now complete, so make sure the element defining it is loaded if necessary.
+ sniffer_class = elem.get( 'type', None )
+ if sniffer_class is not None:
+ if sniffer_class not in sniffer_elem_classes:
+ self.sniffer_elems.append( elem )
def get_datatype_class_by_name( self, name ):
"""
@@ -414,25 +406,28 @@
#
#return datatype
- def get_available_tracks(self):
+ def get_available_tracks( self ):
return self.available_tracks
- def get_mimetype_by_extension(self, ext, default = 'application/octet-stream' ):
+
+ def get_mimetype_by_extension( self, ext, default='application/octet-stream' ):
"""Returns a mimetype based on an extension"""
try:
- mimetype = self.mimetypes_by_extension[ext]
+ mimetype = self.mimetypes_by_extension[ ext ]
except KeyError:
#datatype was never declared
mimetype = default
- self.log.warning('unknown mimetype in data factory %s' % ext)
+ self.log.warning( 'unknown mimetype in data factory %s' % str( ext ) )
return mimetype
- def get_datatype_by_extension(self, ext ):
+
+ def get_datatype_by_extension( self, ext ):
"""Returns a datatype based on an extension"""
try:
- builder = self.datatypes_by_extension[ext]
+ builder = self.datatypes_by_extension[ ext ]
except KeyError:
builder = data.Text()
return builder
- def change_datatype(self, data, ext):
+
+ def change_datatype( self, data, ext ):
data.extension = ext
# call init_meta and copy metadata from itself. The datatype
# being converted *to* will handle any metadata copying and
@@ -441,13 +436,15 @@
data.set_size()
data.init_meta( copy_from=data )
return data
- def old_change_datatype(self, data, ext):
+
+ def old_change_datatype( self, data, ext ):
"""Creates and returns a new datatype based on an existing data and an extension"""
- newdata = factory(ext)(id=data.id)
+ newdata = factory( ext )( id=data.id )
for key, value in data.__dict__.items():
- setattr(newdata, key, value)
+ setattr( newdata, key, value )
newdata.ext = ext
return newdata
+
def load_datatype_converters( self, toolbox, installed_repository_dict=None, deactivate=False ):
"""
If deactivate is False, add datatype converters from self.converters or self.proprietary_converters
@@ -461,9 +458,9 @@
# Load converters defined by local datatypes_conf.xml.
converters = self.converters
for elem in converters:
- tool_config = elem[0]
- source_datatype = elem[1]
- target_datatype = elem[2]
+ tool_config = elem[ 0 ]
+ source_datatype = elem[ 1 ]
+ target_datatype = elem[ 2 ]
if installed_repository_dict:
converter_path = installed_repository_dict[ 'converter_path' ]
else:
@@ -504,6 +501,7 @@
self.log.exception( "Error deactivating converter from (%s): %s" % ( converter_path, str( e ) ) )
else:
self.log.exception( "Error loading converter (%s): %s" % ( converter_path, str( e ) ) )
+
def load_display_applications( self, installed_repository_dict=None, deactivate=False ):
"""
If deactivate is False, add display applications from self.display_app_containers or
@@ -578,6 +576,7 @@
if current_app is None and isinstance( d_type1, type( d_type2 ) ):
self.log.debug( "Adding inherited display application '%s' to datatype '%s'" % ( display_app.id, extension ) )
d_type1.add_display_application( display_app )
+
def load_external_metadata_tool( self, toolbox ):
"""Adds a tool which is used to set external metadata"""
# We need to be able to add a job to the queue to set metadata. The queue will currently only accept jobs with an associated
@@ -602,25 +601,128 @@
toolbox.tools_by_id[ set_meta_tool.id ] = set_meta_tool
self.set_external_metadata_tool = set_meta_tool
self.log.debug( "Loaded external metadata tool: %s", self.set_external_metadata_tool.id )
- def get_converters_by_datatype(self, ext):
+
+ def set_default_values( self ):
+ # Default values.
+ if not self.datatypes_by_extension:
+ self.datatypes_by_extension = {
+ 'ab1' : binary.Ab1(),
+ 'axt' : sequence.Axt(),
+ 'bam' : binary.Bam(),
+ 'bed' : interval.Bed(),
+ 'coverage' : coverage.LastzCoverage(),
+ 'customtrack' : interval.CustomTrack(),
+ 'csfasta' : sequence.csFasta(),
+ 'fasta' : sequence.Fasta(),
+ 'eland' : tabular.Eland(),
+ 'fastq' : sequence.Fastq(),
+ 'fastqsanger' : sequence.FastqSanger(),
+ 'gtf' : interval.Gtf(),
+ 'gff' : interval.Gff(),
+ 'gff3' : interval.Gff3(),
+ 'genetrack' : tracks.GeneTrack(),
+ 'interval' : interval.Interval(),
+ 'laj' : images.Laj(),
+ 'lav' : sequence.Lav(),
+ 'maf' : sequence.Maf(),
+ 'pileup' : tabular.Pileup(),
+ 'qualsolid' : qualityscore.QualityScoreSOLiD(),
+ 'qualsolexa' : qualityscore.QualityScoreSolexa(),
+ 'qual454' : qualityscore.QualityScore454(),
+ 'sam' : tabular.Sam(),
+ 'scf' : binary.Scf(),
+ 'sff' : binary.Sff(),
+ 'tabular' : tabular.Tabular(),
+ 'taxonomy' : tabular.Taxonomy(),
+ 'txt' : data.Text(),
+ 'wig' : interval.Wiggle(),
+ 'xml' : xml.GenericXml(),
+ }
+ self.mimetypes_by_extension = {
+ 'ab1' : 'application/octet-stream',
+ 'axt' : 'text/plain',
+ 'bam' : 'application/octet-stream',
+ 'bed' : 'text/plain',
+ 'customtrack' : 'text/plain',
+ 'csfasta' : 'text/plain',
+ 'eland' : 'application/octet-stream',
+ 'fasta' : 'text/plain',
+ 'fastq' : 'text/plain',
+ 'fastqsanger' : 'text/plain',
+ 'gtf' : 'text/plain',
+ 'gff' : 'text/plain',
+ 'gff3' : 'text/plain',
+ 'interval' : 'text/plain',
+ 'laj' : 'text/plain',
+ 'lav' : 'text/plain',
+ 'maf' : 'text/plain',
+ 'memexml' : 'application/xml',
+ 'pileup' : 'text/plain',
+ 'qualsolid' : 'text/plain',
+ 'qualsolexa' : 'text/plain',
+ 'qual454' : 'text/plain',
+ 'sam' : 'text/plain',
+ 'scf' : 'application/octet-stream',
+ 'sff' : 'application/octet-stream',
+ 'tabular' : 'text/plain',
+ 'taxonomy' : 'text/plain',
+ 'txt' : 'text/plain',
+ 'wig' : 'text/plain',
+ 'xml' : 'application/xml',
+ }
+ # super supertype fix for input steps in workflows.
+ if 'data' not in self.datatypes_by_extension:
+ self.datatypes_by_extension[ 'data' ] = data.Data()
+ self.mimetypes_by_extension[ 'data' ] = 'application/octet-stream'
+ # Default values - the order in which we attempt to determine data types is critical
+ # because some formats are much more flexibly defined than others.
+ if len( self.sniff_order ) < 1:
+ self.sniff_order = [
+ binary.Bam(),
+ binary.Sff(),
+ xml.GenericXml(),
+ sequence.Maf(),
+ sequence.Lav(),
+ sequence.csFasta(),
+ qualityscore.QualityScoreSOLiD(),
+ qualityscore.QualityScore454(),
+ sequence.Fasta(),
+ sequence.Fastq(),
+ interval.Wiggle(),
+ images.Html(),
+ sequence.Axt(),
+ interval.Bed(),
+ interval.CustomTrack(),
+ interval.Gtf(),
+ interval.Gff(),
+ interval.Gff3(),
+ tabular.Pileup(),
+ interval.Interval(),
+ tabular.Sam(),
+ tabular.Eland()
+ ]
+
+ def get_converters_by_datatype( self, ext ):
"""Returns available converters by source type"""
converters = odict()
- source_datatype = type(self.get_datatype_by_extension(ext))
+ source_datatype = type( self.get_datatype_by_extension( ext ) )
for ext2, dict in self.datatype_converters.items():
- converter_datatype = type(self.get_datatype_by_extension(ext2))
- if issubclass(source_datatype, converter_datatype):
- converters.update(dict)
+ converter_datatype = type( self.get_datatype_by_extension( ext2 ) )
+ if issubclass( source_datatype, converter_datatype ):
+ converters.update( dict )
#Ensure ext-level converters are present
if ext in self.datatype_converters.keys():
- converters.update(self.datatype_converters[ext])
+ converters.update( self.datatype_converters[ ext ] )
return converters
- def get_converter_by_target_type(self, source_ext, target_ext):
+
+ def get_converter_by_target_type( self, source_ext, target_ext ):
"""Returns a converter based on source and target datatypes"""
- converters = self.get_converters_by_datatype(source_ext)
+ converters = self.get_converters_by_datatype( source_ext )
if target_ext in converters.keys():
- return converters[target_ext]
+ return converters[ target_ext ]
return None
- def find_conversion_destination_for_dataset_by_extensions( self, dataset, accepted_formats, converter_safe = True ):
+
+ def find_conversion_destination_for_dataset_by_extensions( self, dataset, accepted_formats, converter_safe=True ):
"""Returns ( target_ext, existing converted dataset )"""
for convert_ext in self.get_converters_by_datatype( dataset.ext ):
if self.get_datatype_by_extension( convert_ext ).matches_any( accepted_formats ):
@@ -633,8 +735,10 @@
ret_data = None
return ( convert_ext, ret_data )
return ( None, None )
+
def get_composite_extensions( self ):
return [ ext for ( ext, d_type ) in self.datatypes_by_extension.iteritems() if d_type.composite_type is not None ]
+
def get_upload_metadata_params( self, context, group, tool ):
"""Returns dict of case value:inputs for metadata conditional for upload tool"""
rval = {}
@@ -650,16 +754,17 @@
if 'auto' not in rval and 'txt' in rval: #need to manually add 'auto' datatype
rval[ 'auto' ] = rval[ 'txt' ]
return rval
+
@property
def integrated_datatypes_configs( self ):
if self.xml_filename and os.path.isfile( self.xml_filename ):
return self.xml_filename
self.to_xml_file()
return self.xml_filename
+
def to_xml_file( self ):
if self.xml_filename is not None:
- # If persisted previously, attempt to remove
- # the temporary file in which we were written.
+ # If persisted previously, attempt to remove the temporary file in which we were written.
try:
os.unlink( self.xml_filename )
except:
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh
--- a/lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh
+++ b/lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh
@@ -1,6 +1,9 @@
export GALAXY_SLOTS_CONFIGURED="1"
-if [ -n "$SLURM_JOB_NUM_NODES" ]; then
- GALAXY_SLOTS="$SLURM_JOB_NUM_NODES"
+if [ -n "$SLURM_NTASKS" ]; then
+ # May want to multiply this by ${SLURM_CPUS_PER_TASK:-1}.
+ # SLURM_NTASKS is total tasks over all nodes so this is
+ # shouldn't be used for multi-node requests.
+ GALAXY_SLOTS="$SLURM_NTASKS"
elif [ -n "$NSLOTS" ]; then
GALAXY_SLOTS="$NSLOTS"
elif [ -f "$PBS_NODEFILE" ]; then
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/galaxy/tools/parameters/grouping.py
--- a/lib/galaxy/tools/parameters/grouping.py
+++ b/lib/galaxy/tools/parameters/grouping.py
@@ -493,7 +493,7 @@
rval = {}
# Get the default value for the 'test element' and use it
# to determine the current case
- test_value = self.test_param.get_initial_value( trans, context, history=None )
+ test_value = self.test_param.get_initial_value( trans, context, history=history )
current_case = self.get_current_case( test_value, trans )
# Store the current case in a special value
rval['__current_case__'] = current_case
@@ -502,7 +502,7 @@
# Fill in state for selected case
child_context = ExpressionContext( rval, context )
for child_input in self.cases[current_case].inputs.itervalues():
- rval[ child_input.name ] = child_input.get_initial_value( trans, child_context, history=None )
+ rval[ child_input.name ] = child_input.get_initial_value( trans, child_context, history=history )
return rval
class ConditionalWhen( object ):
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
--- a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
+++ b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
@@ -54,7 +54,7 @@
common_install_util.activate_repository( trans, repository )
except Exception, e:
error_message = "Error activating repository %s: %s" % ( repository.name, str( e ) )
- log.debug( error_message )
+ log.exception( error_message )
message = '%s.<br/>You may be able to resolve this by uninstalling and then reinstalling the repository. Click <a href="%s">here</a> to uninstall the repository.' \
% ( error_message, web.url_for( controller='admin_toolshed', action='deactivate_or_uninstall_repository', id=trans.security.encode_id( repository.id ) ) )
status = 'error'
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/galaxy/webapps/galaxy/controllers/history.py
--- a/lib/galaxy/webapps/galaxy/controllers/history.py
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py
@@ -17,10 +17,12 @@
log = logging.getLogger( __name__ )
+
class NameColumn( grids.TextColumn ):
def get_value( self, trans, grid, history ):
return history.get_display_name()
+
class HistoryListGrid( grids.Grid ):
# Custom column types
@@ -303,32 +305,33 @@
if history.users_shared_with:
message_parts.append( "History (%s) has been shared with others, unshare it before deleting it. " % history.name )
status = ERROR
- elif not history.deleted:
- # We'll not eliminate any DefaultHistoryPermissions in case we undelete the history later
- history.deleted = True
- # If deleting the current history, make a new current.
- if history == trans.get_history():
- deleted_current = True
- trans.log_event( "History (%s) marked as deleted" % history.name )
- n_deleted += 1
- if purge and trans.app.config.allow_user_dataset_purge:
- for hda in history.datasets:
- if trans.user:
- trans.user.total_disk_usage -= hda.quota_amount( trans.user )
- hda.purged = True
- trans.sa_session.add( hda )
- trans.log_event( "HDA id %s has been purged" % hda.id )
- trans.sa_session.flush()
- if hda.dataset.user_can_purge:
- try:
- hda.dataset.full_delete()
- trans.log_event( "Dataset id %s has been purged upon the the purge of HDA id %s" % ( hda.dataset.id, hda.id ) )
- trans.sa_session.add( hda.dataset )
- except:
- log.exception( 'Unable to purge dataset (%s) on purge of hda (%s):' % ( hda.dataset.id, hda.id ) )
- history.purged = True
- self.sa_session.add( history )
- self.sa_session.flush()
+ else:
+ if not history.deleted:
+ # We'll not eliminate any DefaultHistoryPermissions in case we undelete the history later
+ history.deleted = True
+ # If deleting the current history, make a new current.
+ if history == trans.get_history():
+ deleted_current = True
+ trans.log_event( "History (%s) marked as deleted" % history.name )
+ n_deleted += 1
+ if purge and trans.app.config.allow_user_dataset_purge:
+ for hda in history.datasets:
+ if trans.user:
+ trans.user.total_disk_usage -= hda.quota_amount( trans.user )
+ hda.purged = True
+ trans.sa_session.add( hda )
+ trans.log_event( "HDA id %s has been purged" % hda.id )
+ trans.sa_session.flush()
+ if hda.dataset.user_can_purge:
+ try:
+ hda.dataset.full_delete()
+ trans.log_event( "Dataset id %s has been purged upon the the purge of HDA id %s" % ( hda.dataset.id, hda.id ) )
+ trans.sa_session.add( hda.dataset )
+ except:
+ log.exception( 'Unable to purge dataset (%s) on purge of hda (%s):' % ( hda.dataset.id, hda.id ) )
+ history.purged = True
+ self.sa_session.add( history )
+ self.sa_session.flush()
trans.sa_session.flush()
if n_deleted:
part = "Deleted %d %s" % ( n_deleted, iff( n_deleted != 1, "histories", "history" ) )
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py
--- a/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py
+++ b/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py
@@ -2,6 +2,7 @@
from galaxy import model, util
from galaxy.web.framework.helpers import iff, grids
+from galaxy.web import url_for
from galaxy.model.orm import or_
import tool_shed.util.shed_util_common as suc
from tool_shed.util import tool_dependency_util
@@ -13,42 +14,42 @@
deprecated_tip_str = 'class="icon-button" title="This repository is deprecated in the Tool Shed"'
else:
deprecated_tip_str = ''
- return '<img src="/static/images/icon_error_sml.gif" %s/>' % deprecated_tip_str
+ return '<img src="%s/images/icon_error_sml.gif" %s/>' % ( url_for( '/static' ), deprecated_tip_str )
def generate_includes_workflows_img_str( include_mouse_over=False ):
if include_mouse_over:
deprecated_tip_str = 'class="icon-button" title="This repository contains exported workflows"'
else:
deprecated_tip_str = ''
- return '<img src="/static/images/fugue/gear.png" %s/>' % deprecated_tip_str
+ return '<img src="%s/images/fugue/gear.png" %s/>' % ( url_for( '/static' ), deprecated_tip_str )
def generate_latest_revision_img_str( include_mouse_over=False ):
if include_mouse_over:
latest_revision_tip_str = 'class="icon-button" title="This is the latest installable revision of this repository"'
else:
latest_revision_tip_str = ''
- return '<img src="/static/june_2007_style/blue/ok_small.png" %s/>' % latest_revision_tip_str
+ return '<img src="%s/june_2007_style/blue/ok_small.png" %s/>' % ( url_for( '/static' ), latest_revision_tip_str )
def generate_revision_updates_img_str( include_mouse_over=False ):
if include_mouse_over:
revision_updates_tip_str = 'class="icon-button" title="Updates are available in the Tool Shed for this revision"'
else:
revision_updates_tip_str = ''
- return '<img src="/static/images/icon_warning_sml.gif" %s/>' % revision_updates_tip_str
+ return '<img src="%s/images/icon_warning_sml.gif" %s/>' % ( url_for( '/static' ), revision_updates_tip_str )
def generate_revision_upgrades_img_str( include_mouse_over=False ):
if include_mouse_over:
revision_upgrades_tip_str = 'class="icon-button" title="A newer installable revision is available for this repository"'
else:
revision_upgrades_tip_str = ''
- return '<img src="/static/images/up.gif" %s/>' % revision_upgrades_tip_str
+ return '<img src="%s/images/up.gif" %s/>' % ( url_for( '/static' ), revision_upgrades_tip_str )
def generate_unknown_img_str( include_mouse_over=False ):
if include_mouse_over:
unknown_tip_str = 'class="icon-button" title="Unable to get information from the Tool Shed"'
else:
unknown_tip_str = ''
- return '<img src="/static/june_2007_style/blue/question-octagon-frame.png" %s/>' % unknown_tip_str
+ return '<img src="%s/june_2007_style/blue/question-octagon-frame.png" %s/>' % ( url_for( '/static' ), unknown_tip_str )
class InstalledRepositoryGrid( grids.Grid ):
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
@@ -337,6 +337,18 @@
print '\nSkipping installation of tool dependency', package_name, 'version', package_version, \
'since it is installed in', install_dir, '\n'
can_install_tool_dependency = False
+ # This tool dependency was previously installed, but the record was missing from the database due to some
+ # activity outside of the control of the tool shed. Since a new record was created for it and we don't know
+ # the state of the files on disk, we will set it to an error state. If we are running functional tests, the
+ # state will be set to Installed, because previously compiled tool dependencies are not deleted by default.
+ if app.config.running_functional_tests:
+ tool_dependency.status = app.model.ToolDependency.installation_status.INSTALLED
+ else:
+ error_message = 'The installation directory for this tool dependency had contents, but the database had no record. '
+ error_message += 'The installation log may show this tool dependency to be correctly installed, but due to the '
+ error_message += 'missing database record, it is automatically set to Error.'
+ tool_dependency.status = app.model.ToolDependency.installation_status.ERROR
+ tool_dependency.error_message = error_message
else:
error_message = '\nInstallation path %s for tool dependency %s version %s exists, but the expected file %s' % \
( install_dir, package_name, package_version, fabric_util.INSTALLATION_LOG )
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -1016,9 +1016,9 @@
}
this.zo_link = $("<a/>").attr("id", "zoom-out").attr("title", "Zoom out").tooltip( {placement: 'bottom'} )
- .click(function() { view.zoom_out(); view.request_redraw(); }).appendTo(this.nav_controls);
+ .click(function() { view.zoom_out(); }).appendTo(this.nav_controls);
this.zi_link = $("<a/>").attr("id", "zoom-in").attr("title", "Zoom in").tooltip( {placement: 'bottom'} )
- .click(function() { view.zoom_in(); view.request_redraw(); }).appendTo(this.nav_controls);
+ .click(function() { view.zoom_in(); }).appendTo(this.nav_controls);
// Get initial set of chroms.
this.load_chroms_deferred = this.load_chroms({low: 0});
@@ -1097,14 +1097,9 @@
// Dragging in the top label track allows selecting a region
// to zoom in
this.top_labeltrack.bind( "dragstart", function( e, d ) {
- return $("<div />").css( {
- "height": view.browser_content_div.height() + view.top_labeltrack.height() + view.nav_labeltrack.height() + 1,
- "top": "0px",
- "position": "absolute",
- "background-color": "#ccf",
- "opacity": 0.5,
- "z-index": 1000
- } ).appendTo( $(this) );
+ return $("<div/>").addClass('zoom-area').css(
+ "height", view.browser_content_div.height() + view.top_labeltrack.height() + view.nav_labeltrack.height() + 1
+ ).appendTo( $(this) );
}).bind( "drag", function( e, d ) {
$( d.proxy ).css({ left: Math.min( e.pageX, d.startX ) - view.container.offset().left, width: Math.abs( e.pageX - d.startX ) });
var min = Math.min(e.pageX, d.startX ) - view.container.offset().left,
@@ -1549,6 +1544,7 @@
}
this.low = Math.round(cur_center - new_half);
this.high = Math.round(cur_center + new_half);
+
this.changed();
this.request_redraw();
},
@@ -3079,7 +3075,7 @@
// Step (c) for (re)moving tiles when clear_after is false.
if (!clear_after) { this.tiles_div.children(".remove").removeClass("remove").remove(); }
- // Use interval to check if tiles have been drawn. When all tiles are drawn, call post-draw actions.
+ // When all tiles are drawn, call post-draw actions.
var track = this;
$.when.apply($, tile_promises).then(function() {
// Step (c) for (re)moving tiles when clear_after is true:
@@ -3373,7 +3369,7 @@
if (this.left_offset) {
left -= this.left_offset;
}
- tile_element.css({ position: 'absolute', top: 0, left: left });
+ tile_element.css('left', left);
if ( tile_element.hasClass("remove") ) {
// Step (b) for (re)moving tiles. See _draw() function for description of algorithm
@@ -3528,13 +3524,12 @@
tickDistance = Math.floor( Math.pow( 10, Math.floor( Math.log( range ) / Math.log( 10 ) ) ) ),
position = Math.floor( view.low / tickDistance ) * tickDistance,
width = this.view.container.width(),
- new_div = $("<div style='position: relative; height: 1.3em;'></div>");
+ new_div = $("<div/>").addClass('label-container');
while ( position < view.high ) {
var screenPosition = ( position - view.low ) / range * width;
- new_div.append( $("<div class='label'>" + commatize( position ) + "</div>").css( {
- position: "absolute",
+ new_div.append( $("<div/>").addClass('label').text(commatize( position )).css( {
// Reduce by one to account for border
- left: screenPosition - 1
+ left: screenPosition
}));
position += tickDistance;
}
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1540,7 +1540,7 @@
.dataset [class$=messagesmall]{margin:6px 10px 2px 8px;font-size:90%}
.dataset .help-text{font-weight:normal;font-style:italic;font-size:90%;color:#555}
.dataset .dataset-title-bar{cursor:pointer;padding:6px 10px 6px 8px}
-.dataset .dataset-title-bar .dataset-title{display:inline;font-weight:bold;text-decoration:underline;word-break:normal;line-height:16px}
+.dataset .dataset-title-bar .dataset-title{display:inline;font-weight:bold;text-decoration:underline;word-wrap:break-word;word-break:break-all;line-height:16px}
.dataset .dataset-primary-actions{float:right;margin:6px 10px 0}.dataset .dataset-primary-actions .icon-btn{margin-left:2px}
.dataset .dataset-body{display:none;background-color:rgba(255,255,255,0.30000000000000004);padding:6px 10px 6px 8px}.dataset .dataset-body [class$=messagesmall]{margin:0px 0px 8px 0px}
.dataset .dataset-body label{margin:0px;padding:0px;font-weight:normal}
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f static/style/blue/trackster.css
--- a/static/style/blue/trackster.css
+++ b/static/style/blue/trackster.css
@@ -26,14 +26,15 @@
.track-name{float:left;margin-top:2px}
.tiles{background:url('../images/tracks/diag_bg.gif');position:relative;overflow:hidden}
.overlay{position:absolute;left:0;top:0}
-.track-tile{background:white}.track-tile canvas{position:relative;z-index:1}
+.track-tile{position:absolute;background:white}.track-tile canvas{position:relative;z-index:1}
.tile-message{border-bottom:solid 1px red;text-align:center;color:red;background-color:white}
.track{border-bottom:1px solid #bbb}.track.error{background-color:#ECB4AF;background-image:none}
.track.nodata .track-content{background-color:white;background-image:none}
.track.pending .track-content{background-color:white;background-image:none}
.track-content{text-align:center;position:relative;min-height:20px;padding:0px 0px 1px 0px}
.loading{min-height:100px}
-.label-track{font-size:10px;border:none;padding:0;margin:0;height:1.5em;overflow:hidden}.label-track .label{border-left:solid #999 1px;padding:1px;padding-bottom:2px;display:inline-block}
+.label-track{font-size:10px;border:none;padding:0;margin:0;height:1.5em;overflow:hidden}.label-track .label-container{position:relative;height:1.3em}
+.label-track .label{position:absolute;border-left:solid #999 1px;padding:1px;padding-bottom:2px;display:inline-block}
.label-track .track-content{border:none;background:white}
.reference-track{border:none;margin:0;padding:0;line-height:1}.reference-track .track-content{min-height:0px}
.right-float{float:right;margin-left:5px}
@@ -75,3 +76,4 @@
.icon.more-across{background:url('../images/fugue/arrow-transition-bw.png') no-repeat 0 0}
.feature-popup{position:absolute;z-index:2;padding:5px;font-size:10px;filter:alpha(opacity=80);background-repeat:no-repeat;background-image:url(../images/tipsy.gif);background-position:top center}
.feature-popup-inner{padding:5px 8px 4px 8px;background-color:black;color:white}
+.zoom-area{position:absolute;top:0px;background-color:#ccf;opacity:0.5;z-index:2}
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f static/style/src/less/history.less
--- a/static/style/src/less/history.less
+++ b/static/style/src/less/history.less
@@ -257,7 +257,8 @@
display: inline;
font-weight: bold;
text-decoration: underline;
- word-break: normal;
+ word-wrap: break-word;
+ word-break: break-all;
line-height: 16px;
}
}
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f static/style/src/less/trackster.less
--- a/static/style/src/less/trackster.less
+++ b/static/style/src/less/trackster.less
@@ -227,6 +227,7 @@
}
.track-tile {
+ position: absolute;
background: white;
canvas {
@@ -285,7 +286,13 @@
height: 1.5em;
overflow: hidden;
+ .label-container {
+ position: relative;
+ height: 1.3em;
+ }
+
.label {
+ position: absolute;
border-left: solid #999 1px;
padding: 1px;
padding-bottom: 2px;
@@ -509,3 +516,11 @@
background-color: black;
color: white;
}
+
+.zoom-area {
+ position: absolute;
+ top: 0px;
+ background-color: #ccf;
+ opacity: 0.5;
+ z-index: @overlay-index;
+}
\ No newline at end of file
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f templates/webapps/galaxy/admin/index.mako
--- a/templates/webapps/galaxy/admin/index.mako
+++ b/templates/webapps/galaxy/admin/index.mako
@@ -52,7 +52,7 @@
<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>
- <div class="toolTitle"><a href="${h.url_for( controller='userskeys', action='all_users' )}" target="galaxy_main">Manage users API keys</a></div>
+ <div class="toolTitle"><a href="${h.url_for( controller='userskeys', action='all_users' )}" target="galaxy_main">Manage users API keys</a></div>
%if trans.app.config.allow_user_impersonation:
<div class="toolTitle"><a href="${h.url_for( controller='admin', action='impersonate' )}" target="galaxy_main">Impersonate a user</a></div>
%endif
@@ -62,7 +62,9 @@
<div class="toolSectionTitle">Data</div><div class="toolSectionBody"><div class="toolSectionBg">
- <div class="toolTitle"><a href="${h.url_for( controller='admin', action='quotas' )}" target="galaxy_main">Manage quotas</a></div>
+ %if trans.app.config.enable_quotas:
+ <div class="toolTitle"><a href="${h.url_for( controller='admin', action='quotas' )}" target="galaxy_main">Manage quotas</a></div>
+ %endif
<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>
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f templates/webapps/galaxy/galaxy.masthead.mako
--- a/templates/webapps/galaxy/galaxy.masthead.mako
+++ b/templates/webapps/galaxy/galaxy.masthead.mako
@@ -72,38 +72,40 @@
$('<link href="' + galaxy_config.root + 'static/style/galaxy.frame.masthead.css" rel="stylesheet">').appendTo('head');
## load galaxy js-modules
- require(['galaxy.masthead', 'galaxy.menu', 'galaxy.modal', 'galaxy.frame', 'galaxy.upload'],
- function(mod_masthead, mod_menu, mod_modal, mod_frame, mod_upload)
- {
- ## check if masthead is available
- if (Galaxy.masthead)
- return;
+ $(function() {
+ require(['galaxy.masthead', 'galaxy.menu', 'galaxy.modal', 'galaxy.frame', 'galaxy.upload'],
+ function(mod_masthead, mod_menu, mod_modal, mod_frame, mod_upload)
+ {
+ ## check if masthead is available
+ if (Galaxy.masthead)
+ return;
- ## get configuration
- var masthead_config = ${ h.to_json_string( masthead_config ) };
+ ## get configuration
+ var masthead_config = ${ h.to_json_string( masthead_config ) };
- ## set up the quota meter (And fetch the current user data from trans)
- Galaxy.currUser = new User(masthead_config.user.json);
+ ## set up the quota meter (And fetch the current user data from trans)
+ Galaxy.currUser = new User(masthead_config.user.json);
- ## load global galaxy objects
- Galaxy.masthead = new mod_masthead.GalaxyMasthead(masthead_config);
- Galaxy.modal = new mod_modal.GalaxyModal();
- Galaxy.frame = new mod_frame.GalaxyFrame();
+ ## load global galaxy objects
+ Galaxy.masthead = new mod_masthead.GalaxyMasthead(masthead_config);
+ Galaxy.modal = new mod_modal.GalaxyModal();
+ Galaxy.frame = new mod_frame.GalaxyFrame();
- ## construct default menu options
- Galaxy.menu = new mod_menu.GalaxyMenu({
- masthead: Galaxy.masthead,
- config: masthead_config
+ ## construct default menu options
+ Galaxy.menu = new mod_menu.GalaxyMenu({
+ masthead: Galaxy.masthead,
+ config: masthead_config
+ });
+
+ ## add upload plugin
+ ##Galaxy.upload = new mod_upload.GalaxyUpload();
+
+ ## add quota meter to masthead
+ Galaxy.quotaMeter = new UserQuotaMeter({
+ model : Galaxy.currUser,
+ el : $(Galaxy.masthead.el).find('.quota-meter-container')
+ }).render();
});
-
- ## add upload plugin
- ##Galaxy.upload = new mod_upload.GalaxyUpload();
-
- ## add quota meter to masthead
- Galaxy.quotaMeter = new UserQuotaMeter({
- model : Galaxy.currUser,
- el : $(Galaxy.masthead.el).find('.quota-meter-container')
- }).render();
});
</script></%def>
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f test-data/1.bam
Binary file test-data/1.bam has changed
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f test-data/3unsorted.bam
Binary file test-data/3unsorted.bam has changed
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f test/base/twilltestcase.py
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -224,9 +224,9 @@
"""Pasted data in the upload utility"""
self.visit_page( "tool_runner/index?tool_id=upload1" )
try:
- tc.fv( "1", "file_type", ftype )
- tc.fv( "1", "dbkey", dbkey )
- tc.fv( "1", "url_paste", url_paste )
+ self.refresh_form( "file_type", ftype ) #Refresh, to support composite files
+ tc.fv( "tool_form", "dbkey", dbkey )
+ tc.fv( "tool_form", "url_paste", url_paste )
tc.submit( "runtool_btn" )
self.home()
except Exception, e:
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f test/install_and_test_tool_shed_repositories/base/twilltestcase.py
--- a/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
+++ b/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
@@ -163,6 +163,8 @@
url = '/admin_toolshed/uninstall_tool_dependencies?repository_id=%s&inst_td_ids=%s&operation=uninstall' % \
( encoded_repository_id, tool_dependency_ids )
self.visit_url( url )
- tc.fv( 'uninstall_tool_dependencies', 'tool_dependency_ids', tool_dependency_ids )
- tc.submit( 'uninstall_tool_dependencies_button' )
+ html = self.last_page()
+ if 'uninstall_tool_dependencies' in html:
+ tc.fv( 'uninstall_tool_dependencies', 'tool_dependency_ids', tool_dependency_ids )
+ tc.submit( 'uninstall_tool_dependencies_button' )
\ No newline at end of file
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f test/install_and_test_tool_shed_repositories/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/functional_tests.py
@@ -1037,16 +1037,20 @@
# If a tool dependency fails to install correctly, this should be considered an installation error,
# and functional tests should be skipped, since the tool dependency needs to be correctly installed
# for the test to be considered reliable.
- log.error( 'One or more tool dependencies of this repository are marked as missing.' )
+ log.error( 'One or more dependencies of this repository are marked as missing.' )
log.error( 'Updating repository and skipping functional tests.' )
# In keeping with the standard display layout, add the error message to the dict for each tool individually.
for dependency in failed_tool_dependencies:
+ log.error( 'Missing tool dependency %s of type %s version %s: %s' % \
+ ( str( dependency.name ), str( dependency.type ), str( dependency.version ), str( dependency.error_message ) ) )
test_result = dict( type=dependency.type,
name=dependency.name,
version=dependency.version,
error_message=dependency.error_message )
repository_status[ 'installation_errors' ][ 'tool_dependencies' ].append( test_result )
for dependency in repository.repository_dependencies_with_installation_errors:
+ log.error( 'Missing repository dependency %s changeset revision %s owned by %s: %s' % \
+ ( str( dependency.name ), str( dependency.changeset_revision ), str( dependency.owner ), str( dependency.error_message ) ) )
test_result = dict( tool_shed=dependency.tool_shed,
name=dependency.name,
owner=dependency.owner,
diff -r a1e670fa596f733defef37746ea2e7aae31b6c87 -r da0672e2518601a4b771d6a89eba0494e8f4aa0f test/tool_shed/test_data/blast/blast_datatypes.tar
Binary file test/tool_shed/test_data/blast/blast_datatypes.tar has changed
https://bitbucket.org/galaxy/galaxy-central/commits/61b52b97b47b/
Changeset: 61b52b97b47b
User: kellrott
Date: 2013-11-13 20:39:57
Summary: Merged galaxy/galaxy-central into default
Affected #: 2 files
diff -r da0672e2518601a4b771d6a89eba0494e8f4aa0f -r 61b52b97b47b0ee8ab850ac266450c41f05842ce lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -85,6 +85,13 @@
return imported_module
if root_dir and config:
+ # If handling_proprietary_datatypes is determined as True below, we'll have an elem that looks something like this:
+ # <datatype display_in_upload="true"
+ # extension="blastxml"
+ # mimetype="application/xml"
+ # proprietary_datatype_module="blast"
+ # proprietary_path="[cloned repository path]"
+ # type="galaxy.datatypes.blast:BlastXml" />
handling_proprietary_datatypes = False
# Parse datatypes_conf.xml
tree = galaxy.util.parse_xml( config )
@@ -117,9 +124,13 @@
type_extension = elem.get( 'type_extension', None )
mimetype = elem.get( 'mimetype', None )
display_in_upload = galaxy.util.string_as_bool( elem.get( 'display_in_upload', False ) )
+ # If make_subclass is True, it does not necessarily imply that we are subclassing a datatype that is contained
+ # in the distribution.
make_subclass = galaxy.util.string_as_bool( elem.get( 'subclass', False ) )
# Proprietary datatypes included in installed tool shed repositories will include two special attributes
# (proprietary_path and proprietary_datatype_module) if they depend on proprietary datatypes classes.
+ # The value of proprietary_path is the path to the cloned location of the tool shed repository's contained
+ # datatypes_conf.xml file.
proprietary_path = elem.get( 'proprietary_path', None )
proprietary_datatype_module = elem.get( 'proprietary_datatype_module', None )
if proprietary_path is not None or proprietary_datatype_module is not None and not handling_proprietary_datatypes:
@@ -182,21 +193,22 @@
except Exception, e:
full_path = os.path.join( proprietary_path, proprietary_datatype_module )
self.log.debug( "Exception importing proprietary code file %s: %s" % ( str( full_path ), str( e ) ) )
- ok = False
finally:
lock.release()
- if ok:
- if datatype_class is None:
- try:
- # The datatype class name must be contained in one of the datatype modules in the Galaxy distribution.
- fields = datatype_module.split( '.' )
- module = __import__( fields.pop( 0 ) )
- for mod in fields:
- module = getattr( module, mod )
- datatype_class = getattr( module, datatype_class_name )
- except Exception, e:
- self.log.exception( 'Error importing datatype module %s: %s' % ( str( datatype_module ), str( e ) ) )
- ok = False
+ # Either the above exception was thrown because the proprietary_datatype_module is not derived from a class
+ # in the repository, or we are loading Galaxy's datatypes. In either case we'll look in the registry.
+ if datatype_class is None:
+ try:
+ # The datatype class name must be contained in one of the datatype modules in the Galaxy distribution.
+ fields = datatype_module.split( '.' )
+ module = __import__( fields.pop( 0 ) )
+ for mod in fields:
+ module = getattr( module, mod )
+ datatype_class = getattr( module, datatype_class_name )
+ self.log.debug( 'Retrieved datatype module %s from the datatype registry.' % str( datatype_module ) )
+ except Exception, e:
+ self.log.exception( 'Error importing datatype module %s: %s' % ( str( datatype_module ), str( e ) ) )
+ ok = False
elif type_extension is not None:
try:
datatype_class = self.datatypes_by_extension[ type_extension ].__class__
diff -r da0672e2518601a4b771d6a89eba0494e8f4aa0f -r 61b52b97b47b0ee8ab850ac266450c41f05842ce lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
@@ -430,7 +430,7 @@
if dst:
dst.close()
if extract:
- if istar( file_path ) or iszip( file_path ):
+ if istar( file_path ) or ( iszip( file_path ) and not isjar( file_path ) ):
archive = CompressedFile( file_path )
extraction_path = archive.extract( install_dir )
else:
https://bitbucket.org/galaxy/galaxy-central/commits/1d7f6cd1e83e/
Changeset: 1d7f6cd1e83e
User: kellrott
Date: 2013-11-14 20:45:50
Summary: Merged galaxy/galaxy-central into default
Affected #: 28 files
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -108,7 +108,10 @@
job_id = job_wrapper.job_id
if hasattr(job_wrapper, 'task_id'):
job_id = "%s_%s" % (job_id, job_wrapper.task_id)
- return self.get_client( job_wrapper.job_destination.params, job_id )
+ params = job_wrapper.job_destination.params.copy()
+ for key, value in params.iteritems():
+ params[key] = model.User.expand_user_properties( job_wrapper.get_job().user, value )
+ return self.get_client( params, job_id )
def get_client_from_state(self, job_state):
job_destination_params = job_state.job_destination.params
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1901,20 +1901,21 @@
callback( "", input, value[input.name] )
else:
input.visit_inputs( "", value[input.name], callback )
- def handle_input( self, trans, incoming, history=None, old_errors=None ):
+ def handle_input( self, trans, incoming, history=None, old_errors=None, process_state='update', source='html' ):
"""
Process incoming parameters for this tool from the dict `incoming`,
update the tool state (or create if none existed), and either return
to the form or execute the tool (only if 'execute' was clicked and
there were no errors).
+
+ process_state can be either 'update' (to incrementally build up the state
+ over several calls - one repeat per handle for instance) or 'populate'
+ force a complete build of the state and submission all at once (like
+ from API). May want an incremental version of the API also at some point,
+ that is why this is not just called for_api.
"""
- # Get the state or create if not found
- if "tool_state" in incoming:
- encoded_state = string_to_object( incoming["tool_state"] )
- state = DefaultToolState()
- state.decode( encoded_state, self, trans.app )
- else:
- state = self.new_state( trans, history=history )
+ state, state_new = self.__fetch_state( trans, incoming, history )
+ if state_new:
# This feels a bit like a hack. It allows forcing full processing
# of inputs even when there is no state in the incoming dictionary
# by providing either 'runtool_btn' (the name of the submit button
@@ -1924,8 +1925,88 @@
if not self.display_interface:
return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
if len(incoming):
- self.update_state( trans, self.inputs_by_page[state.page], state.inputs, incoming, old_errors=old_errors or {} )
+ self.update_state( trans, self.inputs_by_page[state.page], state.inputs, incoming, old_errors=old_errors or {}, source=source )
return "tool_form.mako", dict( errors={}, tool_state=state, param_values={}, incoming={} )
+
+ errors, params = self.__check_param_values( trans, incoming, state, old_errors, process_state, history=history, source=source )
+ if self.__should_refresh_state( incoming ):
+ return self.__handle_state_refresh( trans, state, errors )
+ else:
+ # User actually clicked next or execute.
+
+ # If there were errors, we stay on the same page and display
+ # error messages
+ if errors:
+ error_message = "One or more errors were found in the input you provided. The specific errors are marked below."
+ return "tool_form.mako", dict( errors=errors, tool_state=state, incoming=incoming, error_message=error_message )
+ # If we've completed the last page we can execute the tool
+ elif state.page == self.last_page:
+ return self.__handle_tool_execute( trans, incoming, params, history )
+ # Otherwise move on to the next page
+ else:
+ return self.__handle_page_advance( trans, state, errors )
+
+ def __should_refresh_state( self, incoming ):
+ return not( 'runtool_btn' in incoming or 'URL' in incoming or 'ajax_upload' in incoming )
+
+ def __handle_tool_execute( self, trans, incoming, params, history ):
+ try:
+ rerun_remap_job_id = None
+ if 'rerun_remap_job_id' in incoming:
+ rerun_remap_job_id = trans.app.security.decode_id(incoming['rerun_remap_job_id'])
+ _, out_data = self.execute( trans, incoming=params, history=history, rerun_remap_job_id=rerun_remap_job_id )
+ except httpexceptions.HTTPFound, e:
+ #if it's a paste redirect exception, pass it up the stack
+ raise e
+ except Exception, e:
+ log.exception('Exception caught while attempting tool execution:')
+ return 'message.mako', dict( status='error', message='Error executing tool: %s' % str(e), refresh_frames=[] )
+ try:
+ assert isinstance( out_data, odict )
+ return 'tool_executed.mako', dict( out_data=out_data )
+ except:
+ if isinstance( out_data, str ):
+ message = out_data
+ else:
+ message = 'Failure executing tool (odict not returned from tool execution)'
+ return 'message.mako', dict( status='error', message=message, refresh_frames=[] )
+
+ def __handle_state_refresh( self, trans, state, errors ):
+ try:
+ self.find_fieldstorage( state.inputs )
+ except InterruptedUpload:
+ # If inputs contain a file it won't persist. Most likely this
+ # is an interrupted upload. We should probably find a more
+ # standard method of determining an incomplete POST.
+ return self.handle_interrupted( trans, state.inputs )
+ except:
+ pass
+ # Just a refresh, render the form with updated state and errors.
+ if not self.display_interface:
+ return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
+ return 'tool_form.mako', dict( errors=errors, tool_state=state )
+
+ def __handle_page_advance( self, trans, state, errors ):
+ state.page += 1
+ # Fill in the default values for the next page
+ self.fill_in_new_state( trans, self.inputs_by_page[ state.page ], state.inputs )
+ if not self.display_interface:
+ return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
+ return 'tool_form.mako', dict( errors=errors, tool_state=state )
+
+ def __fetch_state( self, trans, incoming, history ):
+ # Get the state or create if not found
+ if "tool_state" in incoming:
+ encoded_state = string_to_object( incoming["tool_state"] )
+ state = DefaultToolState()
+ state.decode( encoded_state, self, trans.app )
+ new = False
+ else:
+ state = self.new_state( trans, history=history )
+ new = True
+ return state, new
+
+ def __check_param_values( self, trans, incoming, state, old_errors, process_state, history, source ):
# Process incoming data
if not( self.check_values ):
# If `self.check_values` is false we don't do any checking or
@@ -1938,64 +2019,19 @@
else:
# Update state for all inputs on the current page taking new
# values from `incoming`.
- errors = self.update_state( trans, self.inputs_by_page[state.page], state.inputs, incoming, old_errors=old_errors or {} )
+ if process_state == "update":
+ errors = self.update_state( trans, self.inputs_by_page[state.page], state.inputs, incoming, old_errors=old_errors or {}, source=source )
+ elif process_state == "populate":
+ errors = self.populate_state( trans, self.inputs_by_page[state.page], state.inputs, incoming, history, source=source )
+ else:
+ raise Exception("Unknown process_state type %s" % process_state)
# If the tool provides a `validate_input` hook, call it.
validate_input = self.get_hook( 'validate_input' )
if validate_input:
validate_input( trans, errors, state.inputs, self.inputs_by_page[state.page] )
params = state.inputs
- # Did the user actually click next / execute or is this just
- # a refresh?
- if 'runtool_btn' in incoming or 'URL' in incoming or 'ajax_upload' in incoming:
- # If there were errors, we stay on the same page and display
- # error messages
- if errors:
- error_message = "One or more errors were found in the input you provided. The specific errors are marked below."
- return "tool_form.mako", dict( errors=errors, tool_state=state, incoming=incoming, error_message=error_message )
- # If we've completed the last page we can execute the tool
- elif state.page == self.last_page:
- try:
- rerun_remap_job_id = None
- if 'rerun_remap_job_id' in incoming:
- rerun_remap_job_id = trans.app.security.decode_id(incoming['rerun_remap_job_id'])
- _, out_data = self.execute( trans, incoming=params, history=history, rerun_remap_job_id=rerun_remap_job_id )
- except httpexceptions.HTTPFound, e:
- #if it's a paste redirect exception, pass it up the stack
- raise e
- except Exception, e:
- log.exception('Exception caught while attempting tool execution:')
- return 'message.mako', dict( status='error', message='Error executing tool: %s' % str(e), refresh_frames=[] )
- try:
- assert isinstance( out_data, odict )
- return 'tool_executed.mako', dict( out_data=out_data )
- except:
- if isinstance( out_data, str ):
- message = out_data
- else:
- message = 'Failure executing tool (odict not returned from tool execution)'
- return 'message.mako', dict( status='error', message=message, refresh_frames=[] )
- # Otherwise move on to the next page
- else:
- state.page += 1
- # Fill in the default values for the next page
- self.fill_in_new_state( trans, self.inputs_by_page[ state.page ], state.inputs )
- if not self.display_interface:
- return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
- return 'tool_form.mako', dict( errors=errors, tool_state=state )
- else:
- try:
- self.find_fieldstorage( state.inputs )
- except InterruptedUpload:
- # If inputs contain a file it won't persist. Most likely this
- # is an interrupted upload. We should probably find a more
- # standard method of determining an incomplete POST.
- return self.handle_interrupted( trans, state.inputs )
- except:
- pass
- # Just a refresh, render the form with updated state and errors.
- if not self.display_interface:
- return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
- return 'tool_form.mako', dict( errors=errors, tool_state=state )
+ return errors, params
+
def find_fieldstorage( self, x ):
if isinstance( x, FieldStorage ):
raise InterruptedUpload( None )
@@ -2033,7 +2069,141 @@
return 'message.mako', dict( status='error',
message='Your upload was interrupted. If this was uninentional, please retry it.',
refresh_frames=[], cont=None )
- def update_state( self, trans, inputs, state, incoming, prefix="", context=None,
+
+ def populate_state( self, trans, inputs, state, incoming, history, source, prefix="", context=None ):
+ errors = dict()
+ # Push this level onto the context stack
+ context = ExpressionContext( state, context )
+ for input in inputs.itervalues():
+ key = prefix + input.name
+ if isinstance( input, Repeat ):
+ group_state = state[input.name]
+ # Create list of empty errors for each previously existing state
+ group_errors = [ ]
+ any_group_errors = False
+ rep_index = 0
+ while True:
+ rep_name = "%s_%d" % ( key, rep_index )
+ if not any( [ key.startswith(rep_name) for key in incoming.keys() ] ):
+ break
+ if rep_index < input.max:
+ new_state = {}
+ new_state['__index__'] = rep_index
+ self.fill_in_new_state( trans, input.inputs, new_state, context, history=history )
+ group_state.append( new_state )
+ group_errors.append( {} )
+ rep_errors = self.populate_state( trans,
+ input.inputs,
+ new_state,
+ incoming,
+ history,
+ source,
+ prefix=rep_name + "|",
+ context=context )
+ if rep_errors:
+ any_group_errors = True
+ group_errors[rep_index].update( rep_errors )
+
+ else:
+ group_errors[-1] = { '__index__': 'Cannot add repeat (max size=%i).' % input.max }
+ any_group_errors = True
+ rep_index += 1
+ elif isinstance( input, Conditional ):
+ group_state = state[input.name]
+ group_prefix = "%s|" % ( key )
+ # Deal with the 'test' element and see if it's value changed
+ if input.value_ref and not input.value_ref_in_group:
+ # We are referencing an existent parameter, which is not
+ # part of this group
+ test_param_key = prefix + input.test_param.name
+ else:
+ test_param_key = group_prefix + input.test_param.name
+ test_param_error = None
+ test_incoming = get_incoming_value( incoming, test_param_key, None )
+
+ # Get value of test param and determine current case
+ value, test_param_error = \
+ check_param( trans, input.test_param, test_incoming, context, source=source )
+ current_case = input.get_current_case( value, trans )
+ # Current case has changed, throw away old state
+ group_state = state[input.name] = {}
+ # TODO: we should try to preserve values if we can
+ self.fill_in_new_state( trans, input.cases[current_case].inputs, group_state, context, history=history )
+ group_errors = self.populate_state( trans,
+ input.cases[current_case].inputs,
+ group_state,
+ incoming,
+ history,
+ source,
+ prefix=group_prefix,
+ context=context,
+ )
+ if test_param_error:
+ group_errors[ input.test_param.name ] = test_param_error
+ if group_errors:
+ errors[ input.name ] = group_errors
+ # Store the current case in a special value
+ group_state['__current_case__'] = current_case
+ # Store the value of the test element
+ group_state[ input.test_param.name ] = value
+ elif isinstance( input, UploadDataset ):
+ group_state = state[input.name]
+ group_errors = []
+ any_group_errors = False
+ d_type = input.get_datatype( trans, context )
+ writable_files = d_type.writable_files
+ #remove extra files
+ while len( group_state ) > len( writable_files ):
+ del group_state[-1]
+ # Update state
+ max_index = -1
+ for i, rep_state in enumerate( group_state ):
+ rep_index = rep_state['__index__']
+ max_index = max( max_index, rep_index )
+ rep_prefix = "%s_%d|" % ( key, rep_index )
+ rep_errors = self.populate_state( trans,
+ input.inputs,
+ rep_state,
+ incoming,
+ history,
+ source,
+ prefix=rep_prefix,
+ context=context)
+ if rep_errors:
+ any_group_errors = True
+ group_errors.append( rep_errors )
+ else:
+ group_errors.append( {} )
+ # Add new fileupload as needed
+ offset = 1
+ while len( writable_files ) > len( group_state ):
+ new_state = {}
+ new_state['__index__'] = max_index + offset
+ offset += 1
+ self.fill_in_new_state( trans, input.inputs, new_state, context )
+ group_state.append( new_state )
+ if any_group_errors:
+ group_errors.append( {} )
+ # Were there *any* errors for any repetition?
+ if any_group_errors:
+ errors[input.name] = group_errors
+ else:
+ if key not in incoming \
+ and "__force_update__" + key not in incoming:
+ # No new value provided, and we are only updating, so keep
+ # the old value (which should already be in the state) and
+ # preserve the old error message.
+ pass
+ else:
+ incoming_value = get_incoming_value( incoming, key, None )
+ value, error = check_param( trans, input, incoming_value, context, source=source )
+ # If a callback was provided, allow it to process the value
+ if error:
+ errors[ input.name ] = error
+ state[ input.name ] = value
+ return errors
+
+ def update_state( self, trans, inputs, state, incoming, source='html', prefix="", context=None,
update_only=False, old_errors={}, item_callback=None ):
"""
Update the tool state in `state` using the user input in `incoming`.
@@ -2091,6 +2261,7 @@
input.inputs,
rep_state,
incoming,
+ source=source,
prefix=rep_prefix,
context=context,
update_only=update_only,
@@ -2139,7 +2310,7 @@
else:
# Get value of test param and determine current case
value, test_param_error = \
- check_param( trans, input.test_param, test_incoming, context )
+ check_param( trans, input.test_param, test_incoming, context, source=source )
current_case = input.get_current_case( value, trans )
if current_case != old_current_case:
# Current case has changed, throw away old state
@@ -2156,6 +2327,7 @@
incoming,
prefix=group_prefix,
context=context,
+ source=source,
update_only=update_only,
old_errors=group_old_errors,
item_callback=item_callback )
@@ -2197,6 +2369,7 @@
incoming,
prefix=rep_prefix,
context=context,
+ source=source,
update_only=update_only,
old_errors=rep_old_errors,
item_callback=item_callback )
@@ -2229,7 +2402,7 @@
errors[ input.name ] = old_errors[ input.name ]
else:
incoming_value = get_incoming_value( incoming, key, None )
- value, error = check_param( trans, input, incoming_value, context )
+ value, error = check_param( trans, input, incoming_value, context, source=source )
# If a callback was provided, allow it to process the value
if item_callback:
old_value = state.get( input.name, None )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/actions/__init__.py
--- a/lib/galaxy/tools/actions/__init__.py
+++ b/lib/galaxy/tools/actions/__init__.py
@@ -206,12 +206,19 @@
db_datasets[ "chromInfo" ] = db_dataset
incoming[ "chromInfo" ] = db_dataset.file_name
else:
- # For custom builds, chrom info resides in converted dataset; for built-in builds, chrom info resides in tool-data/shared.
+ # -- Get chrom_info from either a custom or built-in build. --
+
chrom_info = None
if trans.user and ( 'dbkeys' in trans.user.preferences ) and ( input_dbkey in from_json_string( trans.user.preferences[ 'dbkeys' ] ) ):
# Custom build.
custom_build_dict = from_json_string( trans.user.preferences[ 'dbkeys' ] )[ input_dbkey ]
- if 'fasta' in custom_build_dict:
+ # HACK: the attempt to get chrom_info below will trigger the
+ # fasta-to-len converter if the dataset is not available or,
+ # which will in turn create a recursive loop when
+ # running the fasta-to-len tool. So, use a hack in the second
+ # condition below to avoid getting chrom_info when running the
+ # fasta-to-len converter.
+ if 'fasta' in custom_build_dict and tool.id != 'CONVERTER_fasta_to_len':
build_fasta_dataset = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( custom_build_dict[ 'fasta' ] )
chrom_info = build_fasta_dataset.get_converted_dataset( trans, 'len' ).file_name
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/actions/upload.py
--- a/lib/galaxy/tools/actions/upload.py
+++ b/lib/galaxy/tools/actions/upload.py
@@ -4,8 +4,10 @@
import logging
log = logging.getLogger( __name__ )
+
class UploadToolAction( ToolAction ):
- def execute( self, tool, trans, incoming={}, set_output_hid = True, history=None, **kwargs ):
+
+ def execute( self, tool, trans, incoming={}, set_output_hid=True, history=None, **kwargs ):
dataset_upload_inputs = []
for input_name, input in tool.inputs.iteritems():
if input.type == "upload_dataset":
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/actions/upload_common.py
--- a/lib/galaxy/tools/actions/upload_common.py
+++ b/lib/galaxy/tools/actions/upload_common.py
@@ -1,4 +1,8 @@
-import os, tempfile, StringIO, pwd, subprocess
+import os
+import tempfile
+import StringIO
+import pwd
+import subprocess
from cgi import FieldStorage
from galaxy import datatypes, util
from galaxy.util.odict import odict
@@ -10,13 +14,13 @@
import logging
log = logging.getLogger( __name__ )
+
def persist_uploads( params ):
"""
Turn any uploads in the submitted form to persisted files.
"""
if 'files' in params:
new_files = []
- temp_files = []
for upload_dataset in params['files']:
f = upload_dataset['file_data']
if isinstance( f, FieldStorage ):
@@ -24,8 +28,8 @@
assert f.file.name != '<fdopen>'
local_filename = util.mkstemp_ln( f.file.name, 'upload_file_data_' )
f.file.close()
- upload_dataset['file_data'] = dict( filename = f.filename,
- local_filename = local_filename )
+ upload_dataset['file_data'] = dict( filename=f.filename,
+ local_filename=local_filename )
elif type( f ) == dict and 'filename' and 'local_filename' not in f:
raise Exception( 'Uploaded file was encoded in a way not understood by Galaxy.' )
if upload_dataset['url_paste'] and upload_dataset['url_paste'].strip() != '':
@@ -35,6 +39,8 @@
new_files.append( upload_dataset )
params['files'] = new_files
return params
+
+
def handle_library_params( trans, params, folder_id, replace_dataset=None ):
# FIXME: the received params has already been parsed by util.Params() by the time it reaches here,
# so no complex objects remain. This is not good because it does not allow for those objects to be
@@ -62,6 +68,8 @@
role = trans.sa_session.query( trans.app.model.Role ).get( role_id )
library_bunch.roles.append( role )
return library_bunch
+
+
def get_precreated_datasets( trans, params, data_obj, controller='root' ):
"""
Get any precreated datasets (when using asynchronous uploads).
@@ -90,6 +98,8 @@
else:
rval.append( data )
return rval
+
+
def get_precreated_dataset( precreated_datasets, name ):
"""
Return a dataset matching a name from the list of precreated (via async
@@ -101,21 +111,24 @@
return precreated_datasets.pop( names.index( name ) )
else:
return None
+
+
def cleanup_unused_precreated_datasets( precreated_datasets ):
for data in precreated_datasets:
log.info( 'Cleaned up unclaimed precreated dataset (%s).' % ( data.id ) )
data.state = data.states.ERROR
data.info = 'No file contents were available.'
+
def __new_history_upload( trans, uploaded_dataset, history=None, state=None ):
if not history:
history = trans.history
- hda = trans.app.model.HistoryDatasetAssociation( name = uploaded_dataset.name,
- extension = uploaded_dataset.file_type,
- dbkey = uploaded_dataset.dbkey,
- history = history,
- create_dataset = True,
- sa_session = trans.sa_session )
+ hda = trans.app.model.HistoryDatasetAssociation( name=uploaded_dataset.name,
+ extension=uploaded_dataset.file_type,
+ dbkey=uploaded_dataset.dbkey,
+ history=history,
+ create_dataset=True,
+ sa_session=trans.sa_session )
if state:
hda.state = state
else:
@@ -128,6 +141,7 @@
trans.sa_session.flush()
return hda
+
def __new_library_upload( trans, cntrller, uploaded_dataset, library_bunch, state=None ):
current_user_roles = trans.get_current_user_roles()
if not ( ( trans.user_is_admin() and cntrller in [ 'library_admin', 'api' ] ) or trans.app.security_agent.can_add_library_item( current_user_roles, library_bunch.folder ) ):
@@ -156,13 +170,13 @@
trans.sa_session.add( ld )
trans.sa_session.flush()
trans.app.security_agent.copy_library_permissions( trans, folder, ld )
- ldda = trans.app.model.LibraryDatasetDatasetAssociation( name = uploaded_dataset.name,
- extension = uploaded_dataset.file_type,
- dbkey = uploaded_dataset.dbkey,
- library_dataset = ld,
- user = trans.user,
- create_dataset = True,
- sa_session = trans.sa_session )
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation( name=uploaded_dataset.name,
+ extension=uploaded_dataset.file_type,
+ dbkey=uploaded_dataset.dbkey,
+ library_dataset=ld,
+ user=trans.user,
+ create_dataset=True,
+ sa_session=trans.sa_session )
trans.sa_session.add( ldda )
if state:
ldda.state = state
@@ -210,12 +224,14 @@
trans.sa_session.flush()
return ldda
+
def new_upload( trans, cntrller, uploaded_dataset, library_bunch=None, history=None, state=None ):
if library_bunch:
return __new_library_upload( trans, cntrller, uploaded_dataset, library_bunch, state )
else:
return __new_history_upload( trans, uploaded_dataset, history=history, state=state )
+
def get_uploaded_datasets( trans, cntrller, params, precreated_datasets, dataset_upload_inputs, library_bunch=None, history=None ):
uploaded_datasets = []
for dataset_upload_input in dataset_upload_inputs:
@@ -256,6 +272,8 @@
history.genome_build = uploaded_dataset.dbkey
uploaded_dataset.data = data
return uploaded_datasets
+
+
def create_paramfile( trans, uploaded_datasets ):
"""
Create the upload tool's JSON "param" file.
@@ -284,14 +302,14 @@
setattr( data.metadata, meta_name, meta_value )
trans.sa_session.add( data )
trans.sa_session.flush()
- json = dict( file_type = uploaded_dataset.file_type,
- dataset_id = data.dataset.id,
- dbkey = uploaded_dataset.dbkey,
- type = uploaded_dataset.type,
- metadata = uploaded_dataset.metadata,
- primary_file = uploaded_dataset.primary_file,
- composite_file_paths = uploaded_dataset.composite_files,
- composite_files = dict( [ ( k, v.__dict__ ) for k, v in data.datatype.get_composite_files( data ).items() ] ) )
+ json = dict( file_type=uploaded_dataset.file_type,
+ dataset_id=data.dataset.id,
+ dbkey=uploaded_dataset.dbkey,
+ type=uploaded_dataset.type,
+ metadata=uploaded_dataset.metadata,
+ primary_file=uploaded_dataset.primary_file,
+ composite_file_paths=uploaded_dataset.composite_files,
+ composite_files=dict( [ ( k, v.__dict__ ) for k, v in data.datatype.get_composite_files( data ).items() ] ) )
else:
try:
is_binary = uploaded_dataset.datatype.is_binary
@@ -305,18 +323,18 @@
uuid_str = uploaded_dataset.uuid
except:
uuid_str = None
- json = dict( file_type = uploaded_dataset.file_type,
- ext = uploaded_dataset.ext,
- name = uploaded_dataset.name,
- dataset_id = data.dataset.id,
- dbkey = uploaded_dataset.dbkey,
- type = uploaded_dataset.type,
- is_binary = is_binary,
- link_data_only = link_data_only,
- uuid = uuid_str,
- space_to_tab = uploaded_dataset.space_to_tab,
- in_place = trans.app.config.external_chown_script is None,
- path = uploaded_dataset.path )
+ json = dict( file_type=uploaded_dataset.file_type,
+ ext=uploaded_dataset.ext,
+ name=uploaded_dataset.name,
+ dataset_id=data.dataset.id,
+ dbkey=uploaded_dataset.dbkey,
+ type=uploaded_dataset.type,
+ is_binary=is_binary,
+ link_data_only=link_data_only,
+ uuid=uuid_str,
+ space_to_tab=uploaded_dataset.space_to_tab,
+ in_place=trans.app.config.external_chown_script is None,
+ path=uploaded_dataset.path )
# TODO: This will have to change when we start bundling inputs.
# Also, in_place above causes the file to be left behind since the
# user cannot remove it unless the parent directory is writable.
@@ -327,6 +345,8 @@
if trans.app.config.external_chown_script:
_chown( json_file_path )
return json_file_path
+
+
def create_job( trans, params, tool, json_file_path, data_list, folder=None, history=None ):
"""
Create the upload job.
@@ -383,6 +403,8 @@
for i, v in enumerate( data_list ):
output[ 'output%i' % i ] = v
return job, output
+
+
def active_folders( trans, folder ):
# Stolen from galaxy.web.controllers.library_common (importing from which causes a circular issues).
# Much faster way of retrieving all active sub-folders within a given folder than the
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/parameters/__init__.py
--- a/lib/galaxy/tools/parameters/__init__.py
+++ b/lib/galaxy/tools/parameters/__init__.py
@@ -40,7 +40,7 @@
if new_value:
input_values[input.name] = new_value
-def check_param( trans, param, incoming_value, param_values ):
+def check_param( trans, param, incoming_value, param_values, source='html' ):
"""
Check the value of a single parameter `param`. The value in
`incoming_value` is converted from its HTML encoding and validated.
@@ -53,7 +53,10 @@
try:
if value is not None or isinstance(param, DataToolParameter):
# Convert value from HTML representation
- value = param.from_html( value, trans, param_values )
+ if source == 'html':
+ value = param.from_html( value, trans, param_values )
+ else:
+ value = param.from_json( value, trans, param_values )
# Allow the value to be converted if neccesary
filtered_value = param.filter_value( value, trans, param_values )
# Then do any further validation on the value
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -2,14 +2,20 @@
Basic tool parameters.
"""
-import logging, string, sys, os, os.path, urllib
+import logging
+import string
+import sys
+import os
+import os.path
+import urllib
from elementtree.ElementTree import XML, Element
from galaxy import config, datatypes, util
from galaxy.web import form_builder
from galaxy.util.bunch import Bunch
from galaxy.util import string_as_bool, sanitize_param, unicodify
from sanitize import ToolParameterSanitizer
-import validation, dynamic_options
+import validation
+import dynamic_options
# For BaseURLToolParameter
from galaxy.web import url_for
from galaxy.model.item_attrs import Dictifiable
@@ -53,8 +59,10 @@
def get_label( self ):
"""Return user friendly name for the parameter"""
- if self.label: return self.label
- else: return self.name
+ if self.label:
+ return self.label
+ else:
+ return self.name
def get_html_field( self, trans=None, value=None, other_values={} ):
raise TypeError( "Abstract Method" )
@@ -73,6 +81,9 @@
"""
return value
+ def from_json( self, value, trans=None, other_values={} ):
+ return self.from_html( value, trans, other_values )
+
def get_initial_value( self, trans, context, history=None ):
"""
Return the starting value of the parameter
@@ -87,7 +98,7 @@
if a value has already been chosen from the history. This is to support the capability to
choose each dataset once
"""
- return self.get_initial_value(trans, context, history=history);
+ return self.get_initial_value(trans, context, history=history)
def get_required_enctype( self ):
"""
@@ -166,7 +177,7 @@
return value
def validate( self, value, history=None ):
- if value=="" and self.optional:
+ if value == "" and self.optional:
return
for validator in self.validators:
validator.validate( value, history )
@@ -219,7 +230,8 @@
self.area = string_as_bool( elem.get( 'area', False ) )
def get_html_field( self, trans=None, value=None, other_values={} ):
- if value is None: value = self.value
+ if value is None:
+ value = self.value
if self.area:
return form_builder.TextArea( self.name, self.size, value )
else:
@@ -228,6 +240,7 @@
def get_initial_value( self, trans, context, history=None ):
return self.value
+
class IntegerToolParameter( TextToolParameter ):
"""
Parameter that takes an integer value.
@@ -412,11 +425,14 @@
checked = self.checked
if value is not None:
checked = form_builder.CheckboxField.is_checked( value )
- return form_builder.CheckboxField( self.name, checked, refresh_on_change = self.refresh_on_change )
+ return form_builder.CheckboxField( self.name, checked, refresh_on_change=self.refresh_on_change )
def from_html( self, value, trans=None, other_values={} ):
return form_builder.CheckboxField.is_checked( value )
+ def from_json( self, value, trans=None, other_values={} ):
+ return string_as_bool( value )
+
def to_html_value( self, value, app ):
if value:
return [ 'true', 'true' ]
@@ -461,7 +477,7 @@
self.ajax = string_as_bool( elem.get( 'ajax-upload' ) )
def get_html_field( self, trans=None, value=None, other_values={} ):
- return form_builder.FileField( self.name, ajax = self.ajax, value = value )
+ return form_builder.FileField( self.name, ajax=self.ajax, value=value )
def from_html( self, value, trans=None, other_values={} ):
# Middleware or proxies may encode files in special ways (TODO: this
@@ -476,8 +492,8 @@
assert local_filename.startswith( upload_store ), \
"Filename provided by nginx is not in correct directory"
value = dict(
- filename = value["name"],
- local_filename = local_filename
+ filename=value["name"],
+ local_filename=local_filename
)
return value
@@ -533,7 +549,7 @@
user_ftp_dir = None
else:
user_ftp_dir = trans.user_ftp_dir
- return form_builder.FTPFileField( self.name, user_ftp_dir, trans.app.config.ftp_upload_site, value = value )
+ return form_builder.FTPFileField( self.name, user_ftp_dir, trans.app.config.ftp_upload_site, value=value )
def from_html( self, value, trans=None, other_values={} ):
try:
@@ -754,8 +770,9 @@
else:
return form_builder.TextField( self.name, value=(value or "") )
if value is not None:
- if not isinstance( value, list ): value = [ value ]
- field = form_builder.SelectField( self.name, self.multiple, self.display, self.refresh_on_change, refresh_on_change_values = self.refresh_on_change_values )
+ if not isinstance( value, list ):
+ value = [ value ]
+ field = form_builder.SelectField( self.name, self.multiple, self.display, self.refresh_on_change, refresh_on_change_values=self.refresh_on_change_values )
options = self.get_options( trans, context )
for text, optval, selected in options:
if isinstance( optval, UnvalidatedValue ):
@@ -793,7 +810,7 @@
rval.append( v )
return rval
else:
- value_is_none = ( value == "None" and "None" not in legal_values )
+ value_is_none = ( value == "None" and "None" not in legal_values )
if value_is_none:
if self.multiple:
if self.optional:
@@ -943,7 +960,7 @@
options = []
try:
options = self.get_options( trans, {} )
- except AssertionError, assertion:
+ except AssertionError:
# we dont/cant set other_values (the {} above), so params that require other params to be filled will error:
# required dependency in filter_options
# associated DataToolParam in get_column_list
@@ -1006,16 +1023,13 @@
self.static_options = [ ( value, key, False ) for key, value in util.dbnames ]
def get_options( self, trans, other_values ):
- if not trans.history:
- yield 'unspecified', '?', False
- else:
+ last_used_build = object()
+ if trans.history:
last_used_build = trans.history.genome_build
- for dbkey, build_name in trans.db_builds:
- yield build_name, dbkey, ( dbkey == last_used_build )
+ for dbkey, build_name in trans.db_builds:
+ yield build_name, dbkey, ( dbkey == last_used_build )
def get_legal_values( self, trans, other_values ):
- if not trans.history:
- return set( '?' )
return set( dbkey for dbkey, _ in trans.db_builds )
def to_dict( self, trans, view='collection', value_mapper=None ):
@@ -1348,7 +1362,7 @@
options = []
for filter_key, filter_value in self.filtered.iteritems():
dataset = other_values[filter_key]
- if dataset.__class__.__name__.endswith( "DatasetFilenameWrapper" ): #this is a bad way to check for this, but problems importing class ( due to circular imports? )
+ if dataset.__class__.__name__.endswith( "DatasetFilenameWrapper" ): # this is a bad way to check for this, but problems importing class ( due to circular imports? )
dataset = dataset.dataset
if dataset:
for meta_key, meta_dict in filter_value.iteritems():
@@ -1531,8 +1545,9 @@
TODO: There should be an alternate display that allows single selects to be
displayed as radio buttons and multiple selects as a set of checkboxes
- TODO: The following must be fixed to test correctly for the new security_check tag in the DataToolParameter ( the last test below is broken )
- Nate's next pass at the dataset security stuff will dramatically alter this anyway.
+ TODO: The following must be fixed to test correctly for the new security_check tag in
+ the DataToolParameter ( the last test below is broken ) Nate's next pass at the dataset
+ security stuff will dramatically alter this anyway.
"""
def __init__( self, tool, elem, trans=None):
@@ -1579,8 +1594,8 @@
# Load conversions required for the dataset input
self.conversions = []
for conv_elem in elem.findall( "conversion" ):
- name = conv_elem.get( "name" ) #name for commandline substitution
- conv_extensions = conv_elem.get( "type" ) #target datatype extension
+ name = conv_elem.get( "name" ) # name for commandline substitution
+ conv_extensions = conv_elem.get( "type" ) # target datatype extension
# FIXME: conv_extensions should be able to be an ordered list
assert None not in [ name, type ], 'A name (%s) and type (%s) are required for explicit conversion' % ( name, type )
conv_types = tool.app.datatypes_registry.get_datatype_by_extension( conv_extensions.lower() )
@@ -1592,14 +1607,15 @@
try:
filter_value = self.options.get_options( trans, other_values )[0][0]
except IndexError:
- pass #no valid options
+ pass # no valid options
assert trans is not None, "DataToolParameter requires a trans"
history = trans.get_history()
assert history is not None, "DataToolParameter requires a history"
if value is not None:
if type( value ) != list:
value = [ value ]
- field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change, refresh_on_change_values = self.refresh_on_change_values )
+ field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change, refresh_on_change_values=self.refresh_on_change_values )
+
# CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field()
def dataset_collector( hdas, parent_hid ):
current_user_roles = trans.get_current_user_roles()
@@ -1654,7 +1670,7 @@
return field
def get_initial_value( self, trans, context, history=None ):
- return self.get_initial_value_from_history_prevent_repeats(trans, context, None, history=history);
+ return self.get_initial_value_from_history_prevent_repeats(trans, context, None, history=history)
def get_initial_value_from_history_prevent_repeats( self, trans, context, already_used, history=None ):
"""
@@ -1676,7 +1692,8 @@
try:
filter_value = self.options.get_options( trans, context )[0][0]
except IndexError:
- pass #no valid options
+ pass # no valid options
+
def dataset_collector( datasets ):
def is_convertable( dataset ):
target_ext, converted_dataset = dataset.find_conversion_destination( self.formats )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/tools/parameters/grouping.py
--- a/lib/galaxy/tools/parameters/grouping.py
+++ b/lib/galaxy/tools/parameters/grouping.py
@@ -113,7 +113,7 @@
for i in range( self.default ):
rval_dict = { '__index__': i}
for input in self.inputs.itervalues():
- rval_dict[ input.name ] = input.get_initial_value( trans, context )
+ rval_dict[ input.name ] = input.get_initial_value( trans, context, history=history )
rval.append( rval_dict )
return rval
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -137,6 +137,11 @@
ElementInclude.include(root)
return tree
+
+def parse_xml_string(xml_string):
+ tree = ElementTree.fromstring(xml_string)
+ return tree
+
def xml_to_string( elem, pretty=False ):
"""Returns a string from an xml tree"""
if pretty:
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -1070,10 +1070,11 @@
the user (chromInfo in history).
"""
dbnames = list()
- datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
- .filter_by( deleted=False, history_id=self.history.id, extension="len" )
- for dataset in datasets:
- dbnames.append( (dataset.dbkey, dataset.name) )
+ if self.history:
+ datasets = self.sa_session.query( self.app.model.HistoryDatasetAssociation ) \
+ .filter_by( deleted=False, history_id=self.history.id, extension="len" )
+ for dataset in datasets:
+ dbnames.append( (dataset.dbkey, dataset.name) )
user = self.get_user()
if user and 'dbkeys' in user.preferences:
user_keys = from_json_string( user.preferences['dbkeys'] )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/webapps/galaxy/api/datasets.py
--- a/lib/galaxy/webapps/galaxy/api/datasets.py
+++ b/lib/galaxy/webapps/galaxy/api/datasets.py
@@ -7,7 +7,7 @@
from galaxy.web.base.controller import UsesHistoryMixin
from galaxy.web.framework.helpers import is_true
from galaxy.datatypes import dataproviders
-
+from galaxy.util import string_as_bool_or_none
import logging
log = logging.getLogger( __name__ )
@@ -245,11 +245,16 @@
@web.expose_api_raw_anonymous
def display( self, trans, history_content_id, history_id,
- preview=False, filename=None, to_ext=None, chunk=None, **kwd ):
+ preview=False, filename=None, to_ext=None, chunk=None, raw=False, **kwd ):
"""
GET /api/histories/{encoded_history_id}/contents/{encoded_content_id}/display
Displays history content (dataset).
+
+ The query parameter 'raw' should be considered experimental and may be dropped at
+ some point in the future without warning. Generally, data should be processed by its
+ datatype prior to display (the defult if raw is unspecified or explicitly false.
"""
+ raw = string_as_bool_or_none( raw )
# Huge amount of code overlap with lib/galaxy/webapps/galaxy/api/history_content:show here.
rval = ''
try:
@@ -269,7 +274,15 @@
hda = self.get_history_dataset_association( trans, history, history_content_id,
check_ownership=True, check_accessible=True )
- rval = hda.datatype.display_data( trans, hda, preview, filename, to_ext, chunk, **kwd )
+ display_kwd = kwd.copy()
+ try:
+ del display_kwd["key"]
+ except KeyError:
+ pass
+ if raw:
+ rval = open( hda.file_name )
+ else:
+ rval = hda.datatype.display_data( trans, hda, preview, filename, to_ext, chunk, **display_kwd )
except Exception, exception:
log.error( "Error getting display data for dataset (%s) from history (%s): %s",
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/webapps/galaxy/api/tools.py
--- a/lib/galaxy/webapps/galaxy/api/tools.py
+++ b/lib/galaxy/webapps/galaxy/api/tools.py
@@ -9,6 +9,7 @@
import logging
log = logging.getLogger( __name__ )
+
class ToolsController( BaseAPIController, UsesVisualizationMixin ):
"""
RESTful controller for interactions with tools.
@@ -113,7 +114,19 @@
# TODO: encode data ids and decode ids.
# TODO: handle dbkeys
params = util.Params( inputs, sanitize = False )
- template, vars = tool.handle_input( trans, params.__dict__, history=target_history )
+ # process_state will be 'populate' or 'update'. When no tool
+ # state is specified in input - it will be 'populate', and
+ # tool will fully expand repeat and conditionals when building
+ # up state. If tool state is found in input
+ # parameters,process_state will be 'update' and complex
+ # submissions (with repeats and conditionals) must be built up
+ # over several iterative calls to the API - mimicing behavior
+ # of web controller (though frankly API never returns
+ # tool_state so this "legacy" behavior is probably impossible
+ # through API currently).
+ incoming = params.__dict__
+ process_state = "update" if "tool_state" in incoming else "populate"
+ template, vars = tool.handle_input( trans, incoming, history=target_history, process_state=process_state, source="json" )
if 'errors' in vars:
trans.response.status = 400
return { "message": { "type": "error", "data" : vars[ 'errors' ] } }
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -86,15 +86,19 @@
class DbKeyColumn( grids.GridColumn ):
""" Column for filtering by and displaying dataset dbkey. """
def filter( self, trans, user, query, dbkey ):
- """ Filter by dbkey; datasets without a dbkey are returned as well. """
- # use raw SQL b/c metadata is a BLOB
+ """ Filter by dbkey. """
+ # Use raw SQL b/c metadata is a BLOB.
dbkey_user, dbkey = decode_dbkey( dbkey )
dbkey = dbkey.replace("'", "\\'")
- return query.filter( or_( \
- or_( "metadata like '%%\"dbkey\": [\"%s\"]%%'" % dbkey, "metadata like '%%\"dbkey\": \"%s\"%%'" % dbkey ), \
- or_( "metadata like '%%\"dbkey\": [\"?\"]%%'", "metadata like '%%\"dbkey\": \"?\"%%'" ) \
- )
- )
+ return query.filter( or_( "metadata like '%%\"dbkey\": [\"%s\"]%%'" % dbkey, "metadata like '%%\"dbkey\": \"%s\"%%'" % dbkey ) )
+
+ #Use this query when datasets with matching dbkey *or* no dbkey can be added to the visualization.
+ #return query.filter( or_( \
+ # or_( "metadata like '%%\"dbkey\": [\"%s\"]%%'" % dbkey, "metadata like '%%\"dbkey\": \"%s\"%%'" % dbkey ), \
+ # or_( "metadata like '%%\"dbkey\": [\"?\"]%%'", "metadata like '%%\"dbkey\": \"?\"%%'" ) \
+ # )
+ # )
+
class HistoryColumn( grids.GridColumn ):
""" Column for filtering by history id. """
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -1093,7 +1093,8 @@
str( repository.name ),
changeset_revision,
file_type,
- export_repository_dependencies )
+ export_repository_dependencies,
+ api=False )
repositories_archive_filename = os.path.basename( repositories_archive.name )
if error_message:
message = error_message
@@ -1104,6 +1105,8 @@
opened_archive = open( repositories_archive.name )
# Make sure the file is removed from disk after the contents have been downloaded.
os.unlink( repositories_archive.name )
+ repositories_archive_path, file_name = os.path.split( repositories_archive )
+ suc.remove_dir( repositories_archive_path )
return opened_archive
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision )
metadata = repository_metadata.metadata
@@ -3091,6 +3094,7 @@
work_dir )
if message:
status = 'error'
+ suc.remove_dir( work_dir )
break
if guid:
tool_lineage = self.get_versions_of_tool( trans, repository, repository_metadata, guid )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/galaxy/webapps/tool_shed/controllers/upload.py
--- a/lib/galaxy/webapps/tool_shed/controllers/upload.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/upload.py
@@ -248,6 +248,8 @@
status = 'error'
# Reset the tool_data_tables by loading the empty tool_data_table_conf.xml file.
tool_util.reset_tool_data_tables( trans.app )
+ if uploaded_directory:
+ suc.remove_dir( uploaded_directory )
trans.response.send_redirect( web.url_for( controller='repository',
action='browse_repository',
id=repository_id,
@@ -255,6 +257,8 @@
message=message,
status=status ) )
else:
+ if uploaded_directory:
+ suc.remove_dir( uploaded_directory )
status = 'error'
# Reset the tool_data_tables by loading the empty tool_data_table_conf.xml file.
tool_util.reset_tool_data_tables( trans.app )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/galaxy_install/install_manager.py
--- a/lib/tool_shed/galaxy_install/install_manager.py
+++ b/lib/tool_shed/galaxy_install/install_manager.py
@@ -464,10 +464,7 @@
if display_path:
# Load proprietary datatype display applications
self.app.datatypes_registry.load_display_applications( installed_repository_dict=repository_dict )
- try:
- shutil.rmtree( work_dir )
- except:
- pass
+ suc.remove_dir( work_dir )
def install_repository( self, repository_elem, tool_shed_repository, install_dependencies, is_repository_dependency=False ):
"""Install a single repository, loading contained tools into the tool panel."""
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/galaxy_install/repository_util.py
--- a/lib/tool_shed/galaxy_install/repository_util.py
+++ b/lib/tool_shed/galaxy_install/repository_util.py
@@ -575,10 +575,7 @@
tool_shed_repository=tool_shed_repository,
tool_dependencies_config=tool_dependencies_config,
tool_dependencies=tool_shed_repository.tool_dependencies )
- try:
- shutil.rmtree( work_dir )
- except:
- pass
+ suc.remove_dir( work_dir )
suc.update_tool_shed_repository_status( trans.app, tool_shed_repository, trans.model.ToolShedRepository.installation_status.INSTALLED )
else:
# An error occurred while cloning the repository, so reset everything necessary to enable another attempt.
@@ -817,10 +814,7 @@
for installed_tool_dependency in installed_tool_dependencies:
if installed_tool_dependency.status in [ trans.model.ToolDependency.installation_status.ERROR ]:
repair_dict = add_repair_dict_entry( repository.name, installed_tool_dependency.error_message )
- try:
- shutil.rmtree( work_dir )
- except:
- pass
+ suc.remove_dir( work_dir )
suc.update_tool_shed_repository_status( trans.app, repository, trans.model.ToolShedRepository.installation_status.INSTALLED )
return repair_dict
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -21,6 +21,7 @@
from fabric.api import lcd
from fabric.api import local
from fabric.api import settings
+from fabric.api import prefix
log = logging.getLogger( __name__ )
@@ -200,6 +201,20 @@
return True
+class EnvFileBuilder( object ):
+
+ def __init__( self, install_dir ):
+ self.install_dir = install_dir
+ self.return_code = 0
+
+ def append_line( self, skip_if_contained=True, make_executable=True, **kwds ):
+ env_var_dict = dict(**kwds)
+ env_entry, env_file = td_common_util.create_or_update_env_shell_file( self.install_dir, env_var_dict )
+ return_code = file_append( env_entry, env_file, skip_if_contained=skip_if_contained, make_executable=make_executable )
+ self.return_code = self.return_code or return_code
+ return self.return_code
+
+
class InstallEnvironment( object ):
"""
Object describing the environment built up as part of the process of building
@@ -231,6 +246,17 @@
log.debug( 'Invalid file %s specified, ignoring %s action.', env_shell_file_path, action_type )
return cmds
+ def __call__( self, install_dir ):
+ with settings( warn_only=True, **td_common_util.get_env_var_values( install_dir ) ):
+ with prefix( self.__setup_environment() ):
+ yield
+
+ def __setup_environment(self):
+ return "&&".join( [". %s" % file for file in self.__valid_env_shell_file_paths() ] )
+
+ def __valid_env_shell_file_paths(self):
+ return [ file for file in self.env_shell_file_paths if os.path.exists( file ) ]
+
def environment_dict(self, action_type='template_command'):
env_vars = dict()
for env_shell_file_path in self.env_shell_file_paths:
@@ -373,11 +399,10 @@
return
# R libraries are installed to $INSTALL_DIR (install_dir), we now set the R_LIBS path to that directory
- # TODO: That code is used a lot for the different environments and should be refactored, once the environments are integrated
- modify_env_command_dict = dict( name="R_LIBS", action="prepend_to", value=install_dir )
- env_entry, env_file = td_common_util.create_or_update_env_shell_file( install_dir, modify_env_command_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=True, make_executable=True )
-
+ env_file_builder = EnvFileBuilder( install_dir )
+ handle_action_shell_file_paths( env_file_builder, action_dict ) # Pull in R environment (runtime).
+ env_file_builder.append_line( name="R_LIBS", action="prepend_to", value=install_dir )
+ return_code = env_file_builder.return_code
if return_code:
return
elif action_type == 'setup_ruby_environment':
@@ -391,6 +416,8 @@
# <package>protk=1.2.4</package>
# <package>http://url-to-some-gem-file.de/protk.gem</package>
# </action>
+ filtered_actions = actions[ 1: ]
+
if action_dict.get( 'env_shell_file_paths', False ):
install_environment.add_env_shell_file_paths( action_dict[ 'env_shell_file_paths' ] )
else:
@@ -428,17 +455,80 @@
if return_code:
return
- # Ruby libraries are installed to $INSTALL_DIR (install_dir), we now set the GEM_PATH path to that directory
- # TODO: That code is used a lot for the different environments and should be refactored, once the environments are integrated
- modify_env_command_dict = dict( name="GEM_PATH", action="prepend_to", value=install_dir )
- env_entry, env_file = td_common_util.create_or_update_env_shell_file( install_dir, modify_env_command_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=True, make_executable=True )
+ env_file_builder = EnvFileBuilder( install_dir )
+ handle_action_shell_file_paths( env_file_builder, action_dict ) # Pull in ruby dependencies (runtime).
+ env_file_builder.append_line( name="GEM_PATH", action="prepend_to", value=install_dir )
+ env_file_builder.append_line( name="PATH", action="prepend_to", value=os.path.join(install_dir, 'bin') )
+ return_code = env_file_builder.return_code
if return_code:
return
+ elif action_type == 'setup_perl_environment':
+ # setup an Perl environment
+ # <action type="setup_perl_environment">
+ # <repository name="package_perl_5_18" owner="bgruening">
+ # <package name="perl" version="5.18.1" />
+ # </repository>
+ # <!-- allow downloading and installing an Perl package from cpan.org-->
+ # <package>XML::Parser</package>
+ # <package>http://search.cpan.org/CPAN/authors/id/C/CJ/CJFIELDS/BioPerl-1.6.922.tar.gz</package>
+ # </action>
+ filtered_actions = actions[ 1: ]
- modify_env_command_dict = dict( name="PATH", action="prepend_to", value=os.path.join(install_dir, 'bin') )
- env_entry, env_file = td_common_util.create_or_update_env_shell_file( install_dir, modify_env_command_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=True, make_executable=True )
+ if action_dict.get( 'env_shell_file_paths', False ):
+ install_environment.add_env_shell_file_paths( action_dict[ 'env_shell_file_paths' ] )
+ else:
+ log.warning( 'Missing Rerl environment. Please check if your specified Rerl installation exists.' )
+ return
+
+ dir = os.path.curdir
+ current_dir = os.path.abspath( os.path.join( work_dir, dir ) )
+ with lcd( current_dir ):
+ with settings( warn_only=True ):
+
+ for package in action_dict[ 'perl_packages' ]:
+ """
+ If set to a true value then MakeMaker's prompt function will always return the default without waiting for user input.
+ """
+ cmd = '''export PERL_MM_USE_DEFAULT=1 && '''
+
+ if package.find('://') != -1:
+ # we assume a URL to a gem file
+ url = package
+ package_name = url.split( '/' )[ -1 ]
+ dir = td_common_util.url_download( work_dir, package_name, url, extract=True )
+ # search for Build.PL or Makefile.PL (ExtUtils::MakeMaker vs. Module::Build)
+
+ tmp_work_dir = os.path.join( work_dir, dir)
+ if os.path.exists( os.path.join( tmp_work_dir, 'Makefile.PL' ) ):
+
+ cmd += '''perl Makefile.PL INSTALL_BASE=$INSTALL_DIR && make && make install'''
+ elif os.path.exists( os.path.join( tmp_work_dir, 'Build.PL' ) ):
+ cmd += '''perl Build.PL --install_base $INSTALL_DIR && perl Build && perl Build install'''
+ else:
+ log.warning( 'No Makefile.PL or Build.PL file found in %s. Skip installation of %s.' % ( url, package_name ) )
+ return
+ with lcd( tmp_work_dir ):
+ cmd = install_environment.build_command( td_common_util.evaluate_template( cmd, install_dir ) )
+ return_code = handle_command( app, tool_dependency, install_dir, cmd )
+ if return_code:
+ return
+ else:
+ # perl package from CPAN without version number
+ # cpanm should be installed with the parent perl distribution, otherwise this will not work
+ cmd += '''cpanm --local-lib=$INSTALL_DIR %s''' % ( package )
+
+ cmd = install_environment.build_command( td_common_util.evaluate_template( cmd, install_dir ) )
+ return_code = handle_command( app, tool_dependency, install_dir, cmd )
+ if return_code:
+ return
+
+ env_file_builder = EnvFileBuilder( install_dir )
+ # Recursively add dependent PERL5LIB and PATH to env.sh & anything else needed.
+ handle_action_shell_file_paths( env_file_builder, action_dict ) # Pull in ruby dependencies (runtime).
+
+ env_file_builder.append_line( name="PERL5LIB", action="prepend_to", value=os.path.join(install_dir, 'lib', 'perl5') )
+ env_file_builder.append_line( name="PATH", action="prepend_to", value=os.path.join(install_dir, 'bin') )
+ return_code = env_file_builder.return_code
if return_code:
return
@@ -480,13 +570,14 @@
# in the set_environment action.
cmds = install_environment.environment_commands( 'set_environment' )
env_var_dicts = action_dict[ 'environment_variable' ]
+ env_file_builder = EnvFileBuilder( install_dir )
for env_var_dict in env_var_dicts:
# Check for the presence of the $ENV[] key string and populate it if possible.
env_var_dict = handle_environment_variables( app, tool_dependency, install_dir, env_var_dict, cmds )
- env_entry, env_file = td_common_util.create_or_update_env_shell_file( install_dir, env_var_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=True, make_executable=True )
- if return_code:
- return
+ env_file_builder.append_line( **env_var_dict )
+ return_code = env_file_builder.return_code
+ if return_code:
+ return
elif action_type == 'set_environment_for_install':
# Currently the only action supported in this category is a list of paths to one or more tool dependency env.sh files,
# the environment setting in each of which will be injected into the environment for all <action type="shell_command">
@@ -525,14 +616,10 @@
if not os.path.exists( output.stdout ):
log.error( "virtualenv's site-packages directory '%s' does not exist", output.stdout )
return
- modify_env_command_dict = dict( name="PYTHONPATH", action="prepend_to", value=output.stdout )
- env_entry, env_file = td_common_util.create_or_update_env_shell_file( install_dir, modify_env_command_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=True, make_executable=True )
- if return_code:
- return
- modify_env_command_dict = dict( name="PATH", action="prepend_to", value=os.path.join( venv_directory, "bin" ) )
- env_entry, env_file = td_common_util.create_or_update_env_shell_file( install_dir, modify_env_command_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=True, make_executable=True )
+ env_file_builder = EnvFileBuilder( install_dir )
+ env_file_builder.append_line( name="PYTHONPATH", action="prepend_to", value=output.stdout )
+ env_file_builder.append_line( name="PATH", action="prepend_to", value=os.path.join( venv_directory, "bin" ) )
+ return_code = env_file_builder.return_code
if return_code:
return
elif action_type == 'shell_command':
@@ -619,6 +706,13 @@
source=downloaded_filename,
destination=full_path_to_dir )
+
+def handle_action_shell_file_paths( env_file_builder, action_dict ):
+ shell_file_paths = action_dict.get( 'action_shell_file_paths', [])
+ for shell_file_path in shell_file_paths:
+ env_file_builder.append_line( action="source", value=shell_file_path )
+
+
def log_results( command, fabric_AttributeString, file_path ):
"""
Write attributes of fabric.operations._AttributeString (which is the output of executing command using fabric's local() method)
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
@@ -665,11 +665,7 @@
# <!-- allow installing an R packages -->
# <package>https://github.com/bgruening/download_store/raw/master/DESeq2-1_0_18/BiocGe…</package>
# </action>
- env_shell_file_paths = td_common_util.get_env_shell_file_paths( app, action_elem.find('repository') )
-
- all_env_shell_file_paths.extend( env_shell_file_paths )
- if all_env_shell_file_paths:
- action_dict[ 'env_shell_file_paths' ] = all_env_shell_file_paths
+ td_common_util.parse_setup_environment_repositories( app, all_env_shell_file_paths, action_elem, action_dict )
r_packages = list()
for env_elem in action_elem:
if env_elem.tag == 'package':
@@ -690,11 +686,7 @@
# <package>protk=1.2.4</package>
# <package>http://url-to-some-gem-file.de/protk.gem</package>
# </action>
-
- env_shell_file_paths = td_common_util.get_env_shell_file_paths( app, action_elem.find('repository') )
- all_env_shell_file_paths.extend( env_shell_file_paths )
- if all_env_shell_file_paths:
- action_dict[ 'env_shell_file_paths' ] = all_env_shell_file_paths
+ td_common_util.parse_setup_environment_repositories( app, all_env_shell_file_paths, action_elem, action_dict )
ruby_packages = list()
for env_elem in action_elem:
if env_elem.tag == 'package':
@@ -719,6 +711,32 @@
action_dict[ 'ruby_packages' ] = ruby_packages
else:
continue
+ elif action_type == 'setup_perl_environment':
+ # setup an Perl environment
+ # <action type="setup_perl_environment">
+ # <repository name="package_perl_5_18" owner="bgruening">
+ # <package name="perl" version="5.18.1" />
+ # </repository>
+ # <!-- allow downloading and installing an Perl package from cpan.org-->
+ # <package>XML::Parser</package>
+ # <package>http://search.cpan.org/CPAN/authors/id/C/CJ/CJFIELDS/BioPerl-1.6.922.tar.gz</package>
+ # </action>
+ td_common_util.parse_setup_environment_repositories( app, all_env_shell_file_paths, action_elem, action_dict )
+ perl_packages = list()
+ for env_elem in action_elem:
+ if env_elem.tag == 'package':
+ """
+ A valid package definition can be:
+ XML::Parser
+ http://search.cpan.org/CPAN/authors/id/C/CJ/CJFIELDS/BioPerl-1.6.922.tar.gz
+ Unfortunately, CPAN does not support versioning. If you want real Reproducibility,
+ you need to specify the tarball path and the right order of different tarballs manually.
+ """
+ perl_packages.append( env_elem.text.strip() )
+ if perl_packages:
+ action_dict[ 'perl_packages' ] = perl_packages
+ else:
+ continue
elif action_type == 'make_install':
# make; make install; allow providing make options
if action_elem.text:
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
@@ -146,16 +146,21 @@
return None
def create_or_update_env_shell_file( install_dir, env_var_dict ):
- env_var_name = env_var_dict[ 'name' ]
env_var_action = env_var_dict[ 'action' ]
env_var_value = env_var_dict[ 'value' ]
- if env_var_action == 'prepend_to':
- changed_value = '%s:$%s' % ( env_var_value, env_var_name )
- elif env_var_action == 'set_to':
- changed_value = '%s' % env_var_value
- elif env_var_action == 'append_to':
- changed_value = '$%s:%s' % ( env_var_name, env_var_value )
- line = "%s=%s; export %s" % ( env_var_name, changed_value, env_var_name )
+ if env_var_action in ['prepend_to', 'set_to', 'append_to']:
+ env_var_name = env_var_dict[ 'name' ]
+ if env_var_action == 'prepend_to':
+ changed_value = '%s:$%s' % ( env_var_value, env_var_name )
+ elif env_var_action == 'set_to':
+ changed_value = '%s' % env_var_value
+ elif env_var_action == 'append_to':
+ changed_value = '$%s:%s' % ( env_var_name, env_var_value )
+ line = "%s=%s; export %s" % ( env_var_name, changed_value, env_var_name )
+ elif env_var_action == "source":
+ line = ". %s" % env_var_value
+ else:
+ raise Exception( "Unknown shell file action %s" % env_var_action )
env_shell_file_path = os.path.join( install_dir, 'env.sh' )
return line, env_shell_file_path
@@ -193,6 +198,7 @@
return os.path.abspath( os.path.join( root, name ) )
return None
+
def get_env_shell_file_paths( app, elem ):
# Currently only the following tag set is supported.
# <repository toolshed="http://localhost:9009/" name="package_numpy_1_7" owner="test" changeset_revision="c84c6a8be056">
@@ -383,7 +389,7 @@
# platform. Append the child element to the list of elements to process.
actions_elem_list.append( child_element )
elif child_element.tag == 'action':
- # Any <action> tags within an <actions_group> tag set must come after all <actions> tags.
+ # Any <action> tags within an <actions_group> tag set must come after all <actions> tags.
if actions_elems_processed == actions_elem_count:
# If all <actions> elements have been processed, then this <action> element can be appended to the list of actions to
# execute within this group.
@@ -409,6 +415,16 @@
continue
return actions_elem_tuples
+
+def parse_setup_environment_repositories( app, all_env_shell_file_paths, action_elem, action_dict ):
+ env_shell_file_paths = get_env_shell_file_paths( app, action_elem.find('repository') )
+
+ all_env_shell_file_paths.extend( env_shell_file_paths )
+ if all_env_shell_file_paths:
+ action_dict[ 'env_shell_file_paths' ] = all_env_shell_file_paths
+ action_dict[ 'action_shell_file_paths' ] = env_shell_file_paths
+
+
def url_download( install_dir, downloaded_file_name, download_url, extract=True ):
file_path = os.path.join( install_dir, downloaded_file_name )
src = None
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/util/export_util.py
--- a/lib/tool_shed/util/export_util.py
+++ b/lib/tool_shed/util/export_util.py
@@ -103,7 +103,7 @@
attributes, sub_elements = get_repository_attributes_and_sub_elements( ordered_repository, archive_name )
elem = xml_util.create_element( 'repository', attributes=attributes, sub_elements=sub_elements )
exported_repository_registry.exported_repository_elems.append( elem )
- shutil.rmtree( work_dir )
+ suc.remove_dir( work_dir )
# Keep information about the export in a file name export_info.xml in the archive.
sub_elements = generate_export_elem( tool_shed_url, repository, changeset_revision, export_repository_dependencies, api )
export_elem = xml_util.create_element( 'export_info', attributes=None, sub_elements=sub_elements )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -747,6 +747,7 @@
# Reset the value of the app's tool_data_path and tool_data_table_config_path to their respective original values.
app.config.tool_data_path = original_tool_data_path
app.config.tool_data_table_config_path = original_tool_data_table_config_path
+ suc.remove_dir( work_dir )
return metadata_dict, invalid_file_tups
def generate_package_dependency_metadata( app, elem, valid_tool_dependencies_dict, invalid_tool_dependencies_dict ):
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb static/scripts/galaxy.grids.js
--- a/static/scripts/galaxy.grids.js
+++ b/static/scripts/galaxy.grids.js
@@ -133,27 +133,8 @@
// Code to handle grid operations: filtering, sorting, paging, and operations.
//
-// Init operation buttons.
-function init_operation_buttons() {
- // Initialize operation buttons.
- $('input[name=operation]:submit').each(function() {
- $(this).click( function() {
- var operation_name = $(this).val();
- // For some reason, $('input[name=id]:checked').val() does not return all ids for checked boxes.
- // The code below performs this function.
- var item_ids = [];
- $('input[name=id]:checked').each(function() {
- item_ids.push( $(this).val() );
- });
- do_operation(operation_name, item_ids);
- });
- });
-}
-
// Initialize grid controls
function init_grid_controls() {
- init_operation_buttons();
-
// Initialize submit image elements.
$('.submit-image').each( function() {
// On mousedown, add class to simulate click.
@@ -498,7 +479,6 @@
// Init grid.
init_grid_elements();
- init_operation_buttons();
make_popup_menus();
// Hide loading overlay.
@@ -558,11 +538,13 @@
if(!confirm(confirmation_text))
return false;
- // set up hidden field to parse the command/operation to controller
- $('#operation').val(selected_button.value);
-
- // submit form
- selected_button.form.submit();
+ // add ids
+ var operation_name = selected_button.value;
+ var item_ids = [];
+ $('input[name=id]:checked').each(function() {
+ item_ids.push( $(this).val() );
+ });
+ do_operation(operation_name, item_ids);
// return
return true;
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb templates/webapps/galaxy/tool_form.mako
--- a/templates/webapps/galaxy/tool_form.mako
+++ b/templates/webapps/galaxy/tool_form.mako
@@ -218,12 +218,11 @@
else:
cls = "form-row"
- label = param.get_label()
-
field = param.get_html_field( trans, parent_state[ param.name ], other_values )
field.refresh_on_change = param.refresh_on_change
- # Field may contain characters submitted by user and these characters may be unicode; handle non-ascii characters gracefully.
+ # Field may contain characters submitted by user and these characters may
+ # be unicode; handle non-ascii characters gracefully.
field_html = field.get_html( prefix )
if type( field_html ) is not unicode:
field_html = unicode( field_html, 'utf-8', 'replace' )
@@ -232,25 +231,39 @@
return field_html
%><div class="${cls}">
- %if label:
- <label for="${param.name}">${label}:</label>
- %endif
- <div class="form-row-input">${field_html}</div>
- %if parent_errors.has_key( param.name ):
- <div class="form-row-error-message">
- <div><img style="vertical-align: middle;" src="${h.url_for('/static/style/error_small.png')}"> <span style="vertical-align: middle;">${parent_errors[param.name]}</span></div>
- </div>
- %endif
+ ${label_for_param( param )}
+ ${input_for_param( param, field_html )}
+ ${errors_for_param( param, parent_errors )}
+ ${help_for_param( param )}
+ <div style="clear: both;"></div>
+ </div>
+</%def>
- %if param.help:
- <div class="toolParamHelp" style="clear: both;">
- ${param.help}
- </div>
- %endif
+<%def name="input_for_param( param, field_html )">
+ <div class="form-row-input">${field_html}</div>
+</%def>
- <div style="clear: both;"></div>
+<%def name="label_for_param( param )">
+ <% label = param.get_label()%>
+ %if label:
+ <label for="${param.name}">${label}:</label>
+ %endif
+</%def>
- </div>
+<%def name="errors_for_param( param, parent_errors )">
+ %if parent_errors.has_key( param.name ):
+ <div class="form-row-error-message">
+ <div><img style="vertical-align: middle;" src="${h.url_for('/static/style/error_small.png')}"> <span style="vertical-align: middle;">${parent_errors[param.name]}</span></div>
+ </div>
+ %endif
+</%def>
+
+<%def name="help_for_param( param )">
+ %if param.help:
+ <div class="toolParamHelp" style="clear: both;">
+ ${param.help}
+ </div>
+ %endif
</%def><%def name="row_for_rerun()">
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb test/install_and_test_tool_shed_repositories/base/test_db_util.py
--- a/test/install_and_test_tool_shed_repositories/base/test_db_util.py
+++ b/test/install_and_test_tool_shed_repositories/base/test_db_util.py
@@ -36,12 +36,17 @@
return role
raise AssertionError( "Private role not found for user '%s'" % user.email )
-def get_tool_dependencies_for_installed_repository( repository_id, status=None ):
+def get_tool_dependencies_for_installed_repository( repository_id, status=None, exclude_status=None ):
if status is not None:
return sa_session.query( model.ToolDependency ) \
.filter( and_( model.ToolDependency.table.c.tool_shed_repository_id == repository_id,
model.ToolDependency.table.c.status == status ) ) \
.all()
+ elif exclude_status is not None:
+ return sa_session.query( model.ToolDependency ) \
+ .filter( and_( model.ToolDependency.table.c.tool_shed_repository_id == repository_id,
+ model.ToolDependency.table.c.status != exclude_status ) ) \
+ .all()
else:
return sa_session.query( model.ToolDependency ) \
.filter( model.ToolDependency.table.c.tool_shed_repository_id == repository_id ) \
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb test/install_and_test_tool_shed_repositories/base/twilltestcase.py
--- a/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
+++ b/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
@@ -150,10 +150,10 @@
else:
strings_displayed.append( 'has been uninstalled' )
self.check_for_strings( strings_displayed, strings_not_displayed=[] )
- # Get all tool dependencies that are in an error state and uninstall them explicitly, so that the next installation attempt
+ # Get all tool dependencies that are not in an installed state and uninstall them explicitly, so that the next installation attempt
# may succeed.
- error_state = model.ToolDependency.installation_status.ERROR
- tool_dependencies = test_db_util.get_tool_dependencies_for_installed_repository( installed_repository.id, status=error_state )
+ installed_state = model.ToolDependency.installation_status.INSTALLED
+ tool_dependencies = test_db_util.get_tool_dependencies_for_installed_repository( installed_repository.id, exclude_status=installed_state )
if len( tool_dependencies ) > 0:
encoded_tool_dependency_ids = [ self.security.encode_id( tool_dependency.id ) for tool_dependency in tool_dependencies ]
self.uninstall_tool_dependencies( self.security.encode_id( installed_repository.id ), encoded_tool_dependency_ids )
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb test/unit/tool_shed/test_fabric_util.py
--- /dev/null
+++ b/test/unit/tool_shed/test_fabric_util.py
@@ -0,0 +1,45 @@
+from contextlib import contextmanager
+from tool_shed.galaxy_install.tool_dependencies import fabric_util
+
+
+def test_env_file_builder():
+ install_dir = "/opt/galaxy/dependencies/foo/"
+ env_file_builder = fabric_util.EnvFileBuilder( install_dir )
+ added_lines = []
+ mock_return = dict(value=0)
+
+ def mock_file_append( text, file_path, **kwds ):
+ added_lines.append(text)
+ return mock_return["value"]
+
+ with __mock_fabric_util_method("file_append", mock_file_append):
+ env_file_builder.append_line( name="PATH", action="prepend_to", value="/usr/bin/local/R" )
+ assert added_lines == [ "PATH=/usr/bin/local/R:$PATH; export PATH" ]
+ assert env_file_builder.return_code == 0
+
+ # Reset mock lines
+ del added_lines[:]
+ # Next time file_append will fail
+ mock_return["value"] = 1
+
+ env_file_builder.append_line( action="source", value="/usr/bin/local/R/env.sh" )
+ assert added_lines == [ ". /usr/bin/local/R/env.sh" ]
+ # Check failure
+ assert env_file_builder.return_code == 1
+
+ mock_return["value"] = 0
+ env_file_builder.append_line( name="LD_LIBRARY_PATH", action="append_to", value="/usr/bin/local/R/lib" )
+ # Verify even though last append succeeded, previous failure still recorded.
+ assert env_file_builder.return_code == 1
+
+
+## Poor man's mocking. Need to get a real mocking library as real Galaxy development
+## dependnecy.
+@contextmanager
+def __mock_fabric_util_method(name, mock_method):
+ real_method = getattr(fabric_util, name)
+ try:
+ setattr(fabric_util, name, mock_method)
+ yield
+ finally:
+ setattr(fabric_util, name, real_method)
diff -r 61b52b97b47b0ee8ab850ac266450c41f05842ce -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb test/unit/tool_shed/test_td_common_util.py
--- /dev/null
+++ b/test/unit/tool_shed/test_td_common_util.py
@@ -0,0 +1,81 @@
+from os.path import join
+from contextlib import contextmanager
+from galaxy.util import parse_xml_string
+
+from tool_shed.galaxy_install.tool_dependencies import td_common_util
+
+
+TEST_DEPENDENCIES_DIR = "/opt/galaxy/dependencies"
+TEST_INSTALL_DIR = "%s/test_install_dir" % TEST_DEPENDENCIES_DIR
+
+
+class MockApp( object ):
+
+ def __init__( self ):
+ pass
+
+
+def test_create_or_update_env_shell_file( ):
+ test_path = "/usr/share/R/libs"
+ line, path = td_common_util.create_or_update_env_shell_file( TEST_INSTALL_DIR, dict(action="append_to", name="R_LIBS", value=test_path))
+ assert path == join( TEST_INSTALL_DIR, "env.sh" )
+ assert line == "R_LIBS=$R_LIBS:/usr/share/R/libs; export R_LIBS"
+
+ line, path = td_common_util.create_or_update_env_shell_file( TEST_INSTALL_DIR, dict(action="prepend_to", name="R_LIBS", value=test_path))
+ assert path == join( TEST_INSTALL_DIR, "env.sh" )
+ assert line == "R_LIBS=/usr/share/R/libs:$R_LIBS; export R_LIBS"
+
+ line, path = td_common_util.create_or_update_env_shell_file( TEST_INSTALL_DIR, dict(action="set_to", name="R_LIBS", value=test_path))
+ assert path == join( TEST_INSTALL_DIR, "env.sh" )
+ assert line == "R_LIBS=/usr/share/R/libs; export R_LIBS"
+
+ line, path = td_common_util.create_or_update_env_shell_file( TEST_INSTALL_DIR, dict(action="source", value=test_path))
+ assert path == join( TEST_INSTALL_DIR, "env.sh" )
+ assert line == ". /usr/share/R/libs"
+
+
+def test_parse_setup_environment_repositories( ):
+ xml = """<action name="setup_r_environment">
+ <repository name="package_r_3_0_1" owner="bgruening" toolshed="toolshed.g2.bx.psu.edu" changeset_revision="1234567">
+ <package name="R" version="3.0.1" />
+ </repository>
+ </action>
+ """
+ mock_app = MockApp()
+ action_elem = parse_xml_string( xml )
+ required_for_install_env_sh = '/path/to/existing.sh'
+ all_env_paths = [ required_for_install_env_sh ]
+ action_dict = {}
+
+ r_env_sh = '/path/to/go/env.sh'
+
+ def mock_get_env_shell_file_paths( app, elem):
+ assert app == mock_app
+ assert elem.get( 'name' ) == "package_r_3_0_1"
+ return [ r_env_sh ]
+
+ with __mock_common_util_method("get_env_shell_file_paths", mock_get_env_shell_file_paths):
+ td_common_util.parse_setup_environment_repositories( mock_app, all_env_paths, action_elem, action_dict )
+ ## Verify old env files weren't deleted.
+ assert required_for_install_env_sh in all_env_paths
+ ## Verify new ones added.
+ assert r_env_sh in all_env_paths
+ ## env_shell_file_paths includes everything
+ assert all( [env in action_dict[ 'env_shell_file_paths' ] for env in all_env_paths] )
+
+ ## action_shell_file_paths includes only env files defined in
+ ## inside the setup_ action element.
+ assert required_for_install_env_sh not in action_dict[ 'action_shell_file_paths' ]
+ assert r_env_sh in action_dict[ 'action_shell_file_paths' ]
+
+
+## Poor man's mocking. Need to get a real mocking library as real Galaxy development
+## dependnecy.
+@contextmanager
+def __mock_common_util_method(name, mock_method):
+ real_method = getattr(td_common_util, name)
+ try:
+ setattr(td_common_util, name, mock_method)
+ yield
+ finally:
+ setattr(td_common_util, name, real_method)
https://bitbucket.org/galaxy/galaxy-central/commits/fb1338d601ad/
Changeset: fb1338d601ad
User: kellrott
Date: 2013-11-15 20:18:26
Summary: Merged galaxy/galaxy-central into default
Affected #: 11 files
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -358,6 +358,7 @@
for index, s_e_c in enumerate( sniffer_elem_classes ):
if sniffer_class == s_e_c:
del self.sniffer_elems[ index ]
+ sniffer_elem_classes = [ e.attrib[ 'type' ] for e in self.sniffer_elems ]
self.log.debug( "Removed sniffer element for datatype '%s'" % str( dtype ) )
break
for sniffer_class in self.sniff_order:
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -2084,7 +2084,7 @@
rep_index = 0
while True:
rep_name = "%s_%d" % ( key, rep_index )
- if not any( [ key.startswith(rep_name) for key in incoming.keys() ] ):
+ if not any( [ incoming_key.startswith(rep_name) for incoming_key in incoming.keys() ] ):
break
if rep_index < input.max:
new_state = {}
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d lib/galaxy/webapps/galaxy/api/tools.py
--- a/lib/galaxy/webapps/galaxy/api/tools.py
+++ b/lib/galaxy/webapps/galaxy/api/tools.py
@@ -95,7 +95,7 @@
inputs = payload.get( 'inputs', {} )
# Find files coming in as multipart file data and add to inputs.
for k, v in payload.iteritems():
- if k.startswith("files_"):
+ if k.startswith("files_") or k.startswith("__files_"):
inputs[k] = v
#for inputs that are coming from the Library, copy them into the history
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -1105,7 +1105,7 @@
opened_archive = open( repositories_archive.name )
# Make sure the file is removed from disk after the contents have been downloaded.
os.unlink( repositories_archive.name )
- repositories_archive_path, file_name = os.path.split( repositories_archive )
+ repositories_archive_path, file_name = os.path.split( repositories_archive.name )
suc.remove_dir( repositories_archive_path )
return opened_archive
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision )
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -655,7 +655,7 @@
if 'prefix=' in configure_opts:
pre_cmd = './configure %s && make && make install' % configure_opts
else:
- pre_cmd = './configure prefix=$INSTALL_DIR %s && make && make install' % configure_opts
+ pre_cmd = './configure --prefix=$INSTALL_DIR %s && make && make install' % configure_opts
cmd = install_environment.build_command( td_common_util.evaluate_template( pre_cmd, install_dir ) )
return_code = handle_command( app, tool_dependency, install_dir, cmd )
if return_code:
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d lib/tool_shed/util/commit_util.py
--- a/lib/tool_shed/util/commit_util.py
+++ b/lib/tool_shed/util/commit_util.py
@@ -142,14 +142,13 @@
# <repository name="package_eigen_2_0" owner="test" prior_installation_required="True" />
revised, repository_elem, error_message = handle_repository_dependency_elem( trans, sub_elem, unpopulate=unpopulate )
if error_message:
- exception_message = 'The tool_dependencies.xml file contains an invalid <repository> tag. %s' % error_message
- raise Exception( exception_message )
+ error_message = 'The tool_dependencies.xml file contains an invalid <repository> tag. %s' % error_message
if revised:
elem[ sub_elem_index ] = repository_elem
sub_elem_altered = True
if not altered:
altered = True
- return altered, sub_elem_altered, elem
+ return altered, sub_elem_altered, elem, error_message
def handle_directory_changes( trans, repository, full_path, filenames_in_archive, remove_repo_files_not_in_tar, new_repo_alert, commit_message,
undesirable_dirs_removed, undesirable_files_removed ):
@@ -359,13 +358,16 @@
for package_index, package_elem in enumerate( root_elem ):
if package_elem.tag == 'repository':
# We have a complex repository dependency.
- altered, package_altered, root_elem = handle_complex_repository_dependency_elem( trans,
- root_elem,
- package_index,
- package_elem,
- package_altered,
- altered,
- unpopulate=unpopulate )
+ altered, package_altered, root_elem, message = \
+ handle_complex_repository_dependency_elem( trans,
+ root_elem,
+ package_index,
+ package_elem,
+ package_altered,
+ altered,
+ unpopulate=unpopulate )
+ if message:
+ error_message += message
elif package_elem.tag == 'install':
# <install version="1.0">
for actions_index, actions_elem in enumerate( package_elem ):
@@ -392,7 +394,7 @@
for last_actions_elem_package_index, last_actions_elem_package_elem in enumerate( last_actions_elem ):
if last_actions_elem_package_elem.tag == 'repository':
# We have a complex repository dependency.
- altered, last_actions_package_altered, last_actions_elem = \
+ altered, last_actions_package_altered, last_actions_elem, message = \
handle_complex_repository_dependency_elem( trans,
last_actions_elem,
last_actions_elem_package_index,
@@ -400,6 +402,8 @@
last_actions_package_altered,
altered,
unpopulate=unpopulate )
+ if message:
+ error_message += message
if last_actions_package_altered:
last_actions_elem[ last_actions_elem_package_index ] = last_actions_elem_package_elem
actions_group_elem[ last_actions_index ] = last_actions_elem
@@ -438,8 +442,6 @@
error_message += 'tag set.'
if package_altered:
package_elem[ actions_index ] = actions_elem
- if package_altered:
- root_elem[ package_index ] = package_elem
if package_altered:
root[ root_index ] = root_elem
return altered, root, error_message
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d test/base/twilltestcase.py
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -1,7 +1,20 @@
import pkg_resources
pkg_resources.require( "twill==0.9" )
-import StringIO, os, filecmp, time, unittest, urllib, logging, difflib, tarfile, zipfile, tempfile, re, shutil, subprocess
+import StringIO
+import os
+import filecmp
+import time
+import unittest
+import urllib
+import logging
+import difflib
+import tarfile
+import zipfile
+import tempfile
+import re
+import shutil
+import subprocess
import pprint
import twill
@@ -26,6 +39,7 @@
logging.getLogger( "ClientCookie.cookies" ).setLevel( logging.WARNING )
log = logging.getLogger( __name__ )
+
class TwillTestCase( unittest.TestCase ):
def setUp( self ):
@@ -46,10 +60,10 @@
self.shed_tools_dict = {}
self.keepOutdir = os.environ.get( 'GALAXY_TEST_SAVE', '' )
if self.keepOutdir > '':
- try:
- os.makedirs(self.keepOutdir)
- except:
- pass
+ try:
+ os.makedirs(self.keepOutdir)
+ except:
+ pass
self.home()
# Functions associated with files
@@ -83,7 +97,7 @@
diff_lines = get_lines_diff( diff )
if diff_lines > allowed_diff_count:
diff_slice = diff[0:40]
- #FIXME: This pdf stuff is rather special cased and has not been updated to consider lines_diff
+ #FIXME: This pdf stuff is rather special cased and has not been updated to consider lines_diff
#due to unknown desired behavior when used in conjunction with a non-zero lines_diff
#PDF forgiveness can probably be handled better by not special casing by __extension__ here
#and instead using lines_diff or a regular expression matching
@@ -109,13 +123,13 @@
break
if not valid_diff:
invalid_diff_lines += 1
- log.info('## files diff on %s and %s lines_diff=%d, found diff = %d, found pdf invalid diff = %d' % (file1,file2,allowed_diff_count,diff_lines,invalid_diff_lines))
+ log.info('## files diff on %s and %s lines_diff=%d, found diff = %d, found pdf invalid diff = %d' % (file1, file2, allowed_diff_count, diff_lines, invalid_diff_lines))
if invalid_diff_lines > allowed_diff_count:
# Print out diff_slice so we can see what failed
print "###### diff_slice ######"
raise AssertionError( "".join( diff_slice ) )
else:
- log.info('## files diff on %s and %s lines_diff=%d, found diff = %d' % (file1,file2,allowed_diff_count,diff_lines))
+ log.info('## files diff on %s and %s lines_diff=%d, found diff = %d' % (file1, file2, allowed_diff_count, diff_lines))
for line in diff_slice:
for char in line:
if ord( char ) > 128:
@@ -124,7 +138,7 @@
def files_re_match( self, file1, file2, attributes=None ):
"""Checks the contents of 2 files for differences using re.match"""
- local_file = open( file1, 'U' ).readlines() #regex file
+ local_file = open( file1, 'U' ).readlines() # regex file
history_data = open( file2, 'U' ).readlines()
assert len( local_file ) == len( history_data ), 'Data File and Regular Expression File contain a different number of lines (%s != %s)\nHistory Data (first 40 lines):\n%s' % ( len( local_file ), len( history_data ), ''.join( history_data[:40] ) )
if attributes is None:
@@ -139,11 +153,11 @@
line_diff_count += 1
diffs.append( 'Regular Expression: %s\nData file : %s' % ( local_file[i].rstrip( '\r\n' ), history_data[i].rstrip( '\r\n' ) ) )
if line_diff_count > lines_diff:
- raise AssertionError, "Regular expression did not match data file (allowed variants=%i):\n%s" % ( lines_diff, "".join( diffs ) )
+ raise AssertionError( "Regular expression did not match data file (allowed variants=%i):\n%s" % ( lines_diff, "".join( diffs ) ) )
def files_re_match_multiline( self, file1, file2, attributes=None ):
"""Checks the contents of 2 files for differences using re.match in multiline mode"""
- local_file = open( file1, 'U' ).read() #regex file
+ local_file = open( file1, 'U' ).read() # regex file
if attributes is None:
attributes = {}
if attributes.get( 'sort', False ):
@@ -157,7 +171,7 @@
def files_contains( self, file1, file2, attributes=None ):
"""Checks the contents of file2 for substrings found in file1, on a per-line basis"""
- local_file = open( file1, 'U' ).readlines() #regex file
+ local_file = open( file1, 'U' ).readlines() # regex file
#TODO: allow forcing ordering of contains
history_data = open( file2, 'U' ).read()
lines_diff = int( attributes.get( 'lines_diff', 0 ) )
@@ -167,7 +181,7 @@
if contains not in history_data:
line_diff_count += 1
if line_diff_count > lines_diff:
- raise AssertionError, "Failed to find '%s' in history data. (lines_diff=%i):\n" % ( contains, lines_diff )
+ raise AssertionError( "Failed to find '%s' in history data. (lines_diff=%i):\n" % ( contains, lines_diff ) )
def get_filename( self, filename, shed_tool_id=None ):
if shed_tool_id and self.shed_tools_dict:
@@ -189,8 +203,8 @@
so the tool-data directory of test data files is contained in the installed tool shed repository.
"""
self.visit_url( "%s/tool_runner?tool_id=upload1" % self.url )
- try:
- self.refresh_form( "file_type", ftype ) #Refresh, to support composite files
+ try:
+ self.refresh_form( "file_type", ftype ) # Refresh, to support composite files
tc.fv( "tool_form", "dbkey", dbkey )
if metadata:
for elem in metadata:
@@ -214,9 +228,9 @@
hids = self.get_hids_in_history()
for hid in hids:
try:
- valid_hid = int( hid )
+ int( hid )
except:
- raise AssertionError, "Invalid hid (%s) created when uploading file %s" % ( hid, filename )
+ raise AssertionError( "Invalid hid (%s) created when uploading file %s" % ( hid, filename ) )
# Wait for upload processing to finish (TODO: this should be done in each test case instead)
self.wait()
@@ -231,21 +245,21 @@
self.home()
except Exception, e:
errmsg = "Problem executing upload utility using url_paste: %s" % str( e )
- raise AssertionError( e )
+ raise AssertionError( errmsg )
# Make sure every history item has a valid hid
hids = self.get_hids_in_history()
for hid in hids:
try:
- valid_hid = int( hid )
+ int( hid )
except:
- raise AssertionError, "Invalid hid (%s) created when pasting %s" % ( hid, url_paste )
+ raise AssertionError( "Invalid hid (%s) created when pasting %s" % ( hid, url_paste ) )
# Wait for upload processing to finish (TODO: this should be done in each test case instead)
self.wait()
def json_from_url( self, url ):
self.visit_url( url )
return from_json_string( self.last_page() )
-
+
# Functions associated with histories
def get_history_from_api( self, encoded_history_id=None ):
if encoded_history_id is None:
@@ -255,7 +269,7 @@
def get_latest_history( self ):
return self.json_from_url( '/api/histories' )[ 0 ]
-
+
def find_hda_by_dataset_name( self, name, history=None ):
if history is None:
history = self.get_history_from_api()
@@ -269,7 +283,7 @@
self.visit_page( "history" )
page = self.last_page()
if page.find( 'error' ) > -1:
- raise AssertionError('Errors in the history for user %s' % self.user )
+ raise AssertionError( 'Errors in the history for user %s' % self.user )
def check_history_for_string( self, patt, show_deleted=False ):
"""Breaks patt on whitespace and searches for each element seperately in the history"""
@@ -320,7 +334,7 @@
# twill stores the regex match in a special stack variable
match = twill.namespaces.get_twill_glocals()[1][ '__match__' ]
json_data = from_json_string( match )
- assert check_fn( json_data ), 'failed check_fn: %s' %( check_fn.func_name )
+ assert check_fn( json_data ), 'failed check_fn: %s' % ( check_fn.func_name )
except Exception, exc:
log.error( exc, exc_info=True )
@@ -374,6 +388,7 @@
num_deleted = len( id.split( ',' ) )
self.home()
self.visit_page( "history/list?operation=delete&id=%s" % ( id ) )
+
check_str = 'Deleted %d %s' % ( num_deleted, iff( num_deleted != 1, "histories", "history" ) )
self.check_page_for_string( check_str )
self.home()
@@ -423,7 +438,7 @@
if active_datasets:
self.check_page_for_string( 'Create</a> a new empty history' )
self.check_page_for_string( 'Construct workflow</a> from current history' )
- self.check_page_for_string( 'Copy</a> current history' )
+ self.check_page_for_string( 'Copy</a> current history' )
self.check_page_for_string( 'Share</a> current history' )
self.check_page_for_string( 'Change default permissions</a> for current history' )
if histories_shared_by_others:
@@ -447,7 +462,7 @@
def rename_history( self, id, old_name, new_name ):
"""Rename an existing history"""
self.home()
- self.visit_page( "history/rename?id=%s&name=%s" %( id, new_name ) )
+ self.visit_page( "history/rename?id=%s&name=%s" % ( id, new_name ) )
check_str = 'History: %s renamed to: %s' % ( old_name, urllib.unquote( new_name ) )
self.check_page_for_string( check_str )
self.home()
@@ -476,6 +491,7 @@
for check_str in action_strings_displayed:
self.check_page_for_string( check_str )
tc.fv( 'share_restricted', 'action', action )
+
tc.submit( "share_restricted_button" )
for check_str in action_strings_displayed_after_submit:
self.check_page_for_string( check_str )
@@ -495,6 +511,7 @@
# If we have an action, then we are sharing datasets with users that do not have access permissions on them
tc.fv( 'share_restricted', 'action', action )
tc.submit( "share_restricted_button" )
+
for check_str in action_strings_displayed:
self.check_page_for_string( check_str )
self.home()
@@ -525,6 +542,7 @@
for check_str in strings_displayed:
self.check_page_for_string( check_str )
self.home()
+
def view_stored_deleted_histories( self, strings_displayed=[] ):
self.home()
self.visit_page( "history/list?f-deleted=True" )
@@ -534,12 +552,14 @@
for check_str in strings_displayed:
self.check_page_for_string( check_str )
self.home()
+
def view_shared_histories( self, strings_displayed=[] ):
self.home()
self.visit_page( "history/list_shared" )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
self.home()
+
def copy_history( self, history_id, copy_choice, strings_displayed=[], strings_displayed_after_submit=[] ):
self.home()
self.visit_page( "history/copy?id=%s" % history_id )
@@ -550,6 +570,7 @@
for check_str in strings_displayed_after_submit:
self.check_page_for_string( check_str )
self.home()
+
def make_accessible_via_link( self, history_id, strings_displayed=[], strings_displayed_after_submit=[] ):
self.home()
self.visit_page( "history/list?operation=share+or+publish&id=%s" % history_id )
@@ -562,6 +583,7 @@
for check_str in strings_displayed_after_submit:
self.check_page_for_string( check_str )
self.home()
+
def disable_access_via_link( self, history_id, strings_displayed=[], strings_displayed_after_submit=[] ):
self.home()
self.visit_page( "history/list?operation=share+or+publish&id=%s" % history_id )
@@ -574,6 +596,7 @@
for check_str in strings_displayed_after_submit:
self.check_page_for_string( check_str )
self.home()
+
def import_history_via_url( self, history_id, email, strings_displayed_after_submit=[] ):
self.home()
self.visit_page( "history/imp?&id=%s" % history_id )
@@ -584,9 +607,10 @@
# Functions associated with datasets (history items) and meta data
def _get_job_stream_output( self, hda_id, stream, format ):
self.visit_page( "datasets/%s/%s" % ( self.security.encode_id( hda_id ), stream ) )
+
output = self.last_page()
if format:
- msg = "---------------------- >> begin tool %s << -----------------------\n" % stream
+ msg = "---------------------- >> begin tool %s << -----------------------\n" % stream
msg += output + "\n"
msg += "----------------------- >> end tool %s << ------------------------\n" % stream
else:
@@ -609,43 +633,48 @@
"""Looks for 'patt' in the edit page when editing a dataset"""
data_list = self.get_history_as_data_list()
self.assertTrue( data_list )
- if hid is None: # take last hid
+ if hid is None: # take last hid
elem = data_list[-1]
hid = int( elem.get('hid') )
self.assertTrue( hid )
self.visit_page( "dataset/edit?hid=%s" % hid )
for subpatt in patt.split():
tc.find(subpatt)
+
def delete_history_item( self, hda_id, strings_displayed=[] ):
"""Deletes an item from a history"""
try:
hda_id = int( hda_id )
except:
- raise AssertionError, "Invalid hda_id '%s' - must be int" % hda_id
+ raise AssertionError( "Invalid hda_id '%s' - must be int" % hda_id )
self.visit_url( "%s/datasets/%s/delete?show_deleted_on_refresh=False" % ( self.url, self.security.encode_id( hda_id ) ) )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
+
def undelete_history_item( self, hda_id, strings_displayed=[] ):
"""Un-deletes a deleted item in a history"""
try:
hda_id = int( hda_id )
except:
- raise AssertionError, "Invalid hda_id '%s' - must be int" % hda_id
+ raise AssertionError( "Invalid hda_id '%s' - must be int" % hda_id )
self.visit_url( "%s/datasets/%s/undelete" % ( self.url, self.security.encode_id( hda_id ) ) )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
+
def display_history_item( self, hda_id, strings_displayed=[] ):
"""Displays a history item - simulates eye icon click"""
self.visit_url( '%s/datasets/%s/display/' % ( self.url, self.security.encode_id( hda_id ) ) )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
self.home()
+
def view_history( self, history_id, strings_displayed=[] ):
"""Displays a history for viewing"""
self.visit_url( '%s/history/view?id=%s' % ( self.url, self.security.encode_id( history_id ) ) )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
self.home()
+
def edit_hda_attribute_info( self, hda_id, new_name='', new_info='', new_dbkey='', new_startcol='',
strings_displayed=[], strings_not_displayed=[] ):
"""Edit history_dataset_association attribute information"""
@@ -673,14 +702,16 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) incorrectly displayed on Edit Attributes page." % check_str
+ raise AssertionError( "String (%s) incorrectly displayed on Edit Attributes page." % check_str )
except:
pass
self.home()
+
def check_hda_attribute_info( self, hda_id, strings_displayed=[] ):
"""Edit history_dataset_association attribute information"""
for check_str in strings_displayed:
self.check_page_for_string( check_str )
+
def auto_detect_metadata( self, hda_id ):
"""Auto-detect history_dataset_association metadata"""
self.home()
@@ -695,6 +726,7 @@
self.check_page_for_string( 'Attributes updated' )
#self.check_page_for_string( 'Attributes updated' )
self.home()
+
def convert_format( self, hda_id, target_type ):
"""Convert format of history_dataset_association"""
self.home()
@@ -703,8 +735,9 @@
tc.fv( 'convert_data', 'target_type', target_type )
tc.submit( 'convert_data' )
self.check_page_for_string( 'The file conversion of Convert BED to GFF on data' )
- self.wait() #wait for the format convert tool to finish before returning
+ self.wait() # wait for the format convert tool to finish before returning
self.home()
+
def change_datatype( self, hda_id, datatype ):
"""Change format of history_dataset_association"""
self.home()
@@ -714,6 +747,7 @@
tc.submit( 'change' )
self.check_page_for_string( 'Changed the type of dataset' )
self.home()
+
def copy_history_item( self, source_dataset_id=None, target_history_id=None, all_target_history_ids=[],
deleted_history_ids=[] ):
"""
@@ -730,10 +764,10 @@
for id in deleted_history_ids:
try:
self.check_page_for_string( id )
- raise AssertionError, "deleted history id %d displayed in list of target histories" % id
+ raise AssertionError( "deleted history id %d displayed in list of target histories" % id )
except:
pass
-
+
tc.fv( '1', 'target_history_id', target_history_id )
tc.submit( 'do_copy' )
check_str = '1 dataset copied to 1 history'
@@ -760,17 +794,17 @@
def makeTfname(self, fname=None):
"""safe temp name - preserve the file extension for tools that interpret it"""
- suffix = os.path.split(fname)[-1] # ignore full path
- fd,temp_prefix = tempfile.mkstemp(prefix='tmp',suffix=suffix)
+ suffix = os.path.split(fname)[-1] # ignore full path
+ fd, temp_prefix = tempfile.mkstemp(prefix='tmp', suffix=suffix)
return temp_prefix
def verify_dataset_correctness( self, filename, hid=None, wait=True, maxseconds=120, attributes=None, shed_tool_id=None ):
"""Verifies that the attributes and contents of a history item meet expectations"""
if wait:
- self.wait( maxseconds=maxseconds ) #wait for job to finish
+ self.wait( maxseconds=maxseconds ) # wait for job to finish
data_list = self.get_history_as_data_list()
self.assertTrue( data_list )
- if hid is None: # take last hid
+ if hid is None: # take last hid
elem = data_list[-1]
hid = str( elem.get('hid') )
else:
@@ -804,9 +838,9 @@
raise AssertionError( errmsg )
if filename is not None:
local_name = self.get_filename( filename, shed_tool_id=shed_tool_id )
- temp_name = self.makeTfname(fname = filename)
+ temp_name = self.makeTfname(fname=filename)
file( temp_name, 'wb' ).write( data )
-
+
# if the server's env has GALAXY_TEST_SAVE, save the output file to that dir
if self.keepOutdir:
ofn = os.path.join( self.keepOutdir, os.path.basename( local_name ) )
@@ -814,12 +848,11 @@
try:
shutil.copy( temp_name, ofn )
except Exception, exc:
- error_log_msg = ( 'TwillTestCase could not save output file %s to %s: ' % ( temp_name, ofn ) )
+ error_log_msg = ( 'TwillTestCase could not save output file %s to %s: ' % ( temp_name, ofn ) )
error_log_msg += str( exc )
log.error( error_log_msg, exc_info=True )
else:
log.debug('## GALAXY_TEST_SAVE=%s. saved %s' % ( self.keepOutdir, ofn ) )
-
try:
# have to nest try-except in try-finally to handle 2.4
try:
@@ -837,15 +870,15 @@
elif compare == 're_match_multiline':
self.files_re_match_multiline( local_name, temp_name, attributes=attributes )
elif compare == 'sim_size':
- delta = attributes.get('delta','100')
+ delta = attributes.get('delta', '100')
s1 = len(data)
s2 = os.path.getsize(local_name)
- if abs(s1-s2) > int(delta):
- raise Exception, 'Files %s=%db but %s=%db - compare (delta=%s) failed' % (temp_name,s1,local_name,s2,delta)
+ if abs(s1 - s2) > int(delta):
+ raise Exception( 'Files %s=%db but %s=%db - compare (delta=%s) failed' % (temp_name, s1, local_name, s2, delta) )
elif compare == "contains":
self.files_contains( local_name, temp_name, attributes=attributes )
else:
- raise Exception, 'Unimplemented Compare type: %s' % compare
+ raise Exception( 'Unimplemented Compare type: %s' % compare )
if extra_files:
self.verify_extra_files_content( extra_files, elem.get( 'id' ), shed_tool_id=shed_tool_id )
except AssertionError, err:
@@ -876,22 +909,22 @@
for filename in os.listdir( self.get_filename( extra_value, shed_tool_id=shed_tool_id ) ):
files_list.append( ( filename, os.path.join( extra_value, filename ), extra_attributes ) )
else:
- raise ValueError, 'unknown extra_files type: %s' % extra_type
+ raise ValueError( 'unknown extra_files type: %s' % extra_type )
for filename, filepath, attributes in files_list:
self.verify_composite_datatype_file_content( filepath, hda_id, base_name=filename, attributes=attributes, shed_tool_id=shed_tool_id )
-
+
def verify_composite_datatype_file_content( self, file_name, hda_id, base_name=None, attributes=None, shed_tool_id=None ):
local_name = self.get_filename( file_name, shed_tool_id=shed_tool_id )
if base_name is None:
base_name = os.path.split(file_name)[-1]
- temp_name = self.makeTfname(fname = base_name)
+ temp_name = self.makeTfname(fname=base_name)
self.visit_url( "%s/datasets/%s/display/%s" % ( self.url, self.security.encode_id( hda_id ), base_name ) )
data = self.last_page()
file( temp_name, 'wb' ).write( data )
if self.keepOutdir > '':
- ofn = os.path.join(self.keepOutdir,base_name)
- shutil.copy(temp_name,ofn)
- log.debug('## GALAXY_TEST_SAVE=%s. saved %s' % (self.keepOutdir,ofn))
+ ofn = os.path.join(self.keepOutdir, base_name)
+ shutil.copy(temp_name, ofn)
+ log.debug('## GALAXY_TEST_SAVE=%s. saved %s' % (self.keepOutdir, ofn))
try:
# have to nest try-except in try-finally to handle 2.4
try:
@@ -905,13 +938,13 @@
elif compare == 're_match_multiline':
self.files_re_match_multiline( local_name, temp_name, attributes=attributes )
elif compare == 'sim_size':
- delta = attributes.get('delta','100')
+ delta = attributes.get('delta', '100')
s1 = len(data)
s2 = os.path.getsize(local_name)
- if abs(s1-s2) > int(delta):
- raise Exception, 'Files %s=%db but %s=%db - compare (delta=%s) failed' % (temp_name,s1,local_name,s2,delta)
+ if abs(s1 - s2) > int(delta):
+ raise Exception( 'Files %s=%db but %s=%db - compare (delta=%s) failed' % (temp_name, s1, local_name, s2, delta) )
else:
- raise Exception, 'Unimplemented Compare type: %s' % compare
+ raise Exception( 'Unimplemented Compare type: %s' % compare )
except AssertionError, err:
errmsg = 'Composite file (%s) of History item %s different than expected, difference (using %s):\n' % ( base_name, hda_id, compare )
errmsg += str( err )
@@ -925,7 +958,7 @@
return True
def is_binary( self, filename ):
- temp = open( filename, "U" ) # why is this not filename? Where did temp_name come from
+ temp = open( filename, "U" ) # why is this not filename? Where did temp_name come from
lineno = 0
for line in temp:
lineno += 1
@@ -949,7 +982,7 @@
# Functions associated with user accounts
def create( self, cntrller='user', email='test(a)bx.psu.edu', password='testuser', username='admin-user', redirect='' ):
- # HACK: don't use panels because late_javascripts() messes up the twill browser and it
+ # HACK: don't use panels because late_javascripts() messes up the twill browser and it
# can't find form fields (and hence user can't be logged in).
self.visit_url( "%s/user/create?cntrller=%s&use_panels=False" % ( self.url, cntrller ) )
tc.fv( 'registration', 'email', email )
@@ -980,6 +1013,7 @@
except:
pass
return previously_created, username_taken, invalid_username
+
def create_user_with_info( self, email, password, username, user_info_values, user_type_fd_id='', cntrller='user',
strings_displayed=[], strings_displayed_after_submit=[] ):
# This method creates a new user with associated info
@@ -998,6 +1032,7 @@
for check_str in strings_displayed:
self.check_page_for_string( check_str)
tc.submit( "create_user_button" )
+
def edit_user_info( self, cntrller='user', id='', new_email='', new_username='', password='', new_password='',
info_values=[], strings_displayed=[], strings_displayed_after_submit=[] ):
if cntrller == 'admin':
@@ -1021,51 +1056,53 @@
tc.submit( "change_password_button" )
if info_values:
for index, ( field_name, info_value ) in enumerate( info_values ):
- field_index = index + 1
tc.fv( "user_info", field_name, info_value )
tc.submit( "edit_user_info_button" )
for check_str in strings_displayed_after_submit:
self.check_page_for_string( check_str )
self.home()
+
def user_set_default_permissions( self, cntrller='user', permissions_out=[], permissions_in=[], role_id='2' ):
- # role.id = 2 is Private Role for test2(a)bx.psu.edu
- # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value
+ # role.id = 2 is Private Role for test2(a)bx.psu.edu
+ # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value
# in each select list or twill throws an exception, which is: ParseError: OPTION outside of SELECT
- # Due to this bug, we'll bypass visiting the page, and simply pass the permissions on to the
+ # Due to this bug, we'll bypass visiting the page, and simply pass the permissions on to the
# /user/set_default_permissions method.
url = "user/set_default_permissions?cntrller=%s&update_roles_button=Save&id=None" % cntrller
for po in permissions_out:
key = '%s_out' % po
- url ="%s&%s=%s" % ( url, key, str( role_id ) )
+ url = "%s&%s=%s" % ( url, key, str( role_id ) )
for pi in permissions_in:
key = '%s_in' % pi
- url ="%s&%s=%s" % ( url, key, str( role_id ) )
+ url = "%s&%s=%s" % ( url, key, str( role_id ) )
self.visit_url( "%s/%s" % ( self.url, url ) )
self.check_page_for_string( 'Default new history permissions have been changed.' )
self.home()
- def history_set_default_permissions( self, permissions_out=[], permissions_in=[], role_id=3 ): # role.id = 3 is Private Role for test3(a)bx.psu.edu
- # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value
+
+ def history_set_default_permissions( self, permissions_out=[], permissions_in=[], role_id=3 ): # role.id = 3 is Private Role for test3(a)bx.psu.edu
+ # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value
# in each select list or twill throws an exception, which is: ParseError: OPTION outside of SELECT
- # Due to this bug, we'll bypass visiting the page, and simply pass the permissions on to the
+ # Due to this bug, we'll bypass visiting the page, and simply pass the permissions on to the
# /user/set_default_permissions method.
url = "root/history_set_default_permissions?update_roles_button=Save&id=None&dataset=True"
for po in permissions_out:
key = '%s_out' % po
- url ="%s&%s=%s" % ( url, key, str( role_id ) )
+ url = "%s&%s=%s" % ( url, key, str( role_id ) )
for pi in permissions_in:
key = '%s_in' % pi
- url ="%s&%s=%s" % ( url, key, str( role_id ) )
+ url = "%s&%s=%s" % ( url, key, str( role_id ) )
self.home()
self.visit_url( "%s/%s" % ( self.url, url ) )
self.check_page_for_string( 'Default history permissions have been changed.' )
self.home()
+
def login( self, email='test(a)bx.psu.edu', password='testuser', username='admin-user', redirect='' ):
# test(a)bx.psu.edu is configured as an admin user
previously_created, username_taken, invalid_username = \
self.create( email=email, password=password, username=username, redirect=redirect )
if previously_created:
# The acount has previously been created, so just login.
- # HACK: don't use panels because late_javascripts() messes up the twill browser and it
+ # HACK: don't use panels because late_javascripts() messes up the twill browser and it
# can't find form fields (and hence user can't be logged in).
self.visit_url( "%s/user/login?use_panels=False" % self.url )
self.submit_form( 1, 'login_button', email=email, redirect=redirect, password=password )
@@ -1075,7 +1112,7 @@
self.visit_page( "user/logout" )
self.check_page_for_string( "You have been logged out" )
self.home()
-
+
# Functions associated with browsers, cookies, HTML forms and page visits
def check_for_strings( self, strings_displayed=[], strings_not_displayed=[] ):
@@ -1087,15 +1124,15 @@
self.check_string_not_in_page( string )
def check_page_for_string( self, patt ):
- """Looks for 'patt' in the current browser page"""
+ """Looks for 'patt' in the current browser page"""
page = self.last_page()
if page.find( patt ) == -1:
fname = self.write_temp_file( page )
errmsg = "no match to '%s'\npage content written to '%s'" % ( patt, fname )
raise AssertionError( errmsg )
-
+
def check_string_count_in_page( self, patt, min_count ):
- """Checks the number of 'patt' occurrences in the current browser page"""
+ """Checks the number of 'patt' occurrences in the current browser page"""
page = self.last_page()
patt_count = page.count( patt )
# The number of occurrences of patt in the page should be at least min_count
@@ -1104,15 +1141,15 @@
fname = self.write_temp_file( page )
errmsg = "%i occurrences of '%s' found instead of %i.\npage content written to '%s' " % ( min_count, patt, patt_count, fname )
raise AssertionError( errmsg )
-
+
def check_string_not_in_page( self, patt ):
- """Checks to make sure 'patt' is NOT in the page."""
+ """Checks to make sure 'patt' is NOT in the page."""
page = self.last_page()
if page.find( patt ) != -1:
fname = self.write_temp_file( page )
errmsg = "string (%s) incorrectly displayed in page.\npage content written to '%s'" % ( patt, fname )
raise AssertionError( errmsg )
-
+
def check_page(self, strings_displayed, strings_displayed_count, strings_not_displayed):
"""Checks a page for strings displayed, not displayed and number of occurrences of a string"""
for check_str in strings_displayed:
@@ -1122,7 +1159,6 @@
for check_str in strings_not_displayed:
self.check_string_not_in_page( check_str )
-
def write_temp_file( self, content, suffix='.html' ):
fd, fname = tempfile.mkstemp( suffix=suffix, prefix='twilltestcase-' )
f = os.fdopen( fd, "w" )
@@ -1174,50 +1210,50 @@
for i, control in enumerate( f.controls ):
formcontrols.append( "control %d: %s" % ( i, str( control ) ) )
for i, control in enumerate( f.controls ):
- if not hc_prefix in str( control ):
- try:
- #check if a repeat element needs to be added
- if control.name is not None:
- if control.name not in kwd and control.name.endswith( '_add' ):
- #control name doesn't exist, could be repeat
- repeat_startswith = control.name[0:-4]
- if repeat_startswith and not [ c_name for c_name in controls.keys() if c_name.startswith( repeat_startswith ) ] and [ c_name for c_name in kwd.keys() if c_name.startswith( repeat_startswith ) ]:
- tc.browser.clicked( f, control )
- tc.submit( control.name )
+ if not hc_prefix in str( control ):
+ try:
+ #check if a repeat element needs to be added
+ if control.name is not None:
+ if control.name not in kwd and control.name.endswith( '_add' ):
+ #control name doesn't exist, could be repeat
+ repeat_startswith = control.name[0:-4]
+ if repeat_startswith and not [ c_name for c_name in controls.keys() if c_name.startswith( repeat_startswith ) ] and [ c_name for c_name in kwd.keys() if c_name.startswith( repeat_startswith ) ]:
+ tc.browser.clicked( f, control )
+ tc.submit( control.name )
+ return self.submit_form( form_no=form_no, button=button, **kwd )
+ # Check for refresh_on_change attribute, submit a change if required
+ if hasattr( control, 'attrs' ) and 'refresh_on_change' in control.attrs.keys():
+ changed = False
+ # For DataToolParameter, control.value is the HDA id, but kwd contains the filename.
+ # This loop gets the filename/label for the selected values.
+ item_labels = [ item.attrs[ 'label' ] for item in control.get_items() if item.selected ]
+ for value in kwd[ control.name ]:
+ if value not in control.value and True not in [ value in item_label for item_label in item_labels ]:
+ changed = True
+ break
+ if changed:
+ # Clear Control and set to proper value
+ control.clear()
+ # kwd[control.name] should be a singlelist
+ for elem in kwd[ control.name ]:
+ tc.fv( f.name, control.name, str( elem ) )
+ # Create a new submit control, allows form to refresh, instead of going to next page
+ control = ClientForm.SubmitControl( 'SubmitControl', '___refresh_grouping___', {'name': 'refresh_grouping'} )
+ control.add_to_form( f )
+ control.fixup()
+ # Submit for refresh
+ tc.submit( '___refresh_grouping___' )
return self.submit_form( form_no=form_no, button=button, **kwd )
- # Check for refresh_on_change attribute, submit a change if required
- if hasattr( control, 'attrs' ) and 'refresh_on_change' in control.attrs.keys():
- changed = False
- # For DataToolParameter, control.value is the HDA id, but kwd contains the filename.
- # This loop gets the filename/label for the selected values.
- item_labels = [ item.attrs[ 'label' ] for item in control.get_items() if item.selected ]
- for value in kwd[ control.name ]:
- if value not in control.value and True not in [ value in item_label for item_label in item_labels ]:
- changed = True
- break
- if changed:
- # Clear Control and set to proper value
- control.clear()
- # kwd[control.name] should be a singlelist
- for elem in kwd[ control.name ]:
- tc.fv( f.name, control.name, str( elem ) )
- # Create a new submit control, allows form to refresh, instead of going to next page
- control = ClientForm.SubmitControl( 'SubmitControl', '___refresh_grouping___', {'name':'refresh_grouping'} )
- control.add_to_form( f )
- control.fixup()
- # Submit for refresh
- tc.submit( '___refresh_grouping___' )
- return self.submit_form( form_no=form_no, button=button, **kwd )
- except Exception, e:
- log.exception( "In submit_form, continuing, but caught exception." )
- for formcontrol in formcontrols:
- log.debug( formcontrol )
- continue
- controls[ control.name ] = control
+ except Exception:
+ log.exception( "In submit_form, continuing, but caught exception." )
+ for formcontrol in formcontrols:
+ log.debug( formcontrol )
+ continue
+ controls[ control.name ] = control
# No refresh_on_change attribute found in current form, so process as usual
for control_name, control_value in kwd.items():
if control_name not in controls:
- continue # these cannot be handled safely - cause the test to barf out
+ continue # these cannot be handled safely - cause the test to barf out
if not isinstance( control_value, list ):
control_value = [ control_value ]
control = controls[ control_name ]
@@ -1247,7 +1283,7 @@
else:
for elem in control_value:
control.get( name=elem ).selected = True
- else: # control.is_of_kind( "singlelist" )
+ else: # control.is_of_kind( "singlelist" )
for elem in control_value:
try:
tc.fv( f.name, control.name, str( elem ) )
@@ -1298,15 +1334,16 @@
control.clear()
tc.fv( f.name, control.name, value )
# Create a new submit control, allows form to refresh, instead of going to next page
- control = ClientForm.SubmitControl( 'SubmitControl', '___refresh_grouping___', {'name':'refresh_grouping'} )
+ control = ClientForm.SubmitControl( 'SubmitControl', '___refresh_grouping___', {'name': 'refresh_grouping'} )
control.add_to_form( f )
control.fixup()
# Submit for refresh
tc.submit( '___refresh_grouping___' )
+
def visit_page( self, page ):
# tc.go("./%s" % page)
if not page.startswith( "/" ):
- page = "/" + page
+ page = "/" + page
tc.go( self.url + page )
tc.code( 200 )
@@ -1322,7 +1359,7 @@
if repeat_name is not None:
repeat_button = '%s_add' % repeat_name
# Must click somewhere in tool_form, to disambiguate what form
- # is being targetted.
+ # is being targetted.
tc.browser.clicked( tc.browser.get_form( 'tool_form' ), None )
# Submit the "repeat" form button to add an input)
tc.submit( repeat_button )
@@ -1336,9 +1373,9 @@
galaxy_url = urllib.quote_plus( "%s/tool_runner/index?" % self.url )
self.visit_url( "http://genome.ucsc.edu/cgi-bin/hgTables?GALAXY_URL=%s&hgta_compressType=non…" % ( galaxy_url, tool_id, track_string ) )
tc.fv( "mainForm", "hgta_doTopSubmit", "get output" )
- self.submit_form( button="get output" )#, **track_params )
+ self.submit_form( button="get output" )
tc.fv( 2, "hgta_doGalaxyQuery", "Send query to Galaxy" )
- self.submit_form( button="Send query to Galaxy" )#, **output_params ) #AssertionError: Attempting to set field 'fbQual' to value '['whole']' in form 'None' threw exception: no matching forms! control: <RadioControl(fbQual=[whole, upstreamAll, endAll])>
+ self.submit_form( button="Send query to Galaxy" )
def get_running_datasets( self ):
self.visit_url( '/api/histories' )
@@ -1363,7 +1400,7 @@
slept += sleep_amount
sleep_amount *= 2
if slept + sleep_amount > maxseconds:
- sleep_amount = maxseconds - slept # don't overshoot maxseconds
+ sleep_amount = maxseconds - slept # don't overshoot maxseconds
else:
break
assert slept < maxseconds
@@ -1373,7 +1410,7 @@
def create_new_account_as_admin( self, email='test4(a)bx.psu.edu', password='testuser',
username='regular-user4', redirect='' ):
"""Create a new account for another user"""
- # HACK: don't use panels because late_javascripts() messes up the twill browser and it
+ # HACK: don't use panels because late_javascripts() messes up the twill browser and it
# can't find form fields (and hence user can't be logged in).
self.visit_url( "%s/user/create?cntrller=admin" % self.url )
self.submit_form( 1, 'create_user_button', email=email, redirect=redirect, password=password, confirm=password, username=username )
@@ -1409,6 +1446,7 @@
tc.submit( "reset_user_password_button" )
self.check_page_for_string( "Passwords reset for 1 user." )
self.home()
+
def mark_user_deleted( self, user_id, email='' ):
"""Mark a user as deleted"""
self.home()
@@ -1416,6 +1454,7 @@
check_str = "Deleted 1 users"
self.check_page_for_string( check_str )
self.home()
+
def undelete_user( self, user_id, email='' ):
"""Undelete a user"""
self.home()
@@ -1423,6 +1462,7 @@
check_str = "Undeleted 1 users"
self.check_page_for_string( check_str )
self.home()
+
def purge_user( self, user_id, email ):
"""Purge a user account"""
self.home()
@@ -1430,6 +1470,7 @@
check_str = "Purged 1 users"
self.check_page_for_string( check_str )
self.home()
+
def manage_roles_and_groups_for_user( self, user_id, in_role_ids=[], out_role_ids=[],
in_group_ids=[], out_group_ids=[], strings_displayed=[] ):
self.home()
@@ -1448,12 +1489,13 @@
for check_str in strings_displayed:
self.check_page_for_string( check_str )
self.home()
+ # Tests associated with roles
- # Tests associated with roles
def browse_roles( self, strings_displayed=[] ):
self.visit_url( '%s/admin/roles' % self.url )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
+
def create_role( self,
name='Role One',
description="This is Role One",
@@ -1488,6 +1530,7 @@
self.visit_url( "%s/admin/roles" % self.url )
self.check_page_for_string( name )
self.home()
+
def rename_role( self, role_id, name='Role One Renamed', description='This is Role One Re-described' ):
"""Rename a role"""
self.home()
@@ -1497,6 +1540,7 @@
tc.fv( "1", "description", description )
tc.submit( "rename_role_button" )
self.home()
+
def mark_role_deleted( self, role_id, role_name ):
"""Mark a role as deleted"""
self.home()
@@ -1504,6 +1548,7 @@
check_str = "Deleted 1 roles: %s" % role_name
self.check_page_for_string( check_str )
self.home()
+
def undelete_role( self, role_id, role_name ):
"""Undelete an existing role"""
self.home()
@@ -1511,6 +1556,7 @@
check_str = "Undeleted 1 roles: %s" % role_name
self.check_page_for_string( check_str )
self.home()
+
def purge_role( self, role_id, role_name ):
"""Purge an existing role"""
self.home()
@@ -1518,6 +1564,7 @@
check_str = "Purged 1 roles: %s" % role_name
self.check_page_for_string( check_str )
self.home()
+
def associate_users_and_groups_with_role( self, role_id, role_name, user_ids=[], group_ids=[] ):
self.home()
url = "%s/admin/role?id=%s&role_members_edit_button=Save" % ( self.url, role_id )
@@ -1548,10 +1595,12 @@
self.visit_url( "%s/admin/groups" % self.url )
self.check_page_for_string( name )
self.home()
+
def browse_groups( self, strings_displayed=[] ):
self.visit_url( '%s/admin/groups' % self.url )
for check_str in strings_displayed:
self.check_page_for_string( check_str )
+
def rename_group( self, group_id, name='Group One Renamed' ):
"""Rename a group"""
self.home()
@@ -1560,6 +1609,7 @@
tc.fv( "1", "name", name )
tc.submit( "rename_group_button" )
self.home()
+
def associate_users_and_roles_with_group( self, group_id, group_name, user_ids=[], role_ids=[] ):
self.home()
url = "%s/admin/manage_users_and_roles_for_group?id=%s&group_roles_users_edit_button=Save" % ( self.url, group_id )
@@ -1571,6 +1621,7 @@
check_str = "Group '%s' has been updated with %d associated roles and %d associated users" % ( group_name, len( role_ids ), len( user_ids ) )
self.check_page_for_string( check_str )
self.home()
+
def mark_group_deleted( self, group_id, group_name ):
"""Mark a group as deleted"""
self.home()
@@ -1578,6 +1629,7 @@
check_str = "Deleted 1 groups: %s" % group_name
self.check_page_for_string( check_str )
self.home()
+
def undelete_group( self, group_id, group_name ):
"""Undelete an existing group"""
self.home()
@@ -1585,6 +1637,7 @@
check_str = "Undeleted 1 groups: %s" % group_name
self.check_page_for_string( check_str )
self.home()
+
def purge_group( self, group_id, group_name ):
"""Purge an existing group"""
self.home()
@@ -1628,7 +1681,7 @@
if num_options == 0:
# Default to 2 options
num_options = 2
- for index2 in range( 1, num_options+1 ):
+ for index2 in range( 1, num_options + 1 ):
tc.submit( "addoption_0" )
# Add contents to the new options fields
for index2 in range( num_options ):
@@ -1644,6 +1697,7 @@
for check_str in strings_displayed_after_submit:
self.check_page_for_string( check_str )
self.home()
+
def edit_form( self, id, form_type='', new_form_name='', new_form_desc='', field_dicts=[], field_index=0,
strings_displayed=[], strings_not_displayed=[], strings_displayed_after_submit=[] ):
"""Edit form details; name and description"""
@@ -1771,10 +1825,10 @@
url = "request_type/request_type_permissions?id=%s&update_roles_button=Save" % ( request_type_id )
for po in permissions_out:
key = '%s_out' % po
- url ="%s&%s=%s" % ( url, key, role_ids_str )
+ url = "%s&%s=%s" % ( url, key, role_ids_str )
for pi in permissions_in:
key = '%s_in' % pi
- url ="%s&%s=%s" % ( url, key, role_ids_str )
+ url = "%s&%s=%s" % ( url, key, role_ids_str )
self.home()
self.visit_url( "%s/%s" % ( self.url, url ) )
check_str = "Permissions updated for request type '%s'" % request_type_name
@@ -2080,7 +2134,7 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) incorrectly displayed when browing library." % check_str
+ raise AssertionError( "String (%s) incorrectly displayed when browing library." % check_str )
except:
pass
def browse_libraries_regular_user( self, strings_displayed=[], strings_not_displayed=[] ):
@@ -2090,7 +2144,7 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) incorrectly displayed when browing library." % check_str
+ raise AssertionError( "String (%s) incorrectly displayed when browing library." % check_str )
except:
pass
def browse_library( self, cntrller, library_id, show_deleted=False, strings_displayed=[], strings_not_displayed=[] ):
@@ -2100,7 +2154,7 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) incorrectly displayed when browing library." % check_str
+ raise AssertionError( "String (%s) incorrectly displayed when browing library." % check_str )
except:
pass
def create_library( self, name='Library One', description='This is Library One', synopsis='Synopsis for Library One' ):
@@ -2156,10 +2210,10 @@
url = "library_common/library_permissions?id=%s&cntrller=%s&update_roles_button=Save" % ( library_id, cntrller )
for po in permissions_out:
key = '%s_out' % po
- url ="%s&%s=%s" % ( url, key, role_ids_str )
+ url = "%s&%s=%s" % ( url, key, role_ids_str )
for pi in permissions_in:
key = '%s_in' % pi
- url ="%s&%s=%s" % ( url, key, role_ids_str )
+ url = "%s&%s=%s" % ( url, key, role_ids_str )
self.home()
self.visit_url( "%s/%s" % ( self.url, url ) )
check_str = "Permissions updated for library '%s'." % library_name
@@ -2208,7 +2262,7 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) incorrectly displayed." % check_str
+ raise AssertionError( "String (%s) incorrectly displayed." % check_str )
except:
pass
if template_refresh_field_contents:
@@ -2227,7 +2281,7 @@
for check_str in strings_not_displayed_after_submit:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) incorrectly displayed." % check_str
+ raise AssertionError( "String (%s) incorrectly displayed." % check_str )
except:
pass
self.home()
@@ -2301,10 +2355,10 @@
( self.url, cntrller, library_id, folder_id, id )
for po in permissions_out:
key = '%s_out' % po
- url ="%s&%s=%s" % ( url, key, role_ids_str )
+ url = "%s&%s=%s" % ( url, key, role_ids_str )
for pi in permissions_in:
key = '%s_in' % pi
- url ="%s&%s=%s" % ( url, key, role_ids_str )
+ url = "%s&%s=%s" % ( url, key, role_ids_str )
if permissions_in or permissions_out:
url += "&update_roles_button=Save"
self.visit_url( url )
@@ -2322,7 +2376,7 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) should not have been displayed on ldda info page." % check_str
+ raise AssertionError( "String (%s) should not have been displayed on ldda info page." % check_str )
except:
pass
self.home()
@@ -2354,7 +2408,7 @@
for check_str in strings_not_displayed:
try:
self.check_page_for_string( check_str )
- raise AssertionError, "String (%s) should not have been displayed on ldda Edit Attributes page." % check_str
+ raise AssertionError( "String (%s) should not have been displayed on ldda Edit Attributes page." % check_str )
except:
pass
self.home()
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d test/install_and_test_tool_shed_repositories/base/test_db_util.py
--- a/test/install_and_test_tool_shed_repositories/base/test_db_util.py
+++ b/test/install_and_test_tool_shed_repositories/base/test_db_util.py
@@ -30,6 +30,12 @@
model.ToolShedRepository.table.c.installed_changeset_revision == changeset_revision ) ) \
.one()
+def get_missing_tool_dependencies( repository ):
+ return sa_session.query( model.ToolDependency ) \
+ .filter( and_( model.ToolDependency.table.c.tool_shed_repository_id == repository_id,
+ model.ToolDependency.table.c.status != model.ToolDependency.installation_status.INSTALLED ) ) \
+ .all()
+
def get_private_role( user ):
for role in user.all_roles():
if role.name == user.email and role.description == 'Private Role for %s' % user.email:
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d test/install_and_test_tool_shed_repositories/base/twilltestcase.py
--- a/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
+++ b/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
@@ -61,8 +61,7 @@
changeset_revision = repository_info_dict[ 'changeset_revision' ]
encoded_repository_id = repository_info_dict[ 'repository_id' ]
tool_shed_url = repository_info_dict[ 'tool_shed_url' ]
- preview_params = urllib.urlencode( dict( repository_id=encoded_repository_id, changeset_revision=changeset_revision ) )
- self.visit_url( '%s/repository/preview_tools_in_changeset?%s' % ( tool_shed_url, preview_params ) )
+ # Pass galaxy_url to the tool shed in order to set cookies and redirects correctly.
install_params = urllib.urlencode( dict( repository_ids=encoded_repository_id,
changeset_revisions=changeset_revision,
galaxy_url=self.url ) )
@@ -136,35 +135,17 @@
break
time.sleep( 1 )
- def uninstall_repository( self, installed_repository, deactivate_only=False ):
+ def deactivate_or_uninstall_repository( self, installed_repository, deactivate=False ):
url = '/admin_toolshed/deactivate_or_uninstall_repository?id=%s' % self.security.encode_id( installed_repository.id )
self.visit_url( url )
- if deactivate_only:
+ if deactivate:
tc.fv ( 1, "remove_from_disk", 'false' )
else:
tc.fv ( 1, "remove_from_disk", 'true' )
tc.submit( 'deactivate_or_uninstall_repository_button' )
strings_displayed = [ 'The repository named' ]
- if deactivate_only:
+ if deactivate:
strings_displayed.append( 'has been deactivated' )
else:
strings_displayed.append( 'has been uninstalled' )
self.check_for_strings( strings_displayed, strings_not_displayed=[] )
- # Get all tool dependencies that are not in an installed state and uninstall them explicitly, so that the next installation attempt
- # may succeed.
- installed_state = model.ToolDependency.installation_status.INSTALLED
- tool_dependencies = test_db_util.get_tool_dependencies_for_installed_repository( installed_repository.id, exclude_status=installed_state )
- if len( tool_dependencies ) > 0:
- encoded_tool_dependency_ids = [ self.security.encode_id( tool_dependency.id ) for tool_dependency in tool_dependencies ]
- self.uninstall_tool_dependencies( self.security.encode_id( installed_repository.id ), encoded_tool_dependency_ids )
-
- def uninstall_tool_dependencies( self, encoded_repository_id, encoded_tool_dependency_ids ):
- tool_dependency_ids = ','.join( encoded_tool_dependency_ids )
- url = '/admin_toolshed/uninstall_tool_dependencies?repository_id=%s&inst_td_ids=%s&operation=uninstall' % \
- ( encoded_repository_id, tool_dependency_ids )
- self.visit_url( url )
- html = self.last_page()
- if 'uninstall_tool_dependencies' in html:
- tc.fv( 'uninstall_tool_dependencies', 'tool_dependency_ids', tool_dependency_ids )
- tc.submit( 'uninstall_tool_dependencies_button' )
-
\ No newline at end of file
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py
--- a/test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py
+++ b/test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py
@@ -16,7 +16,7 @@
# Install the repository through the web interface using twill.
self.install_repository( repository_info_dict )
- def do_uninstallation( self, repository_info_dict, deactivate_only=False ):
+ def do_deactivate_or_uninstall( self, repository_info_dict, deactivate=False ):
self.logout()
self.login( email='test(a)bx.psu.edu', username='test' )
admin_user = test_db_util.get_user( 'test(a)bx.psu.edu' )
@@ -27,7 +27,7 @@
repository_info_dict[ 'changeset_revision' ] )
admin_user_private_role = test_db_util.get_private_role( admin_user )
# Uninstall the repository through the web interface using twill.
- self.uninstall_repository( repository, deactivate_only )
+ self.deactivate_or_uninstall_repository( repository, deactivate )
def generate_install_method( repository_dict=None ):
"""Generate abstract test cases for the defined list of repositories."""
@@ -52,11 +52,11 @@
namespace[ 'install_repository_%s' % repository_dict[ 'name' ] ] = test_method
# The new.classobj function returns a new class object, with name name, derived
# from baseclasses (which should be a tuple of classes) and with namespace dict.
- new_class_obj = new.classobj( name, baseclasses, namespace )
+ new_class_obj = new.classobj( str( name ), baseclasses, namespace )
G[ name ] = new_class_obj
-def generate_uninstall_method( repository_dict=None, deactivate_only=False ):
- """Generate abstract test cases for the defined list of repositories."""
+def generate_deactivate_or_uninstall_method( repository_dict=None, deactivate=False ):
+ """Generate abstract test cases for the received repository_dict."""
if repository_dict is None:
return
# Push all the toolbox tests to module level
@@ -69,14 +69,15 @@
name = "TestUninstallRepository_%s_%s" % ( repository_dict[ 'name' ], repository_dict[ 'changeset_revision' ] )
baseclasses = ( InstallTestRepositories, )
namespace = dict()
- def make_uninstall_method( repository_dict ):
+ def make_deactivate_or_uninstall_method( repository_dict ):
def test_install_repository( self ):
- self.do_uninstallation( repository_dict, deactivate_only )
+ self.do_deactivate_or_uninstall( repository_dict, deactivate )
return test_install_repository
- test_method = make_uninstall_method( repository_dict )
- test_method.__doc__ = "Uninstall the repository %s." % repository_dict[ 'name' ]
- namespace[ 'uninstall_repository_%s_%s' % ( repository_dict[ 'name' ], repository_dict[ 'changeset_revision' ] ) ] = test_method
+ test_method = make_deactivate_or_uninstall_method( repository_dict )
+ test_method.__doc__ = "Deactivate or uninstall the repository %s." % repository_dict[ 'name' ]
+ namespace[ 'uninstall_repository_%s_%s' % ( str( repository_dict[ 'name' ] ), repository_dict[ 'changeset_revision' ] ) ] = test_method
# The new.classobj function returns a new class object, with name name, derived
# from baseclasses (which should be a tuple of classes) and with namespace dict.
- new_class_obj = new.classobj( name, baseclasses, namespace )
- G[ name ] = new_class_obj
\ No newline at end of file
+ new_class_obj = new.classobj( str( name ), baseclasses, namespace )
+ G[ name ] = new_class_obj
+
diff -r 1d7f6cd1e83ebf15813a7797cb4062f601b97abb -r fb1338d601ad2bcf9a5c1e2e3237283e2edb1e7d test/install_and_test_tool_shed_repositories/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/functional_tests.py
@@ -4,7 +4,24 @@
# order to run functional tests on repository tools after installation. The install_and_test_tool_shed_repositories.sh
# will execute this script with the appropriate parameters.
-import os, sys, shutil, tempfile, re, string, urllib, platform
+import atexit
+import httplib
+import logging
+import os
+import os.path
+import platform
+import random
+import re
+import shutil
+import socket
+import string
+import sys
+import tempfile
+import time
+import threading
+import unittest
+import urllib
+import urllib2
from time import strftime
# Assume we are run from the galaxy root directory, add lib to the python path
@@ -36,6 +53,9 @@
eggs.require( "Cheetah" )
eggs.require( "simplejson" )
+import simplejson
+import twill
+
# This should not be required, but it is under certain conditions, thanks to this bug: http://code.google.com/p/python-nose/issues/detail?id=284
eggs.require( "pysqlite" )
@@ -43,20 +63,17 @@
import install_and_test_tool_shed_repositories.base.test_db_util as test_db_util
import functional.test_toolbox as test_toolbox
-import atexit, logging, os, os.path, sys, tempfile, simplejson
-import twill, unittest, time
-import sys, threading, random
-import httplib, socket
from paste import httpserver
# This is for the galaxy application.
import galaxy.app
from galaxy.app import UniverseApplication
from galaxy.web import buildapp
-from galaxy.util import parse_xml
+from galaxy.util import parse_xml, asbool
from galaxy.util.json import from_json_string, to_json_string
-from tool_shed.util.shed_util_common import url_join
+import tool_shed.util.shed_util_common as suc
+from tool_shed.util import tool_dependency_util
import nose.core
import nose.config
@@ -243,27 +260,29 @@
return passed_tests
return []
-def execute_uninstall_method( app, deactivate_only=False ):
- # Clean out any generated tests.
+def deactivate_repository( app, repository_info_dict ):
+ # Clean out any generated tests. This is necessary for Twill.
remove_generated_tests( app )
sa_session = app.model.context.current
- repositories_to_uninstall = sa_session.query( app.model.ToolShedRepository ).all()
- for repository in repositories_to_uninstall:
- if repository.status in [ app.model.ToolShedRepository.installation_status.UNINSTALLED,
- app.model.ToolShedRepository.installation_status.DEACTIVATED ]:
- continue
- if repository.status not in [ app.model.ToolShedRepository.installation_status.ERROR,
- app.model.ToolShedRepository.installation_status.INSTALLED ]:
- repository.status = app.model.ToolShedRepository.installation_status.ERROR
- sa_session.add( repository )
- sa_session.flush()
- name = str( repository.name )
- owner = str( repository.owner )
- changeset_revision = str( repository.installed_changeset_revision )
- log.debug( 'Changeset revision %s of %s repository %s queued for uninstallation.', changeset_revision, repository.status, name )
- repository_dict = dict( name=name, owner=owner, changeset_revision=changeset_revision )
+ # The dict contains the only repository the app should have installed at this point.
+ repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( repository_info_dict[ 'name' ],
+ repository_info_dict[ 'owner' ],
+ repository_info_dict[ 'changeset_revision' ] )
+ # We have to do this through Twill, in order to maintain app.toolbox and shed_tool_conf.xml in a state that is valid for future tests.
+ for required_repository in repository.repository_dependencies:
+ repository_dict = dict( name=required_repository.name,
+ owner=required_repository.owner,
+ changeset_revision=required_repository.changeset_revision )
# Generate a test method to uninstall this repository through the embedded Galaxy application's web interface.
- test_install_repositories.generate_uninstall_method( repository_dict, deactivate_only )
+ test_install_repositories.generate_deactivate_or_uninstall_method( repository_dict, deactivate=True )
+ log.debug( 'Changeset revision %s of %s repository %s selected for deactivation.' % \
+ ( required_repository.changeset_revision, required_repository.status, required_repository.name ) )
+ repository_dict = dict( name=repository.name,
+ owner=repository.owner,
+ changeset_revision=repository.changeset_revision )
+ test_install_repositories.generate_deactivate_or_uninstall_method( repository_dict, deactivate=True )
+ log.debug( 'Changeset revision %s of %s repository %s selected for deactivation.' % \
+ ( repository.changeset_revision, repository.status, repository.name ) )
# Set up nose to run the generated uninstall method as a functional test.
test_config = nose.config.Config( env=os.environ, plugins=nose.plugins.manager.DefaultPluginManager() )
test_config.configure( sys.argv )
@@ -324,7 +343,7 @@
parts.insert( 0, 'api' )
elif 'api' not in parts:
parts.insert( 0, 'api' )
- url = url_join( base, *parts )
+ url = suc.url_join( base, *parts )
if key:
url += '?%s' % urllib.urlencode( dict( key=key ) )
else:
@@ -333,14 +352,6 @@
url += '&%s' % params
return url
-def get_failed_tool_dependencies( repository ):
- missing_dependencies = repository.missing_tool_dependencies
- for repository_dependency in repository.repository_dependencies:
- if not repository_dependency.includes_tool_dependencies:
- continue
- missing_dependencies.extend( get_failed_tool_dependencies( repository_dependency ) )
- return missing_dependencies
-
def get_latest_downloadable_changeset_revision( url, name, owner ):
api_url_parts = [ 'api', 'repositories', 'get_ordered_installable_revisions' ]
params = urllib.urlencode( dict( name=name, owner=owner ) )
@@ -351,6 +362,14 @@
else:
return '000000000000'
+def get_missing_tool_dependencies( repository ):
+ missing_tool_dependencies = repository.missing_tool_dependencies
+ for repository_dependency in repository.repository_dependencies:
+ if not repository_dependency.includes_tool_dependencies:
+ continue
+ missing_tool_dependencies.extend( get_missing_tool_dependencies( repository_dependency ) )
+ return missing_tool_dependencies
+
def get_repository_info_from_api( url, repository_info_dict ):
parts = [ 'api', 'repositories', repository_info_dict[ 'repository_id' ] ]
api_url = get_api_url( base=url, parts=parts )
@@ -459,6 +478,37 @@
return dict()
return tool_test_results
+def install_repository( repository_info_dict ):
+ """
+ The repository_info_dict looks something like:
+ {
+ "changeset_revision": "13fa22a258b5",
+ "contents_url": "/api/repositories/529fd61ab1c6cc36/contents",
+ "deleted": false,
+ "deprecated": false,
+ "description": "Convert column case.",
+ "downloadable": true,
+ "id": "529fd61ab1c6cc36",
+ "long_description": "This tool takes the specified columns and converts them to uppercase or lowercase.",
+ "malicious": false,
+ "name": "change_case",
+ "owner": "test",
+ "private": false,
+ "repository_id": "529fd61ab1c6cc36",
+ "times_downloaded": 0,
+ "tool_shed_url": "http://toolshed.local:10001",
+ "url": "/api/repository_revisions/529fd61ab1c6cc36",
+ "user_id": "529fd61ab1c6cc36"
+ }
+ """
+ data[ 'tool_shed_url' ] = repository_info_dict[ 'tool_shed_url' ]
+ data[ 'name' ] = repository_info_dict[ 'name' ]
+ data[ 'owner' ] = repository_info_dict[ 'owner' ]
+ data[ 'changeset_revision' ] = repository_info_dict[ 'changeset_revision' ]
+ data[ 'install_repository_dependencies' ] = True
+ data[ 'install_tool_dependencies' ] = True
+ submit( options.api, '%s%s' % ( galaxy_url.strip( '/' ), '/api/tool_shed_repositories/new/install_repository_revision' ), data )
+
def is_latest_downloadable_revision( url, repository_info_dict ):
latest_revision = get_latest_downloadable_changeset_revision( url, name=repository_info_dict[ 'name' ], owner=repository_info_dict[ 'owner' ] )
return str( repository_info_dict[ 'changeset_revision' ] ) == str( latest_revision )
@@ -534,7 +584,7 @@
if '-info_only' in sys.argv or 'GALAXY_INSTALL_TEST_INFO_ONLY' in os.environ:
return {}
else:
- return update( tool_shed_api_key, '%s' % ( url_join( galaxy_tool_shed_url, 'api', 'repository_revisions', metadata_id ) ), params, return_formatted=False )
+ return update( tool_shed_api_key, '%s' % ( suc.url_join( galaxy_tool_shed_url, 'api', 'repository_revisions', metadata_id ) ), params, return_formatted=False )
def remove_generated_tests( app ):
# Delete any configured tool functional tests from the test_toolbox.__dict__, otherwise nose will find them
@@ -558,6 +608,21 @@
for tool in tools_to_delete:
del app.toolbox.tools_by_id[ tool ]
+def remove_install_tests( app ):
+ # Delete any configured repository installation tests from the test_toolbox.__dict__, otherwise nose will find them
+ # and try to install the repository again while running tool functional tests.
+ tests_to_delete = []
+ global test_toolbox
+ # Push all the toolbox tests to module level
+ for key in test_install_repositories.__dict__:
+ if key.startswith( 'TestInstallRepository_' ):
+ log.info( 'Repository installation process found, deleting: %s', key )
+ # We can't delete this test just yet, we're still iterating over __dict__.
+ tests_to_delete.append( key )
+ for key in tests_to_delete:
+ # Now delete the tests found in the previous loop.
+ del test_install_repositories.__dict__[ key ]
+
def run_tests( test_config ):
loader = nose.loader.TestLoader( config=test_config )
test_config.plugins.addPlugin( ReportResults() )
@@ -585,6 +650,53 @@
for repository in repositories_by_owner[ owner ]:
print "# %s owned by %s, changeset revision %s" % ( repository[ 'name' ], repository[ 'owner' ], repository[ 'changeset_revision' ] )
+def uninstall_repository( app, repository_info_dict ):
+ # Clean out any generated tests. This is necessary for Twill.
+ remove_generated_tests( app )
+ sa_session = app.model.context.current
+ # The dict contains the only repository the app should have installed at this point.
+ repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( repository_info_dict[ 'name' ],
+ repository_info_dict[ 'owner' ],
+ repository_info_dict[ 'changeset_revision' ] )
+ # We have to do this through Twill, in order to maintain app.toolbox and shed_tool_conf.xml in a state that is valid for future tests.
+ for required_repository in repository.repository_dependencies:
+ repository_dict = dict( name=required_repository.name,
+ owner=required_repository.owner,
+ changeset_revision=required_repository.changeset_revision )
+ # Generate a test method to uninstall this repository through the embedded Galaxy application's web interface.
+ test_install_repositories.generate_deactivate_or_uninstall_method( repository_dict, deactivate=False )
+ log.debug( 'Changeset revision %s of %s repository %s selected for uninstallation.' % \
+ ( required_repository.changeset_revision, required_repository.status, required_repository.name ) )
+ repository_dict = dict( name=repository.name,
+ owner=repository.owner,
+ changeset_revision=repository.changeset_revision )
+ test_install_repositories.generate_deactivate_or_uninstall_method( repository_dict, deactivate=False )
+ log.debug( 'Changeset revision %s of %s repository %s selected for uninstallation.' % \
+ ( repository.changeset_revision, repository.status, repository.name ) )
+ # Set up nose to run the generated uninstall method as a functional test.
+ test_config = nose.config.Config( env=os.environ, plugins=nose.plugins.manager.DefaultPluginManager() )
+ test_config.configure( sys.argv )
+ # Run the uninstall method. This method uses the Galaxy web interface to uninstall the previously installed
+ # repository and delete it from disk.
+ result, _ = run_tests( test_config )
+ success = result.wasSuccessful()
+ return success
+
+def uninstall_tool_dependency( app, tool_dependency ):
+ # Clean out any generated tests. This is necessary for Twill.
+ tool_dependency_install_path = tool_dependency.installation_directory( app )
+ uninstalled, error_message = tool_dependency_util.remove_tool_dependency( app, tool_dependency )
+ if error_message:
+ log.debug( error_message )
+ sa_session = app.model.context.current
+ if not uninstalled or tool_dependency.status != app.model.ToolDependency.installation_status.UNINSTALLED:
+ tool_dependency.status = app.model.ToolDependency.installation_status.UNINSTALLED
+ sa_session.add( tool_dependency )
+ sa_session.flush()
+ if os.path.exists( tool_dependency_install_path ):
+ log.debug( 'Uninstallation of tool dependency succeeded, but the installation path still exists on the filesystem. It is now being explicitly deleted.')
+ suc.remove_dir( tool_dependency_install_path )
+
def main():
# ---- Configuration ------------------------------------------------------
galaxy_test_host = os.environ.get( 'GALAXY_INSTALL_TEST_HOST', default_galaxy_test_host )
@@ -601,10 +713,7 @@
os.mkdir( galaxy_test_tmp_dir )
galaxy_test_proxy_port = None
# Allow the option to keep or delete tool dependencies when a repository has been tested.
- if 'GALAXY_INSTALL_TEST_KEEP_TOOL_DEPENDENCIES' in os.environ:
- deactivate_only = True
- else:
- deactivate_only = False
+ deactivate = asbool( os.environ.get( 'GALAXY_INSTALL_TEST_KEEP_TOOL_DEPENDENCIES', False ) )
# Set up the configuration files for the Galaxy instance.
shed_tool_data_table_conf_file = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DATA_TABLE_CONF', os.path.join( galaxy_test_tmp_dir, 'test_shed_tool_data_table_conf.xml' ) )
galaxy_tool_data_table_conf_file = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_DATA_TABLE_CONF', tool_data_table_conf )
@@ -766,10 +875,7 @@
else:
raise Exception( "Test HTTP server did not return '200 OK' after 10 tries" )
log.info( "Embedded galaxy web server started" )
- if galaxy_test_proxy_port:
- log.info( "The embedded Galaxy application is running on %s:%s", galaxy_test_host, galaxy_test_proxy_port )
- else:
- log.info( "The embedded Galaxy application is running on %s:%s", galaxy_test_host, galaxy_test_port )
+ log.info( "The embedded Galaxy application is running on %s:%s", galaxy_test_host, galaxy_test_port )
log.info( "Repositories will be installed from the tool shed at %s", galaxy_tool_shed_url )
success = False
# If a tool_data_table_conf.test.xml file was found, add the entries from it into the app's tool data tables.
@@ -889,6 +995,7 @@
# Use the repository information dict to generate an install method that will install the repository into the embedded
# Galaxy application, with tool dependencies and repository dependencies, if any.
test_install_repositories.generate_install_method( repository_info_dict )
+ # Set the GALAXY_INSTALL_TEST_HOST variable so that Twill will have the Galaxy url to install repositories into.
os.environ[ 'GALAXY_INSTALL_TEST_HOST' ] = galaxy_test_host
# Configure nose to run the install method as a test.
test_config = nose.config.Config( env=os.environ, plugins=nose.plugins.manager.DefaultPluginManager() )
@@ -907,6 +1014,8 @@
# If the installation succeeds, configure and run functional tests for this repository. This is equivalent to
# sh run_functional_tests.sh -installed
if success:
+ # Clean out repository installation methods.
+ remove_install_tests( app )
log.debug( 'Installation of %s succeeded, running all defined functional tests.', name )
# Generate the shed_tools_dict that specifies the location of test data contained within this repository. If the repository
# does not have a test-data directory, this will return has_test_data = False, and we will set the do_not_test flag to True,
@@ -996,51 +1105,15 @@
# "reason": "The Galaxy development team has determined that this repository should not be installed and tested by the automated framework."
# }
# }
- failed_tool_dependencies = get_failed_tool_dependencies( repository )
- failed_repository_dependencies = repository.repository_dependencies_with_installation_errors
if 'missing_test_components' not in repository_status:
repository_status[ 'missing_test_components' ] = []
- if not has_test_data:
- # If the repository does not have a test-data directory, any functional tests in the tool configuration will
- # fail. Mark the repository as failed and skip installation.
- log.error( 'Test data is missing for this repository. Updating repository and skipping functional tests.' )
- # Record the lack of test data if the repository metadata defines tools.
- if 'tools' in repository.metadata:
- for tool in repository.metadata[ 'tools' ]:
- tool_id = tool[ 'id' ]
- tool_version = tool[ 'version' ]
- tool_guid = tool[ 'guid' ]
- # In keeping with the standard display layout, add the error message to the dict for each tool individually.
- missing_components = dict( tool_id=tool_id, tool_version=tool_version, tool_guid=tool_guid,
- missing_components="Repository %s is missing a test-data directory." % name )
- if missing_components not in repository_status[ 'missing_test_components' ]:
- repository_status[ 'missing_test_components' ].append( missing_components )
- else:
- continue
- # Record the status of this repository in the tool shed.
- set_do_not_test = not is_latest_downloadable_revision( galaxy_tool_shed_url, repository_info_dict )
- params[ 'tools_functionally_correct' ] = False
- params[ 'missing_test_components' ] = True
- params[ 'do_not_test' ] = str( set_do_not_test )
- register_test_result( galaxy_tool_shed_url,
- metadata_revision_id,
- repository_status,
- repository_info_dict,
- params )
- # Run the cleanup method. This removes tool functional test methods from the test_toolbox module and uninstalls the
- # repository using Twill.
- execute_uninstall_method( app, deactivate_only )
- # Set the test_toolbox.toolbox module-level variable to the new app.toolbox.
- test_toolbox.toolbox = app.toolbox
- repositories_failed.append( dict( name=name, owner=owner, changeset_revision=changeset_revision ) )
- elif failed_tool_dependencies or failed_repository_dependencies:
+ if repository.missing_tool_dependencies or repository.missing_repository_dependencies:
# If a tool dependency fails to install correctly, this should be considered an installation error,
# and functional tests should be skipped, since the tool dependency needs to be correctly installed
# for the test to be considered reliable.
- log.error( 'One or more dependencies of this repository are marked as missing.' )
- log.error( 'Updating repository and skipping functional tests.' )
+ log.error( 'The following dependencies of this repository are missing, skipping functional tests.' )
# In keeping with the standard display layout, add the error message to the dict for each tool individually.
- for dependency in failed_tool_dependencies:
+ for dependency in repository.missing_tool_dependencies:
log.error( 'Missing tool dependency %s of type %s version %s: %s' % \
( str( dependency.name ), str( dependency.type ), str( dependency.version ), str( dependency.error_message ) ) )
test_result = dict( type=dependency.type,
@@ -1048,7 +1121,7 @@
version=dependency.version,
error_message=dependency.error_message )
repository_status[ 'installation_errors' ][ 'tool_dependencies' ].append( test_result )
- for dependency in repository.repository_dependencies_with_installation_errors:
+ for dependency in repository.missing_repository_dependencies:
log.error( 'Missing repository dependency %s changeset revision %s owned by %s: %s' % \
( str( dependency.name ), str( dependency.changeset_revision ), str( dependency.owner ), str( dependency.error_message ) ) )
test_result = dict( tool_shed=dependency.tool_shed,
@@ -1066,11 +1139,22 @@
repository_status,
repository_info_dict,
params )
- # Run the cleanup method. This removes tool functional test methods from the test_toolbox module and uninstalls the
- # repository using Twill. If tool dependencies failed installation, select to uninstall instead of deavctivate,
- # to make way for the next attempt. Otherwise, default to the value determined by the environment variable
- # GALAXY_INSTALL_TEST_KEEP_TOOL_DEPENDENCIES.
- execute_uninstall_method( app, deactivate_only=deactivate_only )
+ # Since this repository is missing components, we do not want to test it. Deactivate this repository.
+ # The deactivate flag is set to True if the environment variable GALAXY_INSTALL_TEST_KEEP_TOOL_DEPENDENCIES
+ # is set to 'true'.
+ if deactivate:
+ # Recursively retrieve every missing tool dependency for this repository and its required repositories.
+ log.debug( 'Due to the above missing tool dependencies, we are now uninstalling the following tool dependencies, but not changing their repositories.' )
+ missing_tool_dependencies = get_missing_tool_dependencies( repository )
+ for missing_tool_dependency in missing_tool_dependencies:
+ uninstall_tool_dependency( app, missing_tool_dependency )
+ # We are deactivating this repository and all of its repository dependencies.
+ log.debug( 'Due to the above missing repository dependencies, we are now deactivating the following repositories.' )
+ deactivate_repository( app, repository_info_dict )
+ else:
+ # We are uninstalling this repository and all of its repository dependencies.
+ log.debug( 'Due to the above missing repository dependencies, we are now uninstalling the following repositories.' )
+ uninstall_repository( app, repository_info_dict )
# Set the test_toolbox.toolbox module-level variable to the new app.toolbox.
test_toolbox.toolbox = app.toolbox
repositories_failed_install.append( dict( name=name, owner=owner, changeset_revision=changeset_revision ) )
@@ -1142,10 +1226,18 @@
changeset_revision, name )
# Run the uninstall method. This removes tool functional test methods from the test_toolbox module and uninstalls the
# repository using Twill.
- log.debug( 'Uninstalling changeset revision %s of repository %s',
- repository_info_dict[ 'changeset_revision' ],
- repository_info_dict[ 'name' ] )
- success = execute_uninstall_method( app, deactivate_only )
+ if deactivate:
+ log.debug( 'Deactivating changeset revision %s of repository %s',
+ repository_info_dict[ 'changeset_revision' ],
+ repository_info_dict[ 'name' ] )
+ # We are deactivating this repository and all of its repository dependencies.
+ success = deactivate_repository( app, repository_info_dict )
+ else:
+ log.debug( 'Uninstalling changeset revision %s of repository %s',
+ repository_info_dict[ 'changeset_revision' ],
+ repository_info_dict[ 'name' ] )
+ # We are uninstalling this repository and all of its repository dependencies.
+ success = uninstall_repository( app, repository_info_dict )
if not success:
log.error( 'Repository %s failed to uninstall.', repository_info_dict[ 'name' ] )
# Set the test_toolbox.toolbox module-level variable to the new app.toolbox.
@@ -1172,9 +1264,14 @@
repository_info_dict,
params )
try:
- success = execute_uninstall_method( app, deactivate_only )
+ if deactivate:
+ # We are deactivating this repository and all of its repository dependencies.
+ success = deactivate_repository( app, repository_info_dict )
+ else:
+ # We are uninstalling this repository and all of its repository dependencies.
+ success = uninstall_repository( app, repository_info_dict )
except:
- log.exception( 'Encountered error attempting to uninstall %s.', repository_info_dict[ 'name' ] )
+ log.exception( 'Encountered error attempting to deactivate or uninstall %s.', repository_info_dict[ 'name' ] )
success = False
if not success:
log.error( 'Repository %s failed to uninstall.', repository_info_dict[ 'name' ] )
@@ -1248,4 +1345,4 @@
print "####################################################################################"
print "# %s - running repository installation and testing script." % now
print "####################################################################################"
- sys.exit( main() )
\ No newline at end of file
+ sys.exit( main() )
https://bitbucket.org/galaxy/galaxy-central/commits/0f07d64e1be2/
Changeset: 0f07d64e1be2
Branch: search
User: Kyle Ellrott
Date: 2013-11-15 20:29:10
Summary: Default merge
Affected #: 242 files
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 .hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -4,3 +4,4 @@
2cc8d10988e03257dc7b97f8bb332c7df745d1dd security_2013.04.08
524f246ca85395082719ae7a6ff72260d7ad5612 release_2013.06.03
1ae95b3aa98d1ccf15b243ac3ce6a895eb7efc53 release_2013.08.12
+26f58e05aa1068761660681583821e21e6cbf7ab release_2013.11.04
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/Gruntfile.js
--- a/config/plugins/visualizations/scatterplot/Gruntfile.js
+++ b/config/plugins/visualizations/scatterplot/Gruntfile.js
@@ -6,6 +6,7 @@
pkg: grunt.file.readJSON( 'package.json' ),
handlebars: {
+ // compile all hb templates into a single file in the build dir
compile: {
options: {
namespace: 'Templates',
@@ -20,6 +21,7 @@
},
concat: {
+ // concat the template file and any js files in the src dir into a single file in the build dir
options: {
separator: ';\n'
},
@@ -31,16 +33,19 @@
},
uglify: {
+ // uglify the concat single file directly into the static dir
options: {
+ //mangle : false,
+ //beautify : true
},
dist: {
src : 'build/scatterplot-concat.js',
- // uglify directly into static dir
- dest: 'static/scatterplot.js'
+ dest: 'static/scatterplot-edit.js'
}
},
watch: {
+ // watch for changes in the src dir
files: [ 'src/**.js', 'src/handlebars/*.handlebars' ],
tasks: [ 'default' ]
}
@@ -52,5 +57,6 @@
grunt.loadNpmTasks( 'grunt-contrib-watch' );
grunt.registerTask( 'default', [ 'handlebars', 'concat', 'uglify' ]);
- grunt.registerTask( 'watch', [ 'handlebars', 'concat', 'uglify', 'watch' ]);
+ // you can run grunt watch directly:
+ // grunt watch
};
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/package.json
--- a/config/plugins/visualizations/scatterplot/package.json
+++ b/config/plugins/visualizations/scatterplot/package.json
@@ -19,6 +19,6 @@
"grunt-contrib-handlebars": "~0.5.10",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-uglify": "~0.2.2",
- "grunt-contrib-watch": "~0.5.1"
+ "grunt-contrib-watch": "~0.5.3"
}
}
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars
+++ /dev/null
@@ -1,56 +0,0 @@
-<p class="help-text">
- Use the following controls to how the chart is displayed.
- The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
- Move the focus between controls by using the tab or shift+tab keys on your keyboard.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- <div id="datapointSize" class="form-input numeric-slider-input">
- <label for="datapointSize">Size of data point: </label>
- <div class="slider-output">{{datapointSize}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- Size of the graphic representation of each data point
- </p>
- </div>
-
- <div id="animDuration" class="form-input checkbox-input">
- <label for="animate-chart">Animate chart transitions?: </label>
- <input type="checkbox" id="animate-chart"
- class="checkbox control"{{#if animDuration}} checked="true"{{/if}} />
- <p class="form-help help-text-small">
- Uncheck this to disable the animations used on the chart
- </p>
- </div>
-
- <div id="width" class="form-input numeric-slider-input">
- <label for="width">Chart width: </label>
- <div class="slider-output">{{width}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="height" class="form-input numeric-slider-input">
- <label for="height">Chart height: </label>
- <div class="slider-output">{{height}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="X-axis-label"class="text-input form-input">
- <label for="X-axis-label">Re-label the X axis: </label>
- <input type="text" name="X-axis-label" id="X-axis-label" value="{{xLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <div id="Y-axis-label" class="text-input form-input">
- <label for="Y-axis-label">Re-label the Y axis: </label>
- <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{yLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
\ No newline at end of file
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars
+++ /dev/null
@@ -1,1 +0,0 @@
-<svg width="{{width}}" height="{{height}}"></svg>
\ No newline at end of file
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
@@ -0,0 +1,47 @@
+<p class="help-text">
+ Use the following controls to how the chart is displayed.
+ The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
+ Move the focus between controls by using the tab or shift+tab keys on your keyboard.
+ Use the 'Draw' button to render (or re-render) the chart with the current settings.
+</p>
+
+<div data-config-key="datapointSize" class="form-input numeric-slider-input">
+ <label for="datapointSize">Size of data point: </label>
+ <div class="slider-output">{{datapointSize}}</div>
+ <div class="slider"></div>
+ <p class="form-help help-text-small">
+ Size of the graphic representation of each data point
+ </p>
+</div>
+
+<div data-config-key="width" class="form-input numeric-slider-input">
+ <label for="width">Chart width: </label>
+ <div class="slider-output">{{width}}</div>
+ <div class="slider"></div>
+ <p class="form-help help-text-small">
+ (not including chart margins and axes)
+ </p>
+</div>
+
+<div data-config-key="height" class="form-input numeric-slider-input">
+ <label for="height">Chart height: </label>
+ <div class="slider-output">{{height}}</div>
+ <div class="slider"></div>
+ <p class="form-help help-text-small">
+ (not including chart margins and axes)
+ </p>
+</div>
+
+<div data-config-key="X-axis-label"class="text-input form-input">
+ <label for="X-axis-label">Re-label the X axis: </label>
+ <input type="text" name="X-axis-label" id="X-axis-label" value="{{x.label}}" />
+ <p class="form-help help-text-small"></p>
+</div>
+
+<div data-config-key="Y-axis-label" class="text-input form-input">
+ <label for="Y-axis-label">Re-label the Y axis: </label>
+ <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{y.label}}" />
+ <p class="form-help help-text-small"></p>
+</div>
+
+<button class="render-button btn btn-primary active">Draw</button>
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars
+++ /dev/null
@@ -1,56 +0,0 @@
-<p class="help-text">
- Use the following controls to change the data used by the chart.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- {{! column selector containers }}
- <div class="column-select">
- <label for="X-select">Data column for X: </label>
- <select name="X" id="X-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- <div class="column-select">
- <label for="Y-select">Data column for Y: </label>
- <select name="Y" id="Y-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! optional id column }}
- <div id="include-id">
- <label for="include-id-checkbox">Include a third column as data point IDs?</label>
- <input type="checkbox" name="include-id" id="include-id-checkbox" />
- <p class="help-text-small">
- These will be displayed (along with the x and y values) when you hover over
- a data point.
- </p>
- </div>
- <div class="column-select" style="display: none">
- <label for="ID-select">Data column for IDs: </label>
- <select name="ID" id="ID-select">
- {{#each allColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
- <div id="first-line-header" style="display: none;">
- <p>Possible headers: {{ possibleHeaders }}
- </p>
- <label for="first-line-header-checkbox">Use the above as column headers?</label>
- <input type="checkbox" name="include-id" id="first-line-header-checkbox"
- {{#if usePossibleHeaders }}checked="true"{{/if}}/>
- <p class="help-text-small">
- It looks like Galaxy couldn't get proper column headers for this data.
- Would you like to use the column headers above as column names to select columns?
- </p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
- <div class="clear"></div>
\ No newline at end of file
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
@@ -0,0 +1,55 @@
+<p class="help-text">
+ Use the following controls to change the data used by the chart.
+ Use the 'Draw' button to render (or re-render) the chart with the current settings.
+</p>
+
+{{! column selector containers }}
+<div class="column-select">
+ <label>Data column for X: </label>
+ <select name="xColumn">
+ {{#each numericColumns}}
+ <option value="{{index}}">{{name}}</option>
+ {{/each}}
+ </select>
+</div>
+<div class="column-select">
+ <label>Data column for Y: </label>
+ <select name="yColumn">
+ {{#each numericColumns}}
+ <option value="{{index}}">{{name}}</option>
+ {{/each}}
+ </select>
+</div>
+
+{{! optional id column }}
+<div id="include-id">
+ <label for="include-id-checkbox">Include a third column as data point IDs?</label>
+ <input type="checkbox" name="include-id" id="include-id-checkbox" />
+ <p class="help-text-small">
+ These will be displayed (along with the x and y values) when you hover over
+ a data point.
+ </p>
+</div>
+<div class="column-select" style="display: none">
+ <label for="ID-select">Data column for IDs: </label>
+ <select name="idColumn">
+ {{#each allColumns}}
+ <option value="{{index}}">{{name}}</option>
+ {{/each}}
+ </select>
+</div>
+
+{{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
+<div id="first-line-header" style="display: none;">
+ <p>Possible headers: {{ possibleHeaders }}
+ </p>
+ <label for="first-line-header-checkbox">Use the above as column headers?</label>
+ <input type="checkbox" name="include-id" id="first-line-header-checkbox"
+ {{#if usePossibleHeaders }}checked="true"{{/if}}/>
+ <p class="help-text-small">
+ It looks like Galaxy couldn't get proper column headers for this data.
+ Would you like to use the column headers above as column names to select columns?
+ </p>
+</div>
+
+<button class="render-button btn btn-primary active">Draw</button>
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars
@@ -0,0 +1,36 @@
+<div class="scatterplot-editor tabbable tabs-left">
+ {{! tab buttons/headers using Bootstrap }}
+ <ul class="nav nav-tabs">
+ {{! start with the data controls as the displayed tab }}
+ <li class="active">
+ <a title="Use this tab to change which data are used"
+ href="#data-control" data-toggle="tab">Data Controls</a>
+ </li>
+ <li>
+ <a title="Use this tab to change how the chart is drawn"
+ href="#chart-control" data-toggle="tab" >Chart Controls</a>
+ </li>
+ {{! both stats and chart start as disabled since there's no info yet }}
+ <li class="disabled">
+ <a title="This tab will display the chart"
+ href="#chart-display" data-toggle="tab">Chart</a>
+ </li>
+ </ul>
+
+ {{! data form, chart config form, stats, and chart all get their own tab }}
+ <div class="tab-content">
+ {{! ---------------------------- tab for data settings form }}
+ <div id="data-control" class="scatterplot-config-control tab-pane active">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for chart graphics control form }}
+ <div id="chart-control" class="scatterplot-config-control tab-pane">
+ {{! rendered separately }}
+ </div>
+
+ {{! ---------------------------- tab for actual chart }}
+ <div id="chart-display" class="scatterplot-display tab-pane"></div>
+
+ </div>{{! end .tab-content }}
+</div>{{! end .chart-control }}
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars
+++ /dev/null
@@ -1,46 +0,0 @@
-{{! main layout }}
-
-<div class="scatterplot-container chart-container tabbable tabs-left">
- {{! tab buttons/headers using Bootstrap }}
- <ul class="nav nav-tabs">
- {{! start with the data controls as the displayed tab }}
- <li class="active"><a href="#data-control" data-toggle="tab" class="tooltip"
- title="Use this tab to change which data are used">Data Controls</a></li>
- <li><a href="#chart-control" data-toggle="tab" class="tooltip"
- title="Use this tab to change how the chart is drawn">Chart Controls</a></li>
- <li><a href="#stats-display" data-toggle="tab" class="tooltip"
- title="This tab will display overall statistics for your data">Statistics</a></li>
- <li><a href="#chart-display" data-toggle="tab" class="tooltip"
- title="This tab will display the chart">Chart</a>
- {{! loading indicator - initially hidden }}
- <div id="loading-indicator" style="display: none;">
- <img class="loading-img" src="{{loadingIndicatorImagePath}}" />
- <span class="loading-message">{{message}}</span>
- </div>
- </li>
- </ul>
-
- {{! data form, chart config form, stats, and chart all get their own tab }}
- <div class="tab-content">
- {{! ---------------------------- tab for data settings form }}
- <div id="data-control" class="tab-pane active">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for chart graphics control form }}
- <div id="chart-control" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for data statistics }}
- <div id="stats-display" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for actual chart }}
- <div id="chart-display" class="tab-pane">
- {{! chart rendered separately }}
- </div>
-
- </div>{{! end .tab-content }}
-</div>{{! end .chart-control }}
\ No newline at end of file
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars
+++ /dev/null
@@ -1,8 +0,0 @@
-<p class="help-text">By column:</p>
- <table id="chart-stats-table">
- <thead><th></th><th>X</th><th>Y</th></thead>
- {{#each stats}}
- <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
- </tr>
- {{/each}}
- </table>
\ No newline at end of file
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -0,0 +1,237 @@
+/* =============================================================================
+todo:
+ Remove 'chart' names
+ Make this (the config control/editor) and the ScatterplotView (in scatterplot.js) both
+ views onto a visualization/revision model
+ Move margins into wid/hi calcs (so final svg dims are w/h)
+ Better separation of AJAX in scatterplot.js (maybe pass in function?)
+ Labels should auto fill in chart control when dataset has column_names
+ Allow column selection/config using the peek output as a base for UI
+ Allow setting perPage of config
+ Auto render if given data and/or config
+ Allow option to auto set width/height based on screen real estate avail.
+ Use d3.nest to allow grouping, pagination/filtration by group (e.g. chromCol)
+ Semantic HTML (figure, caption)
+ Save as visualization, load from visualization
+ Save as SVG/png
+ Does it work w/ Galaxy.Frame?
+ Embedding
+ Small multiples
+ Drag & Drop other splots onto current (redraw with new axis and differentiate the datasets)
+
+ Subclass on specific datatypes? (vcf, cuffdiff, etc.)
+ What can be common/useful to other visualizations?
+
+============================================================================= */
+/**
+ * Scatterplot config control UI as a backbone view
+ * handles:
+ * configuring which data will be used
+ * configuring the plot display
+ */
+var ScatterplotConfigEditor = BaseView.extend( LoggableMixin ).extend({
+ //TODO: !should be a view on a visualization model
+ //logger : console,
+ className : 'scatterplot-control-form',
+
+ /** initialize requires a configuration Object containing a dataset Object */
+ initialize : function( attributes ){
+ //console.log( this + '.initialize, attributes:', attributes );
+ if( !attributes || !attributes.config || !attributes.config.dataset ){
+ throw new Error( "ScatterplotView requires a configuration and dataset" );
+ }
+ this.dataset = attributes.config.dataset;
+ //console.log( 'dataset:', this.dataset );
+
+ this.plotView = new ScatterplotView({
+ config : attributes.config
+ });
+ },
+
+ // ------------------------------------------------------------------------- CONTROLS RENDERING
+ render : function(){
+ //console.log( this + '.render' );
+
+ // render the tab controls, areas and loading indicator
+ this.$el.append( ScatterplotConfigEditor.templates.mainLayout({
+ }));
+
+ // render the tab content
+ this.$el.find( '#data-control' ).append( this._render_dataControl() );
+ this._render_chartControls( this.$el.find( '#chart-control' ) );
+ //this.$statsDisplay = this.$el.find( '.tab-pane#stats-display' );
+ this._render_chartDisplay();
+
+ //TODO: auto render if given both x, y column choices in query for page
+
+ // set up behaviours
+ this.$el.find( '[title]' ).tooltip();
+
+ // uncomment any of the following to have that tab show on initial load (for testing)
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#data-control"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-control"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#stats-display"]' ).tab( 'show' );
+ //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ return this;
+ },
+
+ _render_dataControl : function(){
+ // controls for which columns are used to plot datapoints (and ids/additional info to attach if desired)
+ var dataset = this.dataset;
+ //console.log( 'metadata_column_types:', this.dataset.metadata_column_types );
+ //console.log( 'metadata_column_names:', this.dataset.metadata_column_names );
+
+ var allColumns = _.map( dataset.metadata_column_types, function( type, i ){
+ var column = { index: i, type: type, name: ( 'column ' + ( i + 1 ) ) };
+ if( dataset.metadata_column_names && dataset.metadata_column_names[ i ] ){
+ column.name = dataset.metadata_column_names[ i ];
+ }
+ return column;
+ });
+ var numericColumns = _.filter( allColumns, function( column, i ){
+ return ( ( column.type === 'int' ) || ( column.type === 'float' ) );
+ });
+ if( numericColumns < 2 ){
+ numericColumns = allColumns;
+ }
+ //console.log( 'allColumns:', allColumns );
+ //console.log( 'numericColumns:', numericColumns );
+
+ // render the html
+ var $dataControl = this.$el.find( '.tab-pane#data-control' );
+ $dataControl.html( ScatterplotConfigEditor.templates.dataControl({
+ allColumns : allColumns,
+ numericColumns : numericColumns
+ }));
+
+ // preset to column selectors if they were passed in the config in the query string
+ $dataControl.find( '[name="xColumn"]' ).val( this.plotView.config.xColumn || numericColumns[0].index );
+ $dataControl.find( '[name="yColumn"]' ).val( this.plotView.config.yColumn || numericColumns[1].index );
+ if( this.plotView.config.idColumn !== undefined ){
+ $dataControl.find( '#include-id-checkbox' ).prop( 'checked', true ).trigger( 'change' );
+ $dataControl.find( 'select[name="idColumn"]' ).val( this.plotView.config.idColumn );
+ }
+
+ return $dataControl;
+ },
+
+ _render_chartControls : function( $chartControls ){
+ // tab content to control how the chart is rendered (data glyph size, chart size, etc.)
+ $chartControls.html( ScatterplotConfigEditor.templates.chartControl( this.plotView.config ) );
+ //console.debug( '$chartControl:', $chartControls );
+
+ // set up behaviours, js on sliders
+ //console.debug( 'numeric sliders:', $chartControls.find( '.numeric-slider-input' ) );
+ // what to do when the slider changes: update display and update chartConfig
+ var view = this,
+ // limits for controls (by control/chartConfig id)
+ //TODO: move into TwoVarScatterplot
+ controlRanges = {
+ 'datapointSize' : { min: 2, max: 10, step: 1 },
+ 'width' : { min: 200, max: 800, step: 20 },
+ 'height' : { min: 200, max: 800, step: 20 }
+ };
+
+ function onSliderChange(){
+ var $this = $( this );
+ $this.siblings( '.slider-output' ).text( $this.slider( 'value' ) );
+ }
+ $chartControls.find( '.numeric-slider-input' ).each( function(){
+ var $this = $( this ),
+ configKey = $this.attr( 'data-config-key' ),
+ sliderSettings = _.extend( controlRanges[ configKey ], {
+ value : view.plotView.config[ configKey ],
+ change : onSliderChange,
+ slide : onSliderChange
+ });
+ //console.debug( configKey + ' slider settings:', sliderSettings );
+ $this.find( '.slider' ).slider( sliderSettings );
+ });
+//TODO: to more common area (like render)?
+ // set label inputs to current x, y metadata_column_names (if any)
+ if( this.dataset.metadata_column_names ){
+ //var colNames = this.dataset.metadata_column_names;
+ //$chartControls.find( 'input[name="X-axis-label"]' ).val( colNames );
+ //$chartControls.find( 'input[name="Y-axis-label"]' ).val( colNames );
+//TODO: on change of x, y data controls
+ }
+
+ //console.debug( '$chartControls:', $chartControls );
+ return $chartControls;
+ },
+
+ _render_chartDisplay : function(){
+ // render the tab content where the chart is displayed (but not the chart itself)
+ var $chartDisplay = this.$el.find( '.tab-pane#chart-display' );
+ this.plotView.setElement( $chartDisplay );
+ this.plotView.render();
+ return $chartDisplay;
+ },
+
+ // ------------------------------------------------------------------------- EVENTS
+ events : {
+ 'change #include-id-checkbox' : 'toggleThirdColumnSelector',
+ 'click #data-control .render-button' : 'renderChart',
+ 'click #chart-control .render-button' : 'renderChart'
+ },
+
+ toggleThirdColumnSelector : function(){
+ // show/hide the id selector on the data settings panel
+ this.$el.find( 'select[name="idColumn"]' ).parent().toggle();
+ },
+
+ // ------------------------------------------------------------------------- CHART/STATS RENDERING
+ renderChart : function(){
+ //console.log( this + '.renderChart' );
+ // fetch the data, (re-)render the chart
+ this.$el.find( '.nav li.disabled' ).removeClass( 'disabled' );
+ this.updateConfigWithDataSettings();
+ this.updateConfigWithChartSettings();
+ this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
+ this.plotView.fetchData();
+ //console.debug( this.plotView.$el );
+ },
+
+ // ------------------------------------------------------------------------- GET DATA/CHART SETTINGS
+ updateConfigWithDataSettings : function(){
+ // parse the column values for both indeces (for the data fetch) and names (for the chart)
+ var $dataControls = this.$el.find( '#data-control' );
+ var settings = {
+ xColumn : $dataControls.find( '[name="xColumn"]' ).val(),
+ yColumn : $dataControls.find( '[name="yColumn"]' ).val()
+ };
+ if( $dataControls.find( '#include-id-checkbox' ).prop( 'checked' ) ){
+ settings.idColumn = $dataControls.find( '[name="idColumn"]' ).val();
+ }
+ //console.log( '\t data settings:', settings );
+ return _.extend( this.plotView.config, settings );
+ },
+
+ updateConfigWithChartSettings : function(){
+ // gets the user-selected chartConfig from the chart settings panel
+ var plotView = this.plotView,
+ $chartControls = this.$el.find( '#chart-control' );
+ // use a loop of config keys to get the form values for these sliders
+ [ 'datapointSize', 'width', 'height' ].forEach( function( v, i ){
+ plotView.config[ v ] = $chartControls.find( '.numeric-slider-input[data-config-key="' + v + '"]' )
+ .find( '.slider' ).slider( 'value' );
+ });
+ // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName
+ plotView.config.x.label = $chartControls.find( 'input[name="X-axis-label"]' ).val();
+ plotView.config.y.label = $chartControls.find( 'input[name="Y-axis-label"]' ).val();
+ //console.log( '\t chartSettings:', settings );
+ return plotView.config;
+ },
+
+ toString : function(){
+ return 'ScatterplotConfigEditor(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')';
+ }
+});
+
+ScatterplotConfigEditor.templates = {
+ mainLayout : Templates.editor,
+ dataControl : Templates.datacontrol,
+ chartControl : Templates.chartcontrol
+};
+
+//==============================================================================
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/scatterplot-display.js
--- /dev/null
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
@@ -0,0 +1,288 @@
+// =============================================================================
+/**
+ * Scatterplot display control UI as a backbone view
+ * handles:
+ * fetching the data (if needed)
+ * computing and displaying data stats
+ * controls for pagination of data (if needed)
+ */
+var ScatterplotView = Backbone.View.extend({
+ //TODO: should be a view on visualization(revision) model
+
+ defaults : {
+ dataset : {
+ },
+ metadata : {
+ dataLines : undefined
+ },
+
+ ajaxFn : null,
+
+ pagination : {
+ currPage : 0,
+ perPage : 3000
+ },
+
+ width : 400,
+ height : 400,
+
+ margin : {
+ top : 16,
+ right : 16,
+ bottom : 40,
+ left : 54
+ },
+
+ x : {
+ ticks : 10,
+ label : 'X'
+ },
+ y : {
+ ticks : 10,
+ label : 'Y'
+ },
+
+ datapointSize : 4,
+ animDuration : 500
+ },
+
+ initialize : function( attributes ){
+ this.config = _.extend( _.clone( this.defaults ), attributes.config || {});
+ //console.debug( this + '.config:', this.config );
+ },
+
+ updateConfig : function( newConfig ){
+ //console.log( this + '.updateConfig:', newConfig );
+ this.config = this.config || {};
+ //TODO: validate here
+ _.extend( this.config, newConfig );
+ //TODO: implement rerender flag
+ },
+
+ fetchData : function(){
+//TODO: doesn't work bc it's rendered in render()...
+ this.showLoadingIndicator( 'getting data' );
+ //console.debug( 'currPage', this.config.pagination.currPage );
+ var view = this;
+//TODO: very tied to datasets - should be generalized eventually
+ xhr = jQuery.getJSON( '/api/datasets/' + this.config.dataset.id, {
+ data_type : 'raw_data',
+ provider : 'dataset-column',
+ limit : this.config.pagination.perPage,
+ offset : ( this.config.pagination.currPage * this.config.pagination.perPage )
+ });
+ xhr.done( function( data ){
+ view.renderData( data.data );
+ });
+ xhr.fail( function( xhr, status, message ){
+ alert( 'Error loading data:\n' + xhr.responseText );
+ console.error( xhr, status, message );
+ });
+ xhr.always( function(){
+ view.hideLoadingIndicator();
+ });
+ return xhr;
+ },
+
+ render : function( data ){
+ this.$el.addClass( 'scatterplot-display' ).html([
+ '<div class="controls clear"></div>',
+ '<div class="loading-indicator">',
+ '<span class="fa fa-spinner fa-spin"></span>',
+ '<span class="loading-indicator-message"></span>',
+ '</div>',
+ '<svg/>', //TODO: id
+ '<div class="stats-display"></div>'
+ ].join( '' ));
+ this.$el.children().hide();
+
+ if( data ){
+ this.renderData( data );
+ }
+ return this;
+ },
+
+ showLoadingIndicator : function( message, speed ){
+ // display the loading indicator over the tab panels if hidden, update message (if passed)
+//TODO: move loading indicator into data-info-text
+ message = message || '';
+ speed = speed || 'fast';
+ var $indicator = this.$el.find( '.loading-indicator' );
+
+ if( message ){ $indicator.find( '.loading-indicator-message' ).text( message ); }
+ if( !$indicator.is( ':visible' ) ){
+ this.toggleStats( false );
+ $indicator.css({ left: ( this.config.width / 2 ), top: this.config.height / 2 }).show();
+ }
+ },
+
+ hideLoadingIndicator : function( speed ){
+ speed = speed || 'fast';
+ this.$el.find( '.loading-indicator' ).hide();
+ },
+
+ renderData : function( data ){
+ this.$el.find( '.controls' ).empty().append( this.renderControls( data ) ).show();
+ this.renderPlot( data );
+ this.getStats( data );
+ },
+
+ renderControls : function( data ){
+ var view = this;
+ var $left = $( '<div class="left"></div>' ),
+ $right = $( '<div class="right"></div>' );
+
+ $left.append([
+ this.renderPrevNext( data ),
+ this.renderPagination( data )
+ ]);
+ $right.append([
+ this.renderLineInfo( data ),
+ $( '<button>Stats</button>' ).addClass( 'stats-toggle-btn' )
+ .click( function(){
+ view.toggleStats();
+ }),
+ $( '<button>Redraw</button>' ).addClass( 'rerender-btn' )
+ .click( function(){
+ view.renderPlot( data );
+ })
+ ]);
+ return [ $left, $right ];
+ },
+
+ renderLineInfo : function( data ){
+ var totalLines = this.config.dataset.metadata_data_lines || 'an unknown number of',
+ lineStart = ( this.config.pagination.currPage * this.config.pagination.perPage ),
+ lineEnd = lineStart + data.length;
+ return $( '<p/>' ).addClass( 'scatterplot-data-info' )
+ .text([ 'Displaying lines', lineStart + 1, 'to', lineEnd, 'of', totalLines, 'lines' ].join( ' ' ));
+ },
+
+ renderPrevNext : function( data ){
+ // this is cra-zazy
+ if( !data
+ || ( this.config.pagination.currPage === 0 && data.length < this.config.pagination.perPage ) ){ return null; }
+
+ function makePage$Li( text ){
+ return $([ '<li><a href="javascript:void(0);">', text, '</a></li>' ].join( '' ));
+ }
+//TODO: cache numPages/numLines in config
+ var view = this,
+ dataLines = this.config.dataset.metadata_data_lines,
+ numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined );
+ //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages );
+
+ // prev next buttons
+ var $prev = makePage$Li( 'Prev' ).click( function(){
+ if( view.config.pagination.currPage > 0 ){
+ view.config.pagination.currPage -= 1;
+ view.fetchData();
+ }
+ }),
+ $next = makePage$Li( 'Next' ).click( function(){
+ if( !numPages || view.config.pagination.currPage < ( numPages - 1 ) ){
+ view.config.pagination.currPage += 1;
+ view.fetchData();
+ }
+ }),
+ $prevNextList = $( '<ul/>' ).addClass( 'pagination data-prev-next' )
+ .append([ $prev, $next ]);
+
+ if( view.config.pagination.currPage === 0 ){
+ $prev.addClass( 'disabled' );
+ }
+ if( numPages && view.config.pagination.currPage === ( numPages - 1 ) ){
+ $next.addClass( 'disabled' );
+ }
+ return $prevNextList;
+ },
+
+ renderPagination : function( data ){
+ // this is cra-zazy
+ if( !data
+ || ( this.config.pagination.currPage === 0 && data.length < this.config.pagination.perPage ) ){ return null; }
+
+ function makePage$Li( text ){
+ return $([ '<li><a href="javascript:void(0);">', text, '</a></li>' ].join( '' ));
+ }
+//TODO: cache numPages/numLines in config
+ var view = this,
+ dataLines = this.config.dataset.metadata_data_lines,
+ numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined );
+ //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages );
+
+ // page numbers (as separate control)
+ //var $paginationContainer = $( '<div/>' ).addClass( 'pagination-container' ),
+ var $pagesList = $( '<ul/>' ).addClass( 'pagination data-pages' );
+ function pageNumClick( ev ){
+ view.config.pagination.currPage = $( this ).data( 'page' );
+ view.fetchData();
+ }
+ for( var i=0; i<numPages; i+=1 ){
+ // add page data for later event handling
+ var $pageLi = makePage$Li( i + 1 ).attr( 'data-page', i ).click( pageNumClick );
+ if( i === this.config.pagination.currPage ){
+ $pageLi.addClass( 'active' );
+ }
+ $pagesList.append( $pageLi );
+ }
+ // placing the pages list in an extra container allows us to set a max-width and scroll if overflow
+ //$paginationContainer.append( $pagesList );
+ //return $paginationContainer;
+ return $pagesList;
+ },
+
+ renderPlot : function( data ){
+ this.toggleStats( false );
+ var $svg = this.$el.find( 'svg' );
+ $svg.off().empty().show();
+ scatterplot( $svg.get( 0 ), this.config, data );
+ },
+
+ getStats : function( data ){
+ var view = this;
+ meanWorker = new Worker( '/plugins/visualizations/scatterplot/static/worker-stats.js' );
+ meanWorker.postMessage({
+ data : data,
+ keys : [ this.config.xColumn, this.config.yColumn ]
+ });
+ meanWorker.onerror = function( event ){
+ meanWorker.terminate();
+ };
+ meanWorker.onmessage = function( event ){
+ view.renderStats( event.data );
+ };
+ },
+
+ renderStats : function( stats, error ){
+ //console.debug( 'renderStats:', stats, error );
+ //console.debug( JSON.stringify( stats, null, ' ' ) );
+ var $statsTable = this.$el.find( '.stats-display' );
+
+ var xLabel = this.config.x.label, yLabel = this.config.y.label,
+ $table = $( '<table/>' ).addClass( 'table' )
+ .append([ '<thead><th></th><th>', xLabel, '</th><th>', yLabel, '</th></thead>' ].join( '' ))
+ .append( _.map( stats, function( stat, key ){
+ return $([ '<tr><td>', key, '</td><td>', stat[0], '</td><td>', stat[1], '</td></tr>' ].join( '' ));
+ }));
+ $statsTable.empty().append( $table );
+ },
+
+ toggleStats : function( showStats ){
+ var $statsDisplay = this.$el.find( '.stats-display' );
+ showStats = ( showStats === undefined )?( $statsDisplay.is( ':hidden' ) ):( showStats );
+ if( showStats ){
+ this.$el.find( 'svg' ).hide();
+ $statsDisplay.show();
+ this.$el.find( '.controls .stats-toggle-btn' ).text( 'Plot' );
+ } else {
+ $statsDisplay.hide();
+ this.$el.find( 'svg' ).show();
+ this.$el.find( '.controls .stats-toggle-btn' ).text( 'Stats' );
+ }
+ },
+
+ toString : function(){
+ return 'ScatterplotView()';
+ }
+});
diff -r 654dfe820960de076de5dd2faaf506a4410f156d -r 0f07d64e1be248b0219d015d882189e29da92a01 config/plugins/visualizations/scatterplot/src/scatterplot.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot.js
@@ -1,488 +1,278 @@
-/* =============================================================================
-todo:
- outside this:
- BUG: setting width, height in plot controls doesn't re-interpolate data locations!!
- BUG?: get metadata_column_names (from datatype if necessary)
- BUG: single vis in popupmenu should have tooltip with that name NOT 'Visualizations'
-
- wire label setters, anim setter
-
- TwoVarScatterplot:
- ??: maybe better to do this with a canvas...
- save as visualization
- to seperate file?
- remove underscore dependencies
- add interface to change values (seperate)?
- download svg -> base64 encode
- incorporate glyphs, glyph state renderers
-
- ScatterplotSettingsForm:
- some css bug that lowers the width of settings form when plot-controls tab is open
- causes chart to shift
- what can be abstracted/reused for other graphs?
- avoid direct manipulation of this.plot
- allow option to put plot into seperate tab of interface (for small multiples)
-
- provide callback in view to load data incrementally - for large sets
- paginate
- handle rerender
- use endpoint (here and on the server (fileptr))
- fetch (new?) data
- handle rerender
- use d3.TSV?
- render warning on long data (> maxDataPoints)
- adjust endpoint
-
- selectable list of preset column comparisons (rnaseq etc.)
- how to know what sort of Tabular the data is?
- smarter about headers
- validate columns selection (here or server)
-
- set stats column names by selected columns
- move chart into tabbed area...
-
- Scatterplot.mako:
- multiple plots on one page (small multiples)
- ?? ensure svg styles thru d3 or css?
- d3: configable (easily)
- css: standard - better maintenance
- ? override at config
-
-============================================================================= */
+// =============================================================================
/**
* Two Variable scatterplot visualization using d3
* Uses semi transparent circles to show density of data in x, y grid
* usage :
- * var plot = new TwoVarScatterplot({ containerSelector : 'div#my-plot', ... })
- * plot.render( xColumnData, yColumnData );
- *
- * depends on: d3, underscore
+ * var plot = new scatterplot( $( 'svg' ).get(0), config, data )
*/
-function TwoVarScatterplot( config ){
- var TICK_LINE_AND_PADDING = 10,
- GUESS_AT_SVG_CHAR_WIDTH = 7,
- GUESS_AT_SVG_CHAR_HEIGHT = 10,
- PADDING = 8,
- X_LABEL_TOO_LONG_AT = 5;
-
- // set up logging
- //this.debugging = true;
- this.log = function(){
- if( this.debugging && console && console.debug ){
- var args = Array.prototype.slice.call( arguments );
- args.unshift( this.toString() );
- console.debug.apply( console, args );
- }
- };
- this.log( 'new TwoVarScatterplot:', config );
-
- // ........................................................ set up chart config
- // config will default to these values when not passed in
- //NOTE: called on new
- this.defaults = {
- id : 'TwoVarScatterplot',
- containerSelector : 'body',
- //TODO??: needed?
- maxDataPoints : 30000,
- datapointSize : 4,
- animDuration : 500,
- //TODO: variable effect (not always exactly # of ticks set to)
- xNumTicks : 10,
- yNumTicks : 10,
- xAxisLabelBumpY : 40,
- yAxisLabelBumpX : -40,
- width : 400,
- height : 400,
- //TODO: anyway to make this a sub-obj?
- 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, config );
- this.log( 'intial config:', this.config );
-
- this.updateConfig = function( newConfig, rerender ){
- // setter for chart config
- //TODO: validate here
- _.extend( this.config, newConfig );
- this.log( this + '.updateConfig:', this.config );
- //TODO: implement rerender flag
- };
-
- // ........................................................ helpers
- this.toString = function(){
- return this.config.id;
- };
- // conv. methods for svg transforms
- this.translateStr = function( x, y ){
- return 'translate(' + x + ',' + y + ')';
- };
- this.rotateStr = function( d, x, y ){
- return 'rotate(' + d + ',' + x + ',' + y + ')';
+function scatterplot( renderTo, config, data ){
+ //console.log( 'scatterplot', config );
+
+ var translateStr = function( x, y ){
+ return 'translate(' + x + ',' + y + ')';
+ },
+ rotateStr = function( d, x, y ){
+ return 'rotate(' + d + ',' + x + ',' + y + ')';
+ },
+ getX = function( d, i ){
+ //console.debug( d[ config.xColumn ] );
+ return d[ config.xColumn ];
+ },
+ getY = function( d, i ){
+ //console.debug( d[ config.yColumn ] );
+ return d[ config.yColumn ];
+ };
+
+ // .................................................................... scales
+ var stats = {
+ x : { extent: d3.extent( data, getX ) },
+ y : { extent: d3.extent( data, getY ) }
+ };
+
+ //TODO: set pan/zoom limits
+ // from http://stackoverflow.com/questions/10422738/limiting-domain-when-zooming-or…
+ //self.x.domain([Math.max(self.x.domain()[0], self.options.xmin), Math.min(self.x.domain()[1], self.options.xmax)]);
+ //self.y.domain([Math.max(self.y.domain()[0], self.options.ymin), Math.min(self.y.domain()[1], self.options.ymax)]);
+ var interpolaterFns = {
+ x : d3.scale.linear()
+ .domain( stats.x.extent )
+ .range([ 0, config.width ]),
+ y : d3.scale.linear()
+ .domain( stats.y.extent )
+ .range([ config.height, 0 ])
};
- // ........................................................ initial element creation
- this.adjustChartDimensions = function( top, right, bottom, left ){
- //this.log( this + '.adjustChartDimensions', arguments );
- top = top || 0;
- right = right || 0;
- bottom = bottom || 0;
- left = left || 0;
- this.svg
- .attr( "width", this.config.width + ( this.config.marginRight + right )
- + ( this.config.marginLeft + left ) )
- .attr( "height", this.config.height + ( this.config.marginTop + top )
- + ( this.config.marginBottom + bottom ) )
- // initial is hidden - show it
- .style( 'display', 'block' );
-
- // move content group away from margins
- //TODO: allow top, right axis
- this.content = this.svg.select( "g.content" )
- .attr( "transform", this.translateStr( this.config.marginLeft + left, this.config.marginTop + top ) );
- };
-
- // ........................................................ data and scales
- this.preprocessData = function( data, min, max ){
- //this.log( this + '.preprocessData', arguments );
- //TODO: filter by min, max if set
-
- // set a cap on the data, limit to first n points
- return ( data.length > this.config.maxDataPoints )? ( data.slice( 0, this.config.maxDataPoints ) ): ( data );
- };
-
- this.findMinMaxes = function( xCol, yCol, meta ){
- //this.log( this + '.findMinMaxes', arguments );
- // configuration takes priority, otherwise meta (from the server) if passed, last-resort: compute it here
- this.xMin = this.config.xMin || ( meta )?( meta[0].min ):( d3.min( xCol ) );
- this.xMax = this.config.xMax || ( meta )?( meta[0].max ):( d3.max( xCol ) );
- this.yMin = this.config.yMin || ( meta )?( meta[1].min ):( d3.min( yCol ) );
- this.yMax = this.config.yMax || ( meta )?( meta[1].max ):( d3.max( yCol ) );
- };
-
- this.setUpScales = function(){
- //this.log( this + '.setUpScales', arguments );
- // Interpolation for x, y based on data domains
- 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 ]);
- };
-
- // ........................................................ axis and ticks
- this.setUpXAxis = function(){
- //this.log( this + '.setUpXAxis', arguments );
- // origin: bottom, left
- //TODO: incoporate top, right
- this.xAxisFn = d3.svg.axis()
- .scale( this.xScale )
- .ticks( this.config.xNumTicks )
- .orient( 'bottom' );
- this.xAxis// = content.select( 'g#x-axis' )
- .attr( 'transform', this.translateStr( 0, this.config.height ) )
- .call( this.xAxisFn );
- //this.log( 'xAxis:', this.xAxis );
-
- //TODO: adjust ticks when tick labels are long - move odds down and extend tick line
- // (for now) hide them
- var xLongestTickLabel = d3.max( _.map( [ this.xMin, this.xMax ],
- function( number ){ return ( String( number ) ).length; } ) );
- //this.log( 'xLongestTickLabel:', xLongestTickLabel );
- if( xLongestTickLabel >= X_LABEL_TOO_LONG_AT ){
- this.xAxis.selectAll( 'g' ).filter( ':nth-child(odd)' ).style( 'display', 'none' );
- }
-
- this.log( 'this.config.xLabel:', this.config.xLabel );
- this.xAxisLabel// = xAxis.select( 'text#x-axis-label' )
- .attr( 'x', this.config.width / 2 )
- .attr( 'y', this.config.xAxisLabelBumpY )
- .attr( 'text-anchor', 'middle' )
- .text( this.config.xLabel );
- this.log( 'xAxisLabel:', this.xAxisLabel );
+ // .................................................................... main components
+ var zoom = d3.behavior.zoom()
+ .x( interpolaterFns.x )
+ .y( interpolaterFns.y )
+ .scaleExtent([ 1, 10 ]);
+//TODO: you can prog. set the zoom and pan with zoom.scale( val ) and zoom.translate([ x, y ])...
+
+ //console.debug( renderTo );
+ var svg = d3.select( renderTo )
+ .attr( "class", "scatterplot" )
+ //.attr( "width", config.width + ( config.margin.right + config.margin.left ) )
+ .attr( "width", '100%' )
+ .attr( "height", config.height + ( config.margin.top + config.margin.bottom ) );
+
+ var content = svg.append( "g" )
+ .attr( "class", "content" )
+ .attr( "transform", translateStr( config.margin.left, config.margin.top ) )
+ .call( zoom );
+
+ // a BIG gotcha - zoom (or any mouse/touch event in SVG?) requires the pointer to be over an object
+ // create a transparent rect to be that object here
+ content.append( 'rect' )
+ .attr( "class", "zoom-rect" )
+ .attr( "width", config.width ).attr( "height", config.height )
+ .style( "fill", "transparent" );
+
+ //console.log( 'svg:', svg, 'content:', content );
+
+ // .................................................................... axes
+ var axis = { x : {}, y : {} };
+ //console.log( 'x.ticks:', config.x.ticks );
+ //console.log( 'y.ticks:', config.y.ticks );
+ axis.x.fn = d3.svg.axis()
+ .orient( 'bottom' )
+ .scale( interpolaterFns.x )
+ .ticks( config.x.ticks )
+ // this will convert thousands -> k, millions -> M, etc.
+ .tickFormat( d3.format( 's' ) );
+
+ axis.y.fn = d3.svg.axis()
+ .orient( 'left' )
+ .scale( interpolaterFns.y )
+ .ticks( config.y.ticks )
+ .tickFormat( d3.format( 's' ) );
+
+ axis.x.g = content.append( 'g' )
+ .attr( 'class', 'x axis' )
+ .attr( 'transform', translateStr( 0, config.height ) )
+ .call( axis.x.fn );
+ //console.log( 'axis.x.g:', axis.x.g );
+
+ axis.y.g = content.append( 'g' )
+ .attr( 'class', 'y axis' )
+ .call( axis.y.fn );
+ //console.log( 'axis.y.g:', axis.y.g );
+
+ // ................................ axis labels
+ var padding = 4;
+ // x-axis label
+ axis.x.label = svg.append( 'text' )
+ .attr( 'class', 'axis-label' )
+ .text( config.x.label )
+ // align to the top-middle
+ .attr( 'text-anchor', 'middle' )
+ .attr( 'dominant-baseline', 'text-after-edge' )
+ .attr( 'x', ( config.width / 2 ) + config.margin.left )
+ // place 4 pixels below the axis bounds
+ .attr( 'y', ( config.height + config.margin.bottom + config.margin.top ) - padding );
+ //console.log( 'axis.x.label:', axis.x.label );
+
+//TODO: anchor to left of x margin/graph
+ // y-axis label
+ // place 4 pixels left of the axis.y.g left edge
+ axis.y.label = svg.append( 'text' )
+ .attr( 'class', 'axis-label' )
+ .text( config.y.label )
+ // align to bottom-middle
+ .attr( 'text-anchor', 'middle' )
+ .attr( 'dominant-baseline', 'text-before-edge' )
+ .attr( 'x', padding )
+ .attr( 'y', config.height / 2 )
+ // rotate around the alignment point
+ .attr( 'transform', rotateStr( -90, padding, config.height / 2 ) );
+ //console.log( 'axis.y.label:', axis.y.label );
+
+ axis.redraw = function _redrawAxis(){
+ svg.select( ".x.axis" ).call( axis.x.fn );
+ svg.select( ".y.axis" ).call( axis.y.fn );
};
- this.setUpYAxis = function(){
- //this.log( this + '.setUpYAxis', arguments );
- this.yAxisFn = d3.svg.axis()
- .scale( this.yScale )
- .ticks( this.config.yNumTicks )
- .orient( 'left' );
- this.yAxis// = content.select( 'g#y-axis' )
- .call( this.yAxisFn );
- //this.log( 'yAxis:', this.yAxis );
-
- // a too complicated section for increasing the left margin when tick labels are long
- // get the tick labels for the y axis
- var yTickLabels = this.yAxis.selectAll( 'text' ).filter( function( e, i ){ return i !== 0; } );
- this.log( 'yTickLabels:', yTickLabels );
-
- // get the longest label length (or 0 if no labels)
- this.yLongestLabel = d3.max(
- //NOTE: d3 returns an nested array - use the plain array inside ([0])
- yTickLabels[0].map( function( e, i ){
- return ( d3.select( e ).text() ).length;
- })
- ) || 0;
- //this.log( 'yLongestLabel:', this.yLongestLabel );
- //TODO: lose the guessing if possible
- var neededY = TICK_LINE_AND_PADDING + ( this.yLongestLabel * GUESS_AT_SVG_CHAR_WIDTH )
- + PADDING + GUESS_AT_SVG_CHAR_HEIGHT;
- //this.log( 'neededY:', neededY );
-
- // increase width for yLongerStr, increase margin for y
- //TODO??: (or transform each number: 2k)
- this.config.yAxisLabelBumpX = -( neededY - GUESS_AT_SVG_CHAR_HEIGHT );
- if( this.config.marginLeft < neededY ){
- var adjusting = ( neededY ) - this.config.marginLeft;
- adjusting = ( adjusting < 0 )?( 0 ):( adjusting );
- //this.log( 'adjusting:', adjusting );
-
- // update dimensions, translations
- this.adjustChartDimensions( 0, 0, 0, adjusting );
+ // .................................................................... grid
+ function renderGrid(){
+ var grid = { v : {}, h: {} };
+ // vertical
+ grid.v.lines = content.selectAll( 'line.v-grid-line' )
+ // data are the axis ticks; enter, update, exit
+ .data( interpolaterFns.x.ticks( axis.x.fn.ticks()[0] ) );
+ // enter: append any extra lines needed (more ticks)
+ grid.v.lines.enter()
+ .append( 'svg:line' )
+ .classed( 'grid-line v-grid-line', true );
+ // update: set coords
+ grid.v.lines
+ .attr( 'x1', interpolaterFns.x )
+ .attr( 'x2', interpolaterFns.x )
+ .attr( 'y1', 0 )
+ .attr( 'y2', config.height );
+ // exit: just remove them
+ grid.v.lines.exit().remove();
+ //console.log( 'grid.v.lines:', grid.v.lines );
+
+ // horizontal
+ grid.h.lines = content.selectAll( 'line.h-grid-line' )
+ .data( interpolaterFns.y.ticks( axis.y.fn.ticks()[0] ) );
+ grid.h.lines.enter()
+ .append( 'svg:line' )
+ .classed( 'grid-line h-grid-line', true );
+ grid.h.lines
+ .attr( 'x1', 0 )
+ .attr( 'x2', config.width )
+ .attr( 'y1', interpolaterFns.y )
+ .attr( 'y2', interpolaterFns.y );
+ grid.h.lines.exit().remove();
+ //console.log( 'grid.h.lines:', grid.h.lines );
+ return grid;
+ }
+ var grid = renderGrid();
+
+ //// .................................................................... datapoints
+ var datapoints = content.selectAll( '.glyph' ).data( data )
+ // enter - NEW data to be added as glyphs
+ .enter().append( 'svg:circle' )
+ .classed( "glyph", true )
+ .attr( "cx", function( d, i ){ return interpolaterFns.x( getX( d, i ) ); })
+ // give them a 'entry' position and style
+ .attr( "cy", config.height )
+ .attr( "r", 0 );
+
+ // for all EXISTING glyphs and those that need to be added: transition anim to final state
+ datapoints.transition().duration( config.animDuration )
+ .attr( "cy", function( d, i ){ return interpolaterFns.y( getY( d, i ) ); })
+ .attr( "r", config.datapointSize );
+ //console.log( 'datapoints:', datapoints );
+
+ function _redrawDatapointsClipped(){
+ return datapoints
+ .attr( "cx", function( d, i ){ return interpolaterFns.x( getX( d, i ) ); })
+ .attr( "cy", function( d, i ){ return interpolaterFns.y( getY( d, i ) ); })
+ .style( 'display', 'block' )
+ // filter out points now outside the graph content area and hide them
+ .filter( function( d, i ){
+ var cx = d3.select( this ).attr( "cx" ),
+ cy = d3.select( this ).attr( "cy" );
+ if( cx < 0 || cx > config.width ){ return true; }
+ if( cy < 0 || cy > config.height ){ return true; }
+ return false;
+ }).style( 'display', 'none' );
+ }
+
+ // .................................................................... behaviors
+ function zoomed( scale, translateX, translateY ){
+ //console.debug( 'zoom', this, scale, translateX, translateY, arguments );
+ // re-render axis, grid, and datapoints
+ axis.redraw();
+ _redrawDatapointsClipped();
+ grid = renderGrid();
+ $( '.chart-info-box' ).remove();
+ $( svg.node() ).trigger( 'zoom.scatterplot', [] );
+ }
+ //TODO: programmatically set zoom/pan and save in config
+ //TODO: set pan/zoom limits
+ zoom.on( "zoom", zoomed );
+
+
+ function infoBox( top, left, d ){
+ // create an abs pos. element containing datapoint data (d) near the point (top, left)
+ // with added padding to clear the mouse pointer
+ left += 8;
+ return $([
+ '<div class="chart-info-box" style="position: absolute">',
+ (( config.idColumn )?( '<div>' + d[ config.idColumn ] + '</div>' ):( '' )),
+ '<div>', getX( d ), '</div>',
+ '<div>', getY( d ), '</div>',
+ '</div>'
+ ].join( '' ) ).css({ top: top, left: left, 'z-index': 2 });
+ }
+
+ datapoints.on( 'mouseover', function( d, i ){
+ var datapoint = d3.select( this );
+ datapoint
+ .style( 'fill', 'red' )
+ .style( 'fill-opacity', 1 );
+
+ // create horiz line to axis
+ content.append( 'line' )
+ .attr( 'stroke', 'red' )
+ .attr( 'stroke-width', 1 )
+ // start not at center, but at the edge of the circle - to prevent mouseover thrashing
+ .attr( 'x1', datapoint.attr( 'cx' ) - config.datapointSize )
+ .attr( 'y1', datapoint.attr( 'cy' ) )
+ .attr( 'x2', 0 )
+ .attr( 'y2', datapoint.attr( 'cy' ) )
+ .classed( 'hoverline', true );
+
+ // create vertical line to axis - if not on the x axis
+ if( datapoint.attr( 'cy' ) < config.height ){
+ content.append( 'line' )
+ .attr( 'stroke', 'red' )
+ .attr( 'stroke-width', 1 )
+ .attr( 'x1', datapoint.attr( 'cx' ) )
+ // attributes are strings so, (accrd. to js) '3' - 1 = 2 but '3' + 1 = '31': coerce
+ .attr( 'y1', +datapoint.attr( 'cy' ) + config.datapointSize )
+ .attr( 'x2', datapoint.attr( 'cx' ) )
+ .attr( 'y2', config.height )
+ .classed( 'hoverline', true );
}
- //this.log( 'this.config.yAxisLableBumpx, this.config.marginLeft:',
- // this.config.yAxisLabelBumpX, this.config.marginLeft );
-
- this.yAxisLabel// = yAxis.select( 'text#y-axis-label' )
- .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 );
- };
-
- // ........................................................ grid lines
- this.renderGrid = function(){
- //this.log( this + '.renderGrid', arguments );
- // VERTICAL
- // select existing
- this.vGridLines = this.content.selectAll( 'line.v-grid-line' )
- .data( this.xScale.ticks( this.xAxisFn.ticks()[0] ) );
-
- // append any extra lines needed (more ticks)
- this.vGridLines.enter().append( 'svg:line' )
- .classed( 'grid-line v-grid-line', true );
-
- // update the attributes of existing and appended
- this.vGridLines
- .attr( 'x1', this.xScale )
- .attr( 'y1', 0 )
- .attr( 'x2', this.xScale )
- .attr( 'y2', this.config.height );
-
- // remove unneeded (less ticks)
- this.vGridLines.exit().remove();
- //this.log( 'vGridLines:', this.vGridLines );
- // HORIZONTAL
- 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 );
- };
-
- // ........................................................ data points
- this.renderDatapoints = function( xCol, yCol, ids ){
- this.log( this + '.renderDatapoints', arguments );
- var count = 0,
- plot = this,
- xPosFn = function( d, i ){
- //if( d ){ this.log( 'x.data:', newXCol[ i ], 'plotted:', plot.xScale( newXCol[ i ] ) ); }
- return plot.xScale( xCol[ i ] );
- },
- yPosFn = function( d, i ){
- //if( d ){ this.log( 'y.data:', newYCol[ i ], 'plotted:', plot.yScale( newYCol[ i ] ) ); }
- return plot.yScale( yCol[ i ] );
- };
+ // show the info box and trigger an event
+ var bbox = this.getBoundingClientRect();
+ $( 'body' ).append( infoBox( bbox.top, bbox.right, d ) );
+ $( svg.node() ).trigger( 'mouseover-datapoint.scatterplot', [ this, d, i ] );
+ });
- //this.datapoints = this.addDatapoints( xCol, yCol, ids, ".glyph" );
- var datapoints = this.content.selectAll( '.glyph' ).data( xCol );
-
- // enter - NEW data to be added as glyphs: give them a 'entry' position and style
- count = 0;
- datapoints.enter()
- .append( 'svg:circle' )
- .each( function(){ count += 1; } )
- .classed( "glyph", true )
- .attr( "cx", 0 )
- .attr( "cy", this.config.height )
- // start all bubbles small...
- .attr( "r", 0 );
- this.log( count, ' new glyphs created' );
-
- // for all EXISTING glyphs and those that need to be added: transition anim to final state
- count = 0;
- datapoints
- // ...animate to final position
- .transition().duration( this.config.animDuration )
- .each( function(){ count += 1; } )
- .attr( "cx", xPosFn )
- .attr( "cy", yPosFn )
- .attr( "r", plot.config.datapointSize );
- this.log( count, ' existing glyphs transitioned' );
-
- // events
- // glyphs that need to be removed: transition to from normal state to 'exit' state, remove from DOM
- datapoints.exit()
- .each( function(){ count += 1; } )
- .transition().duration( this.config.animDuration )
- .attr( "cy", this.config.height )
- .attr( "r", 0 )
- .remove();
- this.log( count, ' glyphs removed' );
-
- this._addDatapointEventhandlers( datapoints, xCol, yCol, ids );
- };
-
- this._addDatapointEventhandlers = function( datapoints, xCol, yCol, ids ){
- var plot = this;
- datapoints
- //TODO: remove magic numbers
- .on( 'mouseover', function( d, i ){
- var datapoint = d3.select( this );
- datapoint
- .style( 'fill', 'red' )
- .style( 'fill-opacity', 1 );
-
- // create horiz, vert lines to axis
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- // start not at center, but at the edge of the circle - to prevent mouseover thrashing
- .attr( 'x1', datapoint.attr( 'cx' ) - plot.config.datapointSize )
- .attr( 'y1', datapoint.attr( 'cy' ) )
- .attr( 'x2', 0 )
- .attr( 'y2', datapoint.attr( 'cy' ) )
- .classed( 'hoverline', true );
-
- // if the vertical hoverline
- if( datapoint.attr( 'cy' ) < plot.config.height ){
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- .attr( 'x1', datapoint.attr( 'cx' ) )
- .attr( 'y1', datapoint.attr( 'cy' ) + plot.config.datapointSize )
- .attr( 'x2', datapoint.attr( 'cx' ) )
- .attr( 'y2', plot.config.height )
- .classed( 'hoverline', true );
- }
-
- var datapointWindowPos = $( this ).offset();
- plot.datapointInfoBox = plot.infoBox(
- datapointWindowPos.top, datapointWindowPos.left,
- plot.infoHtml( xCol[ i ], yCol[ i ], ( ids )?( ids[ i ] ):( undefined ) )
- );
- $( 'body' ).append( plot.datapointInfoBox );
- })
- .on( 'mouseout', function(){
- d3.select( this )
- .style( 'fill', 'black' )
- .style( 'fill-opacity', 0.2 );
- plot.content.selectAll( '.hoverline' ).remove();
- if( plot.datapointInfoBox ){
- plot.datapointInfoBox.remove();
- }
- });
- },
-
- this.render = function( columnData, meta ){
- this.log( this + '.render', arguments );
- this.log( '\t config:', this.config );
-
- // prepare the data
- //pre: columns passed are numeric
- //pre: at least two columns are passed
- //assume: first column is x, second column is y, any remaining aren't used
- var xCol = columnData[0],
- yCol = columnData[1],
- ids = ( columnData.length > 2 )?( columnData[2] ):( undefined );
- //this.log( this + '.render', xCol.length, yCol.length, this.config );
-
- //pre: xCol.len == yCol.len
- xCol = this.preprocessData( xCol );
- yCol = this.preprocessData( yCol );
- this.log( 'xCol len', xCol.length, 'yCol len', yCol.length );
-
- this.findMinMaxes( xCol, yCol, meta );
- //this.log( 'xMin, xMax, yMin, yMax:', this.xMin, this.xMax, this.yMin, this.yMax );
- this.setUpScales();
-
- // find (or build if it doesn't exist) the svg dom infrastructure
- if( !this.svg ){ this.svg = d3.select( 'svg' ).attr( "class", "chart" ); }
- if( !this.content ){
- this.content = this.svg.append( "svg:g" ).attr( "class", "content" ).attr( 'id', this.config.id );
- }
- //this.log( 'svg:', this.svg );
- //this.log( 'content:', this.content );
-
- this.adjustChartDimensions();
-
- if( !this.xAxis ){ this.xAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'x-axis' ); }
- if( !this.xAxisLabel ){
- this.xAxisLabel = this.xAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'x-axis-label' );
- }
- //this.log( 'xAxis:', this.xAxis, 'xAxisLabel:', this.xAxisLabel );
-
- if( !this.yAxis ){ this.yAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'y-axis' ); }
- if( !this.yAxisLabel ){
- this.yAxisLabel = this.yAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'y-axis-label' );
- }
- //this.log( 'yAxis:', this.yAxis, 'yAxisLabel:', this.yAxisLabel );
-
- this.setUpXAxis();
- this.setUpYAxis();
-
- this.renderGrid();
- this.renderDatapoints( xCol, yCol, ids );
- };
-
- this.infoHtml = function( x, y, id ){
- var retDiv = $( '<div/>' );
- if( id ){
- $( '<div/>' ).text( id ).css( 'font-weight', 'bold' ).appendTo( retDiv );
- }
- $( '<div/>' ).text( x ).appendTo( retDiv );
- $( '<div/>' ).text( y ).appendTo( retDiv );
- return retDiv.html();
- };
-
- //TODO: html for now
- this.infoBox = function( top, left, html, adjTop, adjLeft ){
- adjTop = adjTop || 0;
- adjLeft = adjLeft || 20;
- var infoBox = $( '<div />' )
- .addClass( 'chart-info-box' )
- .css({
- 'position' : 'absolute',
- 'top' : top + adjTop,
- 'left' : left + adjLeft
- });
- infoBox.html( html );
- return infoBox;
- };
-
+ datapoints.on( 'mouseout', function(){
+ // return the point to normal, remove hoverlines and info box
+ d3.select( this )
+ .style( 'fill', 'black' )
+ .style( 'fill-opacity', 0.2 );
+ content.selectAll( '.hoverline' ).remove();
+ $( '.chart-info-box' ).remove();
+ });
}
//==============================================================================
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/846a89f96cc5/
Changeset: 846a89f96cc5
Branch: search
User: Kyle Ellrott
Date: 2013-11-15 20:46:52
Summary: Adding in history_id selection to hda view, so hda elements can be selected by their parent history
Affected #: 1 file
diff -r 0f07d64e1be248b0219d015d882189e29da92a01 -r 846a89f96cc5f6dabbf5dec4fd8bf45d0dbf1bcd lib/galaxy/model/search.py
--- a/lib/galaxy/model/search.py
+++ b/lib/galaxy/model/search.py
@@ -297,6 +297,7 @@
FIELDS = {
'name' : ViewField('name', sqlalchemy_field=HistoryDatasetAssociation.name),
'id' : ViewField('id',sqlalchemy_field=HistoryDatasetAssociation.id, id_decode=True),
+ 'history_id' : ViewField('history_id',sqlalchemy_field=HistoryDatasetAssociation.history_id, id_decode=True),
'tag' : ViewField("tag", handler=history_dataset_handle_tag),
'copied_from_ldda_id' : ViewField("copied_from_ldda_id",
sqlalchemy_field=HistoryDatasetAssociation.copied_from_library_dataset_dataset_association_id,
https://bitbucket.org/galaxy/galaxy-central/commits/3871ca1bbfcf/
Changeset: 3871ca1bbfcf
Branch: search
User: Kyle Ellrott
Date: 2013-11-16 00:26:24
Summary: Adding 'copied_from_hda_id' field to hda view.
Affected #: 1 file
diff -r 846a89f96cc5f6dabbf5dec4fd8bf45d0dbf1bcd -r 3871ca1bbfcfad27d63ca7ba8464714313a64ec0 lib/galaxy/model/search.py
--- a/lib/galaxy/model/search.py
+++ b/lib/galaxy/model/search.py
@@ -302,6 +302,9 @@
'copied_from_ldda_id' : ViewField("copied_from_ldda_id",
sqlalchemy_field=HistoryDatasetAssociation.copied_from_library_dataset_dataset_association_id,
id_decode=True),
+ 'copied_from_hda_id' : ViewField("copied_from_hda_id",
+ sqlalchemy_field=HistoryDatasetAssociation.copied_from_history_dataset_association_id,
+ id_decode=True),
'deleted' : ViewField('deleted', sqlalchemy_field=HistoryDatasetAssociation.deleted)
}
https://bitbucket.org/galaxy/galaxy-central/commits/5ebf2d14c2ee/
Changeset: 5ebf2d14c2ee
User: dannon
Date: 2013-11-19 20:20:33
Summary: Merged in kellrott/galaxy-central/search (pull request #261)
Adding history_id selection to hda search
Affected #: 1 file
diff -r e5d2170d26ab226d326b80003e8838b2d255d6c4 -r 5ebf2d14c2eea6bd8425e22ded37b7346b65c282 lib/galaxy/model/search.py
--- a/lib/galaxy/model/search.py
+++ b/lib/galaxy/model/search.py
@@ -297,10 +297,14 @@
FIELDS = {
'name' : ViewField('name', sqlalchemy_field=HistoryDatasetAssociation.name),
'id' : ViewField('id',sqlalchemy_field=HistoryDatasetAssociation.id, id_decode=True),
+ 'history_id' : ViewField('history_id',sqlalchemy_field=HistoryDatasetAssociation.history_id, id_decode=True),
'tag' : ViewField("tag", handler=history_dataset_handle_tag),
'copied_from_ldda_id' : ViewField("copied_from_ldda_id",
sqlalchemy_field=HistoryDatasetAssociation.copied_from_library_dataset_dataset_association_id,
id_decode=True),
+ 'copied_from_hda_id' : ViewField("copied_from_hda_id",
+ sqlalchemy_field=HistoryDatasetAssociation.copied_from_history_dataset_association_id,
+ id_decode=True),
'deleted' : ViewField('deleted', sqlalchemy_field=HistoryDatasetAssociation.deleted)
}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: nsoranzo: Fix API workflow show for workflows created with history Extract Workflow.
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e5d2170d26ab/
Changeset: e5d2170d26ab
User: nsoranzo
Date: 2013-11-19 19:36:17
Summary: Fix API workflow show for workflows created with history Extract Workflow.
To reproduce:
1) for an existing history, select "Extract Workflow" from the history menu
2) find the id of the new workflow, e.g. by opening /api/workflows/
3) open /api/workflows/$workflow_id
Result is an Internal Server Error.
In Galaxy log:
galaxy.web.framework ERROR 2013-11-19 12:44:17,367 Uncaught exception in exposed API method:
Traceback (most recent call last):
File "/srv/galaxy/lib/galaxy/web/framework/__init__.py", line 197, in decorator
rval = func( self, trans, *args, **kwargs)
File "/srv/galaxy/lib/galaxy/webapps/galaxy/api/workflows.py", line 77, in show
inputs[step.id] = {'label':step.tool_inputs['name'], 'value':""}
TypeError: 'NoneType' object has no attribute '__getitem__'
Reported-by: Simone Leo <simone.leo(a)crs4.it>
Affected #: 2 files
diff -r f2186f4796ad4c6aa2c9191f9804d8f5d59eb15d -r e5d2170d26ab226d326b80003e8838b2d255d6c4 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -73,7 +73,10 @@
inputs = {}
for step in latest_workflow.steps:
if step.type == 'data_input':
- inputs[step.id] = {'label':step.tool_inputs['name'], 'value':""}
+ if step.tool_inputs and "name" in step.tool_inputs:
+ inputs[step.id] = {'label':step.tool_inputs['name'], 'value':""}
+ else:
+ inputs[step.id] = {'label':"Input Dataset", 'value':""}
else:
pass
# Eventually, allow regular tool parameters to be inserted and modified at runtime.
diff -r f2186f4796ad4c6aa2c9191f9804d8f5d59eb15d -r e5d2170d26ab226d326b80003e8838b2d255d6c4 lib/galaxy/webapps/galaxy/controllers/workflow.py
--- a/lib/galaxy/webapps/galaxy/controllers/workflow.py
+++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py
@@ -1223,6 +1223,7 @@
for hid in dataset_ids:
step = model.WorkflowStep()
step.type = 'data_input'
+ step.tool_inputs = dict( name="Input Dataset" )
hid_to_output_pair[ hid ] = ( step, 'output' )
steps.append( step )
# Tool steps
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Remove print from f166a09
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f2186f4796ad/
Changeset: f2186f4796ad
User: carlfeberhard
Date: 2013-11-19 16:29:38
Summary: Remove print from f166a09
Affected #: 1 file
diff -r f166a093ebe3237d1374c3ebda520f085a85ba1d -r f2186f4796ad4c6aa2c9191f9804d8f5d59eb15d lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -510,9 +510,6 @@
# result type should tell the registry how to convert the result before the test
test_result_type = test_elem.get( 'result_type', 'string' )
- print
- print test_attr, test_result_type
- print
# test functions should be sent an object to test, and the parsed result expected from the test
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/22fd4e79f08c/
Changeset: 22fd4e79f08c
User: carlfeberhard
Date: 2013-11-19 16:24:35
Summary: Visualizations API: fix create/update validation
Affected #: 1 file
diff -r b812508869b95cc994d049f724d9475aafd417d9 -r 22fd4e79f08c0185be71fbe37fa43d4aad631e78 lib/galaxy/webapps/galaxy/api/visualizations.py
--- a/lib/galaxy/webapps/galaxy/api/visualizations.py
+++ b/lib/galaxy/webapps/galaxy/api/visualizations.py
@@ -10,7 +10,7 @@
from sqlalchemy import or_
from galaxy import web, util
-from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin
+from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin, SharableMixin
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.exceptions import ( ItemAccessibilityException, ItemDeletionException, ItemOwnershipException,
MessageException )
@@ -20,7 +20,7 @@
import logging
log = logging.getLogger( __name__ )
-class VisualizationsController( BaseAPIController, UsesVisualizationMixin, UsesAnnotations ):
+class VisualizationsController( BaseAPIController, UsesVisualizationMixin, SharableMixin, UsesAnnotations ):
"""
RESTful controller for interactions with visualizations.
"""
@@ -122,9 +122,10 @@
else:
payload = self._validate_and_parse_payload( payload )
+ vis_type = payload.pop( 'type', False )
payload[ 'save' ] = True
- # create needs defaults like wizard needs food - generate defaults - this will err if given a weird key?
- visualization = self.create_visualization( trans, **payload )
+ # generate defaults - this will err if given a weird key?
+ visualization = self.create_visualization( trans, vis_type, **payload )
rval = { 'id' : trans.security.encode_id( visualization.id ) }
@@ -217,11 +218,20 @@
#TODO: deleted
#TODO: importable
+ # must have a type (I've taken this to be the visualization name)
+ if 'type' not in payload:
+ raise ValueError( "key/value 'type' is required" )
+
validated_payload = {}
for key, val in payload.items():
- if key == 'config':
+ #TODO: validate types in VALID_TYPES/registry names at the mixin/model level?
+ if key == 'type':
+ if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
+ raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
+ val = util.sanitize_html.sanitize_html( val, 'utf-8' )
+ elif key == 'config':
if not isinstance( val, dict ):
- raise ValueError( '%s must be a dictionary (JSON): %s' %( key, str( type( val ) ) ) )
+ raise ValueError( '%s must be a dictionary: %s' %( key, str( type( val ) ) ) )
elif key == 'annotation':
if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
@@ -235,21 +245,16 @@
raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
elif key == 'slug':
- if not isinstance( val, str ):
+ if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) )
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
- elif key == 'type':
- if not isinstance( val, str ):
- raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) )
- val = util.sanitize_html.sanitize_html( val, 'utf-8' )
- #TODO: validate types in VALID_TYPES/registry names at the mixin/model level?
elif key == 'dbkey':
if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
val = util.sanitize_html.sanitize_html( val, 'utf-8' )
- elif key not in valid_but_uneditable_keys:
- raise AttributeError( 'unknown key: %s' %( str( key ) ) )
+ #elif key not in valid_but_uneditable_keys:
+ # raise AttributeError( 'unknown key: %s' %( str( key ) ) )
validated_payload[ key ] = val
return validated_payload
https://bitbucket.org/galaxy/galaxy-central/commits/f166a093ebe3/
Changeset: f166a093ebe3
User: carlfeberhard
Date: 2013-11-19 16:26:48
Summary: Visualizations Registry: propery default to str eqv test for test_type, properly call getattr_lambda
Affected #: 1 file
diff -r 22fd4e79f08c0185be71fbe37fa43d4aad631e78 -r f166a093ebe3237d1374c3ebda520f085a85ba1d lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -477,7 +477,7 @@
return lambda o: getattr( o, next_attr_name )
# recursive case
- return lambda o: getattr( self._build_getattr_lambda( attr_name_list[:-1] ), next_attr_name )
+ return lambda o: getattr( self._build_getattr_lambda( attr_name_list[:-1] )( o ), next_attr_name )
def parse_tests( self, xml_tree_list ):
"""
@@ -493,7 +493,7 @@
return tests
for test_elem in xml_tree_list:
- test_type = test_elem.get( 'type' )
+ test_type = test_elem.get( 'type', 'eq' )
test_result = test_elem.text
if not test_type or not test_result:
log.warn( 'Skipping test. Needs both type attribute and text node to be parsed: '
@@ -509,9 +509,13 @@
getter = self._build_getattr_lambda( test_attr )
# result type should tell the registry how to convert the result before the test
- test_result_type = test_elem.get( 'result_type' ) or 'string'
+ test_result_type = test_elem.get( 'result_type', 'string' )
+ print
+ print test_attr, test_result_type
+ print
# test functions should be sent an object to test, and the parsed result expected from the test
+
# is test_attr attribute an instance of result
if test_type == 'isinstance':
#TODO: wish we could take this further but it would mean passing in the datatypes_registry
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Scatterplot (plugin version): small fixes, move dataset out of config, namespace hbrs templates
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b812508869b9/
Changeset: b812508869b9
User: carlfeberhard
Date: 2013-11-19 16:19:22
Summary: Scatterplot (plugin version): small fixes, move dataset out of config, namespace hbrs templates
Affected #: 6 files
diff -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 -r b812508869b95cc994d049f724d9475aafd417d9 config/plugins/visualizations/scatterplot/Gruntfile.js
--- a/config/plugins/visualizations/scatterplot/Gruntfile.js
+++ b/config/plugins/visualizations/scatterplot/Gruntfile.js
@@ -9,7 +9,7 @@
// compile all hb templates into a single file in the build dir
compile: {
options: {
- namespace: 'Templates',
+ namespace: 'scatterplot',
processName : function( filepath ){
return filepath.match( /\w*\.handlebars/ )[0].replace( '.handlebars', '' );
}
diff -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 -r b812508869b95cc994d049f724d9475aafd417d9 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -37,14 +37,19 @@
/** initialize requires a configuration Object containing a dataset Object */
initialize : function( attributes ){
//console.log( this + '.initialize, attributes:', attributes );
- if( !attributes || !attributes.config || !attributes.config.dataset ){
+ if( !attributes || !attributes.config || !attributes.dataset ){
throw new Error( "ScatterplotView requires a configuration and dataset" );
}
- this.dataset = attributes.config.dataset;
+ //console.log( 'config:', attributes.config );
+
+ this.dataset = attributes.dataset;
//console.log( 'dataset:', this.dataset );
+//TODO: ScatterplotView -> ScatterplotDisplay, this.plotView -> this.display
this.plotView = new ScatterplotView({
+ dataset : attributes.dataset,
config : attributes.config
+//TODO: if data
});
},
@@ -197,8 +202,8 @@
// parse the column values for both indeces (for the data fetch) and names (for the chart)
var $dataControls = this.$el.find( '#data-control' );
var settings = {
- xColumn : $dataControls.find( '[name="xColumn"]' ).val(),
- yColumn : $dataControls.find( '[name="yColumn"]' ).val()
+ xColumn : Number( $dataControls.find( '[name="xColumn"]' ).val() ),
+ yColumn : Number( $dataControls.find( '[name="yColumn"]' ).val() )
};
if( $dataControls.find( '#include-id-checkbox' ).prop( 'checked' ) ){
settings.idColumn = $dataControls.find( '[name="idColumn"]' ).val();
@@ -229,9 +234,9 @@
});
ScatterplotConfigEditor.templates = {
- mainLayout : Templates.editor,
- dataControl : Templates.datacontrol,
- chartControl : Templates.chartcontrol
+ mainLayout : scatterplot.editor,
+ dataControl : scatterplot.datacontrol,
+ chartControl : scatterplot.chartcontrol
};
//==============================================================================
diff -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 -r b812508869b95cc994d049f724d9475aafd417d9 config/plugins/visualizations/scatterplot/src/scatterplot-display.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
@@ -10,14 +10,10 @@
//TODO: should be a view on visualization(revision) model
defaults : {
- dataset : {
- },
metadata : {
dataLines : undefined
},
- ajaxFn : null,
-
pagination : {
currPage : 0,
perPage : 3000
@@ -48,6 +44,7 @@
initialize : function( attributes ){
this.config = _.extend( _.clone( this.defaults ), attributes.config || {});
+ this.dataset = attributes.dataset;
//console.debug( this + '.config:', this.config );
},
@@ -65,7 +62,7 @@
//console.debug( 'currPage', this.config.pagination.currPage );
var view = this;
//TODO: very tied to datasets - should be generalized eventually
- xhr = jQuery.getJSON( '/api/datasets/' + this.config.dataset.id, {
+ xhr = jQuery.getJSON( '/api/datasets/' + this.dataset.id, {
data_type : 'raw_data',
provider : 'dataset-column',
limit : this.config.pagination.perPage,
@@ -151,7 +148,7 @@
},
renderLineInfo : function( data ){
- var totalLines = this.config.dataset.metadata_data_lines || 'an unknown number of',
+ var totalLines = this.dataset.metadata_data_lines || 'an unknown number of',
lineStart = ( this.config.pagination.currPage * this.config.pagination.perPage ),
lineEnd = lineStart + data.length;
return $( '<p/>' ).addClass( 'scatterplot-data-info' )
@@ -168,9 +165,9 @@
}
//TODO: cache numPages/numLines in config
var view = this,
- dataLines = this.config.dataset.metadata_data_lines,
+ dataLines = this.dataset.metadata_data_lines,
numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined );
- //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages );
+ //console.debug( 'data:', this.dataset.metadata_data_lines, 'numPages:', numPages );
// prev next buttons
var $prev = makePage$Li( 'Prev' ).click( function(){
@@ -207,9 +204,9 @@
}
//TODO: cache numPages/numLines in config
var view = this,
- dataLines = this.config.dataset.metadata_data_lines,
+ dataLines = this.dataset.metadata_data_lines,
numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined );
- //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages );
+ //console.debug( 'data:', this.dataset.metadata_data_lines, 'numPages:', numPages );
// page numbers (as separate control)
//var $paginationContainer = $( '<div/>' ).addClass( 'pagination-container' ),
diff -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 -r b812508869b95cc994d049f724d9475aafd417d9 config/plugins/visualizations/scatterplot/src/visualization-templates.html
--- a/config/plugins/visualizations/scatterplot/src/visualization-templates.html
+++ /dev/null
@@ -1,197 +0,0 @@
-<script type="text/template" class="template-visualization" id="template-visualization-scatterplotControlForm">
-{{! main layout }}
-
-<h1>WHAAAAA?</h1>
-<div class="scatterplot-container chart-container tabbable tabs-left">
- {{! tab buttons/headers using Bootstrap }}
- <ul class="nav nav-tabs">
- {{! start with the data controls as the displayed tab }}
- <li class="active">
- <a title="Use this tab to change which data are used"
- href="#data-control" data-toggle="tab">Data Controls</a>
- </li>
- <li>
- <a title="Use this tab to change how the chart is drawn"
- href="#chart-control" data-toggle="tab" >Chart Controls</a>
- </li>
- <li>
- <a title="This tab will display overall statistics for your data"
- href="#stats-display" data-toggle="tab">Statistics</a>
- </li>
- <li>
- <a title="This tab will display the chart"
- href="#chart-display" data-toggle="tab">Chart</a>
-
- <div id="loading-indicator" style="display: none;">
- <img class="loading-img" src="{{loadingIndicatorImagePath}}" />
- <span class="loading-message">{{message}}</span>
- </div>
- </li>
- </ul>
-
- {{! data form, chart config form, stats, and chart all get their own tab }}
- <div class="tab-content">
- {{! ---------------------------- tab for data settings form }}
- <div id="data-control" class="tab-pane active">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for chart graphics control form }}
- <div id="chart-control" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for data statistics }}
- <div id="stats-display" class="tab-pane">
- <p class="help-text">By column:</p>
- <table id="chart-stats-table">
- <thead><th></th><th>X</th><th>Y</th></thead>
- {{#each stats}}
- <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
- </tr>
- {{/each}}
- </table>
- </div>
-
- {{! ---------------------------- tab for actual chart }}
- <div id="chart-display" class="tab-pane">
- <svg width="{{width}}" height="{{height}}"></svg>
- </div>
-
- </div>{{! end .tab-content }}
-</div>{{! end .chart-control }}
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-dataControl">
-
- <p class="help-text">
- Use the following controls to change the data used by the chart.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- {{! column selector containers }}
- <div class="column-select">
- <label for="X-select">Data column for X: </label>
- <select name="X" id="X-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- <div class="column-select">
- <label for="Y-select">Data column for Y: </label>
- <select name="Y" id="Y-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! optional id column }}
- <div id="include-id">
- <label for="include-id-checkbox">Include a third column as data point IDs?</label>
- <input type="checkbox" name="include-id" id="include-id-checkbox" />
- <p class="help-text-small">
- These will be displayed (along with the x and y values) when you hover over
- a data point.
- </p>
- </div>
- <div class="column-select" style="display: none">
- <label for="ID-select">Data column for IDs: </label>
- <select name="ID" id="ID-select">
- {{#each allColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
- <div id="first-line-header" style="display: none;">
- <p>Possible headers: {{ possibleHeaders }}
- </p>
- <label for="first-line-header-checkbox">Use the above as column headers?</label>
- <input type="checkbox" name="include-id" id="first-line-header-checkbox"
- {{#if usePossibleHeaders }}checked="true"{{/if}}/>
- <p class="help-text-small">
- It looks like Galaxy couldn't get proper column headers for this data.
- Would you like to use the column headers above as column names to select columns?
- </p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
- <div class="clear"></div>
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-chartControl">
- <p class="help-text">
- Use the following controls to how the chart is displayed.
- The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
- Move the focus between controls by using the tab or shift+tab keys on your keyboard.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- <div id="datapointSize" class="form-input numeric-slider-input">
- <label for="datapointSize">Size of data point: </label>
- <div class="slider-output">{{datapointSize}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- Size of the graphic representation of each data point
- </p>
- </div>
-
- <div id="animDuration" class="form-input checkbox-input">
- <label for="animate-chart">Animate chart transitions?: </label>
- <input type="checkbox" id="animate-chart"
- class="checkbox control"{{#if animDuration}} checked="true"{{/if}} />
- <p class="form-help help-text-small">
- Uncheck this to disable the animations used on the chart
- </p>
- </div>
-
- <div id="width" class="form-input numeric-slider-input">
- <label for="width">Chart width: </label>
- <div class="slider-output">{{width}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="height" class="form-input numeric-slider-input">
- <label for="height">Chart height: </label>
- <div class="slider-output">{{height}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="X-axis-label"class="text-input form-input">
- <label for="X-axis-label">Re-label the X axis: </label>
- <input type="text" name="X-axis-label" id="X-axis-label" value="{{xLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <div id="Y-axis-label" class="text-input form-input">
- <label for="Y-axis-label">Re-label the Y axis: </label>
- <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{yLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-statsDisplay">
- <p class="help-text">By column:</p>
- <table id="chart-stats-table">
- <thead><th></th><th>X</th><th>Y</th></thead>
- {{#each stats}}
- <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
- </tr>
- {{/each}}
- </table>
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-chartDisplay">
- <svg width="{{width}}" height="{{height}}"></svg>
-</script>
diff -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 -r b812508869b95cc994d049f724d9475aafd417d9 config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
--- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
@@ -1,1 +1,1 @@
-function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){q.redraw(),e(),s=d(),$(".chart-info-box").remove(),$(o.node()).trigger("zoom.scatterplot",[])}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,10]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=4;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",b.height).attr("r",0);t.transition().duration(b.animDuration).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",b.datapointSize),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.Templates=this.Templates||{},this.Templates.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.Templates.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n</div>\n<div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.Templates.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(!a||!a.config||!a.config.dataset)throw new Error("ScatterplotView requires a configuration and dataset");this.dataset=a.config.dataset,this.plotView=new ScatterplotView({config:a.config})},render:function(){return this.$el.append(ScatterplotConfigEditor.templates.mainLayout({})),this.$el.find("#data-control").append(this._render_dataControl()),this._render_chartControls(this.$el.find("#chart-control")),this._render_chartDisplay(),this.$el.find("[title]").tooltip(),this},_render_dataControl:function(){var a=this.dataset,b=_.map(a.metadata_column_types,function(b,c){var d={index:c,type:b,name:"column "+(c+1)};return a.metadata_column_names&&a.metadata_column_names[c]&&(d.name=a.metadata_column_names[c]),d}),c=_.filter(b,function(a){return"int"===a.type||"float"===a.type});2>c&&(c=b);var d=this.$el.find(".tab-pane#data-control");return d.html(ScatterplotConfigEditor.templates.dataControl({allColumns:b,numericColumns:c})),d.find('[name="xColumn"]').val(this.plotView.config.xColumn||c[0].index),d.find('[name="yColumn"]').val(this.plotView.config.yColumn||c[1].index),void 0!==this.plotView.config.idColumn&&(d.find("#include-id-checkbox").prop("checked",!0).trigger("change"),d.find('select[name="idColumn"]').val(this.plotView.config.idColumn)),d},_render_chartControls:function(a){function b(){var a=$(this);a.siblings(".slider-output").text(a.slider("value"))}a.html(ScatterplotConfigEditor.templates.chartControl(this.plotView.config));var c=this,d={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};return a.find(".numeric-slider-input").each(function(){var a=$(this),e=a.attr("data-config-key"),f=_.extend(d[e],{value:c.plotView.config[e],change:b,slide:b});a.find(".slider").slider(f)}),this.dataset.metadata_column_names,a},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");return this.plotView.setElement(a),this.plotView.render(),a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart"},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.updateConfigWithDataSettings(),this.updateConfigWithChartSettings(),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.plotView.fetchData()},updateConfigWithDataSettings:function(){var a=this.$el.find("#data-control"),b={xColumn:a.find('[name="xColumn"]').val(),yColumn:a.find('[name="yColumn"]').val()};return a.find("#include-id-checkbox").prop("checked")&&(b.idColumn=a.find('[name="idColumn"]').val()),_.extend(this.plotView.config,b)},updateConfigWithChartSettings:function(){var a=this.plotView,b=this.$el.find("#chart-control");return["datapointSize","width","height"].forEach(function(c){a.config[c]=b.find('.numeric-slider-input[data-config-key="'+c+'"]').find(".slider").slider("value")}),a.config.x.label=b.find('input[name="X-axis-label"]').val(),a.config.y.label=b.find('input[name="Y-axis-label"]').val(),a.config},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:Templates.editor,dataControl:Templates.datacontrol,chartControl:Templates.chartcontrol};var ScatterplotView=Backbone.View.extend({defaults:{dataset:{},metadata:{dataLines:void 0},ajaxFn:null,pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500},initialize:function(a){this.config=_.extend(_.clone(this.defaults),a.config||{})},updateConfig:function(a){this.config=this.config||{},_.extend(this.config,a)},fetchData:function(){this.showLoadingIndicator("getting data");var a=this;return xhr=jQuery.getJSON("/api/datasets/"+this.config.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:this.config.pagination.perPage,offset:this.config.pagination.currPage*this.config.pagination.perPage}),xhr.done(function(b){a.renderData(b.data)}),xhr.fail(function(a,b,c){alert("Error loading data:\n"+a.responseText),console.error(a,b,c)}),xhr.always(function(){a.hideLoadingIndicator()}),xhr},render:function(a){return this.$el.addClass("scatterplot-display").html(['<div class="controls clear"></div>','<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message"></span>',"</div>","<svg/>",'<div class="stats-display"></div>'].join("")),this.$el.children().hide(),a&&this.renderData(a),this},showLoadingIndicator:function(a,b){a=a||"",b=b||"fast";var c=this.$el.find(".loading-indicator");a&&c.find(".loading-indicator-message").text(a),c.is(":visible")||(this.toggleStats(!1),c.css({left:this.config.width/2,top:this.config.height/2}).show())},hideLoadingIndicator:function(a){a=a||"fast",this.$el.find(".loading-indicator").hide()},renderData:function(a){this.$el.find(".controls").empty().append(this.renderControls(a)).show(),this.renderPlot(a),this.getStats(a)},renderControls:function(a){var b=this,c=$('<div class="left"></div>'),d=$('<div class="right"></div>');return c.append([this.renderPrevNext(a),this.renderPagination(a)]),d.append([this.renderLineInfo(a),$("<button>Stats</button>").addClass("stats-toggle-btn").click(function(){b.toggleStats()}),$("<button>Redraw</button>").addClass("rerender-btn").click(function(){b.renderPlot(a)})]),[c,d]},renderLineInfo:function(a){var b=this.config.dataset.metadata_data_lines||"an unknown number of",c=this.config.pagination.currPage*this.config.pagination.perPage,d=c+a.length;return $("<p/>").addClass("scatterplot-data-info").text(["Displaying lines",c+1,"to",d,"of",b,"lines"].join(" "))},renderPrevNext:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;var c=this,d=this.config.dataset.metadata_data_lines,e=d?Math.ceil(d/this.config.pagination.perPage):void 0,f=b("Prev").click(function(){c.config.pagination.currPage>0&&(c.config.pagination.currPage-=1,c.fetchData())}),g=b("Next").click(function(){(!e||c.config.pagination.currPage<e-1)&&(c.config.pagination.currPage+=1,c.fetchData())}),h=$("<ul/>").addClass("pagination data-prev-next").append([f,g]);return 0===c.config.pagination.currPage&&f.addClass("disabled"),e&&c.config.pagination.currPage===e-1&&g.addClass("disabled"),h},renderPagination:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}function c(){d.config.pagination.currPage=$(this).data("page"),d.fetchData()}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;for(var d=this,e=this.config.dataset.metadata_data_lines,f=e?Math.ceil(e/this.config.pagination.perPage):void 0,g=$("<ul/>").addClass("pagination data-pages"),h=0;f>h;h+=1){var i=b(h+1).attr("data-page",h).click(c);h===this.config.pagination.currPage&&i.addClass("active"),g.append(i)}return g},renderPlot:function(a){this.toggleStats(!1);var b=this.$el.find("svg");b.off().empty().show(),scatterplot(b.get(0),this.config,a)},getStats:function(a){var b=this;meanWorker=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js"),meanWorker.postMessage({data:a,keys:[this.config.xColumn,this.config.yColumn]}),meanWorker.onerror=function(){meanWorker.terminate()},meanWorker.onmessage=function(a){b.renderStats(a.data)}},renderStats:function(a){var b=this.$el.find(".stats-display"),c=this.config.x.label,d=this.config.y.label,e=$("<table/>").addClass("table").append(["<thead><th></th><th>",c,"</th><th>",d,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));b.empty().append(e)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}});
\ No newline at end of file
+function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){q.redraw(),e(),s=d(),$(".chart-info-box").remove(),$(o.node()).trigger("zoom.scatterplot",[])}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,10]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=4;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",b.height).attr("r",0);t.transition().duration(b.animDuration).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",b.datapointSize),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.Templates=this.Templates||{},this.Templates.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.Templates.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n</div>\n<div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.Templates.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(!a||!a.config||!a.dataset)throw new Error("ScatterplotView requires a configuration and dataset");this.dataset=a.dataset,this.plotView=new ScatterplotView({dataset:a.dataset,config:a.config})},render:function(){return this.$el.append(ScatterplotConfigEditor.templates.mainLayout({})),this.$el.find("#data-control").append(this._render_dataControl()),this._render_chartControls(this.$el.find("#chart-control")),this._render_chartDisplay(),this.$el.find("[title]").tooltip(),this},_render_dataControl:function(){var a=this.dataset,b=_.map(a.metadata_column_types,function(b,c){var d={index:c,type:b,name:"column "+(c+1)};return a.metadata_column_names&&a.metadata_column_names[c]&&(d.name=a.metadata_column_names[c]),d}),c=_.filter(b,function(a){return"int"===a.type||"float"===a.type});2>c&&(c=b);var d=this.$el.find(".tab-pane#data-control");return d.html(ScatterplotConfigEditor.templates.dataControl({allColumns:b,numericColumns:c})),d.find('[name="xColumn"]').val(this.plotView.config.xColumn||c[0].index),d.find('[name="yColumn"]').val(this.plotView.config.yColumn||c[1].index),void 0!==this.plotView.config.idColumn&&(d.find("#include-id-checkbox").prop("checked",!0).trigger("change"),d.find('select[name="idColumn"]').val(this.plotView.config.idColumn)),d},_render_chartControls:function(a){function b(){var a=$(this);a.siblings(".slider-output").text(a.slider("value"))}a.html(ScatterplotConfigEditor.templates.chartControl(this.plotView.config));var c=this,d={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};return a.find(".numeric-slider-input").each(function(){var a=$(this),e=a.attr("data-config-key"),f=_.extend(d[e],{value:c.plotView.config[e],change:b,slide:b});a.find(".slider").slider(f)}),this.dataset.metadata_column_names,a},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");return this.plotView.setElement(a),this.plotView.render(),a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart"},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.updateConfigWithDataSettings(),this.updateConfigWithChartSettings(),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.plotView.fetchData()},updateConfigWithDataSettings:function(){var a=this.$el.find("#data-control"),b={xColumn:Number(a.find('[name="xColumn"]').val()),yColumn:Number(a.find('[name="yColumn"]').val())};return a.find("#include-id-checkbox").prop("checked")&&(b.idColumn=a.find('[name="idColumn"]').val()),_.extend(this.plotView.config,b)},updateConfigWithChartSettings:function(){var a=this.plotView,b=this.$el.find("#chart-control");return["datapointSize","width","height"].forEach(function(c){a.config[c]=b.find('.numeric-slider-input[data-config-key="'+c+'"]').find(".slider").slider("value")}),a.config.x.label=b.find('input[name="X-axis-label"]').val(),a.config.y.label=b.find('input[name="Y-axis-label"]').val(),a.config},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:Templates.editor,dataControl:Templates.datacontrol,chartControl:Templates.chartcontrol};var ScatterplotView=Backbone.View.extend({defaults:{metadata:{dataLines:void 0},pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500},initialize:function(a){this.config=_.extend(_.clone(this.defaults),a.config||{}),this.dataset=a.dataset},updateConfig:function(a){this.config=this.config||{},_.extend(this.config,a)},fetchData:function(){this.showLoadingIndicator("getting data");var a=this;return xhr=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:this.config.pagination.perPage,offset:this.config.pagination.currPage*this.config.pagination.perPage}),xhr.done(function(b){a.renderData(b.data)}),xhr.fail(function(a,b,c){alert("Error loading data:\n"+a.responseText),console.error(a,b,c)}),xhr.always(function(){a.hideLoadingIndicator()}),xhr},render:function(a){return this.$el.addClass("scatterplot-display").html(['<div class="controls clear"></div>','<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message"></span>',"</div>","<svg/>",'<div class="stats-display"></div>'].join("")),this.$el.children().hide(),a&&this.renderData(a),this},showLoadingIndicator:function(a,b){a=a||"",b=b||"fast";var c=this.$el.find(".loading-indicator");a&&c.find(".loading-indicator-message").text(a),c.is(":visible")||(this.toggleStats(!1),c.css({left:this.config.width/2,top:this.config.height/2}).show())},hideLoadingIndicator:function(a){a=a||"fast",this.$el.find(".loading-indicator").hide()},renderData:function(a){this.$el.find(".controls").empty().append(this.renderControls(a)).show(),this.renderPlot(a),this.getStats(a)},renderControls:function(a){var b=this,c=$('<div class="left"></div>'),d=$('<div class="right"></div>');return c.append([this.renderPrevNext(a),this.renderPagination(a)]),d.append([this.renderLineInfo(a),$("<button>Stats</button>").addClass("stats-toggle-btn").click(function(){b.toggleStats()}),$("<button>Redraw</button>").addClass("rerender-btn").click(function(){b.renderPlot(a)})]),[c,d]},renderLineInfo:function(a){var b=this.dataset.metadata_data_lines||"an unknown number of",c=this.config.pagination.currPage*this.config.pagination.perPage,d=c+a.length;return $("<p/>").addClass("scatterplot-data-info").text(["Displaying lines",c+1,"to",d,"of",b,"lines"].join(" "))},renderPrevNext:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;var c=this,d=this.dataset.metadata_data_lines,e=d?Math.ceil(d/this.config.pagination.perPage):void 0,f=b("Prev").click(function(){c.config.pagination.currPage>0&&(c.config.pagination.currPage-=1,c.fetchData())}),g=b("Next").click(function(){(!e||c.config.pagination.currPage<e-1)&&(c.config.pagination.currPage+=1,c.fetchData())}),h=$("<ul/>").addClass("pagination data-prev-next").append([f,g]);return 0===c.config.pagination.currPage&&f.addClass("disabled"),e&&c.config.pagination.currPage===e-1&&g.addClass("disabled"),h},renderPagination:function(a){function b(a){return $(['<li><a href="javascript:void(0);">',a,"</a></li>"].join(""))}function c(){d.config.pagination.currPage=$(this).data("page"),d.fetchData()}if(!a||0===this.config.pagination.currPage&&a.length<this.config.pagination.perPage)return null;for(var d=this,e=this.dataset.metadata_data_lines,f=e?Math.ceil(e/this.config.pagination.perPage):void 0,g=$("<ul/>").addClass("pagination data-pages"),h=0;f>h;h+=1){var i=b(h+1).attr("data-page",h).click(c);h===this.config.pagination.currPage&&i.addClass("active"),g.append(i)}return g},renderPlot:function(a){this.toggleStats(!1);var b=this.$el.find("svg");b.off().empty().show(),scatterplot(b.get(0),this.config,a)},getStats:function(a){var b=this;meanWorker=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js"),meanWorker.postMessage({data:a,keys:[this.config.xColumn,this.config.yColumn]}),meanWorker.onerror=function(){meanWorker.terminate()},meanWorker.onmessage=function(a){b.renderStats(a.data)}},renderStats:function(a){var b=this.$el.find(".stats-display"),c=this.config.x.label,d=this.config.y.label,e=$("<table/>").addClass("table").append(["<thead><th></th><th>",c,"</th><th>",d,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));b.empty().append(e)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}});
\ No newline at end of file
diff -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 -r b812508869b95cc994d049f724d9475aafd417d9 config/plugins/visualizations/scatterplot/templates/scatterplot.mako
--- a/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
+++ b/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
@@ -43,22 +43,18 @@
data = None
##data = list( hda.datatype.dataset_column_dataprovider( hda, limit=10000 ) )
%>
- var hda = ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ) )},
- data = ${h.to_json_string( data )},
- querySettings = ${h.to_json_string( query_args )},
- config = _.extend( querySettings, {
- containerSelector : '#chart',
- dataset : hda,
- });
- //console.debug( querySettings );
+ var hda = ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ) )};
var editor = new ScatterplotConfigEditor({
el : $( '.scatterplot-editor' ).attr( 'id', 'scatterplot-editor-hda-' + hda.id ),
- config : config
+ config : ${h.to_json_string( query_args )},
+ dataset : ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ) )}
}).render();
+ window.editor = editor;
// uncomment to auto render for development
//$( '.render-button:visible' ).click();
});
+
</script>
%endif
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Log more information about tool dependency and repository installation status when installing and testing.
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/aa8204e99326/
Changeset: aa8204e99326
User: Dave Bouvier
Date: 2013-11-19 15:51:30
Summary: Log more information about tool dependency and repository installation status when installing and testing.
Affected #: 2 files
diff -r 73b57b7e1b73dbbf8b786d5dc749b67d635f8338 -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 test/install_and_test_tool_shed_repositories/base/twilltestcase.py
--- a/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
+++ b/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
@@ -107,6 +107,7 @@
self.submit_form( 1, 'select_tool_panel_section_button', **kwd )
self.check_for_strings( post_submit_strings_displayed, strings_not_displayed )
repository_ids = self.initiate_installation_process( new_tool_panel_section=new_tool_panel_section )
+ log.debug( 'Waiting for the installation of repository IDs: %s' % str( repository_ids ) )
self.wait_for_repository_installation( repository_ids )
def visit_url( self, url, allowed_codes=[ 200 ] ):
@@ -124,10 +125,14 @@
if repository_ids:
for repository_id in repository_ids:
galaxy_repository = test_db_util.get_repository( self.security.decode_id( repository_id ) )
+ log.debug( 'Repository %s with ID %s has initial state %s.' % ( str( galaxy_repository.name ), str( repository_id ), str( galaxy_repository.status ) ) )
timeout_counter = 0
while galaxy_repository.status not in final_states:
test_db_util.refresh( galaxy_repository )
+ log.debug( 'Repository %s with ID %s is in state %s, continuing to wait.' % ( str( galaxy_repository.name ), str( repository_id ), str( galaxy_repository.status ) ) )
timeout_counter = timeout_counter + 1
+ if timeout_counter % 10 == 0:
+ log.debug( 'Waited %d seconds for repository %s.' % ( timeout_counter, str( galaxy_repository.name ) ) )
# This timeout currently defaults to 180 seconds, or 3 minutes.
if timeout_counter > common.repository_installation_timeout:
raise AssertionError( 'Repository installation timed out, %d seconds elapsed, repository state is %s.' % \
diff -r 73b57b7e1b73dbbf8b786d5dc749b67d635f8338 -r aa8204e99326288ea07e1af26c7d3ffd11f0aff1 test/install_and_test_tool_shed_repositories/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/functional_tests.py
@@ -363,7 +363,10 @@
return '000000000000'
def get_missing_tool_dependencies( repository ):
+ log.debug( 'Checking %s repository %s for missing tool dependencies.' % ( repository.status, repository.name ) )
missing_tool_dependencies = repository.missing_tool_dependencies
+ for tool_dependency in repository.tool_dependencies:
+ log.debug( 'Tool dependency %s version %s has status %s.' % ( tool_dependency.name, tool_dependency.version, tool_dependency.status ) )
for repository_dependency in repository.repository_dependencies:
if not repository_dependency.includes_tool_dependencies:
continue
@@ -687,7 +690,10 @@
tool_dependency_install_path = tool_dependency.installation_directory( app )
uninstalled, error_message = tool_dependency_util.remove_tool_dependency( app, tool_dependency )
if error_message:
+ log.debug( 'There was an error attempting to remove directory: %s' % str( tool_dependency_install_path ) )
log.debug( error_message )
+ else:
+ log.debug( 'Successfully removed tool dependency installation directory: %s' % str( tool_dependency_install_path ) )
sa_session = app.model.context.current
if not uninstalled or tool_dependency.status != app.model.ToolDependency.installation_status.UNINSTALLED:
tool_dependency.status = app.model.ToolDependency.installation_status.UNINSTALLED
@@ -1107,7 +1113,8 @@
# }
if 'missing_test_components' not in repository_status:
repository_status[ 'missing_test_components' ] = []
- if repository.missing_tool_dependencies or repository.missing_repository_dependencies:
+ missing_tool_dependencies = get_missing_tool_dependencies( repository )
+ if missing_tool_dependencies or repository.missing_repository_dependencies:
# If a tool dependency fails to install correctly, this should be considered an installation error,
# and functional tests should be skipped, since the tool dependency needs to be correctly installed
# for the test to be considered reliable.
@@ -1143,9 +1150,7 @@
# The deactivate flag is set to True if the environment variable GALAXY_INSTALL_TEST_KEEP_TOOL_DEPENDENCIES
# is set to 'true'.
if deactivate:
- # Recursively retrieve every missing tool dependency for this repository and its required repositories.
log.debug( 'Due to the above missing tool dependencies, we are now uninstalling the following tool dependencies, but not changing their repositories.' )
- missing_tool_dependencies = get_missing_tool_dependencies( repository )
for missing_tool_dependency in missing_tool_dependencies:
uninstall_tool_dependency( app, missing_tool_dependency )
# We are deactivating this repository and all of its repository dependencies.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fix for populating the missing tool dependencies container in certain special cases in Tool Shed repositories.
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/73b57b7e1b73/
Changeset: 73b57b7e1b73
User: greg
Date: 2013-11-19 14:55:23
Summary: Fix for populating the missing tool dependencies container in certain special cases in Tool Shed repositories.
Affected #: 1 file
diff -r 0632a6fe11a2027bdd15e5ddbc4c9c192bb6eaeb -r 73b57b7e1b73dbbf8b786d5dc749b67d635f8338 lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -1507,8 +1507,14 @@
common_install_util.get_installed_and_missing_repository_dependencies( trans, repository )
# Handle the current repository's tool dependencies.
repository_tool_dependencies = metadata.get( 'tool_dependencies', None )
+ # Make sure to display missing tool dependencies as well.
+ repository_invalid_tool_dependencies = metadata.get( 'invalid_tool_dependencies', None )
+ if repository_invalid_tool_dependencies is not None:
+ if repository_tool_dependencies is None:
+ repository_tool_dependencies = {}
+ repository_tool_dependencies.update( repository_invalid_tool_dependencies )
repository_installed_tool_dependencies, repository_missing_tool_dependencies = \
- tool_dependency_util.get_installed_and_missing_tool_dependencies( trans, repository, repository_tool_dependencies )
+ tool_dependency_util.get_installed_and_missing_tool_dependencies_for_installed_repository( trans, repository, repository_tool_dependencies )
if reinstalling:
installed_tool_dependencies, missing_tool_dependencies = \
tool_dependency_util.populate_tool_dependencies_dicts( trans=trans,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fix for populating and unpopulating complex repository dependency tags sets in tool dependency definitions contained in tool shed repositories.
by commits-noreply@bitbucket.org 19 Nov '13
by commits-noreply@bitbucket.org 19 Nov '13
19 Nov '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0632a6fe11a2/
Changeset: 0632a6fe11a2
User: greg
Date: 2013-11-19 14:52:20
Summary: Fix for populating and unpopulating complex repository dependency tags sets in tool dependency definitions contained in tool shed repositories.
Affected #: 1 file
diff -r facb9f9d2ee4d614d21e4fc1a4a6d99f2668349a -r 0632a6fe11a2027bdd15e5ddbc4c9c192bb6eaeb lib/tool_shed/util/commit_util.py
--- a/lib/tool_shed/util/commit_util.py
+++ b/lib/tool_shed/util/commit_util.py
@@ -368,6 +368,8 @@
unpopulate=unpopulate )
if message:
error_message += message
+ if package_altered:
+ root[ root_index ] = root_elem
elif package_elem.tag == 'install':
# <install version="1.0">
for actions_index, actions_elem in enumerate( package_elem ):
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0