commit/galaxy-central: 10 new changesets
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/BiocGenerics_0.6.0.tar.gz</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@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@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@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@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@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@bx.psu.edu', password='testuser', username='admin-user', redirect='' ): # test@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=none&tool_id=%s&%s" % ( 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@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@bx.psu.edu', username='test' ) admin_user = test_db_util.get_user( 'test@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.
participants (1)
-
commits-noreply@bitbucket.org