galaxy-commits
Threads by month
- ----- 2026 -----
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions
commit/galaxy-central: carlfeberhard: Visualizations API: rough draft of index, show, create, and update; Browser tests: tests for visualizations API
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/ae31fdf708ef/
Changeset: ae31fdf708ef
User: carlfeberhard
Date: 2013-05-17 21:06:51
Summary: Visualizations API: rough draft of index, show, create, and update; Browser tests: tests for visualizations API
Affected #: 3 files
diff -r fcf327d7dd51c19b2478fe2ab4361b67fe8d3897 -r ae31fdf708efc84f1db7176b170596c9eb7f0212 lib/galaxy/webapps/galaxy/api/visualizations.py
--- a/lib/galaxy/webapps/galaxy/api/visualizations.py
+++ b/lib/galaxy/webapps/galaxy/api/visualizations.py
@@ -1,28 +1,263 @@
-from galaxy import web
-from galaxy.web.base.controller import BaseController, BaseAPIController
+"""
+Visualizations resource control over the API.
-class VisualizationsController( BaseAPIController ):
+NOTE!: this is a work in progress and functionality and data structures
+may change often.
+"""
+
+import pkg_resources
+pkg_resources.require( 'SQLAlchemy >= 0.4' )
+from sqlalchemy import or_
+
+from galaxy import web, util
+from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin
+from galaxy.model.item_attrs import UsesAnnotations
+from galaxy.exceptions import ( ItemAccessibilityException, ItemDeletionException, ItemOwnershipException,
+ MessageException )
+
+from galaxy.web import url_for
+
+import logging
+log = logging.getLogger( __name__ )
+
+class VisualizationsController( BaseAPIController, UsesVisualizationMixin, UsesAnnotations ):
"""
RESTful controller for interactions with visualizations.
"""
@web.expose_api
- def index( self, trans, **kwds ):
+ def index( self, trans, **kwargs ):
"""
GET /api/visualizations:
"""
- pass
+ #TODO: search for vizsesses that apply to an object (sending model class and id? - how to do this?)
+ rval = []
+ try:
+ if not trans.user:
+ raise ItemAccessibilityException( 'You must be logged in to access visualizations' )
+ user = trans.user
+
+ #TODO: search for: title, made by user, creation time range, type (vis name), dbkey, etc.
+ #TODO: limit, offset, order_by
+ #TODO: deleted
+
+ # this is the default search - user's vis, vis shared with user, published vis
+ visualizations = self.get_visualizations_by_user( trans, user )
+ visualizations += self.get_visualizations_shared_with_user( trans, user )
+ visualizations += self.get_published_visualizations( trans, exclude_user=user )
+ #TODO: the admin case - everything
+
+ for visualization in visualizations:
+ item = self.get_visualization_summary_dict( visualization )
+ item = trans.security.encode_dict_ids( item )
+ item[ 'url' ] = web.url_for( 'visualization', id=item[ 'id' ] )
+ rval.append( item )
+
+ except ItemAccessibilityException, exception:
+ trans.response.status = 403
+ rval = { 'error': str( exception ) }
+ if trans.debug:
+ log.exception( str( exception ) )
+
+ except Exception, exception:
+ trans.response.status = 500
+ rval = { 'error': str( exception ) }
+ log.exception( 'visualizations index failed: %s' %( str( exception ) ) )
+
+ return rval
@web.json
- def show( self, trans, id, **kwd ):
+ def show( self, trans, id, **kwargs ):
"""
GET /api/visualizations/{viz_id}
"""
- pass
+ #TODO: revisions should be a contents/nested controller like viz/xxx/r/xxx)?
+ # the important thing is the config
+ rval = {}
+ #TODO:?? /api/visualizations/registry -> json of registry.listings?
+ try:
+ visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
+ dictionary = trans.security.encode_dict_ids( self.get_visualization_dict( visualization ) )
+ dictionary[ 'url' ] = url_for( controller='visualization',
+ action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug )
+ dictionary[ 'annotation' ] = self.get_item_annotation_str( trans.sa_session, trans.user, visualization )
+
+ # need to encode ids in revisions as well
+ encoded_revisions = []
+ for revision in dictionary[ 'revisions' ]:
+ #NOTE: does not encode ids inside the configs
+ encoded_revisions.append( trans.security.encode_id( revision ) )
+ dictionary[ 'revisions' ] = encoded_revisions
+ dictionary[ 'latest_revision' ] = trans.security.encode_dict_ids( dictionary[ 'latest_revision' ] )
+
+ rval = dictionary
+
+ except ( ItemAccessibilityException, ItemDeletionException ), exception:
+ trans.response.status = 403
+ rval = { 'error': str( exception ) }
+ if trans.debug:
+ log.exception( 'visualization show forbidden (%s): %s' %( id, str( exception ) ) )
+
+ except Exception, exception:
+ trans.response.status = 500
+ rval = { 'error': str( exception ) }
+ log.exception( 'visualization show failed (%s): %s' %( id, str( exception ) ) )
+
+ return rval
@web.expose_api
- def create( self, trans, payload, **kwd ):
+ def create( self, trans, payload, **kwargs ):
"""
POST /api/visualizations
+ creates a new visualization using the given payload
+
+ POST /api/visualizations?import_id={encoded_visualization_id}
+ imports a copy of an existing visualization into the user's workspace
"""
- pass
\ No newline at end of file
+ rval = None
+ try:
+ if 'import_id' in payload:
+ import_id = payload( 'import_id' )
+ visualization = self.import_visualization( trans, import_id, user=trans.user )
+
+ else:
+ payload = self._validate_and_parse_payload( payload )
+ payload[ 'save' ] = True
+ # create needs defaults like wizard needs food - generate defaults - this will err if given a weird key?
+ visualization = self.create_visualization( trans, **payload )
+
+ rval = { 'id' : trans.security.encode_id( visualization.id ) }
+
+ #TODO: exception boilerplate
+ except ( ItemAccessibilityException, ItemDeletionException ), exception:
+ trans.response.status = 403
+ rval = { 'error': str( exception ) }
+ if trans.debug:
+ log.exception( str( exception ) )
+
+ except ( ValueError, AttributeError ), exception:
+ trans.response.status = 400
+ rval = { 'error': str( exception ) }
+ if trans.debug:
+ log.exception( str( exception ) )
+
+ except Exception, exception:
+ trans.response.status = 500
+ rval = { 'error': str( exception ) }
+ log.exception( 'creation of visualization failed: %s' %( str( exception ) ) )
+
+ return rval
+
+ @web.expose_api
+ def update( self, trans, id, payload, **kwargs ):
+ """
+ PUT /api/visualizations/{encoded_visualization_id}
+ """
+ rval = None
+ try:
+ payload = self._validate_and_parse_payload( payload )
+
+ # there's a differentiation here between updating the visualiztion and creating a new revision
+ # that needs to be handled clearly here
+ # or alternately, using a different controller like PUT /api/visualizations/{id}/r/{id}
+
+ #TODO: consider allowing direct alteration of revisions title (without a new revision)
+ # only create a new revsion on a different config
+
+ # only update owned visualizations
+ visualization = self.get_visualization( trans, id, check_ownership=True )
+ title = payload.get( 'title', visualization.latest_revision.title )
+ dbkey = payload.get( 'dbkey', visualization.latest_revision.dbkey )
+ config = payload.get( 'config', visualization.latest_revision.config )
+
+ latest_config = visualization.latest_revision.config
+ if( ( title != visualization.latest_revision.title )
+ or ( dbkey != visualization.latest_revision.dbkey )
+ or ( util.json.to_json_string( config ) != util.json.to_json_string( latest_config ) ) ):
+ revision = self.add_visualization_revision( trans, visualization, config, title, dbkey )
+ #TODO: need to somehow get what changed here?
+ rval = { 'id' : revision.id }
+
+ except ( ItemAccessibilityException, ItemDeletionException ), exception:
+ trans.response.status = 403
+ rval = { 'error': str( exception ) }
+ if trans.debug:
+ log.exception( str( exception ) )
+
+ except ( ValueError, AttributeError ), exception:
+ trans.response.status = 400
+ rval = { 'error': str( exception ) }
+ if trans.debug:
+ log.exception( str( exception ) )
+
+ except Exception, exception:
+ trans.response.status = 500
+ rval = { 'error': str( exception ) }
+ log.exception( 'update of visualization (%s) failed: %s' %( id, str( exception ) ) )
+
+ return rval
+
+ def _validate_and_parse_payload( self, payload ):
+ """
+ Validate and parse incomming data payload for a visualization.
+ """
+ # This layer handles (most of the stricter idiot proofing):
+ # - unknown/unallowed keys
+ # - changing data keys from api key to attribute name
+ # - protection against bad data form/type
+ # - protection against malicious data content
+ # all other conversions and processing (such as permissions, etc.) should happen down the line
+
+ # keys listed here don't error when attempting to set, but fail silently
+ # this allows PUT'ing an entire model back to the server without attribute errors on uneditable attrs
+ valid_but_uneditable_keys = (
+ 'id', 'model_class'
+ #TODO: fill out when we create get_api_value, get_dict, whatevs
+ )
+ #TODO: deleted
+ #TODO: importable
+
+ validated_payload = {}
+ for key, val in payload.items():
+ if key == 'config':
+ if not isinstance( val, dict ):
+ raise ValueError( '%s must be a dictionary (JSON): %s' %( key, str( type( val ) ) ) )
+
+ elif key == 'annotation':
+ if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
+ raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
+ val = util.sanitize_html.sanitize_html( val, 'utf-8' )
+
+ # these are keys that actually only be *updated* at the revision level and not here
+ # (they are still valid for create, tho)
+ elif key == 'title':
+ if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
+ raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
+ val = util.sanitize_html.sanitize_html( val, 'utf-8' )
+ elif key == 'slug':
+ if not isinstance( val, str ):
+ raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) )
+ val = util.sanitize_html.sanitize_html( val, 'utf-8' )
+ elif key == 'type':
+ if not isinstance( val, str ):
+ raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) )
+ val = util.sanitize_html.sanitize_html( val, 'utf-8' )
+ #TODO: validate types in VALID_TYPES/registry names at the mixin/model level?
+ elif key == 'dbkey':
+ if not ( isinstance( val, str ) or isinstance( val, unicode ) ):
+ raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) )
+ val = util.sanitize_html.sanitize_html( val, 'utf-8' )
+
+ elif key not in valid_but_uneditable_keys:
+ raise AttributeError( 'unknown key: %s' %( str( key ) ) )
+
+ validated_payload[ key ] = val
+ return validated_payload
+
+ #(a)web.expose_api
+ #def delete( self, trans, id, **kwd ):
+ # """
+ # DELETE /api/visualizations/{encoded_history_id}
+ # Deletes a visualization from the database
+ # """
+ # pass
diff -r fcf327d7dd51c19b2478fe2ab4361b67fe8d3897 -r ae31fdf708efc84f1db7176b170596c9eb7f0212 test/casperjs/api-visualizations-tests.js
--- /dev/null
+++ b/test/casperjs/api-visualizations-tests.js
@@ -0,0 +1,282 @@
+/* Utility to load a specific page and output html, page text, or a screenshot
+ * Optionally wait for some time, text, or dom selector
+ */
+try {
+ //...if there's a better way - please let me know, universe
+ var scriptDir = require( 'system' ).args[3]
+ // remove the script filename
+ .replace( /[\w|\.|\-|_]*$/, '' )
+ // if given rel. path, prepend the curr dir
+ .replace( /^(?!\/)/, './' ),
+ spaceghost = require( scriptDir + 'spaceghost' ).create({
+ // script options here (can be overridden by CLI)
+ //verbose: true,
+ //logLevel: debug,
+ scriptDir: scriptDir
+ });
+
+} catch( error ){
+ console.debug( error );
+ phantom.exit( 1 );
+}
+spaceghost.start();
+
+
+// =================================================================== SET UP
+var utils = require( 'utils' );
+
+var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+}
+spaceghost.user.loginOrRegisterUser( email, password );
+
+function hasKeys( object, keysArray ){
+ if( !utils.isObject( object ) ){ return false; }
+ for( var i=0; i<keysArray.length; i += 1 ){
+ if( !object.hasOwnProperty( keysArray[i] ) ){
+ spaceghost.debug( 'object missing key: ' + keysArray[i] );
+ return false;
+ }
+ }
+ return true;
+}
+
+function countKeys( object ){
+ if( !utils.isObject( object ) ){ return 0; }
+ var count = 0;
+ for( var key in object ){
+ if( object.hasOwnProperty( key ) ){ count += 1; }
+ }
+ return count;
+}
+
+function compareObjs( obj, compare_to, exclusionList ){
+ exclusionList = exclusionList || [];
+ for( var key in compare_to ){
+ if( compare_to.hasOwnProperty( key ) && exclusionList.indexOf( key ) === -1 ){
+ if( !obj.hasOwnProperty( key ) ){
+ spaceghost.debug( 'obj missing key: ' + key );
+ return false;
+ }
+ if( obj[ key ] !== compare_to[ key ] ){
+ spaceghost.debug( 'obj value not equal. obj: ' + obj[ key ] + ', v.: ' + compare_to[ key ] );
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+function assertRaises( fn, expectedCode, errMsgShouldContain, assertionMsg, debug ){
+ //TODO: errMsgShouldContain optional, any old error
+ assertionMsg = assertionMsg || 'create returned error';
+ debug = debug || false;
+ var errorReturned = {};
+ try {
+ fn.call( spaceghost );
+ } catch( error ){
+ errorReturned = error;
+ }
+ if( errorReturned ){
+ if( debug ){ spaceghost.debug( 'error:\n' + spaceghost.jsonStr( errorReturned ) ); }
+ var errorMsg = errorReturned.message || '';
+ if( errorMsg ){
+ try {
+ errorMsg = JSON.parse( errorReturned.message ).error;
+ } catch( parseErr ){
+ spaceghost.warn( 'error parsing error.message' );
+ }
+ }
+ var properCode = errorReturned.status === expectedCode,
+ properMsg = errorMsg.indexOf( errMsgShouldContain ) !== -1;
+ spaceghost.test.assert( properCode && properMsg,
+ assertionMsg + ': (' + errorReturned.status + ') "' + errorMsg + '"' );
+ if( !( properCode && properMsg ) ){
+ throw errorReturned;
+ }
+ } else {
+ spaceghost.test.fail( assertionMsg + ': (no error)' );
+ }
+}
+
+
+// =================================================================== TESTS
+spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+ var ALWAYS_CREATE = true,
+ indexKeys = [
+ 'id', 'title', 'type', 'dbkey', 'url'
+ ],
+ showKeys = indexKeys.concat([
+ 'user_id', 'model_class', 'revisions', 'latest_revision', 'annotation'
+ ]),
+ revisionKeys = [
+ 'id', 'title', 'visualization_id', 'dbkey', 'model_class', 'config'
+ ];
+
+ // ------------------------------------------------------------------------------------------- set up
+ var visualizationIndex = this.api.visualizations.index();
+ if( ALWAYS_CREATE || !visualizationIndex.length ){
+ // post a visualization
+ this.info( 'creating new visualization for tests' );
+ var testVisualization = this.api.visualizations.create({
+ title : 'Test Visualization',
+ // needs to be unique
+ slug : 'test-visualization-' + Date.now(),
+ type : 'test',
+ dbkey : 'hg17',
+ annotation : 'this is a test of the emergency visualization system',
+ config : {
+ x : 10,
+ y : 12
+ }
+ });
+ this.debug( this.jsonStr( testVisualization ) );
+ }
+
+ // ------------------------------------------------------------------------------------------- INDEX
+ this.test.comment( 'index should get a list of visualizations' );
+ visualizationIndex = this.api.visualizations.index();
+ this.debug( this.jsonStr( visualizationIndex ) );
+ this.test.assert( utils.isArray( visualizationIndex ),
+ "index returned an array: length " + visualizationIndex.length );
+ this.test.assert( visualizationIndex.length >= 1, 'Has at least one visualization' );
+
+ var firstVisualization = visualizationIndex[0];
+ this.test.assert( hasKeys( firstVisualization, indexKeys ), 'Has the proper keys' );
+ this.test.assert( this.api.isEncodedId( firstVisualization.id ), 'Id appears well-formed' );
+
+ //TODO: index searching
+ //TODO: anon user
+ //TODO: admin user
+
+ // ------------------------------------------------------------------------------------------- SHOW
+ this.test.comment( 'show should get a visualization details object' );
+ var visualizationShow = this.api.visualizations.show( firstVisualization.id );
+ this.debug( this.jsonStr( visualizationShow ) );
+ this.test.assert( hasKeys( visualizationShow, showKeys ), 'Has the proper keys' );
+ this.test.assert( visualizationShow.model_class === 'Visualization',
+ 'Has the proper model_class: ' + visualizationShow.model_class );
+
+ this.test.comment( 'a visualization details object should contain an array of revision ids' );
+ var revisions = visualizationShow.revisions;
+ this.test.assert( utils.isArray( revisions ), 'revisions is an array' );
+ this.test.assert( revisions.length >= 1, 'revisions has at least one entry' );
+ var areIds = true;
+ revisions.forEach( function( revision ){
+ if( !spaceghost.api.isEncodedId( revision ) ){ areIds = false; }
+ });
+ this.test.assert( areIds, 'all revisions are ids' );
+
+ this.test.comment( 'a visualization details object should contain a subobject of the latest revision' );
+ var latestRevision = visualizationShow.latest_revision;
+ this.test.assert( utils.isObject( latestRevision ), 'latestRevision is an object' );
+ this.test.assert( hasKeys( latestRevision, revisionKeys ), 'latestRevision has the proper keys' );
+ this.test.assert( latestRevision.model_class === 'VisualizationRevision',
+ 'Has the proper model_class: ' + latestRevision.model_class );
+ this.test.assert( latestRevision.visualization_id === visualizationShow.id,
+ 'revision visualization_id matches containing visualization id: ' + latestRevision.visualization_id );
+ this.test.assert( visualizationShow.revisions.indexOf( latestRevision.id ) !== -1,
+ 'found latest_revision id in revisions' );
+
+ this.test.comment( 'a visualization revision should contain a subobject for the config' );
+ var config = latestRevision.config;
+ this.test.assert( utils.isObject( config ), 'config is an object:\n' + this.jsonStr( config ) );
+
+ //TODO: url in visualizationIndex == show url
+ //TODO: non existing id throws error
+ //TODO: anon user
+ //TODO: user1 has no permissions to show user2
+
+ // ------------------------------------------------------------------------------------------- CREATE
+ this.test.comment( 'Calling create should create a new visualization and allow setting the name' );
+ var visualizationData = {
+ title : 'Created Visualization',
+ // needs to be unique
+ slug : 'created-visualization-' + Date.now(),
+ type : 'test',
+ dbkey : 'hg17',
+ annotation : 'invisible visualization',
+ config : {
+ x : 10,
+ y : 12
+ }
+ };
+ var created = this.api.visualizations.create( visualizationData );
+ this.debug( 'returned from create:\n' + this.jsonStr( created ) );
+ this.test.assert( this.api.isEncodedId( created.id ), "create returned an id: " + created.id );
+
+ // check v. show
+ visualizationShow = this.api.visualizations.show( created.id );
+ this.debug( 'visualizationShow:\n' + this.jsonStr( visualizationShow ) );
+ // config is re-located into a revision and won't be there
+ this.test.assert( compareObjs( visualizationShow, visualizationData, [ 'config' ] ),
+ "show results seem to match create data" );
+
+ // the following errors are produced within base.controller.UsesVisualizationsMixin._create_visualization
+ this.test.comment( 'Calling create with a non-unique slug will cause an API error' );
+ assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'visualization identifier must be unique' );
+
+ this.test.comment( 'Calling create with no title will cause an API error' );
+ visualizationData.title = '';
+ assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'visualization name is required' );
+ visualizationData.title = 'Created Visualization';
+
+ this.test.comment( 'Calling create with improper characters in the slug will cause an API error' );
+ var oldSlug = visualizationData.slug;
+ visualizationData.slug = '123_()';
+ assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" );
+ visualizationData.slug = oldSlug;
+
+ this.test.comment( 'Calling create with an unrecognized key will cause an API error' );
+ visualizationData.bler = 'blah';
+ assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'unknown key: bler' );
+ delete visualizationData.bler;
+
+ this.test.comment( 'Calling create with an unparsable JSON config will cause an API error' );
+ visualizationData.config = '3 = nime';
+ assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'config must be a dictionary (JSON)' );
+
+ // ------------------------------------------------------------------------------------------ UPDATE
+ // ........................................................................................... idiot proofing
+ this.test.comment( 'updating using a nonsense key should fail with an error' );
+ assertRaises( function(){
+ returned = this.api.visualizations.update( created.id, { bler : 'blah' });
+ }, 400, 'unknown key: bler' );
+
+ this.test.comment( 'updating by attempting to change type should cause an error' );
+ assertRaises( function(){
+ returned = this.api.visualizations.update( created.id, { title : 30 });
+ }, 400, 'title must be a string or unicode' );
+ //TODO: the other types...
+
+ // ........................................................................................... title
+ //this.test.comment( 'update should create a new visualization revision' );
+ //
+ //this.test.comment( 'updating with a new title should NOT change the visualization title...' );
+ //latestRevision = this.api.visualizations.show( created.id ).latest_revision;
+ //returned = this.api.visualizations.update( created.id, {
+ // title : 'New title'
+ //});
+ //visualizationShow = this.api.visualizations.show( created.id );
+ //this.debug( this.jsonStr( visualizationShow ) );
+ //this.test.assert( visualizationShow.title === visualizationData.title,
+ // "Title does not set via update: " + visualizationShow.title );
+
+});
+
+// ===================================================================
+spaceghost.run( function(){
+});
diff -r fcf327d7dd51c19b2478fe2ab4361b67fe8d3897 -r ae31fdf708efc84f1db7176b170596c9eb7f0212 test/casperjs/modules/api.js
--- a/test/casperjs/modules/api.js
+++ b/test/casperjs/modules/api.js
@@ -24,6 +24,7 @@
this.tools = new ToolsAPI( this );
this.workflows = new WorkflowsAPI( this );
this.users = new UsersAPI( this );
+ this.visualizations = new VisualizationsAPI( this );
};
exports.API = API;
@@ -73,6 +74,7 @@
if( resp.status !== 200 ){
// grrr... this doesn't lose the \n\r\t
//throw new APIError( resp.responseText.replace( /[\s\n\r\t]+/gm, ' ' ).replace( /"/, '' ) );
+ this.spaceghost.debug( 'api error response status code: ' + resp.status );
throw new APIError( resp.responseText, resp.status );
}
return JSON.parse( resp.responseText );
@@ -490,3 +492,57 @@
};
+// =================================================================== HISTORIES
+var VisualizationsAPI = function VisualizationsAPI( api ){
+ this.api = api;
+};
+VisualizationsAPI.prototype.toString = function toString(){
+ return this.api + '.VisualizationsAPI';
+};
+
+// -------------------------------------------------------------------
+VisualizationsAPI.prototype.urlTpls = {
+ index : 'api/visualizations',
+ show : 'api/visualizations/%s',
+ create : 'api/visualizations',
+ //delete_ : 'api/visualizations/%s',
+ //undelete: 'api/visualizations/deleted/%s/undelete',
+ update : 'api/visualizations/%s'
+};
+
+VisualizationsAPI.prototype.index = function index(){
+ this.api.spaceghost.info( 'visualizations.index' );
+
+ return this.api._ajax( this.urlTpls.index );
+};
+
+VisualizationsAPI.prototype.show = function show( id ){
+ this.api.spaceghost.info( 'visualizations.show' );
+
+ return this.api._ajax( utils.format( this.urlTpls.show, this.api.ensureId( id ) ) );
+};
+
+VisualizationsAPI.prototype.create = function create( payload ){
+ this.api.spaceghost.info( 'visualizations.create: ' + this.api.spaceghost.jsonStr( payload ) );
+
+ // py.payload <-> ajax.data
+ payload = this.api.ensureObject( payload );
+ return this.api._ajax( utils.format( this.urlTpls.create ), {
+ type : 'POST',
+ data : payload
+ });
+};
+
+VisualizationsAPI.prototype.update = function create( id, payload ){
+ this.api.spaceghost.info( 'visualizations.update: ' + id + ',' + this.api.spaceghost.jsonStr( payload ) );
+
+ // py.payload <-> ajax.data
+ id = this.api.ensureId( id );
+ payload = this.api.ensureObject( payload );
+ url = utils.format( this.urlTpls.update, id );
+
+ return this.api._ajax( url, {
+ type : 'PUT',
+ data : payload
+ });
+};
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: API/datasets.show: allow _raw_data to assign a DataProvider by name
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/fcf327d7dd51/
Changeset: fcf327d7dd51
User: carlfeberhard
Date: 2013-05-17 20:50:27
Summary: API/datasets.show: allow _raw_data to assign a DataProvider by name
Affected #: 1 file
diff -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d -r fcf327d7dd51c19b2478fe2ab4361b67fe8d3897 lib/galaxy/webapps/galaxy/api/datasets.py
--- a/lib/galaxy/webapps/galaxy/api/datasets.py
+++ b/lib/galaxy/webapps/galaxy/api/datasets.py
@@ -23,7 +23,7 @@
return 'not implemented'
@web.expose_api
- def show( self, trans, id, hda_ldda='hda', data_type=None, **kwd ):
+ def show( self, trans, id, hda_ldda='hda', data_type=None, provider=None, **kwd ):
"""
GET /api/datasets/{encoded_dataset_id}
Displays information about and/or content of a dataset.
@@ -46,7 +46,7 @@
elif data_type == 'features':
rval = self._search_features( trans, dataset, kwd.get( 'query' ) )
elif data_type == 'raw_data':
- rval = self._raw_data( trans, dataset, **kwd )
+ rval = self._raw_data( trans, dataset, provider, **kwd )
elif data_type == 'track_config':
rval = self.get_new_track_config( trans, dataset )
elif data_type == 'genome_data':
@@ -201,7 +201,7 @@
result.update( { 'dataset_type': data_provider.dataset_type, 'extra_info': extra_info } )
return result
- def _raw_data( self, trans, dataset, **kwargs ):
+ def _raw_data( self, trans, dataset, provider=None, **kwargs ):
"""
Uses original (raw) dataset to return data. This method is useful
when the dataset is not yet indexed and hence using data would
@@ -212,8 +212,15 @@
if msg:
return msg
+ registry = trans.app.data_provider_registry
+ # allow the caller to specifiy which provider is used
+ if provider and provider in registry.dataset_type_name_to_data_provider:
+ data_provider = registry.dataset_type_name_to_data_provider[ provider ]( dataset )
+ # or have it look up by datatype
+ else:
+ data_provider = registry.get_data_provider( trans, raw=True, original_dataset=dataset )
+
# Return data.
- data_provider = trans.app.data_provider_registry.get_data_provider( trans, raw=True, original_dataset=dataset )
data = data_provider.get_data( **kwargs )
return data
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Visualizations framework: allow users to associate custom visualizations with models, datatypes, etc. via an xml configuration file (visualizations_conf.xml)
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/ffc6ff4fe6df/
Changeset: ffc6ff4fe6df
User: carlfeberhard
Date: 2013-05-17 20:45:07
Summary: Visualizations framework: allow users to associate custom visualizations with models, datatypes, etc. via an xml configuration file (visualizations_conf.xml)
Affected #: 19 files
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d .hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -60,7 +60,7 @@
job_conf.xml
data_manager_conf.xml
shed_data_manager_conf.xml
-
+visualizations_conf.xml
static/welcome.html.*
static/welcome.html
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -15,6 +15,7 @@
from galaxy.tags.tag_handler import GalaxyTagHandler
from galaxy.visualization.genomes import Genomes
from galaxy.visualization.data_providers.registry import DataProviderRegistry
+from galaxy.visualization.registry import VisualizationsRegistry
from galaxy.tools.imp_exp import load_history_imp_exp_tools
from galaxy.tools.genome_index import load_genome_index_tools
from galaxy.sample_tracking import external_service_types
@@ -120,6 +121,9 @@
load_history_imp_exp_tools( self.toolbox )
# Load genome indexer tool.
load_genome_index_tools( self.toolbox )
+ # visualizations registry: associates resources with visualizations, controls how to render
+ self.visualizations_registry = ( VisualizationsRegistry( self.config.root, self.config.visualizations_conf_path )
+ if self.config.visualizations_conf_path else None )
# Load security policy.
self.security_agent = self.model.security_agent
self.host_security_agent = galaxy.security.HostAgent( model=self.security_agent.model, permitted_actions=self.security_agent.permitted_actions )
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -275,6 +275,8 @@
self.fluent_log = string_as_bool( kwargs.get( 'fluent_log', False ) )
self.fluent_host = kwargs.get( 'fluent_host', 'localhost' )
self.fluent_port = int( kwargs.get( 'fluent_port', 24224 ) )
+ # visualizations registry config path
+ self.visualizations_conf_path = kwargs.get( 'visualizations_conf_path', None )
@property
def sentry_dsn_public( self ):
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -379,6 +379,36 @@
if not included:
self.sniff_order.append(datatype)
append_to_sniff_order()
+
+ def get_datatype_class_by_name( self, name ):
+ """
+ Return the datatype class where the datatype's `type` attribute
+ (as defined in the datatype_conf.xml file) contains `name`.
+ """
+ #TODO: too roundabout - would be better to generate this once as a map and store in this object
+ found_class = None
+ for ext, datatype_obj in self.datatypes_by_extension.items():
+ datatype_obj_class = datatype_obj.__class__
+ datatype_obj_class_str = str( datatype_obj_class )
+ #print datatype_obj_class_str
+ if name in datatype_obj_class_str:
+ return datatype_obj_class
+ return None
+ # these seem to be connected to the dynamic classes being generated in this file, lines 157-158
+ # they appear when a one of the three are used in inheritance with subclass="True"
+ #TODO: a possible solution is to def a fn in datatypes __init__ for creating the dynamic classes
+
+ #remap = {
+ # 'galaxy.datatypes.registry.Tabular' : galaxy.datatypes.tabular.Tabular,
+ # 'galaxy.datatypes.registry.Text' : galaxy.datatypes.data.Text,
+ # 'galaxy.datatypes.registry.Binary' : galaxy.datatypes.binary.Binary
+ #}
+ #datatype_str = str( datatype )
+ #if datatype_str in remap:
+ # datatype = remap[ datatype_str ]
+ #
+ #return datatype
+
def get_available_tracks(self):
return self.available_tracks
def get_mimetype_by_extension(self, ext, default = 'application/octet-stream' ):
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/visualization/data_providers/registry.py
--- a/lib/galaxy/visualization/data_providers/registry.py
+++ b/lib/galaxy/visualization/data_providers/registry.py
@@ -30,7 +30,9 @@
"bai": genome.BamDataProvider,
"bam": genome.SamDataProvider,
"bigwig": genome.BigWigDataProvider,
- "bigbed": genome.BigBedDataProvider
+ "bigbed": genome.BigBedDataProvider,
+
+ "column": ColumnDataProvider
}
def get_data_provider( self, trans, name=None, source='data', raw=False, original_dataset=None ):
@@ -105,4 +107,4 @@
except NoConverterException:
pass
- return data_provider
\ No newline at end of file
+ return data_provider
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/visualization/registry.py
--- /dev/null
+++ b/lib/galaxy/visualization/registry.py
@@ -0,0 +1,808 @@
+"""
+Lower level of visualization framework which does three main things:
+ - associate visualizations with objects
+ - create urls to visualizations based on some target object(s)
+ - unpack a query string into the desired objects needed for rendering
+"""
+import os
+import shutil
+
+from galaxy import util
+import galaxy.model
+from galaxy.web import url_for
+
+import logging
+log = logging.getLogger( __name__ )
+
+__TODO__ = """
+ BUGS:
+ anon users clicking a viz link gets 'must be' msg in galaxy_main (w/ masthead)
+ should not show visualizations (no icon)?
+ newick files aren't being sniffed prop? - datatype is txt
+
+ have parsers create objects instead of dicts
+ allow data_sources with no model_class but have tests (isAdmin, etc.)
+ maybe that's an instance of User model_class?
+ some confused vocabulary in docs, var names
+ tests:
+ anding, grouping, not
+ data_sources:
+ lists of
+ add description element to visualization.
+"""
+
+class VisualizationsRegistry( object ):
+ """
+ Main responsibilities are:
+ - testing if an object has a visualization that can be applied to it
+ - generating a link to controllers.visualization.render with
+ the appropriate params
+ - validating and parsing params into resources (based on a context)
+ used in the visualization template
+ """
+ # these should be handled somewhat differently - and be passed onto their resp. methods in ctrl.visualization
+ #TODO: change/remove if/when they can be updated to use this system
+ BUILT_IN_VISUALIZATIONS = [
+ 'trackster',
+ 'circster',
+ 'sweepster',
+ 'phyloviz'
+ ]
+ # where to search for visualiztion templates (relative to templates/webapps/galaxy)
+ # this can be overridden individually in the config entries
+ TEMPLATE_ROOT = 'visualization'
+
+ def __str__( self ):
+ listings_keys_str = ','.join( self.listings.keys() ) if self.listings else ''
+ return 'VisualizationsRegistry(%s)' %( listings_keys_str )
+
+ def __init__( self, galaxy_root, configuration_filepath ):
+ # load the registry from the given xml file using the given parser
+ configuration_filepath = os.path.join( galaxy_root, configuration_filepath )
+ configuration_filepath = self.check_conf_filepath( configuration_filepath )
+ self.configuration_filepath = configuration_filepath
+ self.load()
+
+ # what to use to parse query strings into resources/vars for the template
+ self.resource_parser = ResourceParser()
+
+ def check_conf_filepath( self, configuration_filepath ):
+ """
+ If given file at filepath exists, return that filepath. If not,
+ see if filepath + '.sample' exists and, if so, copy that into filepath.
+
+ If neither original or sample exist, throw an IOError (currently,
+ this is a requireed file).
+ """
+ if os.path.exists( configuration_filepath ):
+ return configuration_filepath
+ else:
+ sample_file = configuration_filepath + '.sample'
+ if os.path.exists( sample_file ):
+ shutil.copy2( sample_file, configuration_filepath )
+ return configuration_filepath
+ raise IOError( 'visualization configuration file (%s) not found' %( configuration_filepath ) )
+
+ def load( self ):
+ """
+ Builds the registry by parsing the xml in `self.configuration_filepath`
+ and stores the results in `self.listings`.
+
+ Provided as separate method from `__init__` in order to re-load a
+ new configuration without restarting the instance.
+ """
+ self.listings = VisualizationsConfigParser.parse( self.configuration_filepath )
+
+ # -- building links to visualizations from objects --
+ def get_visualizations( self, trans, target_object ):
+ """
+ Get the names of visualizations usable on the `target_object` and
+ the urls to call in order to render the visualizations.
+ """
+ #TODO:?? a list of objects? YAGNI?
+ # a little weird to pass trans because this registry is part of the trans.app
+ applicable_visualizations = []
+ for vis_name, listing_data in self.listings.items():
+
+ data_sources = listing_data[ 'data_sources' ]
+ for data_source in data_sources:
+ # currently a model class is required
+ model_class = data_source[ 'model_class' ]
+ if not isinstance( target_object, model_class ):
+ continue
+
+ # tests are optional - default is the above class test
+ tests = data_source[ 'tests' ]
+ if tests and not self.is_object_applicable( trans, target_object, tests ):
+ continue
+
+ param_data = data_source[ 'to_params' ]
+ url = self.get_visualization_url( trans, target_object, vis_name, param_data )
+ link_text = listing_data.get( 'link_text', None )
+ if not link_text:
+ # default to visualization name, titlecase, and replace underscores
+ link_text = vis_name.title().replace( '_', ' ' )
+ render_location = listing_data.get( 'render_location' )
+ # remap some of these vars for direct use in ui.js, PopupMenu (e.g. text->html)
+ applicable_visualizations.append({
+ 'href' : url,
+ 'html' : link_text,
+ 'target': render_location
+ })
+
+ return applicable_visualizations
+
+ def is_object_applicable( self, trans, target_object, data_source_tests ):
+ """
+ Run a visualization's data_source tests to find out if
+ it be applied to the target_object.
+ """
+ for test in data_source_tests:
+ test_type = test[ 'type' ]
+ result_type = test[ 'result_type' ]
+ test_result = test[ 'result' ]
+ test_fn = test[ 'fn' ]
+ #log.debug( '%s %s: %s, %s, %s, %s', str( target_object ), 'is_object_applicable',
+ # test_type, result_type, test_result, test_fn )
+
+ if test_type == 'isinstance':
+ # parse test_result based on result_type (curr: only datatype has to do this)
+ if result_type == 'datatype':
+ # convert datatypes to their actual classes (for use with isinstance)
+ test_result = trans.app.datatypes_registry.get_datatype_class_by_name( test_result )
+ if not test_result:
+ # warn if can't find class, but continue
+ log.warn( 'visualizations_registry cannot find class (%s) for applicability test', test_result )
+ continue
+
+ if test_fn( target_object, test_result ):
+ #log.debug( 'test passed' )
+ return True
+
+ return False
+
+ def get_visualization_url( self, trans, target_object, visualization_name, param_data ):
+ """
+ Generates a url for the visualization with `visualization_name`
+ for use with the given `target_object` with a query string built
+ from the configuration data in `param_data`.
+ """
+ #precondition: the target_object should be usable by the visualization (accrd. to data_sources)
+ # convert params using vis.data_source.to_params
+ params = self.get_url_params( trans, target_object, param_data )
+
+ # we want existing visualizations to work as normal but still be part of the registry (without mod'ing)
+ # so generate their urls differently
+ url = None
+ if visualization_name in self.BUILT_IN_VISUALIZATIONS:
+ url = url_for( controller='visualization', action=visualization_name, **params )
+ else:
+ url = url_for( controller='visualization', action='render',
+ visualization_name=visualization_name, **params )
+
+ #TODO:?? not sure if embedded would fit/used here? or added in client...
+ return url
+
+ def get_url_params( self, trans, target_object, param_data ):
+ """
+ Convert the applicable objects and assoc. data into a param dict
+ for a url query string to add to the url that loads the visualization.
+ """
+ params = {}
+ for to_param_name, to_param_data in param_data.items():
+ #TODO??: look into params as well? what is required, etc.
+ target_attr = to_param_data.get( 'param_attr', None )
+ assign = to_param_data.get( 'assign', None )
+ # one or the other is needed
+ # assign takes precedence (goes last, overwrites)?
+ #NOTE this is only one level
+ if target_attr and hasattr( target_object, target_attr ):
+ params[ to_param_name ] = getattr( target_object, target_attr )
+ if assign:
+ params[ to_param_name ] = assign
+
+ #NOTE!: don't expose raw ids: encode id, _id
+ if params:
+ params = trans.security.encode_dict_ids( params )
+ return params
+
+ # -- getting resources for visualization templates from link query strings --
+ def get_resource_params_and_modifiers( self, visualization_name ):
+ """
+ Get params and modifiers for the given visualization as a 2-tuple.
+
+ Both `params` and `param_modifiers` default to an empty dictionary.
+ """
+ visualization = self.listings.get( visualization_name )
+ expected_params = visualization.get( 'params', {} )
+ param_modifiers = visualization.get( 'param_modifiers', {} )
+ return ( expected_params, param_modifiers )
+
+ def query_dict_to_resources( self, trans, controller, visualization_name, query_dict ):
+ """
+ Use a resource parser, controller, and a visualization's param configuration
+ to convert a query string into the resources and variables a visualization
+ template needs to start up.
+ """
+ param_confs, param_modifiers = self.get_resource_params_and_modifiers( visualization_name )
+ resources = self.resource_parser.parse_parameter_dictionary(
+ trans, controller, param_confs, query_dict, param_modifiers )
+ return resources
+
+
+# ------------------------------------------------------------------- parsing the config file
+class ParsingException( ValueError ):
+ """
+ An exception class for errors that occur during parsing of the visualizations
+ framework configuration XML file.
+ """
+ pass
+
+
+class VisualizationsConfigParser( object ):
+ """
+ Class that parses a visualizations configuration XML file.
+
+ Each visualization will get the following info:
+ - how to load a visualization:
+ -- how to find the proper template
+ -- how to convert query string into DB models
+ - when/how to generate a link to the visualization
+ -- what provides the data
+ -- what information needs to be added to the query string
+ """
+ VALID_RENDER_LOCATIONS = [ 'galaxy_main', '_top', '_blank' ]
+
+ @classmethod
+ def parse( cls, xml_filepath, debug=True ):
+ """
+ Static class interface
+ """
+ return cls( debug ).parse_file( xml_filepath )
+
+ def __init__( self, debug=False ):
+ self.debug = debug
+
+ # what parsers should be used for sub-components
+ self.data_source_parser = DataSourceParser()
+ self.param_parser = ParamParser()
+ self.param_modifier_parser = ParamModifierParser()
+
+ def parse_file( self, xml_filepath ):
+ """
+ Parse the given XML file for visualizations data.
+
+ If an error occurs while parsing a visualizations entry it is skipped.
+ """
+ returned = {}
+ try:
+ xml_tree = galaxy.util.parse_xml( xml_filepath )
+ for visualization_conf in xml_tree.getroot().findall( 'visualization' ):
+ visualization = None
+ visualization_name = visualization_conf.get( 'name' )
+
+ try:
+ visualization = self.parse_visualization( visualization_conf )
+ # skip vis' with parsing errors - don't shutdown the startup
+ except ParsingException, parse_exc:
+ log.error( 'Skipped visualization configuration "%s" due to parsing errors: %s',
+ visualization_name, str( parse_exc ), exc_info=self.debug )
+
+ if visualization:
+ returned[ visualization_name ] = visualization
+
+ except Exception, exc:
+ log.error( 'Error parsing visualization configuration file %s: %s',
+ xml_filepath, str( exc ), exc_info=( not self.debug ) )
+ #TODO: change when this is required
+ if self.debug:
+ raise
+
+ return returned
+
+ def parse_visualization( self, xml_tree ):
+ """
+ Parse the template, name, and any data_sources and params from the
+ given `xml_tree` for a visualization.
+ """
+ returned = {}
+
+ # data_sources are the kinds of objects/data associated with the visualization
+ # e.g. views on HDAs can use this to find out what visualizations are applicable to them
+ data_sources = []
+ data_sources_confs = xml_tree.find( 'data_sources' )
+ for data_source_conf in data_sources_confs.findall( 'data_source' ):
+ data_source = self.data_source_parser.parse( data_source_conf )
+ if data_source:
+ data_sources.append( data_source )
+ # data_sources are not required
+ if not data_sources:
+ raise ParsingException( 'No valid data_sources for visualization' )
+ returned[ 'data_sources' ] = data_sources
+
+ # parameters spell out how to convert query string params into resources and data
+ # that will be parsed, fetched, etc. and passed to the template
+ # list or dict? ordered or not?
+ params = {}
+ param_confs = xml_tree.find( 'params' )
+ for param_conf in param_confs.findall( 'param' ):
+ param = self.param_parser.parse( param_conf )
+ if param:
+ params[ param_conf.text ]= param
+ # params are not required
+ if params:
+ returned[ 'params' ] = params
+
+ # param modifiers provide extra information for other params (e.g. hda_ldda='hda' -> dataset_id is an hda id)
+ # store these modifiers in a 2-level dictionary { target_param: { param_modifier_key: { param_mod_data }
+ # ugh - wish we didn't need these
+ param_modifiers = {}
+ for param_modifier_conf in param_confs.findall( 'param_modifier' ):
+ param_modifier = self.param_modifier_parser.parse( param_modifier_conf )
+ # param modifiers map accrd. to the params they modify (for faster lookup)
+ target_param = param_modifier_conf.get( 'modifies' )
+ param_modifier_key = param_modifier_conf.text
+ if param_modifier and target_param in params:
+ # multiple params can modify a single, other param,
+ # so store in a sub-dict, initializing if this is the first
+ if target_param not in param_modifiers:
+ param_modifiers[ target_param ] = {}
+ param_modifiers[ target_param ][ param_modifier_key ] = param_modifier
+
+ # not required
+ if param_modifiers:
+ returned[ 'param_modifiers' ] = param_modifiers
+
+ # the template to use in rendering the visualization (required)
+ template = xml_tree.find( 'template' )
+ if template == None or not template.text:
+ raise ParsingException( 'template filename required' )
+ returned[ 'template' ] = template.text
+
+ # link_text: the string to use for the text of any links/anchors to this visualization
+ link_text = xml_tree.find( 'link_text' )
+ if link_text != None and link_text.text:
+ returned[ 'link_text' ] = link_text
+
+ # render_location: where in the browser to open the rendered visualization
+ # defaults to: galaxy_main
+ render_location = xml_tree.find( 'render_location' )
+ if( ( render_location != None and render_location.text )
+ and ( render_location.text in self.VALID_RENDER_LOCATIONS ) ):
+ returned[ 'render_location' ] = render_location.text
+ else:
+ returned[ 'render_location' ] = 'galaxy_main'
+ # consider unifying the above into it's own element and parsing method
+
+ return returned
+
+
+# ------------------------------------------------------------------- parsing a query string into resources
+class DataSourceParser( object ):
+ """
+ Component class of VisualizationsConfigParser that parses data_source elements
+ within visualization elements.
+
+ data_sources are (in the extreme) any object that can be used to produce
+ data for the visualization to consume (e.g. HDAs, LDDAs, Jobs, Users, etc.).
+ There can be more than one data_source associated with a visualization.
+ """
+ # these are the allowed classes to associate visualizations with (as strings)
+ # any model_class element not in this list will throw a parsing ParsingExcepion
+ ALLOWED_MODEL_CLASSES = [
+ 'HistoryDatasetAssociation',
+ 'LibraryDatasetDatasetAssociation'
+ ]
+ ATTRIBUTE_SPLIT_CHAR = '.'
+ # these are the allowed object attributes to use in data source tests
+ # any attribute element not in this list will throw a parsing ParsingExcepion
+ ALLOWED_DATA_SOURCE_ATTRIBUTES = [
+ 'datatype'
+ ]
+
+ def parse( self, xml_tree ):
+ """
+ Return a visualization data_source dictionary parsed from the given
+ XML element.
+ """
+ returned = {}
+ # model_class (required, only one) - look up and convert model_class to actual galaxy model class
+ model_class = self.parse_model_class( xml_tree.find( 'model_class' ) )
+ if not model_class:
+ raise ParsingException( 'data_source needs a model class' )
+ returned[ 'model_class' ] = model_class
+
+ # tests (optional, 0 or more) - data for boolean test: 'is the visualization usable by this object?'
+ tests = self.parse_tests( xml_tree.findall( 'test' ) )
+ # when no tests are given, default to isinstance( object, model_class )
+ if tests:
+ returned[ 'tests' ] = tests
+
+ # to_params (optional, 0 or more) - tells the registry to set certain params based on the model_clas, tests
+ to_params = self.parse_to_params( xml_tree.findall( 'to_param' ) )
+ if to_params:
+ returned[ 'to_params' ] = to_params
+
+ return returned
+
+ def parse_model_class( self, xml_tree ):
+ """
+ Convert xml model_class element to a galaxy model class
+ (or None if model class is not found).
+
+ This element is required and only the first element is used.
+ The model_class string must be in ALLOWED_MODEL_CLASSES.
+ """
+ if xml_tree is None or not xml_tree.text:
+ raise ParsingException( 'data_source entry requires a model_class' )
+
+ if xml_tree.text not in self.ALLOWED_MODEL_CLASSES:
+ log.debug( 'available data_source model_classes: %s' %( str( self.ALLOWED_MODEL_CLASSES ) ) )
+ raise ParsingException( 'Invalid data_source model_class: %s' %( xml_tree.text ) )
+
+ # look up the model from the model module returning an empty data_source if not found
+ model_class = getattr( galaxy.model, xml_tree.text, None )
+ return model_class
+
+ def _build_getattr_lambda( self, attr_name_list ):
+ """
+ Recursively builds a compound lambda function of getattr's
+ from the attribute names given in `attr_name_list`.
+ """
+ if len( attr_name_list ) == 0:
+ # identity - if list is empty, return object itself
+ return lambda o: o
+
+ next_attr_name = attr_name_list[-1]
+ if len( attr_name_list ) == 1:
+ # recursive base case
+ return lambda o: getattr( o, next_attr_name )
+
+ # recursive case
+ return lambda o: getattr( self._build_getattr_lambda( attr_name_list[:-1] ), next_attr_name )
+
+ def parse_tests( self, xml_tree_list ):
+ """
+ Returns a list of test dictionaries that the registry can use
+ against a given object to determine if the visualization can be
+ used with the object.
+ """
+ # tests should NOT include expensive operations: reading file data, running jobs, etc.
+ # do as much here as possible to reduce the overhead of seeing if a visualization is applicable
+ # currently tests are or'd only (could be and'd or made into compound boolean tests)
+ tests = []
+ if not xml_tree_list:
+ return tests
+
+ for test_elem in xml_tree_list:
+ test_type = test_elem.get( 'type' )
+ test_result = test_elem.text
+ if not test_type or not test_result:
+ log.warn( 'Skipping test. Needs both type attribute and text node to be parsed: '
+ + '%s, %s' %( test_type, test_elem.text ) )
+ continue
+
+ # test_attr can be a dot separated chain of object attributes (e.g. dataset.datatype) - convert to list
+ #TODO: too dangerous - constrain these to some allowed list
+ test_attr = test_elem.get( 'test_attr' )
+ test_attr = test_attr.split( self.ATTRIBUTE_SPLIT_CHAR ) if isinstance( test_attr, str ) else []
+ # build a lambda function that gets the desired attribute to test
+ getter = self._build_getattr_lambda( test_attr )
+
+ # result type should tell the registry how to convert the result before the test
+ test_result_type = test_elem.get( 'result_type' ) or 'string'
+
+ # test functions should be sent an object to test, and the parsed result expected from the test
+ #TODO: currently, isinstance and string equivalance are the only test types supported
+ if test_type == 'isinstance':
+ #TODO: wish we could take this further but it would mean passing in the datatypes_registry
+ test_fn = lambda o, result: isinstance( getter( o ), result )
+
+ # default to simple (string) equilavance (coercing the test_attr to a string)
+ else:
+ test_fn = lambda o, result: str( getter( o ) ) == result
+
+ tests.append({
+ 'type' : test_type,
+ 'result' : test_result,
+ 'result_type' : test_result_type,
+ 'fn' : test_fn
+ })
+
+ return tests
+
+ def parse_to_params( self, xml_tree_list ):
+ """
+ Given a list of `to_param` elements, returns a dictionary that allows
+ the registry to convert the data_source into one or more appropriate
+ params for the visualization.
+ """
+ to_param_dict = {}
+ if not xml_tree_list:
+ return to_param_dict
+
+ for element in xml_tree_list:
+ # param_name required
+ param_name = element.text
+ if not param_name:
+ raise ParsingException( 'to_param requires text (the param name)' )
+
+ param = {}
+ # assign is a shortcut param_attr that assigns a value to a param (as text)
+ assign = element.get( 'assign' )
+ if assign != None:
+ param[ 'assign' ] = assign
+
+ # param_attr is the attribute of the object (that the visualization will be applied to)
+ # that should be converted into a query param (e.g. param_attr="id" -> dataset_id)
+ #TODO:?? use the build attr getter here?
+ # simple (1 lvl) attrs for now
+ param_attr = element.get( 'param_attr' )
+ if param_attr != None:
+ param[ 'param_attr' ] = param_attr
+ # element must have either param_attr or assign? what about no params (the object itself)
+ if not param_attr and not assign:
+ raise ParsingException( 'to_param requires either assign or param_attr attributes: %s', param_name )
+
+ #TODO: consider making the to_param name an attribute (param="hda_ldda") and the text what would
+ # be used for the conversion - this would allow CDATA values to be passed
+ #<to_param param="json" type="assign"><![CDATA[{ "one": 1, "two": 2 }]]></to_param>
+
+ if param:
+ to_param_dict[ param_name ] = param
+
+ return to_param_dict
+
+
+class ParamParser( object ):
+ """
+ Component class of VisualizationsConfigParser that parses param elements
+ within visualization elements.
+
+ params are parameters that will be parsed (based on their `type`, etc.)
+ and sent to the visualization template by controllers.visualization.render.
+ """
+ DEFAULT_PARAM_TYPE = 'str'
+
+ def parse( self, xml_tree ):
+ """
+ Parse a visualization parameter from the given `xml_tree`.
+ """
+ returned = {}
+
+ # don't store key, just check it
+ param_key = xml_tree.text
+ if not param_key:
+ raise ParsingException( 'Param entry requires text' )
+
+ returned[ 'type' ] = self.parse_param_type( xml_tree )
+
+ # is the parameter required in the template and,
+ # if not, what is the default value?
+ required = xml_tree.get( 'required' ) == "true"
+ returned[ 'required' ] = required
+ if not required:
+ # default defaults to None
+ default = None
+ if 'default' in xml_tree.attrib:
+ default = xml_tree.get( 'default' )
+ # convert default based on param_type here
+ returned[ 'default' ] = default
+
+ # does the param have to be within a list of certain values
+ # NOTE: the interpretation of this list is deferred till parsing and based on param type
+ # e.g. it could be 'val in constrain_to', or 'constrain_to is min, max for number', etc.
+ #TODO: currently unused
+ constrain_to = xml_tree.get( 'constrain_to' )
+ if constrain_to:
+ returned[ 'constrain_to' ] = constrain_to.split( ',' )
+
+ # is the param a comma-separated-value list?
+ returned[ 'csv' ] = xml_tree.get( 'csv' ) == "true"
+
+ # remap keys in the params/query string to the var names used in the template
+ var_name_in_template = xml_tree.get( 'var_name_in_template' )
+ if var_name_in_template:
+ returned[ 'var_name_in_template' ] = var_name_in_template
+
+ return returned
+
+ def parse_param_type( self, xml_tree ):
+ """
+ Parse a param type from the given `xml_tree`.
+ """
+ # default to string as param_type
+ param_type = xml_tree.get( 'type' ) or self.DEFAULT_PARAM_TYPE
+ #TODO: set parsers and validaters, convert here
+ return param_type
+
+
+class ParamModifierParser( ParamParser ):
+ """
+ Component class of VisualizationsConfigParser that parses param_modifier
+ elements within visualization elements.
+
+ param_modifiers are params from a dictionary (such as a query string)
+ that are not standalone but modify the parsing/conversion of a separate
+ (normal) param (e.g. 'hda_ldda' can equal 'hda' or 'ldda' and control
+ whether a visualizations 'dataset_id' param is for an HDA or LDDA).
+ """
+ def parse( self, element ):
+ # modifies is required
+ modifies = element.get( 'modifies' )
+ if not modifies:
+ raise ParsingException( 'param_modifier entry requires a target param key (attribute "modifies")' )
+ returned = super( ParamModifierParser, self).parse( element )
+ return returned
+
+
+class ResourceParser( object ):
+ """
+ Given a parameter dictionary (often a converted query string) and a
+ configuration dictionary (curr. only VisualizationsRegistry uses this),
+ convert the entries in the parameter dictionary into resources (Galaxy
+ models, primitive types, lists of either, etc.) and return
+ in a new dictionary.
+
+ The keys used to store the new values can optionally be re-mapped to
+ new keys (e.g. dataset_id="NNN" -> hda=<HistoryDatasetAsscoation>).
+ """
+ #TODO: kinda torn as to whether this belongs here or in controllers.visualization
+ # taking the (questionable) design path of passing a controller in
+ # (which is the responsible party for getting model, etc. resources )
+ # consider making this a base controller? use get_object for the model resources
+ # don't like passing in the app, tho
+ def parse_parameter_dictionary( self, trans, controller, param_config_dict, query_params, param_modifiers=None ):
+ """
+ Parse all expected params from the query dictionary `query_params`.
+
+ If param is required and not present, raises a `KeyError`.
+ """
+ # parse the modifiers first since they modify the params coming next
+ #TODO: this is all really for hda_ldda - which we could replace with model polymorphism
+ params_that_modify_other_params = self.parse_parameter_modifiers(
+ trans, controller, param_modifiers, query_params )
+
+ resources = {}
+ for param_name, param_config in param_config_dict.items():
+ # optionally rename the variable returned, defaulting to the original name
+ var_name_in_template = param_config.get( 'var_name_in_template', param_name )
+
+ # if the param is present, get it's value, any param modifiers for that param, and parse it into a resource
+ # use try catch here and not caller to fall back on the default value or re-raise if required
+ resource = None
+ query_val = query_params.get( param_name, None )
+ if query_val is not None:
+ try:
+ target_param_modifiers = params_that_modify_other_params.get( param_name, None )
+ resource = self.parse_parameter( trans, controller, param_config,
+ query_val, param_modifiers=target_param_modifiers )
+
+ except Exception, exception:
+ log.warn( 'Exception parsing visualization param from query: '
+ + '%s, %s, (%s) %s' %( param_name, query_val, str( type( exception ) ), str( exception ) ))
+ resource = None
+
+ # here - we've either had no value in the query_params or there was a failure to parse
+ # so: error if required, otherwise get a default (which itself defaults to None)
+ if resource == None:
+ if param_config[ 'required' ]:
+ raise KeyError( 'required param %s not found in URL' %( param_name ) )
+ resource = self.parse_parameter_default( trans, param_config )
+
+ resources[ var_name_in_template ] = resource
+
+ return resources
+
+ #TODO: I would LOVE to rip modifiers out completely
+ def parse_parameter_modifiers( self, trans, controller, param_modifiers, query_params ):
+ """
+ Parse and return parameters that are meant to modify other parameters,
+ be grouped with them, or are needed to successfully parse other parameters.
+ """
+ # only one level of modification - down that road lies madness
+ # parse the modifiers out of query_params first since they modify the other params coming next
+ parsed_modifiers = {}
+ if not param_modifiers:
+ return parsed_modifiers
+ #precondition: expects a two level dictionary
+ # { target_param_name -> { param_modifier_name -> { param_modifier_data }}}
+ for target_param_name, modifier_dict in param_modifiers.items():
+ parsed_modifiers[ target_param_name ] = target_modifiers = {}
+
+ for modifier_name, modifier_config in modifier_dict.items():
+ query_val = query_params.get( modifier_name, None )
+ if query_val is not None:
+ modifier = self.parse_parameter( trans, controller, modifier_config, query_val )
+ target_modifiers[ modifier_name ] = modifier
+ else:
+ #TODO: required attr?
+ target_modifiers[ modifier_name ] = self.parse_parameter_default( trans, modifier_config )
+
+ return parsed_modifiers
+
+ def parse_parameter_default( self, trans, param_config ):
+ """
+ Parse any default values for the given param, defaulting the default
+ to `None`.
+ """
+ # currently, *default* default is None, so this is quaranteed to be part of the dictionary
+ default = param_config[ 'default' ]
+ # if default is None, do not attempt to parse it
+ if default == None:
+ return default
+ # otherwise, parse (currently param_config['default'] is a string just like query param and needs to be parsed)
+ # this saves us the trouble of parsing the default when the config file is read
+ # (and adding this code to the xml parser)
+ return self.parse_parameter( trans, param_config, default )
+
+ def parse_parameter( self, trans, controller, expected_param_data, query_param,
+ recurse=True, param_modifiers=None ):
+ """
+ Use data in `expected_param_data` to parse `query_param` from a string into
+ a resource usable directly by a template.
+
+ 'Primitive' types (string, int, etc.) are parsed here and more complex
+ resources (such as ORM models) are parsed via the `controller` passed
+ in.
+ """
+ param_type = expected_param_data.get( 'type' )
+ constrain_to = expected_param_data.get( 'constrain_to' )
+ csv = expected_param_data.get( 'csv' )
+
+ parsed_param = None
+
+ # handle recursion for csv values
+ if csv and recurse:
+ parsed_param = []
+ query_param_list = galaxy.util.listify( query_param )
+ for query_param in query_param_list:
+ parsed_param.append( self._parse_param( trans, expected_param_data, query_param, recurse=False ) )
+ return parsed_param
+
+ primitive_parsers = {
+ 'str' : lambda param: galaxy.util.sanitize_html.sanitize_html( param, 'utf-8' ),
+ 'bool' : lambda param: galaxy.util.string_as_bool( param ),
+ 'int' : lambda param: int( param ),
+ 'float' : lambda param: float( param ),
+ #'date' : lambda param: ,
+ 'json' : ( lambda param: galaxy.util.json.from_json_string(
+ galaxy.util.sanitize_html.sanitize_html( param ) ) ),
+ }
+ parser = primitive_parsers.get( param_type, None )
+ if parser:
+ #TODO: what about param modifiers on primitives?
+ parsed_param = parser( query_param )
+
+ #TODO: constrain_to
+ # this gets complicated - for strings - relatively simple but still requires splitting and using in
+ # for more complicated cases (ints, json) this gets weird quick
+ #TODO:?? remove?
+
+ # db models
+ #TODO: subclass here?
+ elif param_type == 'visualization':
+ encoded_visualization_id = query_param
+ #TODO:?? some fallback if there's no get_X in controller that's passed?
+ parsed_param = controller.get_visualization( trans, encoded_visualization_id,
+ check_ownership=False, check_accessible=True )
+
+ elif param_type == 'dataset':
+ encoded_dataset_id = query_param
+ # really an hda...
+ parsed_param = controller.get_dataset( trans, encoded_dataset_id,
+ check_ownership=False, check_accessible=True )
+
+ elif param_type == 'hda_or_ldda':
+ encoded_dataset_id = query_param
+ # needs info from another param...
+ hda_ldda = param_modifiers.get( 'hda_ldda' )
+ parsed_param = controller.get_hda_or_ldda( trans, hda_ldda, encoded_dataset_id )
+
+ #TODO: ideally this would check v. a list of valid dbkeys
+ elif param_type == 'dbkey':
+ dbkey = query_param
+ parsed_param = galaxy.util.sanitize_html.sanitize_html( dbkey, 'utf-8' )
+
+ #print ( '%s, %s -> %s, %s' %( param_type, query_param, str( type( parsed_param ) ), parsed_param ) )
+ return parsed_param
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/web/base/controller.py
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -14,7 +14,7 @@
from sqlalchemy import func, and_, select
from paste.httpexceptions import HTTPBadRequest, HTTPInternalServerError, HTTPNotImplemented, HTTPRequestRangeNotSatisfiable
-from galaxy import util, web
+from galaxy import util, web, model
from gettext import gettext
from galaxy.datatypes.interval import ChromatinInteractions
from galaxy.exceptions import ItemAccessibilityException, ItemDeletionException, ItemOwnershipException, MessageException
@@ -28,7 +28,6 @@
from galaxy.model.orm import eagerload, eagerload_all
from galaxy.datatypes.data import Text
-
from galaxy.datatypes.display_applications import util as da_util
from galaxy.datatypes.metadata import FileParameter
@@ -486,6 +485,25 @@
error( "Please wait until this dataset finishes uploading before attempting to view it." )
return hda
+ def get_hda_list( self, trans, hda_ids, check_ownership=True, check_accessible=False, check_state=True ):
+ """
+ Returns one or more datasets in a list.
+
+ If a dataset is not found or is inaccessible to trans.user,
+ add None in its place in the list.
+ """
+ # precondtion: dataset_ids is a list of encoded id strings
+ hdas = []
+ for id in hda_ids:
+ hda = None
+ try:
+ hda = self.get_dataset( trans, id,
+ check_ownership=check_ownership, check_accesible=check_accesible, check_state=check_state )
+ except Exception, exception:
+ pass
+ hdas.append( hda )
+ return hdas
+
def get_data( self, dataset, preview=True ):
"""
Gets a dataset's data.
@@ -552,9 +570,13 @@
if meta_files:
hda_dict[ 'meta_files' ] = meta_files
- #hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda )
- #hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda )
- hda_dict[ 'visualizations' ] = hda.get_visualizations()
+ # currently, the viz reg is optional - handle on/off
+ if trans.app.visualizations_registry:
+ hda_dict[ 'visualizations' ] = trans.app.visualizations_registry.get_visualizations( trans, hda )
+ else:
+ hda_dict[ 'visualizations' ] = hda.get_visualizations()
+ #TODO: it may also be wiser to remove from here and add as API call that loads the visualizations
+ # when the visualizations button is clicked (instead of preloading/pre-checking)
# ---- return here if deleted
if hda.deleted and not purged:
@@ -662,18 +684,187 @@
return self.get_object( trans, id, 'LibraryDataset', check_ownership=False, check_accessible=check_accessible )
-class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin,
- UsesLibraryMixinItems ):
- """ Mixin for controllers that use Visualization objects. """
+class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin, UsesLibraryMixinItems ):
+ """
+ Mixin for controllers that use Visualization objects.
+ """
+ DEFAULT_ORDER_BY = [ model.Visualization.title ]
viz_types = [ "trackster" ]
- def create_visualization( self, trans, type, title="Untitled Genome Vis", slug=None, dbkey=None, annotation=None, config={}, save=True ):
- """ Create visualiation and first revision. """
+ def get_visualization( self, trans, id, check_ownership=True, check_accessible=False ):
+ """
+ Get a Visualization from the database by id, verifying ownership.
+ """
+ # Load workflow from database
+ try:
+ visualization = trans.sa_session.query( trans.model.Visualization ).get( trans.security.decode_id( id ) )
+ except TypeError:
+ visualization = None
+ if not visualization:
+ error( "Visualization not found" )
+ else:
+ return self.security_check( trans, visualization, check_ownership, check_accessible )
+
+ def get_visualizations_by_user( self, trans, user, order_by=None, query_only=False ):
+ """
+ Return query or query results of visualizations filtered by a user.
+
+ Set `order_by` to a column or list of columns to change the order
+ returned. Defaults to `DEFAULT_ORDER_BY`.
+ Set `query_only` to return just the query for further filtering or
+ processing.
+ """
+ if not order_by:
+ order_by = self.DEFAULT_ORDER_BY
+ if not isinstance( order_by, list ):
+ order_by = [ order_by ]
+ query = trans.sa_session.query( model.Visualization )
+ query = query.filter( model.Visualization.user == user )
+ if order_by:
+ query = query.order_by( *order_by )
+ if query_only:
+ return query
+ return query.all()
+
+ def get_visualizations_shared_with_user( self, trans, user, order_by=None, query_only=False ):
+ """
+ Return query or query results for visualizations shared with the given user.
+
+ Set `order_by` to a column or list of columns to change the order
+ returned. Defaults to `DEFAULT_ORDER_BY`.
+ Set `query_only` to return just the query for further filtering or
+ processing.
+ """
+ if not order_by:
+ order_by = self.DEFAULT_ORDER_BY
+ if not isinstance( order_by, list ):
+ order_by = [ order_by ]
+ query = trans.sa_session.query( model.Visualization ).join( model.VisualizationUserShareAssociation )
+ query = query.filter( model.VisualizationUserShareAssociation.user_id == user.id )
+ # remove duplicates when a user shares with themselves?
+ query = query.filter( model.Visualization.user_id != user.id )
+ if order_by:
+ query = query.order_by( *order_by )
+ if query_only:
+ return query
+ return query.all()
+
+ def get_published_visualizations( self, trans, exclude_user=None, order_by=None, query_only=False ):
+ """
+ Return query or query results for published visualizations optionally excluding
+ the user in `exclude_user`.
+
+ Set `order_by` to a column or list of columns to change the order
+ returned. Defaults to `DEFAULT_ORDER_BY`.
+ Set `query_only` to return just the query for further filtering or
+ processing.
+ """
+ if not order_by:
+ order_by = self.DEFAULT_ORDER_BY
+ if not isinstance( order_by, list ):
+ order_by = [ order_by ]
+ query = trans.sa_session.query( model.Visualization )
+ query = query.filter( model.Visualization.published == True )
+ if exclude_user:
+ query = query.filter( model.Visualization.user != exclude_user )
+ if order_by:
+ query = query.order_by( *order_by )
+ if query_only:
+ return query
+ return query.all()
+
+ #TODO: move into model (get_api_value)
+ def get_visualization_summary_dict( self, visualization ):
+ """
+ Return a set of summary attributes for a visualization in dictionary form.
+ NOTE: that encoding ids isn't done here should happen at the caller level.
+ """
+ #TODO: deleted
+ #TODO: importable
+ return {
+ 'id' : visualization.id,
+ 'title' : visualization.title,
+ 'type' : visualization.type,
+ 'dbkey' : visualization.dbkey,
+ }
+
+ def get_visualization_dict( self, visualization ):
+ """
+ Return a set of detailed attributes for a visualization in dictionary form.
+ The visualization's latest_revision is returned in its own sub-dictionary.
+ NOTE: that encoding ids isn't done here should happen at the caller level.
+ """
+ return {
+ 'model_class': 'Visualization',
+ 'id' : visualization.id,
+ 'title' : visualization.title,
+ 'type' : visualization.type,
+ 'user_id' : visualization.user.id,
+ 'dbkey' : visualization.dbkey,
+ 'slug' : visualization.slug,
+ # dictify only the latest revision (allow older to be fetched elsewhere)
+ 'latest_revision' : self.get_visualization_revision_dict( visualization.latest_revision ),
+ 'revisions' : [ r.id for r in visualization.revisions ],
+ }
+
+ def get_visualization_revision_dict( self, revision ):
+ """
+ Return a set of detailed attributes for a visualization in dictionary form.
+ NOTE: that encoding ids isn't done here should happen at the caller level.
+ """
+ return {
+ 'model_class': 'VisualizationRevision',
+ 'id' : revision.id,
+ 'visualization_id' : revision.visualization.id,
+ 'title' : revision.title,
+ 'dbkey' : revision.dbkey,
+ 'config' : revision.config,
+ }
+
+ def import_visualization( self, trans, id, user=None ):
+ """
+ Copy the visualization with the given id and associate the copy
+ with the given user (defaults to trans.user).
+
+ Raises `ItemAccessibilityException` if `user` is not passed and
+ the current user is anonymous, and if the visualization is not `importable`.
+ Raises `ItemDeletionException` if the visualization has been deleted.
+ """
+ # default to trans.user, error if anon
+ if not user:
+ if not trans.user:
+ raise ItemAccessibilityException( "You must be logged in to import Galaxy visualizations" )
+ user = trans.user
+
+ # check accessibility
+ visualization = self.get_visualization( trans, id, check_ownership=False )
+ if not visualization.importable:
+ raise ItemAccessibilityException( "The owner of this visualization has disabled imports via this link." )
+ if visualization.deleted:
+ raise ItemDeletionException( "You can't import this visualization because it has been deleted." )
+
+ # copy vis and alter title
+ #TODO: need to handle custom db keys.
+ imported_visualization = visualization.copy( user=user, title="imported: " + visualization.title )
+ trans.sa_session.add( imported_visualization )
+ trans.sa_session.flush()
+ return imported_visualization
+
+ def create_visualization( self, trans, type, title="Untitled Genome Vis", slug=None,
+ dbkey=None, annotation=None, config={}, save=True ):
+ """
+ Create visualiation and first revision.
+ """
visualization = self._create_visualization( trans, title, type, dbkey, slug, annotation, save )
+ #TODO: handle this error structure better either in _create or here
+ if isinstance( visualization, dict ):
+ err_dict = visualization
+ raise ValueError( err_dict[ 'title_err' ] or err_dict[ 'slug_err' ] )
# Create and save first visualization revision
- revision = trans.model.VisualizationRevision( visualization=visualization, title=title, config=config, dbkey=dbkey )
+ revision = trans.model.VisualizationRevision( visualization=visualization, title=title,
+ config=config, dbkey=dbkey )
visualization.latest_revision = revision
if save:
@@ -683,6 +874,21 @@
return visualization
+ def add_visualization_revision( self, trans, visualization, config, title, dbkey ):
+ """
+ Adds a new `VisualizationRevision` to the given `visualization` with
+ the given parameters and set its parent visualization's `latest_revision`
+ to the new revision.
+ """
+ #precondition: only add new revision on owned vis's
+ #TODO:?? should we default title, dbkey, config? to which: visualization or latest_revision?
+ revision = trans.model.VisualizationRevision( visualization, title, dbkey, config )
+ visualization.latest_revision = revision
+ #TODO:?? does this automatically add revision to visualzation.revisions?
+ trans.sa_session.add( revision )
+ trans.sa_session.flush()
+ return revision
+
def save_visualization( self, trans, config, type, id=None, title=None, dbkey=None, slug=None, annotation=None ):
session = trans.sa_session
@@ -697,8 +903,10 @@
# Create new VisualizationRevision that will be attached to the viz
vis_rev = trans.model.VisualizationRevision()
vis_rev.visualization = vis
- vis_rev.title = vis.title
- vis_rev.dbkey = dbkey
+ # do NOT alter the dbkey
+ vis_rev.dbkey = vis.dbkey
+ # do alter the title and config
+ vis_rev.title = title
# -- Validate config. --
@@ -760,18 +968,6 @@
encoded_id = trans.security.encode_id( vis.id )
return { "vis_id": encoded_id, "url": url_for( controller='visualization', action=vis.type, id=encoded_id ) }
- def get_visualization( self, trans, id, check_ownership=True, check_accessible=False ):
- """ Get a Visualization from the database by id, verifying ownership. """
- # Load workflow from database
- try:
- visualization = trans.sa_session.query( trans.model.Visualization ).get( trans.security.decode_id( id ) )
- except TypeError:
- visualization = None
- if not visualization:
- error( "Visualization not found" )
- else:
- return self.security_check( trans, visualization, check_ownership, check_accessible )
-
def get_visualization_config( self, trans, visualization ):
""" Returns a visualization's configuration. Only works for trackster visualizations right now. """
config = None
@@ -911,7 +1107,6 @@
if title_err or slug_err:
return { 'title_err': title_err, 'slug_err': slug_err }
-
# Create visualization
visualization = trans.model.Visualization( user=user, title=title, dbkey=dbkey, type=type )
if slug:
@@ -920,6 +1115,8 @@
self.create_item_slug( trans.sa_session, visualization )
if annotation:
annotation = sanitize_html( annotation, 'utf-8', 'text/html' )
+ #TODO: if this is to stay in the mixin, UsesAnnotations should be added to the superclasses
+ # right now this is depending on the classes that include this mixin to have UsesAnnotations
self.add_item_annotation( trans.sa_session, trans.user, visualization, annotation )
if save:
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -51,17 +51,20 @@
webapp.add_route( '/async/:tool_id/:data_id/:data_secret', controller='async', action='index', tool_id=None, data_id=None, data_secret=None )
webapp.add_route( '/:controller/:action', action='index' )
webapp.add_route( '/:action', controller='root', action='index' )
+
# allow for subdirectories in extra_files_path
webapp.add_route( '/datasets/:dataset_id/display/{filename:.+?}', controller='dataset', action='display', dataset_id=None, filename=None)
webapp.add_route( '/datasets/:dataset_id/:action/:filename', controller='dataset', action='index', dataset_id=None, filename=None)
- webapp.add_route( '/display_application/:dataset_id/:app_name/:link_name/:user_id/:app_action/:action_param', controller='dataset', action='display_application', dataset_id=None, user_id=None, app_name = None, link_name = None, app_action = None, action_param = None )
+ webapp.add_route( '/display_application/:dataset_id/:app_name/:link_name/:user_id/:app_action/:action_param',
+ controller='dataset', action='display_application', dataset_id=None, user_id=None,
+ app_name = None, link_name = None, app_action = None, action_param = None )
webapp.add_route( '/u/:username/d/:slug/:filename', controller='dataset', action='display_by_username_and_slug', filename=None )
webapp.add_route( '/u/:username/p/:slug', controller='page', action='display_by_username_and_slug' )
webapp.add_route( '/u/:username/h/:slug', controller='history', action='display_by_username_and_slug' )
webapp.add_route( '/u/:username/w/:slug', controller='workflow', action='display_by_username_and_slug' )
webapp.add_route( '/u/:username/v/:slug', controller='visualization', action='display_by_username_and_slug' )
webapp.add_route( '/search', controller='search', action='index' )
-
+
# Add the web API
webapp.add_api_controllers( 'galaxy.webapps.galaxy.api', app )
# The /folders section is experimental at this point:
@@ -144,6 +147,10 @@
#webapp.mapper.connect( 'run_workflow', '/api/workflow/{workflow_id}/library/{library_id}', controller='workflows', action='run', workflow_id=None, library_id=None, conditions=dict(method=["GET"]) )
webapp.mapper.resource( 'search', 'search', path_prefix='/api' )
+ # visualizations registry generic template renderer
+ webapp.add_route( '/visualization/show/:visualization_name',
+ controller='visualization', action='render', visualization_name=None )
+
# "POST /api/workflows/import" => ``workflows.import_workflow()``.
# Defines a named route "import_workflow".
webapp.mapper.connect("import_workflow", "/api/workflows/upload", controller="workflows", action="import_new_workflow", conditions=dict(method=["POST"]))
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -1,6 +1,10 @@
from __future__ import absolute_import
+import os
+
from sqlalchemy import desc, or_, and_
+from paste.httpexceptions import HTTPNotFound
+
from galaxy import model, web
from galaxy.model.item_attrs import UsesAnnotations, UsesItemRatings
from galaxy.web.base.controller import BaseUIController, SharableMixin, UsesVisualizationMixin
@@ -16,6 +20,9 @@
from .library import LibraryListGrid
+import logging
+log = logging.getLogger( __name__ )
+
#
# -- Grids --
#
@@ -689,6 +696,54 @@
default_dbkey=kwargs.get("default_dbkey", None) )
@web.expose
+ @web.require_login( "use Galaxy visualizations", use_panels=True )
+ def render( self, trans, visualization_name, embedded=None, **kwargs ):
+ """
+ Render the appropriate visualization template, parsing the `kwargs`
+ into appropriate variables and resources (such as ORM models)
+ based on this visualizations `param` data in visualizations_conf.xml.
+
+ URL: /visualization/show/{visualization_name}
+ """
+ # validate name vs. registry
+ registry = trans.app.visualizations_registry
+ if not registry:
+ raise HTTPNotFound( 'No visualization registry (possibly disabled in universe_wsgi.ini)')
+ if visualization_name not in registry.listings:
+ raise HTTPNotFound( 'Unknown or invalid visualization: ' + visualization_name )
+ # or redirect to list?
+ registry_listing = registry.listings[ visualization_name ]
+
+ returned = None
+ try:
+ # convert query string to resources for template based on registry config
+ #NOTE: passing in controller to keep resource lookup within the controller's responsibilities
+ # (and not the ResourceParser)
+ resources = registry.query_dict_to_resources( trans, self, visualization_name, kwargs )
+
+ # look up template and render
+ template_root = registry_listing.get( 'template_root', registry.TEMPLATE_ROOT )
+ template = registry_listing[ 'template' ]
+ template_path = os.path.join( template_root, template )
+ #NOTE: passing *unparsed* kwargs as query_args
+ #NOTE: shared_vars is a dictionary for shared data in the template
+ # this feels hacky to me but it's what mako recommends:
+ # http://docs.makotemplates.org/en/latest/runtime.html
+ #TODO: embedded
+ returned = trans.fill_template( template_path, visualization_name=visualization_name,
+ embedded=embedded, query_args=kwargs, shared_vars={}, **resources )
+
+ except Exception, exception:
+ log.exception( 'error rendering visualization (%s): %s', visualization_name, str( exception ) )
+ if trans.debug: raise
+ returned = trans.show_error_message(
+ "There was an error rendering the visualization. " +
+ "Contact your Galaxy administrator if the problem persists." +
+ "<br/>Details: " + str( exception ), use_panels=False )
+
+ return returned
+
+ @web.expose
@web.require_login()
def trackster(self, trans, id=None, **kwargs):
"""
@@ -792,6 +847,8 @@
get the visualization with the given id; otherwise, create a new visualization using
a given dataset and regions.
"""
+ print 'sweepster:', id, hda_ldda, dataset_id, regions
+ regions = regions or '{}'
# Need to create history if necessary in order to create tool form.
trans.get_history( create=True )
@@ -803,6 +860,7 @@
else:
# Loading new visualization.
dataset = self.get_hda_or_ldda( trans, hda_ldda, dataset_id )
+ print 'dataset:', dataset
job = get_dataset_job( dataset )
viz_config = {
'dataset_id': dataset_id,
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d static/scripts/mvc/dataset/hda-edit.js
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -239,8 +239,25 @@
* @returns {jQuery} rendered DOM
*/
_render_visualizationsButton : function(){
+ var visualizations = this.model.get( 'visualizations' );
+ if( ( !this.model.hasData() )
+ || ( _.isEmpty( visualizations ) ) ){
+ this.visualizationsButton = null;
+ return null;
+ }
+
+ //TODO: this is a bridge to allow the framework to be switched off
+ // remove this fn and use the other when fully integrated
+ if( _.isObject( visualizations[0] ) ){
+ return this._render_visualizationsFrameworkButton( visualizations );
+ }
+
+ if( !this.urls.visualization ){
+ this.visualizationsButton = null;
+ return null;
+ }
+
var dbkey = this.model.get( 'dbkey' ),
- visualizations = this.model.get( 'visualizations' ),
visualization_url = this.urls.visualization,
popup_menu_dict = {},
params = {
@@ -250,17 +267,10 @@
// Add dbkey to params if it exists.
if( dbkey ){ params.dbkey = dbkey; }
- if( !( this.model.hasData() )
- || !( visualizations && visualizations.length )
- || !( visualization_url ) ){
- this.visualizationsButton = null;
- return null;
- }
-
// render the icon from template
this.visualizationsButton = new IconButtonView({ model : new IconButton({
title : _l( 'Visualize' ),
- href : visualization_url,
+ href : this.urls.visualization,
icon_class : 'chart_curve'
})});
var $icon = this.visualizationsButton.render().$el;
@@ -299,6 +309,40 @@
}
return $icon;
},
+
+ /** Render an icon-button or popupmenu of links based on the applicable visualizations
+ * @returns {jQuery} rendered DOM
+ */
+ _render_visualizationsFrameworkButton : function( visualizations ){
+ if( !( this.model.hasData() )
+ || !( visualizations && !_.isEmpty( visualizations ) ) ){
+ this.visualizationsButton = null;
+ return null;
+ }
+
+ // render the icon from template
+ this.visualizationsButton = new IconButtonView({ model : new IconButton({
+ title : _l( 'Visualize' ),
+ icon_class : 'chart_curve'
+ })});
+ var $icon = this.visualizationsButton.render().$el;
+ $icon.addClass( 'visualize-icon' ); // needed?
+
+ // No need for popup menu because there's a single visualization.
+ if( _.keys( visualizations ).length === 1 ) {
+ $icon.attr( 'title', _.keys( visualizations )[0] );
+ $icon.attr( 'href', _.values( visualizations )[0] );
+
+ // >1: Populate menu dict with visualization fns, make the popupmenu
+ } else {
+ var popup_menu_options = [];
+ _.each( visualizations, function( linkData ) {
+ popup_menu_options.push( linkData );
+ });
+ var popup = new PopupMenu( $icon, popup_menu_options );
+ }
+ return $icon;
+ },
// ......................................................................... secondary actions
/** Render secondary actions: currently tagging and annotation (if user is allowed).
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d 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 @@
-var HDAEditView=HDABaseView.extend(LoggableMixin).extend({initialize:function(a){HDABaseView.prototype.initialize.call(this,a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(c){HDABaseView.prototype._setUpBehaviors.call(this,c);var a=this,b=this.urls.purge,d=c.find("#historyItemPurger-"+this.model.get("id"));if(d){d.attr("href",["javascript","void(0)"].join(":"));d.click(function(e){var f=jQuery.ajax(b);f.success(function(i,g,h){a.model.set("purged",true);a.trigger("purged",a)});f.error(function(h,g,i){a.trigger("error",_l("Unable to purge this dataset"),h,g,i)})})}},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var a=$('<div class="historyItemButtons"></div>');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(a){b.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var a=this,b=a.urls["delete"],c={title:_l("Delete"),href:b,id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete",on_click:function(){$.ajax({url:b,type:"POST",error:function(){a.$el.show()},success:function(){a.model.set({deleted:true})}})}};if(this.model.get("deleted")||this.model.get("purged")){c={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(c)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDABaseView.templates.hdaSummary(a)},_render_errButton:function(){if(this.model.get("state")!==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 c=this.model.get("dbkey"),a=this.model.get("visualizations"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(c){g.dbkey=c}if(!(this.model.hasData())||!(a&&a.length)||!(f)){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:f,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){window.parent.location=f+"/"+h+"?"+$.param(g)}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[_l(h)]=e(i)});make_popupmenu(b,d)}return b},_render_secondaryActionButtons:function(b){var c=$("<div/>"),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||(!this.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.model.hasData())||(!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_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAEditView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAEditView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_body_error:function(a){HDABaseView.prototype._render_body_error.call(this,a);var b=a.find("#primary-actions-"+this.model.get("id"));b.prepend(this._render_errButton())},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('<div class="clear"/>');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayAppArea());this._render_displayApps(a);a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var a=this,d=this.$el.find(".tag-area"),b=d.find(".tag-elt");if(d.is(":hidden")){if(!jQuery.trim(b.html())){$.ajax({url:this.urls.tags.get,error:function(g,e,f){a.log("Tagging failed",g,e,f);a.trigger("error",_l("Tagging failed"),g,e,f)},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){view.log("Annotation failed",xhr,status,error);view.trigger("error",_l("Annotation failed"),xhr,status,error)},success:function(e){if(e===""){e="<em>"+_l("Describe or add notes to dataset")+"</em>"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAEditView.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function create_scatterplot_action_fn(a,b){action=function(){var d=$(window.parent.document).find("iframe#galaxy_main"),c=a+"/scatterplot?"+$.param(b);d.attr("src",c);$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d["f-dbkey"]=b}$.ajax({url:a+"/list_tracks?"+$.param(d),dataType:"html",error:function(){alert(_l("Could not add this dataset to browser")+".")},success:function(e){var f=window.parent;f.show_modal(_l("View Data in a New or Saved Visualization"),"",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal(_l("Add Data to Saved Visualization"),e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.location=a+"/trackster?"+$.param(c)})}})},"View in new visualization":function(){f.location=a+"/trackster?"+$.param(c)}})}});return false}};
\ No newline at end of file
+var HDAEditView=HDABaseView.extend(LoggableMixin).extend({initialize:function(a){HDABaseView.prototype.initialize.call(this,a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(c){HDABaseView.prototype._setUpBehaviors.call(this,c);var a=this,b=this.urls.purge,d=c.find("#historyItemPurger-"+this.model.get("id"));if(d){d.attr("href",["javascript","void(0)"].join(":"));d.click(function(e){var f=jQuery.ajax(b);f.success(function(i,g,h){a.model.set("purged",true);a.trigger("purged",a)});f.error(function(h,g,i){a.trigger("error",_l("Unable to purge this dataset"),h,g,i)})})}},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var a=$('<div class="historyItemButtons"></div>');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(a){b.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var a=this,b=a.urls["delete"],c={title:_l("Delete"),href:b,id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete",on_click:function(){$.ajax({url:b,type:"POST",error:function(){a.$el.show()},success:function(){a.model.set({deleted:true})}})}};if(this.model.get("deleted")||this.model.get("purged")){c={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(c)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDABaseView.templates.hdaSummary(a)},_render_errButton:function(){if(this.model.get("state")!==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 a=this.model.get("visualizations");if((!this.model.hasData())||(_.isEmpty(a))){this.visualizationsButton=null;return null}if(_.isObject(a[0])){return this._render_visualizationsFrameworkButton(a)}if(!this.urls.visualization){this.visualizationsButton=null;return null}var c=this.model.get("dbkey"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(c){g.dbkey=c}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:this.urls.visualization,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){window.parent.location=f+"/"+h+"?"+$.param(g)}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[_l(h)]=e(i)});make_popupmenu(b,d)}return b},_render_visualizationsFrameworkButton:function(a){if(!(this.model.hasData())||!(a&&!_.isEmpty(a))){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),icon_class:"chart_curve"})});var c=this.visualizationsButton.render().$el;c.addClass("visualize-icon");if(_.keys(a).length===1){c.attr("title",_.keys(a)[0]);c.attr("href",_.values(a)[0])}else{var d=[];_.each(a,function(e){d.push(e)});var b=new PopupMenu(c,d)}return c},_render_secondaryActionButtons:function(b){var c=$("<div/>"),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||(!this.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.model.hasData())||(!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_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAEditView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAEditView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_body_error:function(a){HDABaseView.prototype._render_body_error.call(this,a);var b=a.find("#primary-actions-"+this.model.get("id"));b.prepend(this._render_errButton())},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('<div class="clear"/>');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayAppArea());this._render_displayApps(a);a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var a=this,d=this.$el.find(".tag-area"),b=d.find(".tag-elt");if(d.is(":hidden")){if(!jQuery.trim(b.html())){$.ajax({url:this.urls.tags.get,error:function(g,e,f){a.log("Tagging failed",g,e,f);a.trigger("error",_l("Tagging failed"),g,e,f)},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){view.log("Annotation failed",xhr,status,error);view.trigger("error",_l("Annotation failed"),xhr,status,error)},success:function(e){if(e===""){e="<em>"+_l("Describe or add notes to dataset")+"</em>"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAEditView.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function create_scatterplot_action_fn(a,b){action=function(){var d=$(window.parent.document).find("iframe#galaxy_main"),c=a+"/scatterplot?"+$.param(b);d.attr("src",c);$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d["f-dbkey"]=b}$.ajax({url:a+"/list_tracks?"+$.param(d),dataType:"html",error:function(){alert(_l("Could not add this dataset to browser")+".")},success:function(e){var f=window.parent;f.show_modal(_l("View Data in a New or Saved Visualization"),"",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal(_l("Add Data to Saved Visualization"),e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.location=a+"/trackster?"+$.param(c)})}})},"View in new visualization":function(){f.location=a+"/trackster?"+$.param(c)}})}});return false}};
\ No newline at end of file
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d static/scripts/packed/utils/config.js
--- a/static/scripts/packed/utils/config.js
+++ b/static/scripts/packed/utils/config.js
@@ -1,1 +1,1 @@
-define(["libs/underscore","viz/trackster/util"],function(b,e){var c=Backbone.Model.extend({initialize:function(f){var g=this.get("key");this.set("id",g);var h=b.find(c.known_settings_defaults,function(i){return i.key===g});if(h){this.set(b.extend({},h,f))}if(this.get("type")==="color"&&!this.get("value")){this.set("value",e.get_random_color())}this.on("change:value",this.cast_value,this)},cast_value:function(){var f=this.get("type"),g=this.get("value");if(f==="float"){g=parseFloat(g)}else{if(f==="int"){g=parseInt(g,10)}}this.set("value")}},{known_settings_defaults:[{key:"name",label:"Name",type:"text",default_value:""},{key:"color",label:"Color",type:"color",default_value:null},{key:"min_value",label:"Min Value",type:"float",default_value:null},{key:"max_value",label:"Max Value",type:"float",default_value:null},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:32,hidden:true},{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"block_color",label:"Block color",type:"color",default_value:null},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:null},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"Clear value to set automatically"},{key:"mode",type:"string",default_value:this.mode,hidden:true}]});var d=Backbone.Collection.extend({model:c,to_key_value_dict:function(){var f={};this.each(function(g){f[g.get("key")]=g.get("value")});return f},get_value:function(f){var g=this.get(f);if(g){return g.get("value")}return undefined}},{from_config_dict:function(g){var f=b.map(b.keys(g),function(h){return{key:h,value:g[h]}});return new d(f)}});var a=Backbone.View.extend({className:"config-settings-view",render:function(){var i=this.model;var f=this.$el;var h;function g(n,j){for(var r=0;r<n.length;r++){h=n[r];if(h.hidden){continue}var l="param_"+r;var v=i.values[h.key];var x=$("<div class='form-row' />").appendTo(j);x.append($("<label />").attr("for",l).text(h.label+":"));if(type==="bool"){x.append($('<input type="checkbox" />').attr("id",l).attr("name",l).attr("checked",v))}else{if(type==="text"){x.append($('<input type="text"/>').attr("id",l).val(v).click(function(){$(this).select()}))}else{if(type==="select"){var t=$("<select />").attr("id",l);for(var p=0;p<h.options.length;p++){$("<option/>").text(h.options[p].label).attr("value",h.options[p].value).appendTo(t)}t.val(v);x.append(t)}else{if(type==="color"){var w=$("<div/>").appendTo(x),s=$("<input />").attr("id",l).attr("name",l).val(v).css("float","left").appendTo(w).click(function(z){$(".bs-tooltip").removeClass("in");var y=$(this).siblings(".bs-tooltip").addClass("in");y.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top-($(y).height()/2)+($(this).height()/2)}).show();y.click(function(A){A.stopPropagation()});$(document).bind("click.color-picker",function(){y.hide();$(document).unbind("click.color-picker")});z.stopPropagation()}),q=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(w).attr("title","Set new random color").tooltip(),u=$("<div class='bs-tooltip right' style='position: absolute;' />").appendTo(w).hide(),m=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(u),k=$("<div class='tooltip-arrow'></div>").appendTo(u),o=$.farbtastic(m,{width:100,height:100,callback:s,color:v});w.append($("<div/>").css("clear","both"));(function(y){q.click(function(){y.setColor(e.get_random_color())})})(o)}else{x.append($("<input />").attr("id",l).attr("name",l).val(v))}}}}if(h.help){x.append($("<div class='help'/>").text(h.help))}}}g(this.params,f);return this},render_in_modal:function(){var h=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},f=function(){this.update_from_form();hide_modal();$(window).unbind("keypress.check_enter_esc")},g=function(i){if((i.keyCode||i.which)===27){h()}else{if((i.keyCode||i.which)===13){f()}}};$(window).bind("keypress.check_enter_esc",g);if(this.$el.children().length===0){this.render()}show_modal("Configure",drawable.config.build_form(),{Cancel:h,OK:f})},update_from_form:function(){var f=this;this.collection.each(function(h,g){if(!h.get("hidden")){var j="param_"+g;var i=f.$el.find("#"+j).val();if(type==="bool"){i=container.find("#"+j).is(":checked")}h.set("value",i)}})}});return{ConfigSettingCollection:d,ConfigSettingCollectionView:a}});
\ No newline at end of file
+define(["libs/underscore","viz/trackster/util"],function(b,e){var c=Backbone.Model.extend({initialize:function(f){var g=this.get("key");this.set("id",g);var h=b.find(c.known_settings_defaults,function(i){return i.key===g});if(h){this.set(b.extend({},h,f))}if(this.get("type")==="color"&&!this.get("value")){this.set("value",e.get_random_color())}this.on("change:value",this.cast_value,this)},cast_value:function(){var f=this.get("type"),g=this.get("value");if(f==="float"){g=parseFloat(g)}else{if(f==="int"){g=parseInt(g,10)}}this.set("value")}},{known_settings_defaults:[{key:"name",label:"Name",type:"text",default_value:""},{key:"color",label:"Color",type:"color",default_value:null},{key:"min_value",label:"Min Value",type:"float",default_value:null},{key:"max_value",label:"Max Value",type:"float",default_value:null},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:32,hidden:true},{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"block_color",label:"Block color",type:"color",default_value:null},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:null},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true}]});var d=Backbone.Collection.extend({model:c,to_key_value_dict:function(){var f={};this.each(function(g){f[g.get("key")]=g.get("value")});return f},get_value:function(f){var g=this.get(f);if(g){return g.get("value")}return undefined}},{from_config_dict:function(g){var f=b.map(b.keys(g),function(h){return{key:h,value:g[h]}});return new d(f)}});var a=Backbone.View.extend({className:"config-settings-view",render:function(){var i=this.model;var f=this.$el;var h;function g(n,j){for(var r=0;r<n.length;r++){h=n[r];if(h.hidden){continue}var l="param_"+r;var v=i.values[h.key];var x=$("<div class='form-row' />").appendTo(j);x.append($("<label />").attr("for",l).text(h.label+":"));if(type==="bool"){x.append($('<input type="checkbox" />').attr("id",l).attr("name",l).attr("checked",v))}else{if(type==="text"){x.append($('<input type="text"/>').attr("id",l).val(v).click(function(){$(this).select()}))}else{if(type==="select"){var t=$("<select />").attr("id",l);for(var p=0;p<h.options.length;p++){$("<option/>").text(h.options[p].label).attr("value",h.options[p].value).appendTo(t)}t.val(v);x.append(t)}else{if(type==="color"){var w=$("<div/>").appendTo(x),s=$("<input />").attr("id",l).attr("name",l).val(v).css("float","left").appendTo(w).click(function(z){$(".bs-tooltip").removeClass("in");var y=$(this).siblings(".bs-tooltip").addClass("in");y.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top-($(y).height()/2)+($(this).height()/2)}).show();y.click(function(A){A.stopPropagation()});$(document).bind("click.color-picker",function(){y.hide();$(document).unbind("click.color-picker")});z.stopPropagation()}),q=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(w).attr("title","Set new random color").tooltip(),u=$("<div class='bs-tooltip right' style='position: absolute;' />").appendTo(w).hide(),m=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(u),k=$("<div class='tooltip-arrow'></div>").appendTo(u),o=$.farbtastic(m,{width:100,height:100,callback:s,color:v});w.append($("<div/>").css("clear","both"));(function(y){q.click(function(){y.setColor(e.get_random_color())})})(o)}else{x.append($("<input />").attr("id",l).attr("name",l).val(v))}}}}if(h.help){x.append($("<div class='help'/>").text(h.help))}}}g(this.params,f);return this},render_in_modal:function(){var h=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},f=function(){this.update_from_form();hide_modal();$(window).unbind("keypress.check_enter_esc")},g=function(i){if((i.keyCode||i.which)===27){h()}else{if((i.keyCode||i.which)===13){f()}}};$(window).bind("keypress.check_enter_esc",g);if(this.$el.children().length===0){this.render()}show_modal("Configure",drawable.config.build_form(),{Cancel:h,OK:f})},update_from_form:function(){var f=this;this.collection.each(function(h,g){if(!h.get("hidden")){var j="param_"+g;var i=f.$el.find("#"+j).val();if(type==="bool"){i=container.find("#"+j).is(":checked")}h.set("value",i)}})}});return{ConfigSettingCollection:d,ConfigSettingCollectionView:a}});
\ No newline at end of file
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d static/scripts/packed/viz/trackster/painters.js
--- a/static/scripts/packed/viz/trackster/painters.js
+++ b/static/scripts/packed/viz/trackster/painters.js
@@ -1,1 +1,1 @@
-define(["libs/underscore"],function(_){var extend=_.extend;var BEFORE=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005,AFTER=1006;var compute_overlap=function(first_region,second_region){var first_start=first_region[0],first_end=first_region[1],second_start=second_region[0],second_end=second_region[1],overlap;if(first_start<second_start){if(first_end<=second_start){overlap=BEFORE}else{if(first_end<=second_end){overlap=OVERLAP_START}else{overlap=CONTAINS}}}else{if(first_start>second_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q<dashes;q++,x1+=dashX,y1+=dashY){if(q%2!==0){continue}ctx.fillRect(x1,y1,dashLen,1)}};var drawDownwardEquilateralTriangle=function(ctx,down_vertex_x,down_vertex_y,side_len){var x1=down_vertex_x-side_len/2,x2=down_vertex_x+side_len/2,y=down_vertex_y-Math.sqrt(side_len*3/2);ctx.beginPath();ctx.moveTo(x1,y);ctx.lineTo(x2,y);ctx.lineTo(down_vertex_x,down_vertex_y);ctx.lineTo(x1,y);ctx.strokeStyle=this.fillStyle;ctx.fill();ctx.stroke();ctx.closePath()};var Scaler=function(default_val){this.default_val=(default_val?default_val:1)};Scaler.prototype.gen_val=function(input){return this.default_val};var Painter=function(data,view_start,view_end,prefs,mode){this.data=data;this.view_start=view_start;this.view_end=view_end;this.prefs=extend({},this.default_prefs,prefs);this.mode=mode};Painter.prototype.default_prefs={};Painter.prototype.draw=function(ctx,width,height,w_scale){};var SummaryTreePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode)};SummaryTreePainter.prototype.default_prefs={show_counts:false};SummaryTreePainter.prototype.draw=function(ctx,width,height,w_scale){var view_start=this.view_start,points=this.data.data,max=(this.prefs.histogram_max?this.prefs.histogram_max:this.data.max),base_y=height,delta_x_px=Math.ceil(this.data.delta*w_scale);ctx.save();for(var i=0,len=points.length;i<len;i++){var x=Math.floor((points[i][0]-view_start)*w_scale);var y=points[i][1];if(!y){continue}var y_px=y/max*height;if(y!==0&&y_px<1){y_px=1}ctx.fillStyle=this.prefs.block_color;ctx.fillRect(x,base_y-y_px,delta_x_px,y_px);var text_padding_req_x=4;if(this.prefs.show_counts&&(ctx.measureText(y).width+text_padding_req_x)<delta_x_px){ctx.fillStyle=this.prefs.label_color;ctx.textAlign="center";ctx.fillText(y,x+(delta_x_px/2),10)}}ctx.restore()};var LinePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][1])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][1])}this.prefs.max_value=max_value}};LinePainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Histogram",color:"#000",overflow_color:"#F66"};LinePainter.prototype.draw=function(ctx,width,height,w_scale){var in_path=false,min_value=this.prefs.min_value,max_value=this.prefs.max_value,vertical_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data;ctx.save();var y_zero=Math.round(height+min_value/vertical_range*height);if(mode!=="Intensity"){ctx.fillStyle="#aaa";ctx.fillRect(0,y_zero,width,1)}ctx.beginPath();var x_scaled,y,delta_x_px;if(data.length>1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var pref_color=parseInt(this.prefs.color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;i<len;i++){ctx.fillStyle=ctx.strokeStyle=this.prefs.color;x_scaled=Math.round((data[i][0]-view_start-0.5)*w_scale);y=data[i][1];var top_overflow=false,bot_overflow=false;if(y===null){if(in_path&&mode==="Filled"){ctx.lineTo(x_scaled,height_px)}in_path=false;continue}if(y<min_value){bot_overflow=true;y=min_value}else{if(y>max_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=this.prefs.color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i<this.feature_positions[slot].length;i++){feature_dict=this.feature_positions[slot][i];if(x>=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;i<len;i++){var feature=data[i],feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],slot=(slots&&slots[feature_uid]!==undefined?slots[feature_uid]:null);if((feature_start<view_end&&feature_end>view_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;k<k_len;k++){var block=feature_blocks[k],block_start=Math.floor(Math.max(0,(block[0]-tile_low-0.5)*w_scale)),block_end=Math.ceil(Math.min(width,Math.max((block[1]-tile_low-0.5)*w_scale))),last_block_start,last_block_end;if(block_start>block_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_end<thick_start)){var block_thick_start=Math.max(block_start,thick_start),block_thick_end=Math.min(block_end,thick_end);ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height);if(feature_blocks.length===1&&mode==="Pack"){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}if(block_thick_start+14<block_thick_end){block_thick_start+=2;block_thick_end-=2}ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height)}}if(this.draw_individual_connectors&&last_block_start){this.draw_connector(ctx,last_block_start,last_block_end,block_start,block_end,y_center)}last_block_start=block_start;last_block_end=block_end}if(mode==="Pack"){ctx.globalAlpha=1;ctx.fillStyle="white";var hscale_factor=this.height_scaler.gen_val(feature),new_height=Math.ceil(thick_height*hscale_factor),ws_height=Math.round((thick_height-new_height)/2);if(hscale_factor!==1){ctx.fillRect(f_start,cur_y_center+1,f_end-f_start,ws_height);ctx.fillRect(f_start,cur_y_center+thick_height-ws_height+1,f_end-f_start,ws_height)}}}ctx.globalAlpha=1;if(feature_name&&mode==="Pack"&&feature_start>tile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null);this.base_color_fn=base_color_fn};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},_parse_cigar:function(cigar_str){var cigar_ops="MIDNSHP=X";var blocks=[[0,0]],cur_block=blocks[0],base_pos=0,parsed_cigar=_.map(cigar_str.match(/[0-9]+[MIDNSHP=X]/g),function(op){var op_len=parseInt(op.slice(0,-1),10),op_char=op.slice(-1);if(op_char==="N"){if(cur_block[1]!==0){cur_block=[base_pos+op_len,base_pos+op_len];blocks.push(cur_block)}}else{if("ISHP".indexOf(op_char)===-1){cur_block[1]+=op_len;base_pos+=op_len}}return[cigar_ops.indexOf(op_char),op_len]});return{blocks:blocks,cigar:parsed_cigar}},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack");var draw_last=[];if(!cigar){cigar=[[0,read_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id<len;cig_id++){var cig=cigar[cig_id],cig_op="MIDNSHP=X"[cig[0]],cig_len=cig[1];var seq_start=feature_start+base_offset,s_start=Math.floor(Math.max(-0.5*w_scale,(seq_start-tile_low-0.5)*w_scale)),s_end=Math.floor(Math.max(0,(seq_start+cig_len-tile_low-0.5)*w_scale));if(s_start===s_end){s_end+=1}switch(cig_op){case"H":break;case"S":seq_offset+=cig_len;break;case"M":case"=":case"X":if(is_overlap([seq_start,seq_start+cig_len],tile_region)){ctx.fillStyle=block_color;ctx.fillRect(s_start,y_center+(pack_mode?1:4),s_end-s_start,(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT));var seq=read_seq.slice(seq_offset,seq_offset+cig_len),ref_char,read_char;for(var c=0,str_len=seq.length;c<str_len;c++){if(seq_start+c>=tile_low&&seq_start+c<=tile_high){ref_char=(this.ref_seq?this.ref_seq[seq_start-tile_low+c]:null);read_char=seq[c];if((ref_char&&(!this.prefs.show_differences||(read_char.toLowerCase!=="n"&&(ref_char.toLowerCase()!==read_char.toLowerCase()))))||(!ref_char&&!this.prefs.show_differences)){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;c<str_len;c++){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start-(s_end-s_start)/2,y_center)}}else{ctx.fillStyle="yellow";ctx.fillRect(x_center,y_center+(this.mode!=="Dense"?2:5),s_end-s_start,(mode!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i<draw_last.length;i++){item=draw_last[i];type=item.type;data=item.data;if(type==="text"){ctx.save();ctx.font="bold "+ctx.font;ctx.fillText(data[0],data[1],data[2]);ctx.restore()}else{if(type==="triangle"){drawDownwardEquilateralTriangle(ctx,data[0],data[1],data[2])}}}},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],f_start=Math.floor(Math.max(-0.5*w_scale,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),y_center=(mode==="Dense"?0:(0+slot))*y_scale,label_color=this.prefs.label_color;if(feature[5] instanceof Array){var b1_start=Math.floor(Math.max(0,(feature[4][0]-tile_low)*w_scale)),b1_end=Math.ceil(Math.min(width,Math.max(0,(feature[4][1]-tile_low)*w_scale))),b2_start=Math.floor(Math.max(0,(feature[5][0]-tile_low)*w_scale)),b2_end=Math.ceil(Math.min(width,Math.max(0,(feature[5][1]-tile_low)*w_scale))),connector=true;if(feature[4][1]>=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end,y_center+5,b2_start,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>=tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8)}}return[0,0]}});var RefBasedReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){ReadPainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn)};extend(RefBasedReadPainter.prototype,ReadPainter.prototype,FeaturePainter,{draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack"),drawing_blocks=[];var draw_last=[];var t=this._parse_cigar(cigar);cigar=t.cigar;drawing_blocks=t.blocks;for(var i=0;i<drawing_blocks.length;i++){var block=drawing_blocks[i];if(is_overlap([feature_start+block[0],feature_start+block[1]],tile_region)){var s_start=Math.floor(Math.max(-0.5*w_scale,(feature_start+block[0]-tile_low-0.5)*w_scale)),s_end=Math.floor(Math.max(0,(feature_start+block[1]-tile_low-0.5)*w_scale));if(s_start===s_end){s_end+=1}ctx.fillStyle=block_color;ctx.fillRect(s_start,y_center+(pack_mode?1:4),s_end-s_start,(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}for(var cig_id=0,len=cigar.length;cig_id<len;cig_id++){var cig=cigar[cig_id],cig_op="MIDNSHP=X"[cig[0]],cig_len=cig[1];var seq_start=feature_start+base_offset,s_start=Math.floor(Math.max(0,-0.5*w_scale,(seq_start-tile_low-0.5)*w_scale)),s_end=Math.floor(Math.max(0,(seq_start+cig_len-tile_low-0.5)*w_scale));if(s_start===s_end){s_end+=1}switch(cig_op){case"H":case"S":case"P":break;case"M":base_offset+=cig_len;break;case"=":case"X":if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var cur_seq="";if(cig_op==="X"){cur_seq=read_seq.slice(seq_offset,seq_offset+cig_len)}else{if(this.ref_seq){cur_seq=this.ref_seq.slice(Math.max(0,seq_start-tile_low),Math.min(seq_start-tile_low+cig_len,tile_high-tile_low))}}var start_pos=Math.max(seq_start,tile_low);for(var c=0;c<cur_seq.length;c++){if(cur_seq&&!this.prefs.show_differences||cig_op==="X"){var c_start=Math.floor(Math.max(0,(start_pos+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(cur_seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(cur_seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}if(cig_op==="X"){seq_offset+=cig_len}base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;c<str_len;c++){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start-(s_end-s_start)/2,y_center)}}else{ctx.fillStyle="yellow";ctx.fillRect(x_center,y_center+(this.mode!=="Dense"?2:5),s_end-s_start,(mode!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i<draw_last.length;i++){item=draw_last[i];type=item.type;data=item.data;if(type==="text"){ctx.save();ctx.font="bold "+ctx.font;ctx.fillText(data[0],data[1],data[2]);ctx.restore()}else{if(type==="triangle"){drawDownwardEquilateralTriangle(ctx,data[0],data[1],data[2])}}}}});var ArcLinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){LinkedFeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.longest_feature_length=this.calculate_longest_feature_length();this.draw_background_connector=false;this.draw_individual_connectors=true};extend(ArcLinkedFeaturePainter.prototype,FeaturePainter.prototype,LinkedFeaturePainter.prototype,{calculate_longest_feature_length:function(){var longest_feature_length=0;for(var i=0,len=this.data.length;i<len;i++){var feature=this.data[i],feature_start=feature[1],feature_end=feature[2];longest_feature_length=Math.max(longest_feature_length,feature_end-feature_start)}return longest_feature_length},get_top_padding:function(width){var view_range=this.view_end-this.view_start,w_scale=width/view_range;return Math.min(128,Math.ceil((this.longest_feature_length/2)*w_scale))},draw_connector:function(ctx,block1_start,block1_end,block2_start,block2_end,y_center){var x_center=(block1_end+block2_start)/2,radius=block2_start-x_center;var angle1=Math.PI,angle2=0;if(radius>0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{if(rgb.length==7){this.rgb=rgb.substring(1,7).match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g<b?6:0);break;case g:h=(b-r)/d+2;break;case b:h=(r-g)/d+4;break}h/=6}return{h:h*360,s:s,l:l,a:a}},toARGB:function(){var argb=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+argb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][5])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][5])}this.prefs.max_value=max_value}};DiagonalHeatmapPainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Heatmap",pos_color:"#FF8C00",neg_color:"#4169E1"};DiagonalHeatmapPainter.prototype.draw=function(ctx,width,height,w_scale){var min_value=this.prefs.min_value,max_value=this.prefs.max_value,value_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data,invsqrt2=1/Math.sqrt(2);var ramp=(new SplitRamp(this.prefs.neg_color,"#FFFFFF",this.prefs.pos_color,min_value,max_value));var d,s1,e1,s2,e2,value;var scale=function(p){return(p-view_start)*w_scale};ctx.save();ctx.rotate(-45*Math.PI/180);ctx.scale(invsqrt2,invsqrt2);for(var i=0,len=data.length;i<len;i++){d=data[i];s1=scale(d[1]);e1=scale(d[2]);s2=scale(d[4]);e2=scale(d[5]);value=d[6];ctx.fillStyle=(ramp.map_value(value));ctx.fillRect(s1,s2,(e1-s1),(e2-s2))}ctx.restore()};var VariantPainter=function(data,view_start,view_end,prefs,mode,base_color_fn){Painter.call(this,data,view_start,view_end,prefs,mode);this.base_color_fn=base_color_fn;this.divider_height=1};extend(VariantPainter.prototype,Painter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}return height},get_required_height:function(num_samples){var height=this.prefs.summary_height;if(this.prefs.show_sample_data){height+=this.divider_height+num_samples*this.get_row_height()}return height},draw:function(ctx,width,height,w_scale){var locus_data,pos,id,ref,alt,qual,filter,sample_gts,allele_counts,variant,draw_x_start,char_x_start,draw_y_start,genotype,base_px=Math.max(1,Math.floor(w_scale)),row_height=(this.mode==="Squish"?SQUISH_TRACK_HEIGHT:PACK_TRACK_HEIGHT),feature_height=(w_scale<0.1?row_height:(this.mode==="Squish"?SQUISH_FEATURE_HEIGHT:PACK_FEATURE_HEIGHT)),j;if(this.prefs.show_sample_data){ctx.fillStyle="#F3F3F3";ctx.globalAlpha=1;ctx.fillRect(0,this.prefs.summary_height-this.divider_height,width,this.divider_height)}ctx.textAlign="center";for(var i=0;i<this.data.length;i++){locus_data=this.data[i];pos=locus_data[1];alt=locus_data[4].split(",");sample_gts=locus_data[7].split(",");allele_counts=locus_data.slice(8);draw_x_start=Math.floor(Math.max(-0.5*w_scale,(pos-this.view_start-0.5)*w_scale));char_x_start=Math.floor(Math.max(0,(pos-this.view_start)*w_scale));ctx.fillStyle="#999999";ctx.fillRect(draw_x_start,0,base_px,this.prefs.summary_height);draw_y_start=this.prefs.summary_height;for(j=0;j<alt.length;j++){ctx.fillStyle=this.base_color_fn(alt[j]);allele_frac=allele_counts/sample_gts.length;draw_height=Math.ceil(this.prefs.summary_height*allele_frac);ctx.fillRect(draw_x_start,draw_y_start-draw_height,base_px,draw_height);draw_y_start-=draw_height}if(!this.prefs.show_sample_data){continue}draw_y_start=this.prefs.summary_height+this.divider_height;for(j=0;j<sample_gts.length;j++,draw_y_start+=row_height){genotype=(sample_gts[j]?sample_gts[j].split(/\/|\|/):["0","0"]);variant=null;if(genotype[0]===genotype[1]){if(genotype[0]==="."){}else{if(genotype[0]!=="0"){variant=alt[parseInt(genotype[0],10)-1];ctx.globalAlpha=1}}}else{variant=(genotype[0]!=="0"?genotype[0]:genotype[1]);variant=alt[parseInt(variant,10)-1];ctx.globalAlpha=0.4}if(variant){ctx.fillStyle=this.base_color_fn(variant);if(this.mode==="Squish"||w_scale<ctx.canvas.manager.char_width_px){ctx.fillRect(draw_x_start,draw_y_start+1,base_px,feature_height)}else{ctx.fillText(variant,char_x_start,draw_y_start+row_height)}}}}}});return{Scaler:Scaler,SummaryTreePainter:SummaryTreePainter,LinePainter:LinePainter,LinkedFeaturePainter:LinkedFeaturePainter,ReadPainter:ReadPainter,RefBasedReadPainter:RefBasedReadPainter,ArcLinkedFeaturePainter:ArcLinkedFeaturePainter,DiagonalHeatmapPainter:DiagonalHeatmapPainter,VariantPainter:VariantPainter}});
\ No newline at end of file
+define(["libs/underscore"],function(_){var extend=_.extend;var BEFORE=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005,AFTER=1006;var compute_overlap=function(first_region,second_region){var first_start=first_region[0],first_end=first_region[1],second_start=second_region[0],second_end=second_region[1],overlap;if(first_start<second_start){if(first_end<=second_start){overlap=BEFORE}else{if(first_end<=second_end){overlap=OVERLAP_START}else{overlap=CONTAINS}}}else{if(first_start>second_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q<dashes;q++,x1+=dashX,y1+=dashY){if(q%2!==0){continue}ctx.fillRect(x1,y1,dashLen,1)}};var drawDownwardEquilateralTriangle=function(ctx,down_vertex_x,down_vertex_y,side_len){var x1=down_vertex_x-side_len/2,x2=down_vertex_x+side_len/2,y=down_vertex_y-Math.sqrt(side_len*3/2);ctx.beginPath();ctx.moveTo(x1,y);ctx.lineTo(x2,y);ctx.lineTo(down_vertex_x,down_vertex_y);ctx.lineTo(x1,y);ctx.strokeStyle=this.fillStyle;ctx.fill();ctx.stroke();ctx.closePath()};var Scaler=function(default_val){this.default_val=(default_val?default_val:1)};Scaler.prototype.gen_val=function(input){return this.default_val};var Painter=function(data,view_start,view_end,prefs,mode){this.data=data;this.view_start=view_start;this.view_end=view_end;this.prefs=extend({},this.default_prefs,prefs);this.mode=mode};Painter.prototype.default_prefs={};Painter.prototype.draw=function(ctx,width,height,w_scale){};var LinePainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);if(this.prefs.min_value===undefined){this.prefs.min_value=_.min(_.map(this.data,function(d){return d[1]}))||0}if(this.prefs.max_value===undefined){this.prefs.max_value=_.max(_.map(this.data,function(d){return d[1]}))||0}};LinePainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Histogram",color:"#000",overflow_color:"#F66"};LinePainter.prototype.draw=function(ctx,width,height,w_scale){var in_path=false,min_value=this.prefs.min_value,max_value=this.prefs.max_value,vertical_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data;ctx.save();var y_zero=Math.round(height+min_value/vertical_range*height);if(mode!=="Intensity"){ctx.fillStyle="#aaa";ctx.fillRect(0,y_zero,width,1)}ctx.beginPath();var x_scaled,y,delta_x_px;if(data.length>1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var painter_color=this.prefs.block_color||this.prefs.color,pref_color=parseInt(painter_color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;i<len;i++){ctx.fillStyle=ctx.strokeStyle=painter_color;x_scaled=Math.round((data[i][0]-view_start-0.5)*w_scale);y=data[i][1];var top_overflow=false,bot_overflow=false;if(y===null){if(in_path&&mode==="Filled"){ctx.lineTo(x_scaled,height_px)}in_path=false;continue}if(y<min_value){bot_overflow=true;y=min_value}else{if(y>max_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=painter_color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i<this.feature_positions[slot].length;i++){feature_dict=this.feature_positions[slot][i];if(x>=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;i<len;i++){var feature=data[i],feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],slot=(slots&&slots[feature_uid]!==undefined?slots[feature_uid]:null);if((feature_start<view_end&&feature_end>view_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;k<k_len;k++){var block=feature_blocks[k],block_start=Math.floor(Math.max(0,(block[0]-tile_low-0.5)*w_scale)),block_end=Math.ceil(Math.min(width,Math.max((block[1]-tile_low-0.5)*w_scale))),last_block_start,last_block_end;if(block_start>block_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_end<thick_start)){var block_thick_start=Math.max(block_start,thick_start),block_thick_end=Math.min(block_end,thick_end);ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height);if(feature_blocks.length===1&&mode==="Pack"){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}if(block_thick_start+14<block_thick_end){block_thick_start+=2;block_thick_end-=2}ctx.fillRect(block_thick_start,y_center+1,block_thick_end-block_thick_start,thick_height)}}if(this.draw_individual_connectors&&last_block_start){this.draw_connector(ctx,last_block_start,last_block_end,block_start,block_end,y_center)}last_block_start=block_start;last_block_end=block_end}if(mode==="Pack"){ctx.globalAlpha=1;ctx.fillStyle="white";var hscale_factor=this.height_scaler.gen_val(feature),new_height=Math.ceil(thick_height*hscale_factor),ws_height=Math.round((thick_height-new_height)/2);if(hscale_factor!==1){ctx.fillRect(f_start,cur_y_center+1,f_end-f_start,ws_height);ctx.fillRect(f_start,cur_y_center+thick_height-ws_height+1,f_end-f_start,ws_height)}}}ctx.globalAlpha=1;if(feature_name&&mode==="Pack"&&feature_start>tile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null);this.base_color_fn=base_color_fn};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},_parse_cigar:function(cigar_str){var cigar_ops="MIDNSHP=X";var blocks=[[0,0]],cur_block=blocks[0],base_pos=0,parsed_cigar=_.map(cigar_str.match(/[0-9]+[MIDNSHP=X]/g),function(op){var op_len=parseInt(op.slice(0,-1),10),op_char=op.slice(-1);if(op_char==="N"){if(cur_block[1]!==0){cur_block=[base_pos+op_len,base_pos+op_len];blocks.push(cur_block)}}else{if("ISHP".indexOf(op_char)===-1){cur_block[1]+=op_len;base_pos+=op_len}}return[cigar_ops.indexOf(op_char),op_len]});return{blocks:blocks,cigar:parsed_cigar}},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack");var draw_last=[];if(!cigar){cigar=[[0,read_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id<len;cig_id++){var cig=cigar[cig_id],cig_op="MIDNSHP=X"[cig[0]],cig_len=cig[1];var seq_start=feature_start+base_offset,s_start=Math.floor(Math.max(-0.5*w_scale,(seq_start-tile_low-0.5)*w_scale)),s_end=Math.floor(Math.max(0,(seq_start+cig_len-tile_low-0.5)*w_scale));if(s_start===s_end){s_end+=1}switch(cig_op){case"H":break;case"S":seq_offset+=cig_len;break;case"M":case"=":case"X":if(is_overlap([seq_start,seq_start+cig_len],tile_region)){ctx.fillStyle=block_color;ctx.fillRect(s_start,y_center+(pack_mode?1:4),s_end-s_start,(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT));var seq=read_seq.slice(seq_offset,seq_offset+cig_len),ref_char,read_char;for(var c=0,str_len=seq.length;c<str_len;c++){if(seq_start+c>=tile_low&&seq_start+c<=tile_high){ref_char=(this.ref_seq?this.ref_seq[seq_start-tile_low+c]:null);read_char=seq[c];if((ref_char&&(!this.prefs.show_differences||(read_char.toLowerCase!=="n"&&(ref_char.toLowerCase()!==read_char.toLowerCase()))))||(!ref_char&&!this.prefs.show_differences)){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;c<str_len;c++){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start-(s_end-s_start)/2,y_center)}}else{ctx.fillStyle="yellow";ctx.fillRect(x_center,y_center+(this.mode!=="Dense"?2:5),s_end-s_start,(mode!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i<draw_last.length;i++){item=draw_last[i];type=item.type;data=item.data;if(type==="text"){ctx.save();ctx.font="bold "+ctx.font;ctx.fillText(data[0],data[1],data[2]);ctx.restore()}else{if(type==="triangle"){drawDownwardEquilateralTriangle(ctx,data[0],data[1],data[2])}}}},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],f_start=Math.floor(Math.max(-0.5*w_scale,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),y_center=(mode==="Dense"?0:(0+slot))*y_scale,label_color=this.prefs.label_color;if(feature[5] instanceof Array){var b1_start=Math.floor(Math.max(0,(feature[4][0]-tile_low)*w_scale)),b1_end=Math.ceil(Math.min(width,Math.max(0,(feature[4][1]-tile_low)*w_scale))),b2_start=Math.floor(Math.max(0,(feature[5][0]-tile_low)*w_scale)),b2_end=Math.ceil(Math.min(width,Math.max(0,(feature[5][1]-tile_low)*w_scale))),connector=true;if(feature[4][1]>=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end,y_center+5,b2_start,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>=tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8)}}return[0,0]}});var RefBasedReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){ReadPainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn)};extend(RefBasedReadPainter.prototype,ReadPainter.prototype,FeaturePainter,{draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack"),drawing_blocks=[];var draw_last=[];var t=this._parse_cigar(cigar);cigar=t.cigar;drawing_blocks=t.blocks;for(var i=0;i<drawing_blocks.length;i++){var block=drawing_blocks[i];if(is_overlap([feature_start+block[0],feature_start+block[1]],tile_region)){var s_start=Math.floor(Math.max(-0.5*w_scale,(feature_start+block[0]-tile_low-0.5)*w_scale)),s_end=Math.floor(Math.max(0,(feature_start+block[1]-tile_low-0.5)*w_scale));if(s_start===s_end){s_end+=1}ctx.fillStyle=block_color;ctx.fillRect(s_start,y_center+(pack_mode?1:4),s_end-s_start,(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}for(var cig_id=0,len=cigar.length;cig_id<len;cig_id++){var cig=cigar[cig_id],cig_op="MIDNSHP=X"[cig[0]],cig_len=cig[1];var seq_start=feature_start+base_offset,s_start=Math.floor(Math.max(0,-0.5*w_scale,(seq_start-tile_low-0.5)*w_scale)),s_end=Math.floor(Math.max(0,(seq_start+cig_len-tile_low-0.5)*w_scale));if(s_start===s_end){s_end+=1}switch(cig_op){case"H":case"S":case"P":break;case"M":base_offset+=cig_len;break;case"=":case"X":if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var cur_seq="";if(cig_op==="X"){cur_seq=read_seq.slice(seq_offset,seq_offset+cig_len)}else{if(this.ref_seq){cur_seq=this.ref_seq.slice(Math.max(0,seq_start-tile_low),Math.min(seq_start-tile_low+cig_len,tile_high-tile_low))}}var start_pos=Math.max(seq_start,tile_low);for(var c=0;c<cur_seq.length;c++){if(cur_seq&&!this.prefs.show_differences||cig_op==="X"){var c_start=Math.floor(Math.max(0,(start_pos+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(cur_seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(cur_seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}if(cig_op==="X"){seq_offset+=cig_len}base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;c<str_len;c++){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillText(seq[c],c_start-(s_end-s_start)/2,y_center)}}else{ctx.fillStyle="yellow";ctx.fillRect(x_center,y_center+(this.mode!=="Dense"?2:5),s_end-s_start,(mode!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i<draw_last.length;i++){item=draw_last[i];type=item.type;data=item.data;if(type==="text"){ctx.save();ctx.font="bold "+ctx.font;ctx.fillText(data[0],data[1],data[2]);ctx.restore()}else{if(type==="triangle"){drawDownwardEquilateralTriangle(ctx,data[0],data[1],data[2])}}}}});var ArcLinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){LinkedFeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.longest_feature_length=this.calculate_longest_feature_length();this.draw_background_connector=false;this.draw_individual_connectors=true};extend(ArcLinkedFeaturePainter.prototype,FeaturePainter.prototype,LinkedFeaturePainter.prototype,{calculate_longest_feature_length:function(){var longest_feature_length=0;for(var i=0,len=this.data.length;i<len;i++){var feature=this.data[i],feature_start=feature[1],feature_end=feature[2];longest_feature_length=Math.max(longest_feature_length,feature_end-feature_start)}return longest_feature_length},get_top_padding:function(width){var view_range=this.view_end-this.view_start,w_scale=width/view_range;return Math.min(128,Math.ceil((this.longest_feature_length/2)*w_scale))},draw_connector:function(ctx,block1_start,block1_end,block2_start,block2_end,y_center){var x_center=(block1_end+block2_start)/2,radius=block2_start-x_center;var angle1=Math.PI,angle2=0;if(radius>0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{if(rgb.length==7){this.rgb=rgb.substring(1,7).match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g<b?6:0);break;case g:h=(b-r)/d+2;break;case b:h=(r-g)/d+4;break}h/=6}return{h:h*360,s:s,l:l,a:a}},toARGB:function(){var argb=[Math.round(this.alpha*255)].concat(this.rgb);return"#"+argb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i<len;i++){min_value=Math.min(min_value,this.data[i][5])}this.prefs.min_value=min_value}if(this.prefs.max_value===undefined){var max_value=-Infinity;for(i=0,len=this.data.length;i<len;i++){max_value=Math.max(max_value,this.data[i][5])}this.prefs.max_value=max_value}};DiagonalHeatmapPainter.prototype.default_prefs={min_value:undefined,max_value:undefined,mode:"Heatmap",pos_color:"#FF8C00",neg_color:"#4169E1"};DiagonalHeatmapPainter.prototype.draw=function(ctx,width,height,w_scale){var min_value=this.prefs.min_value,max_value=this.prefs.max_value,value_range=max_value-min_value,height_px=height,view_start=this.view_start,mode=this.mode,data=this.data,invsqrt2=1/Math.sqrt(2);var ramp=(new SplitRamp(this.prefs.neg_color,"#FFFFFF",this.prefs.pos_color,min_value,max_value));var d,s1,e1,s2,e2,value;var scale=function(p){return(p-view_start)*w_scale};ctx.save();ctx.rotate(-45*Math.PI/180);ctx.scale(invsqrt2,invsqrt2);for(var i=0,len=data.length;i<len;i++){d=data[i];s1=scale(d[1]);e1=scale(d[2]);s2=scale(d[4]);e2=scale(d[5]);value=d[6];ctx.fillStyle=(ramp.map_value(value));ctx.fillRect(s1,s2,(e1-s1),(e2-s2))}ctx.restore()};var VariantPainter=function(data,view_start,view_end,prefs,mode,base_color_fn){Painter.call(this,data,view_start,view_end,prefs,mode);this.base_color_fn=base_color_fn;this.divider_height=1};extend(VariantPainter.prototype,Painter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}return height},get_required_height:function(num_samples){var height=this.prefs.summary_height;if(this.prefs.show_sample_data){height+=this.divider_height+num_samples*this.get_row_height()}return height},draw:function(ctx,width,height,w_scale){var locus_data,pos,id,ref,alt,qual,filter,sample_gts,allele_counts,variant,draw_x_start,char_x_start,draw_y_start,genotype,base_px=Math.max(1,Math.floor(w_scale)),row_height=(this.mode==="Squish"?SQUISH_TRACK_HEIGHT:PACK_TRACK_HEIGHT),feature_height=(w_scale<0.1?row_height:(this.mode==="Squish"?SQUISH_FEATURE_HEIGHT:PACK_FEATURE_HEIGHT)),j;if(this.prefs.show_sample_data){ctx.fillStyle="#F3F3F3";ctx.globalAlpha=1;ctx.fillRect(0,this.prefs.summary_height-this.divider_height,width,this.divider_height)}ctx.textAlign="center";for(var i=0;i<this.data.length;i++){locus_data=this.data[i];pos=locus_data[1];alt=locus_data[4].split(",");sample_gts=locus_data[7].split(",");allele_counts=locus_data.slice(8);draw_x_start=Math.floor(Math.max(-0.5*w_scale,(pos-this.view_start-0.5)*w_scale));char_x_start=Math.floor(Math.max(0,(pos-this.view_start)*w_scale));ctx.fillStyle="#999999";ctx.fillRect(draw_x_start,0,base_px,this.prefs.summary_height);draw_y_start=this.prefs.summary_height;for(j=0;j<alt.length;j++){ctx.fillStyle=this.base_color_fn(alt[j]);allele_frac=allele_counts/sample_gts.length;draw_height=Math.ceil(this.prefs.summary_height*allele_frac);ctx.fillRect(draw_x_start,draw_y_start-draw_height,base_px,draw_height);draw_y_start-=draw_height}if(!this.prefs.show_sample_data){continue}draw_y_start=this.prefs.summary_height+this.divider_height;for(j=0;j<sample_gts.length;j++,draw_y_start+=row_height){genotype=(sample_gts[j]?sample_gts[j].split(/\/|\|/):["0","0"]);variant=null;if(genotype[0]===genotype[1]){if(genotype[0]==="."){}else{if(genotype[0]!=="0"){variant=alt[parseInt(genotype[0],10)-1];ctx.globalAlpha=1}}}else{variant=(genotype[0]!=="0"?genotype[0]:genotype[1]);variant=alt[parseInt(variant,10)-1];ctx.globalAlpha=0.4}if(variant){ctx.fillStyle=this.base_color_fn(variant);if(this.mode==="Squish"||w_scale<ctx.canvas.manager.char_width_px){ctx.fillRect(draw_x_start,draw_y_start+1,base_px,feature_height)}else{ctx.fillText(variant,char_x_start,draw_y_start+row_height)}}}}}});return{Scaler:Scaler,LinePainter:LinePainter,LinkedFeaturePainter:LinkedFeaturePainter,ReadPainter:ReadPainter,RefBasedReadPainter:RefBasedReadPainter,ArcLinkedFeaturePainter:ArcLinkedFeaturePainter,DiagonalHeatmapPainter:DiagonalHeatmapPainter,VariantPainter:VariantPainter}});
\ No newline at end of file
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d 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","mvc/data","viz/trackster/filters"],function(ab,x,l,u,L,X,i){var q=ab.extend;var U=function(ac){return("isResolved" in ac)};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();if(ap.offsetY<aq){$(this).insertBefore(ai);var ar=n[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable_before(am,ar);return}else{if(ap.offsetY>ah){$(this).insertAfter(ai);var ar=n[ai.attr("id")];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 aa=16,G=9,D=20,A=100,I=12000,R=400,K=5000,w=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.",E="No data for this chrom/contig.",v="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",y="Tool cannot be rerun: ",a="Loading data...",T="Ready for display",Q=10,H=20;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.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("<div/>").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("<div style='clear: both'/>"));this.header_div.dblclick(function(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.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];q(r.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var 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("href","javascript:void(0);").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 z=function(ad,ac,ae){r.call(this,ad,ac,ae);this.obj_type=ae.obj_type;this.drawables=[]};q(z.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(){for(var ac=0;ac<this.drawables.length;ac++){this.drawables[ac]._draw()}},to_dict:function(){var ad=[];for(var ac=0;ac<this.drawables.length;ac++){ad.push(this.drawables[ac].to_dict())}return{name:this.name,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"});z.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,z.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){$(".bs-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(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.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 c){ac++}}if(ao||ac===1){this.action_icons.composite_icon.show()}else{this.action_icons.composite_icon.hide();$(".bs-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 af=[];for(var ad=0;ad<this.drawables.length;ad++){af.push(this.drawables[ad].name)}var ae=new f(this.view,this.view,{name:this.name,drawables:this.drawables});var ac=this.container.replace_drawable(this,ae,true);ae.request_draw()},add_drawable:function(ac){z.prototype.add_drawable.call(this,ac);this.update_icons()},remove_drawable:function(ac){z.prototype.remove_drawable.call(this,ac);this.update_icons()},to_dict:function(){if(this.filters_manager.visible()){this._restore_filter_managers()}var ac=q(z.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(af,ae,ac){for(var ad=0;ad<this.drawables.length;ad++){this.drawables[ad].request_draw(af,ae,ac)}}});var Y=Backbone.View.extend({initialize:function(ac){q(ac,{obj_type:"View"});z.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 x.CanvasManager(this.container.get(0).ownerDocument);this.reset();this.config=new F({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(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(){x.select_datasets(select_datasets_url,add_track_async_url,{"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("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var 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(c),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.values[ac.toLowerCase()+"_color"]||this.config.values.n_color}});q(Y.prototype,z.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=w;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='<option value="">Select Chrom/Contig</option>';for(var ai=0,af=ac.chrom_data.length;ai<af;ai++){var ah=ac.chrom_data[ai].chrom;aj+='<option value="'+ah+'">'+ah+"</option>"}if(ag.prev_chroms){aj+='<option value="previous">Previous '+w+"</option>"}if(ag.next_chroms){aj+='<option value="next">Next '+w+"</option>"}ac.chrom_select.html(aj);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-w});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+w});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();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){z.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){z.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(ac,aj,ak){var ai=this,ag=(ak?[ak]:ai.drawables),ae;var ad;for(var ah=0;ah<ag.length;ah++){ad=ag[ah];ae=-1;for(var af=0;af<ai.tracks_to_be_redrawn.length;af++){if(ai.tracks_to_be_redrawn[af][0]===ad){ae=af;break}}if(ae<0){ai.tracks_to_be_redrawn.push([ad,ac,aj])}else{ai.tracks_to_be_redrawn[ah][1]=ac;ai.tracks_to_be_redrawn[ah][2]=aj}}if(!this.requested_redraw){requestAnimationFrame(function(){ai._redraw()});this.requested_redraw=true}},_redraw:function(){this.requested_redraw=false;var aj=this.low,af=this.high;if(aj<this.max_low){aj=this.max_low}if(af>this.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al<this.min_separation){af=aj+this.min_separation}this.low=Math.floor(aj);this.high=Math.ceil(af);this.update_location(this.low,this.high);this.resolution_b_px=(this.high-this.low)/this.viewport_container.width();this.resolution_px_b=1/this.resolution_b_px;var ac=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var ai=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var am=13;this.overview_box.css({left:ac,width:Math.max(am,ai)}).show();if(ai<am){this.overview_box.css("left",ac-(am-ai)/2)}if(this.overview_highlight){this.overview_highlight.css({left:ac,width:ai})}var ae,ad,ak;for(var ag=0,ah=this.tracks_to_be_redrawn.length;ag<ah;ag++){ae=this.tracks_to_be_redrawn[ag][0];ad=this.tracks_to_be_redrawn[ag][1];ak=this.tracks_to_be_redrawn[ag][2];if(ae){ae._draw(ad,ak)}}this.tracks_to_be_redrawn=[];for(ag=0,ah=this.label_tracks.length;ag<ah;ag++){this.label_tracks[ag]._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(){$(".bs-tooltip").remove();this.overview_viewport.find(".track-tile").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide();view.resize_window();view.overview_drawable=null}});var s=function(ae,aj,af){this.track=ae;this.id=aj.id;this.name=aj.name;this.params=[];var aq=aj.params;for(var ag=0;ag<aq.length;ag++){var al=aq[ag],ad=al.name,ap=al.label,ah=unescape(al.html),ar=al.value,an=al.type;if(an==="number"){this.params.push(new e(ad,ap,ah,(ad in af?af[ad]:ar),al.min,al.max))}else{if(an==="select"){this.params.push(new N(ad,ap,ah,(ad in af?af[ad]:ar)))}else{console.log("WARNING: unrecognized tool parameter type:",ad,an)}}}this.parent_div=$("<div/>").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("<div class='tool-name'>").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("<div>").addClass("param-row").appendTo(ak.parent_div);var au=$("<div>").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("<div/>").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("<div style='clear: both;'/>").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("<div>").addClass("param-row").appendTo(this.parent_div);var ai=$("<input type='submit'>").attr("value","Run on complete dataset").appendTo(at);var ac=$("<input type='submit'>").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};q(s.prototype,{update_params:function(){for(var ac=0;ac<this.params.length;ac++){this.params[ac].update_value()}},state_dict:function(){var ad={};for(var ac=0;ac<this.params.length;ac++){ad[this.params[ac].name]=this.params[ac].value}ad.visible=this.parent_div.is(":visible");return ad},get_param_values_dict:function(){var ac={};this.parent_div.find(":input").each(function(){var ad=$(this).attr("name"),ae=$(this).val();ac[ad]=ae});return ac},get_param_values:function(){var ac=[];this.parent_div.find(":input").each(function(){var ad=$(this).attr("name"),ae=$(this).val();if(ad){ac[ac.length]=ae}});return ac},run_on_dataset:function(){var ac=this;ac.run({target_dataset_id:this.track.dataset.id,action:"rerun",tool_id:ac.id},null,function(ad){show_modal(ac.name+" is Running",ac.name+" is running on the complete dataset. Tool outputs are in dataset's history.",{Close:hide_modal})})},run_on_region:function(){var ai=new x.GenomeRegion({chrom:this.track.view.chrom,start:this.track.view.low,end:this.track.view.high}),ad={target_dataset_id:this.track.dataset.id,action:"rerun",tool_id:this.id,regions:[ai.toJSON()]},ah=this.track,ae=ad.tool_id+ah.tool_region_and_parameters_str(ai),ac;if(ah.container===view){var ag=new P(view,view,{name:this.name});var af=ah.container.replace_drawable(ah,ag,false);ag.container_div.insertBefore(ah.view.content_div.children()[af]);ag.add_drawable(ah);ah.container_div.appendTo(ag.content_div);ac=ag}else{ac=ah.container}var aj=new ah.constructor(view,ac,{name:ae,hda_ldda:"hda"});aj.init_for_tool_data();aj.change_mode(ah.mode);aj.set_filters_manager(ah.filters_manager.copy(aj));aj.update_icons();ac.add_drawable(aj);aj.tiles_div.text("Starting job.");this.update_params();this.run(ad,aj,function(ak){aj.set_dataset(new X.Dataset(ak));aj.tiles_div.text("Running job.");aj.init()})},run:function(ac,ae,af){ac.inputs=this.get_param_values_dict();var ad=new l.ServerStateDeferred({ajax_settings:{url:galaxy_paths.get("tool_url"),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(y+ag.message)}else{af(ag)}}})}});var N=function(ad,ac,ae,af){this.name=ad;this.label=ac;this.html=$(ae);this.value=af};q(N.prototype,{update_value:function(){this.value=$(this.html).val()}});var e=function(ae,ad,ag,ah,af,ac){N.call(this,ae,ad,ag,ah);this.min=af;this.max=ac};q(e.prototype,N.prototype,{update_value:function(){N.prototype.update_value.call(this);this.value=parseFloat(this.value)}});var C=function(ac,ad){L.Scaler.call(this,ad);this.filter=ac};C.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 F=function(ac){this.track=ac.track;this.params=ac.params;this.values={};this.restore_values((ac.saved_values?ac.saved_values:{}));this.onchange=ac.onchange};q(F.prototype,{restore_values:function(ac){var ad=this;$.each(this.params,function(ae,af){if(ac[af.key]!==undefined){ad.values[af.key]=ac[af.key]}else{ad.values[af.key]=af.default_value}})},build_form:function(){var af=this;var ac=$("<div />");var ae;function ad(ak,ag){for(var ao=0;ao<ak.length;ao++){ae=ak[ao];if(ae.hidden){continue}var ai="param_"+ao;var at=af.values[ae.key];var av=$("<div class='form-row' />").appendTo(ag);av.append($("<label />").attr("for",ai).text(ae.label+":"));if(ae.type==="bool"){av.append($('<input type="checkbox" />').attr("id",ai).attr("name",ai).attr("checked",at))}else{if(ae.type==="text"){av.append($('<input type="text"/>').attr("id",ai).val(at).click(function(){$(this).select()}))}else{if(ae.type==="select"){var aq=$("<select />").attr("id",ai);for(var am=0;am<ae.options.length;am++){$("<option/>").text(ae.options[am].label).attr("value",ae.options[am].value).appendTo(aq)}aq.val(at);av.append(aq)}else{if(ae.type==="color"){var au=$("<div/>").appendTo(av),ap=$("<input />").attr("id",ai).attr("name",ai).val(at).css("float","left").appendTo(au).click(function(ax){$(".bs-tooltip").removeClass("in");var aw=$(this).siblings(".bs-tooltip").addClass("in");aw.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top-($(aw).height()/2)+($(this).height()/2)}).show();aw.click(function(ay){ay.stopPropagation()});$(document).bind("click.color-picker",function(){aw.hide();$(document).unbind("click.color-picker")});ax.stopPropagation()}),an=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("<div class='bs-tooltip right' style='position: absolute;' />").appendTo(au).hide(),aj=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(ar),ah=$("<div class='tooltip-arrow'></div>").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("<div/>").css("clear","both"));(function(aw){an.click(function(){aw.setColor(l.get_random_color())})})(al)}else{av.append($("<input />").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("<div class='help'/>").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("<div class='track-tile'/>").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var j=function(ac,ah,ae,ad,af,ag){b.call(this,ac,ah,ae,ad,af);this.max_val=ag};q(j.prototype,b.prototype);var O=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("<div/>").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new x.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("<a href='javascript:void(0);'/>").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("<a href='javascript:void(0);'/>").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()})}};q(O.prototype,b.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 ak=at[0],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]}var af=$("<div/>").attr("id",ak).addClass("feature-popup"),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"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+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=new X.Dataset(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 x.GenomeDataManager({dataset:this.dataset,genome:new x.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=0;this.content_div=$("<div class='track-content'>").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in 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.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-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(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new x.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new x.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.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()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},r.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset.id&&r.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("<div/>").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var 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.name).attr("id",this.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(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.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.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.values.mode?this.config.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 B){return"ReferenceTrack"}else{if(this instanceof h){return"LineTrack"}else{if(this instanceof S){return"ReadTrack"}else{if(this instanceof Z){return"VariantTrack"}else{if(this instanceof f){return"CompositeTrack"}else{if(this instanceof c){return"FeatureTrack"}}}}}}}return""},init:function(ae){var ad=this;ad.enabled=false;ad.tile_cache.clear();ad.data_manager.clear();ad.content_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(){show_modal("Trackster Error","<pre>"+ag.message+"</pre>",{Close:hide_modal})}));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(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(v);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(T);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when(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(){},get_drawables:function(){return this}});var M=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new s(this,af.tool,af.tool_state):null);this.tile_cache=new x.Cache(Q);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("<div/>").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("<div/>").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};q(M.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){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(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(),name:this.name,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():{})}},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;ac.request_draw(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(ae,ad,ac){if(ae){this.tile_cache.clear()}this.view.request_redraw(ad,ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(ad,an){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,ak=ah-al,ae=this.view.container.width(),ap=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;ap=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*R)),am,ai,aj=[],ao=[];while((ac*R*ag)<ah){am=this._get_tile_bounds(ac,ag);ai=this.draw_helper(ad,am,ag,this.tiles_div,ap);aj.push(ai);$.when(ai).then(function(aq){ao.push(aq)});ac+=1}if(!an){this.tiles_div.children(".remove").removeClass("remove").remove()}var af=this;$.when.apply($,aj).then(function(){af.tiles_div.children(".remove").remove();if(ao[0]){af.postdraw_actions(ao,ae,ap,an)}})},_add_yaxis_label:function(af,aj,ad,ah){var ac=this,ag=(af==="max"?"top":"bottom"),ai=(af==="max"?"max":"min"),ah=ah||function(){ac.request_draw(true)},ae=this.container_div.find(".yaxislabel."+ag);if(ae.length!==0){ae.text(aj)}else{ae=$("<div/>").text(aj).make_text_editable({num_cols:12,on_finish:function(ak){$(".bs-tooltip").remove();var ak=parseFloat(ak);ac.prefs[ad]=(!isNaN(ak)?ak:null);ah()},help_text:"Set "+ai+" value"}).addClass("yaxislabel "+ag).css("color",this.prefs.label_color);this.container_div.prepend(ae)}},postdraw_actions:function(ae,af,aj,ac){var ag=ab.find(ae,function(ak){return ak.has_icons});if(ag){ab.each(ae,function(ak){if(!ak.has_icons){ak.html_elt.css("padding-top",D)}})}var ad=this,ah=ae[0];if(ah instanceof j){var ai=(this.prefs.histogram_max?this.prefs.histogram_max:ah.max_val);this._add_yaxis_label("max",ai,"histogram_max")}},draw_helper:function(ac,af,ar,ag,ah,ap){var an=this,ax=this._gen_tile_cache_key(ah,af),aj=function(ay){return(ay&&"track" in ay)};if(!ap){ap={}}var aw=(ac?undefined:an.tile_cache.get_elt(ax));if(aw){if(aj(aw)){an.show_tile(aw,ag,ah)}return aw}var al=true;var at=an.data_manager.get_data(af,an.mode,ar,an.data_url_extra_params);if(U(at)){al=false}var ai;if(view.reference_track){ai=view.reference_track.data_manager.get_data(af,an.mode,ar,view.reference_track.data_url_extra_params);if(U(ai)){al=false}else{ai=view.reference_track.data_manager.subset_entry(ai,af)}}if(al){q(at,ap.more_tile_data);var ak=an.mode;if(ak==="Auto"&&an.get_mode){ak=an.get_mode(at);an.update_auto_mode(ak)}var ae=an.view.canvas_manager.new_canvas(),au=af.get("start"),ad=af.get("end"),ao=Math.ceil((ad-au)*ah)+an.left_offset,am=an.get_canvas_height(at,ak,ah,ao);ae.width=ao;ae.height=am;var aq=ae.getContext("2d");aq.translate(this.left_offset,0);var aw=an.draw_tile(at,aq,ak,ar,af,ah,ai);if(aw!==undefined){an.tile_cache.set_elt(ax,aw);an.show_tile(aw,ag,ah)}return aw}var av=$.Deferred();an.tile_cache.set_elt(ax,av);$.when(at,ai).then(function(){aw=an.draw_helper(true,af,ar,ag,ah,ap);av.resolve(aw)});return av},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_summary_tree_tile:function(ac,ae,ag,af,ah){var ad=new L.SummaryTreePainter(ac,ag.get("start"),ag.get("end"),this.prefs);ad.draw(ae,ae.canvas.width,ae.canvas.height,ah);return new j(this,ag,af,ae.canvas,ac.data,ac.max)},draw_tile:function(ac,ad,ah,af,ag,ai,ae){console.log("Warning: TiledTrack.draw_tile() not implemented.")},show_tile:function(ae,ah,ai){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ai;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{ah.append(ac)}ae.html_elt.height("auto");this.max_height_px=Math.max(this.max_height_px,ae.html_elt.height());ae.html_elt.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")},_get_tile_bounds:function(ac,ad){var af=Math.floor(ac*R*ad),ag=Math.ceil(R*ad),ae=(af+ag<=this.view.max_high?af+ag:this.view.max_high);return new x.GenomeRegion({chrom:this.view.chrom,start:af,end:ae})},tool_region_and_parameters_str:function(ae){var ac=this,ad=(ae!==undefined?ae.toString():"all");return" - region=["+ad+"], parameters=["+ac.tool.get_param_values().join(", ")+"]"},data_and_mode_compatible:function(ac,ad){return true},can_subset:function(ac){return false},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state";this.normal_postdraw_actions=this.postdraw_actions;this.postdraw_actions=function(ae,af,ah,ac){var ad=this;ad.normal_postdraw_actions(ae,af,ah,ac);ad.dataset_check_type="converted_datasets_state";ad.data_query_wait=K;var ag=new l.ServerStateDeferred({url:ad.dataset_state_url,url_params:{dataset_id:ad.dataset.id,hda_ldda:ad.dataset.get("hda_ldda")},interval:ad.data_query_wait,success_fn:function(ai){return ai!=="pending"}});$.when(ag.go()).then(function(){ad.data_manager.set("data_type","data")});ad.postdraw_actions=ad.normal_postdraw_actions}}});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},_draw:function(){var ae=this.view,af=ae.high-ae.low,ai=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/ai)*ai,ag=this.view.container.width(),ad=$("<div style='position: relative; height: 1.3em;'></div>");while(ac<ae.high){var ah=(ac-ae.low)/af*ag;ad.append($("<div class='label'>"+commatize(ac)+"</div>").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){M.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}if(this.drawables.length!==0){this.set_display_modes(this.drawables[0].display_modes,this.drawables[0].mode)}this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,M.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(M.prototype.action_icons_def),to_dict:z.prototype.to_dict,add_drawable:z.prototype.add_drawable,unpack_drawables:z.prototype.unpack_drawables,change_mode:function(ac){M.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,draw_helper:function(ad,ah,av,ai,ak,at){var aq=this,aC=this._gen_tile_cache_key(ak,ah),am=function(aD){return(aD&&"track" in aD)};if(!at){at={}}var aB=(ad?undefined:aq.tile_cache.get_elt(aC));if(aB){if(am(aB)){aq.show_tile(aB,ai,ak)}return aB}var aj=[],aq,ao=true,aw,al;for(var ax=0;ax<this.drawables.length;ax++){aq=this.drawables[ax];aw=aq.data_manager.get_data(ah,aq.mode,av,aq.data_url_extra_params);if(U(aw)){ao=false}aj.push(aw);al=null;if(view.reference_track&&ak>view.canvas_manager.char_width_px){al=view.reference_track.data_manager.get_data(ah,aq.mode,av,view.reference_track.data_url_extra_params);if(U(al)){ao=false}}aj.push(al)}if(ao){q(aw,at.more_tile_data);var ag=aq.view.canvas_manager.new_canvas(),ay=ah.get("start"),ae=ah.get("end"),az=0,ar=Math.ceil((ae-ay)*ak)+this.left_offset,ap=0,af=[],ax;var ac=0;for(ax=0;ax<this.drawables.length;ax++,az+=2){aq=this.drawables[ax];aw=aj[az];var an=aq.mode;if(an==="Auto"){an=aq.get_mode(aw);aq.update_auto_mode(an)}af.push(an);ac=aq.get_canvas_height(aw,an,ak,ar);if(ac>ap){ap=ac}}ag.width=ar;ag.height=(at.height?at.height:ap);az=0;var au=ag.getContext("2d");au.translate(this.left_offset,0);au.globalAlpha=0.5;au.globalCompositeOperation="source-over";for(ax=0;ax<this.drawables.length;ax++,az+=2){aq=this.drawables[ax];aw=aj[az];al=aj[az+1];aB=aq.draw_tile(aw,au,af[ax],av,ah,ak,al)}this.tile_cache.set_elt(aC,aB);this.show_tile(aB,ai,ak);return aB}var aA=$.Deferred();aq=this;aq.tile_cache.set_elt(aC,aA);$.when.apply($,aj).then(function(){aB=aq.draw_helper(true,ah,av,ai,ak,at);aA.resolve(aB)});return aA},show_group:function(){var af=new P(this.view,this.container,{name:this.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(true)},before_draw:function(){M.prototype.before_draw.call(this);var af=Number.MAX_VALUE,ac=-af,ad;for(var ae=0;ae<this.drawables.length;ae++){ad=this.drawables[ae];if(ad instanceof h){if(ad.prefs.min_value<af){af=ad.prefs.min_value}if(ad.prefs.max_value>ac){ac=ad.prefs.max_value}}}this.prefs.min_value=af;this.prefs.max_value=ac;for(var ae=0;ae<this.drawables.length;ae++){ad=this.drawables[ae];ad.prefs.min_value=af;ad.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(true)},postdraw_actions:function(ai,ac,al,ah){M.prototype.postdraw_actions.call(this,ai,ac,al,ah);var ag=-1;for(var ae=0;ae<ai.length;ae++){var aj=ai[ae].html_elt.find("canvas").height();if(aj>ag){ag=aj}}for(var ae=0;ae<ai.length;ae++){var af=ai[ae];if(af.html_elt.find("canvas").height()!==ag){this.draw_helper(true,af.region,af.resolution,af.html_elt.parent(),al,{height:ag});af.html_elt.remove()}}var ad=this,ak=function(){ad.update_all_min_max()};this._add_yaxis_label("min",this.drawables[0].prefs.min_value,"min_value",ak);this._add_yaxis_label("max",this.drawables[0].prefs.max_value,"max_value",ak)}});var B=function(ac){M.call(this,ac,{content_div:ac.top_labeltrack},{resize:false});ac.reference_track=this;this.left_offset=200;this.visible_height_px=12;this.container_div.addClass("reference-track");this.content_div.css("background","none");this.content_div.css("min-height","0px");this.content_div.css("border","none");this.data_url=reference_url+"/"+this.view.dbkey;this.data_url_extra_params={reference:true};this.data_manager=new x.GenomeReferenceDataManager({data_url:this.data_url,can_subset:this.can_subset});this.hide_contents()};q(B.prototype,r.prototype,M.prototype,{build_header_div:function(){},init:function(){this.data_manager.clear();this.enabled=true},can_draw:r.prototype.can_draw,draw_helper:function(ae,af,ac,ag,ah,ad){if(ah>this.view.canvas_manager.char_width_px){this.tiles_div.show();return M.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ah<aj;ah++){al.fillStyle=this.view.get_base_color(ak[ah]);al.fillText(ak[ah],Math.floor(ah*am),10)}return new b(this,ai,ad,ac,ae)}});var h=function(ae,ad,af){var ac=this;this.display_modes=["Histogram","Line","Filled","Intensity"];this.mode="Histogram";M.call(this,ae,ad,af);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{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:32,hidden:true}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);ac.vertical_range=ac.prefs.max_value-ac.prefs.min_value;ac.request_redraw(true)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.vertical_range=this.config.values.max_value-this.config.values.min_value};q(h.prototype,r.prototype,M.prototype,{predraw_init:function(){var ac=this;ac.vertical_range=undefined;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(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}ac.vertical_range=ac.prefs.max_value-ac.prefs.min_value;ac.total_frequency=af.total_frequency})},before_draw:function(){},draw_tile:function(al,aj,ae,ad,ag,ak){var ac=aj.canvas,af=ag.get("start"),ai=ag.get("end"),ah=new L.LinePainter(al.data,af,ai,this.prefs,ae);ah.draw(aj,ac.width,ac.height,ak);return new b(this,ag,ad,ac,al.data)},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",V(this.prefs.max_value,3),"max_value");this._add_yaxis_label("min",V(this.prefs.min_value,3),"min_value")}});var t=function(ae,ad,af){var ac=this;this.display_modes=["Heatmap"];this.mode="Heatmap";M.call(this,ae,ad,af);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{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}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);ac.vertical_range=ac.prefs.max_value-ac.prefs.min_value;this.request_redraw(true)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.vertical_range=this.config.values.max_value-this.config.values.min_value};q(t.prototype,r.prototype,M.prototype,{draw_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new b(this,ah,ag,af,ac.data)}});var c=function(af,ae,ah){var ad=this;this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];M.call(this,af,ae,ah);var ag=l.get_random_color(),ac=l.get_random_color([ag,"#FFFFFF"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block color",type:"color",default_value:ag},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{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:"histogram_max",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:this.visible_height_px,hidden:true}],saved_values:ah.prefs,onchange:function(){ad.set_name(ad.prefs.name);ad.set_painter_from_config();ad.request_draw(true)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.container_div.addClass("feature-track");this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};q(c.prototype,r.prototype,M.prototype,{set_painter_from_config:function(){if(this.config.values.connector_style==="arcs"){this.painter=L.ArcLinkedFeaturePainter}else{this.painter=L.LinkedFeaturePainter}},postdraw_actions:function(ap,ae,aq,ao){M.prototype.postdraw_actions.call(this,ap,ao);var ag=this,ah;if(ag.mode==="Coverage"){var ak=-1;for(ah=0;ah<ap.length;ah++){var an=ap[ah].max_val;if(an>ak){ak=an}}for(ah=0;ah<ap.length;ah++){var am=ap[ah];if(am.max_val!==ak){am.html_elt.remove();ag.draw_helper(true,am.index,am.resolution,am.html_elt.parent(),aq,{more_tile_data:{max:ak}})}}}if(ag.filters_manager){var af=ag.filters_manager.filters;for(var aj=0;aj<af.length;aj++){af[aj].update_ui_elt()}var ai=false,al,ad;for(ah=0;ah<ap.length;ah++){if(ap[ah].data.length){al=ap[ah].data[0];for(var aj=0;aj<af.length;aj++){ad=af[aj];if(ad.applies_to(al)&&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(ap[0] instanceof O){var ac=true;for(ah=0;ah<ap.length;ah++){if(!ap[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){var ac;if(this.mode==="Auto"){if(ac==="no_detail"){ac="feature spans"}else{if(ac==="summary_tree"){ac="coverage histogram"}}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,A,function(ah){return ad.measureText(ah)});this.slotters[ag]=ae}return ae.slot_features(ac)},get_mode:function(ac){if(ac.dataset_type==="summary_tree"){mode="summary_tree"}else{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==="summary_tree"||ag==="Coverage"){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(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(ao==="summary_tree"||ao==="Coverage"){return this._draw_summary_tree_tile(am,aq,af,ar,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at<av;at++){var ah=am.data[at];var au=false;var al;for(var ax=0,aC=ak.length;ax<aC;ax++){al=ak[ax];al.update_attrs(ah);if(!al.keep(ah)){au=true;break}}if(!au){ai.push(ah);if(!(ah[0] in an)){all_slotted=false}}}}var aB=(this.filters_manager.alpha_filter?new C(this.filters_manager.alpha_filter):null),az=(this.filters_manager.height_filter?new C(this.filters_manager.height_filter):null),aA=new (this.painter)(ai,ay,ac,this.prefs,ao,aB,az,ae,function(aD){return ap.view.get_base_color(aD)});var aw=null;aq.fillStyle=this.prefs.block_color;aq.font=aq.canvas.manager.default_font;aq.textAlign="right";if(am.data){aw=aA.draw(aq,ad.width,ad.height,aj,an);aw.translation=-ag}return new O(ap,af,ar,ad,am.data,aj,ao,am.message,all_slotted,aw)},data_and_mode_compatible:function(ac,ad){if(ad==="Auto"){return true}else{if(ad==="Coverage"){return ac.dataset_type==="summary_tree"}else{if(ac.extra_info==="no_detail"||ac.dataset_type==="summary_tree"){return false}else{return true}}}},can_subset:function(ac){if(ac.dataset_type==="summary_tree"||ac.message||ac.extra_info==="no_detail"){return false}return true}});var Z=function(ad,ac,ae){this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];M.call(this,ad,ac,ae);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{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}],saved_values:ae.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=L.VariantPainter;this.summary_draw_height=30;this.left_offset=30};q(Z.prototype,r.prototype,M.prototype,{draw_tile:function(ac,af,ai,ag,ah,aj){if(ac.dataset_type==="summary_tree"){return this._draw_summary_tree_tile(ac,af,ah,ag,aj)}else{var ae=this.view,ad=new (this.painter)(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai,function(ak){return ae.get_base_color(ak)});ad.draw(af,af.canvas.width,af.canvas.height,aj);return new b(this,ah,ag,af.canvas,ac.data)}},get_canvas_height:function(ac,ag,ah,ae){if(ac.dataset_type==="summary_tree"){return this.summary_draw_height}else{var af=new (this.painter)(null,null,null,this.prefs,ag);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}}return af.get_required_height(ad)}},predraw_init:function(){if(!this.dataset.get_metadata("sample_names")){return this.dataset.fetch()}},postdraw_actions:function(ae,af,ah,ad){M.prototype.postdraw_actions.call(this,ae,af,ah,ad);if(!(ae[0] instanceof j)&&this.prefs.show_labels){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 ag=this.dataset.get("metadata").get("sample_names").join("<br/>");this.tiles_div.prepend($("<div/>").html(ag).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 S=function(ae,ad,ag){c.call(this,ae,ad,ag);var af=l.get_random_color(),ac=l.get_random_color([af,"#ffffff"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block and sense strand color",type:"color",default_value:af},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{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:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"Clear value to set automatically"},{key:"mode",type:"string",default_value:this.mode,hidden:true}],saved_values:ag.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=(ae.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};q(S.prototype,r.prototype,M.prototype,c.prototype);var d={CompositeTrack:f,DrawableGroup:P,DiagonalHeatmapTrack:t,FeatureTrack:c,LineTrack:h,ReadTrack:S,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 d[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:P,LineTrack:h,FeatureTrack:c,DiagonalHeatmapTrack:t,ReadTrack:S,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","mvc/data","viz/trackster/filters"],function(ab,w,k,t,L,X,i){var p=ab.extend;var m={};var j=function(ac,ad){m[ac.attr("id")]=ad};var l=function(ac,ae,ag,af){ag=".group";var ad={};m[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=m[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();if(ap.offsetY<aq){$(this).insertBefore(ai);var ar=m[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable_before(am,ar);return}else{if(ap.offsetY>ah){$(this).insertAfter(ai);var ar=m[ai.attr("id")];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)}m[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);m[at.attr("id")].move_drawable(am,al)}}else{if(this!==ak.get(al)){$(this).insertBefore(ak.get(al));m[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 aa=16,G=9,D=20,z=100,I=12000,S=400,K=5000,v=100,n="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",u="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",x="Tool cannot be rerun: ",a="Loading data...",U="Ready for display",R=10,H=20,A=["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 q=function(ad,ac,af){if(!q.id_counter){q.id_counter=0}this.id=q.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("<div/>").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("<div style='clear: both'/>"));this.header_div.dblclick(function(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)}};q.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];p(q.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var 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("href","javascript:void(0);").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 y=function(ad,ac,ae){q.call(this,ad,ac,ae);this.obj_type=ae.obj_type;this.drawables=[]};p(y.prototype,q.prototype,{unpack_drawables:function(ae){this.drawables=[];var ad;for(var ac=0;ac<ae.length;ac++){ad=o(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(){for(var ac=0;ac<this.drawables.length;ac++){this.drawables[ac]._draw()}},to_dict:function(){var ad=[];for(var ac=0;ac<this.drawables.length;ac++){ad.push(this.drawables[ac].to_dict())}return{name:this.name,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 Q=function(ad,ac,af){p(af,{obj_type:"DrawableGroup",drag_handle_class:"group-handle"});y.call(this,ad,ac,af);this.content_div=$("<div/>").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);j(this.container_div,this);j(this.content_div,this);l(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()}}};p(Q.prototype,q.prototype,y.prototype,{action_icons_def:[q.prototype.action_icons_def[0],q.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-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(true)}ac.filters_manager.toggle()}},q.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.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 c){ac++}}if(ao||ac===1){this.action_icons.composite_icon.show()}else{this.action_icons.composite_icon.hide();$(".bs-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.name,drawables:this.drawables});var ac=this.container.replace_drawable(this,ad,true);ad.request_draw()},add_drawable:function(ac){y.prototype.add_drawable.call(this,ac);this.update_icons()},remove_drawable:function(ac){y.prototype.remove_drawable.call(this,ac);this.update_icons()},to_dict:function(){if(this.filters_manager.visible()){this._restore_filter_managers()}var ac=p(y.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(af,ae,ac){for(var ad=0;ad<this.drawables.length;ad++){this.drawables[ad].request_draw(af,ae,ac)}}});var Y=Backbone.View.extend({initialize:function(ac){p(ac,{obj_type:"View"});y.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 w.CanvasManager(this.container.get(0).ownerDocument);this.reset();this.config=new F({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(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;j(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(){w.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(o(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("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var 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(c),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.values[ac.toLowerCase()+"_color"]||this.config.values.n_color}});p(Y.prototype,y.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=v;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='<option value="">Select Chrom/Contig</option>';for(var ai=0,af=ac.chrom_data.length;ai<af;ai++){var ah=ac.chrom_data[ai].chrom;aj+='<option value="'+ah+'">'+ah+"</option>"}if(ag.prev_chroms){aj+='<option value="previous">Previous '+v+"</option>"}if(ag.next_chroms){aj+='<option value="next">Next '+v+"</option>"}ac.chrom_select.html(aj);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-v});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+v});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();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){y.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){y.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(ac,aj,ak){var ai=this,ag=(ak?[ak]:ai.drawables),ae;var ad;for(var ah=0;ah<ag.length;ah++){ad=ag[ah];ae=-1;for(var af=0;af<ai.tracks_to_be_redrawn.length;af++){if(ai.tracks_to_be_redrawn[af][0]===ad){ae=af;break}}if(ae<0){ai.tracks_to_be_redrawn.push([ad,ac,aj])}else{ai.tracks_to_be_redrawn[ah][1]=ac;ai.tracks_to_be_redrawn[ah][2]=aj}}if(!this.requested_redraw){requestAnimationFrame(function(){ai._redraw()});this.requested_redraw=true}},_redraw:function(){this.requested_redraw=false;var aj=this.low,af=this.high;if(aj<this.max_low){aj=this.max_low}if(af>this.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al<this.min_separation){af=aj+this.min_separation}this.low=Math.floor(aj);this.high=Math.ceil(af);this.update_location(this.low,this.high);this.resolution_b_px=(this.high-this.low)/this.viewport_container.width();this.resolution_px_b=1/this.resolution_b_px;var ac=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var ai=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var am=13;this.overview_box.css({left:ac,width:Math.max(am,ai)}).show();if(ai<am){this.overview_box.css("left",ac-(am-ai)/2)}if(this.overview_highlight){this.overview_highlight.css({left:ac,width:ai})}var ae,ad,ak;for(var ag=0,ah=this.tracks_to_be_redrawn.length;ag<ah;ag++){ae=this.tracks_to_be_redrawn[ag][0];ad=this.tracks_to_be_redrawn[ag][1];ak=this.tracks_to_be_redrawn[ag][2];if(ae){ae._draw(ad,ak)}}this.tracks_to_be_redrawn=[];for(ag=0,ah=this.label_tracks.length;ag<ah;ag++){this.label_tracks[ag]._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(){$(".bs-tooltip").remove();this.overview_viewport.find(".track-tile").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide();view.resize_window();view.overview_drawable=null}});var r=function(ae,aj,af){this.track=ae;this.id=aj.id;this.name=aj.name;this.params=[];var aq=aj.params;for(var ag=0;ag<aq.length;ag++){var al=aq[ag],ad=al.name,ap=al.label,ah=unescape(al.html),ar=al.value,an=al.type;if(an==="number"){this.params.push(new e(ad,ap,ah,(ad in af?af[ad]:ar),al.min,al.max))}else{if(an==="select"){this.params.push(new O(ad,ap,ah,(ad in af?af[ad]:ar)))}else{console.log("WARNING: unrecognized tool parameter type:",ad,an)}}}this.parent_div=$("<div/>").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("<div class='tool-name'>").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("<div>").addClass("param-row").appendTo(ak.parent_div);var au=$("<div>").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("<div/>").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("<div style='clear: both;'/>").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("<div>").addClass("param-row").appendTo(this.parent_div);var ai=$("<input type='submit'>").attr("value","Run on complete dataset").appendTo(at);var ac=$("<input type='submit'>").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};p(r.prototype,{update_params:function(){for(var ac=0;ac<this.params.length;ac++){this.params[ac].update_value()}},state_dict:function(){var ad={};for(var ac=0;ac<this.params.length;ac++){ad[this.params[ac].name]=this.params[ac].value}ad.visible=this.parent_div.is(":visible");return ad},get_param_values_dict:function(){var ac={};this.parent_div.find(":input").each(function(){var ad=$(this).attr("name"),ae=$(this).val();ac[ad]=ae});return ac},get_param_values:function(){var ac=[];this.parent_div.find(":input").each(function(){var ad=$(this).attr("name"),ae=$(this).val();if(ad){ac[ac.length]=ae}});return ac},run_on_dataset:function(){var ac=this;ac.run({target_dataset_id:this.track.dataset.id,action:"rerun",tool_id:ac.id},null,function(ad){show_modal(ac.name+" is Running",ac.name+" is running on the complete dataset. Tool outputs are in dataset's history.",{Close:hide_modal})})},run_on_region:function(){var ai=new w.GenomeRegion({chrom:this.track.view.chrom,start:this.track.view.low,end:this.track.view.high}),ad={target_dataset_id:this.track.dataset.id,action:"rerun",tool_id:this.id,regions:[ai.toJSON()]},ah=this.track,ae=ad.tool_id+ah.tool_region_and_parameters_str(ai),ac;if(ah.container===view){var ag=new Q(view,view,{name:this.name});var af=ah.container.replace_drawable(ah,ag,false);ag.container_div.insertBefore(ah.view.content_div.children()[af]);ag.add_drawable(ah);ah.container_div.appendTo(ag.content_div);ac=ag}else{ac=ah.container}var aj=new ah.constructor(view,ac,{name:ae,hda_ldda:"hda"});aj.init_for_tool_data();aj.change_mode(ah.mode);aj.set_filters_manager(ah.filters_manager.copy(aj));aj.update_icons();ac.add_drawable(aj);aj.tiles_div.text("Starting job.");this.update_params();this.run(ad,aj,function(ak){aj.set_dataset(new X.Dataset(ak));aj.tiles_div.text("Running job.");aj.init()})},run:function(ac,ae,af){ac.inputs=this.get_param_values_dict();var ad=new k.ServerStateDeferred({ajax_settings:{url:galaxy_paths.get("tool_url"),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(x+ag.message)}else{af(ag)}}})}});var O=function(ad,ac,ae,af){this.name=ad;this.label=ac;this.html=$(ae);this.value=af};p(O.prototype,{update_value:function(){this.value=$(this.html).val()}});var e=function(ae,ad,ag,ah,af,ac){O.call(this,ae,ad,ag,ah);this.min=af;this.max=ac};p(e.prototype,O.prototype,{update_value:function(){O.prototype.update_value.call(this);this.value=parseFloat(this.value)}});var C=function(ac,ad){L.Scaler.call(this,ad);this.filter=ac};C.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 F=function(ac){this.track=ac.track;this.params=ac.params;this.values={};this.restore_values((ac.saved_values?ac.saved_values:{}));this.onchange=ac.onchange};p(F.prototype,{restore_values:function(ac){var ad=this;$.each(this.params,function(ae,af){if(ac[af.key]!==undefined){ad.values[af.key]=ac[af.key]}else{ad.values[af.key]=af.default_value}})},build_form:function(){var af=this;var ac=$("<div />");var ae;function ad(ak,ag){for(var ao=0;ao<ak.length;ao++){ae=ak[ao];if(ae.hidden){continue}var ai="param_"+ao;var at=af.values[ae.key];var av=$("<div class='form-row' />").appendTo(ag);av.append($("<label />").attr("for",ai).text(ae.label+":"));if(ae.type==="bool"){av.append($('<input type="checkbox" />').attr("id",ai).attr("name",ai).attr("checked",at))}else{if(ae.type==="text"){av.append($('<input type="text"/>').attr("id",ai).val(at).click(function(){$(this).select()}))}else{if(ae.type==="select"){var aq=$("<select />").attr("id",ai);for(var am=0;am<ae.options.length;am++){$("<option/>").text(ae.options[am].label).attr("value",ae.options[am].value).appendTo(aq)}aq.val(at);av.append(aq)}else{if(ae.type==="color"){var au=$("<div/>").appendTo(av),ap=$("<input />").attr("id",ai).attr("name",ai).val(at).css("float","left").appendTo(au).click(function(ax){$(".bs-tooltip").removeClass("in");var aw=$(this).siblings(".bs-tooltip").addClass("in");aw.css({left:$(this).position().left+$(this).width()+5,top:$(this).position().top-($(aw).height()/2)+($(this).height()/2)}).show();aw.click(function(ay){ay.stopPropagation()});$(document).bind("click.color-picker",function(){aw.hide();$(document).unbind("click.color-picker")});ax.stopPropagation()}),an=$("<a href='javascript:void(0)'/>").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("<div class='bs-tooltip right' style='position: absolute;' />").appendTo(au).hide(),aj=$("<div class='tooltip-inner' style='text-align: inherit'></div>").appendTo(ar),ah=$("<div class='tooltip-arrow'></div>").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("<div/>").css("clear","both"));(function(aw){an.click(function(){aw.setColor(k.get_random_color())})})(al)}else{av.append($("<input />").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("<div class='help'/>").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("<div class='track-tile'/>").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var M=function(ac,ag,ae,ad,af){b.call(this,ac,ag,ae,ad,af)};M.prototype.predisplay_actions=function(){};var P=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("<div/>").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new w.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("<a href='javascript:void(0);'/>").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("<a href='javascript:void(0);'/>").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()})}};p(P.prototype,b.prototype);P.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 ak=at[0],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]}var af=$("<div/>").attr("id",ak).addClass("feature-popup"),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"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+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){p(ae,{drag_handle_class:"draghandle"});q.call(this,ad,ac,ae);this.dataset=new X.Dataset(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 w.GenomeDataManager({dataset:this.dataset,genome:new w.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=0;this.content_div=$("<div class='track-content'>").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};p(g.prototype,q.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},q.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)}},q.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.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-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(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new w.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new w.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.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()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},q.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset.id&&q.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("<div/>").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var 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.name).attr("id",this.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(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.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.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.values.mode?this.config.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(){q.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 B){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 c){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(n);if(ag.message){ad.tiles_div.append($("<a href='javascript:void(0);'></a>").text("View error").click(function(){show_modal("Trackster Error","<pre>"+ag.message+"</pre>",{Close:hide_modal})}));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(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(u);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.prefs.min_value=ae;ac.prefs.max_value=ag}})},get_drawables:function(){return this}});var N=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;l(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new r(this,af.tool,af.tool_state):null);this.tile_cache=new w.Cache(R);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("<div/>").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("<div/>").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};p(N.prototype,q.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){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();p(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(),name:this.name,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.values.mode=ad;if(ad==="Auto"){this.data_manager.clear()}ac.request_draw(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(ae,ad,ac){if(ae){this.tile_cache.clear()}this.view.request_redraw(ad,ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(ad,an){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,ak=ah-al,ae=this.view.container.width(),ap=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;ap=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*S)),am,ai,aj=[],ao=[];while((ac*S*ag)<ah){am=this._get_tile_bounds(ac,ag);ai=this.draw_helper(ad,am,ag,this.tiles_div,ap);aj.push(ai);$.when(ai).then(function(aq){ao.push(aq)});ac+=1}if(!an){this.tiles_div.children(".remove").removeClass("remove").remove()}var af=this;$.when.apply($,aj).then(function(){af.tiles_div.children(".remove").remove();if(ao[0]){af.postdraw_actions(ao,ae,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"),ah=ah||function(){ad.request_draw(true)},ae=this.container_div.find(".yaxislabel."+ag);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){$(".bs-tooltip").remove();var aj=V(parseFloat(aj),3);ad.prefs[ac]=(!isNaN(aj)?aj:null);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&&ae.length<af.length){this.max_height_px=0;var ad=this;ab.each(ae,function(aj){aj.html_elt.remove();ad.draw_helper(true,aj.region,aj.resolution,ad.tiles_div,ai,{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",D)}})}},_get_drawables:function(){return[this]},draw_helper:function(ad,am,ag,ao,ap,ae){if(!ae){ae={}}var af=this,ah=this._get_drawables(),an=this._gen_tile_cache_key(ap,am),ai=function(aq){return(aq&&"track" in aq)},aj=ae.mode||af.mode;var ak=(ad?undefined:af.tile_cache.get_elt(an));if(ak){if(ai(ak)){af.show_tile(ak,ao,ap)}return ak}var al=function(){var aq=(ab.find(A,function(at){return at===aj})?"Coverage":aj);var ar=ab.map(ah,function(at){return at.data_manager.get_data(am,aq,ag,af.data_url_extra_params)});if(view.reference_track){ar.push(view.reference_track.data_manager.get_data(am,aj,ag,view.reference_track.data_url_extra_params))}return ar};var ac=$.Deferred();af.tile_cache.set_elt(an,ac);$.when.apply($,al()).then(function(){var aq=al(),aw=aq,aC;if(view.reference_track){aC=view.reference_track.data_manager.subset_entry(aq.pop(),am)}var ax=[],au=[];ab.each(ah,function(aH,aE){var aG=aH.mode,aF=aw[aE];if(aG==="Auto"){aG=aH.get_mode(aF);aH.update_auto_mode(aG)}ax.push(aG);au.push(aH.get_canvas_height(aF,aG,ap,ar))});var av=af.view.canvas_manager.new_canvas(),ay=am.get("start"),aD=am.get("end"),at=0,ar=Math.ceil((aD-ay)*ap)+af.left_offset,aA=ab.max(au),az;av.width=ar;av.height=(ae.height?ae.height:aA);var aB=av.getContext("2d");aB.translate(af.left_offset,0);if(ah.length>1){aB.globalAlpha=0.5;aB.globalCompositeOperation="source-over"}ab.each(ah,function(aF,aE){az=aF.draw_tile(aw[aE],aB,ax[aE],ag,am,ap,aC)});if(az!==undefined){af.tile_cache.set_elt(an,az);af.show_tile(az,ao,ap)}ac.resolve(az)});return ac},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_line_track_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.LinePainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new M(this,ah,ag,af,ac.data)},draw_tile:function(ac,ad,ah,af,ag,ai,ae){console.log("Warning: TiledTrack.draw_tile() not implemented.")},show_tile:function(ae,ah,ai){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ai;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{ah.append(ac)}ae.html_elt.height("auto");this.max_height_px=Math.max(this.max_height_px,ae.html_elt.height());ae.html_elt.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")},_get_tile_bounds:function(ac,ad){var af=Math.floor(ac*S*ad),ag=Math.ceil(S*ad),ae=(af+ag<=this.view.max_high?af+ag:this.view.max_high);return new w.GenomeRegion({chrom:this.view.chrom,start:af,end:ae})},tool_region_and_parameters_str:function(ae){var ac=this,ad=(ae!==undefined?ae.toString():"all");return" - region=["+ad+"], parameters=["+ac.tool.get_param_values().join(", ")+"]"},data_and_mode_compatible:function(ac,ad){return true},can_subset:function(ac){return false},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state";this.normal_postdraw_actions=this.postdraw_actions;this.postdraw_actions=function(ae,af,ah,ac){var ad=this;ad.normal_postdraw_actions(ae,af,ah,ac);ad.dataset_check_type="converted_datasets_state";ad.data_query_wait=K;var ag=new k.ServerStateDeferred({url:ad.dataset_state_url,url_params:{dataset_id:ad.dataset.id,hda_ldda:ad.dataset.get("hda_ldda")},interval:ad.data_query_wait,success_fn:function(ai){return ai!=="pending"}});$.when(ag.go()).then(function(){ad.data_manager.set("data_type","data")});ad.postdraw_actions=ad.normal_postdraw_actions}}});var W=function(ad,ac){var ae={resize:false};g.call(this,ad,ac,ae);this.container_div.addClass("label-track")};p(W.prototype,g.prototype,{build_header_div:function(){},init:function(){this.enabled=true},predraw_init:function(){},_draw:function(){var ae=this.view,af=ae.high-ae.low,ai=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/ai)*ai,ag=this.view.container.width(),ad=$("<div style='position: relative; height: 1.3em;'></div>");while(ac<ae.high){var ah=(ac-ae.low)/af*ag;ad.append($("<div class='label'>"+commatize(ac)+"</div>").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){this.display_modes=A;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]=o(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 c||ah instanceof T){ah.change_mode("Coverage")}});this.update_icons();this.obj_type="CompositeTrack"};p(f.prototype,N.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(N.prototype.action_icons_def),to_dict:y.prototype.to_dict,add_drawable:y.prototype.add_drawable,unpack_drawables:y.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:q.prototype.can_draw,_get_drawables:function(){return this.drawables},show_group:function(){var af=new Q(this.view,this.container,{name:this.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(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(true)},postdraw_actions:function(ai,ac,al,ah){N.prototype.postdraw_actions.call(this,ai,ac,al,ah);var ag=-1;for(var ae=0;ae<ai.length;ae++){var aj=ai[ae].html_elt.find("canvas").height();if(aj>ag){ag=aj}}for(var ae=0;ae<ai.length;ae++){var af=ai[ae];if(af.html_elt.find("canvas").height()!==ag){this.draw_helper(true,af.region,af.resolution,af.html_elt.parent(),al,{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 B=function(ac){N.call(this,ac,{content_div:ac.top_labeltrack},{resize:false});ac.reference_track=this;this.left_offset=200;this.visible_height_px=12;this.container_div.addClass("reference-track");this.content_div.css("background","none");this.content_div.css("min-height","0px");this.content_div.css("border","none");this.data_url=reference_url+"/"+this.view.dbkey;this.data_url_extra_params={reference:true};this.data_manager=new w.GenomeReferenceDataManager({data_url:this.data_url,can_subset:this.can_subset});this.hide_contents()};p(B.prototype,q.prototype,N.prototype,{build_header_div:function(){},init:function(){this.data_manager.clear();this.enabled=true},predraw_init:function(){},can_draw:q.prototype.can_draw,draw_helper:function(ae,af,ac,ag,ah,ad){if(ah>this.view.canvas_manager.char_width_px){this.tiles_div.show();return N.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ah<aj;ah++){al.fillStyle=this.view.get_base_color(ak[ah]);al.fillText(ak[ah],Math.floor(ah*am),10)}return new b(this,ai,ad,ac,ae)}});var h=function(ae,ad,af){var ac=this;this.display_modes=A;this.mode="Histogram";N.call(this,ae,ad,af);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"color",label:"Color",type:"color",default_value:k.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:32,hidden:true}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);ac.request_redraw(true)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height};p(h.prototype,q.prototype,N.prototype,{before_draw:function(){},draw_tile:function(ac,ad,ag,ae,af,ah){return this._draw_line_track_tile(ac,ad,ag,ae,af,ah)},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 s=function(ae,ad,af){var ac=this;this.display_modes=["Heatmap"];this.mode="Heatmap";N.call(this,ae,ad,af);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{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}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);this.request_redraw(true)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height};p(s.prototype,q.prototype,N.prototype,{draw_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new b(this,ah,ag,af,ac.data)}});var c=function(af,ae,ah){var ad=this;this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];N.call(this,af,ae,ah);var ag=k.get_random_color(),ac=k.get_random_color([ag,"#FFFFFF"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block color",type:"color",default_value:ag},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{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:this.visible_height_px,hidden:true}],saved_values:ah.prefs,onchange:function(){ad.set_name(ad.prefs.name);ad.set_painter_from_config();ad.request_draw(true)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.container_div.addClass("feature-track");this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};p(c.prototype,q.prototype,N.prototype,{set_painter_from_config:function(){if(this.config.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;for(var 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(var 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 P){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){var 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 (t.FeatureSlotter)(ag,af,z,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(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=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,ar,af,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at<av;at++){var ah=am.data[at];var au=false;var al;for(var ax=0,aC=ak.length;ax<aC;ax++){al=ak[ax];al.update_attrs(ah);if(!al.keep(ah)){au=true;break}}if(!au){ai.push(ah);if(!(ah[0] in an)){all_slotted=false}}}}var aB=(this.filters_manager.alpha_filter?new C(this.filters_manager.alpha_filter):null),az=(this.filters_manager.height_filter?new C(this.filters_manager.height_filter):null),aA=new (this.painter)(ai,ay,ac,this.prefs,ao,aB,az,ae,function(aD){return ap.view.get_base_color(aD)});var aw=null;aq.fillStyle=this.prefs.block_color;aq.font=aq.canvas.manager.default_font;aq.textAlign="right";if(am.data){aw=aA.draw(aq,ad.width,ad.height,aj,an);aw.translation=-ag}return new P(ap,af,ar,ad,am.data,aj,ao,am.message,all_slotted,aw)},data_and_mode_compatible:function(ac,ad){if(ad==="Auto"){return true}else{if(ad==="Coverage"){return ac.dataset_type==="bigwig"}else{if(ac.extra_info==="no_detail"){return false}else{return true}}}},can_subset:function(ac){if(ac.dataset_type==="bigwig"||ac.message||ac.extra_info==="no_detail"){return false}return true}});var Z=function(ad,ac,ae){this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];N.call(this,ad,ac,ae);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{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}],saved_values:ae.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=L.VariantPainter;this.summary_draw_height=30;this.left_offset=30};p(Z.prototype,q.prototype,N.prototype,{draw_tile:function(ac,af,ai,ag,ah,aj){if(ac.dataset_type==="bigwig"){return this._draw_line_track_tile(ac,af,"Histogram",ag,ah,aj)}else{var ae=this.view,ad=new (this.painter)(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai,function(ak){return ae.get_base_color(ak)});ad.draw(af,af.canvas.width,af.canvas.height,aj);return new b(this,ah,ag,af.canvas,ac.data)}},get_canvas_height:function(ac,ag,ah,ae){if(ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var af=new (this.painter)(null,null,null,this.prefs,ag);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}}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(ae,af,ah,ad){N.prototype.postdraw_actions.call(this,ae,af,ah,ad);if(!(ae[0] instanceof M)&&this.prefs.show_labels){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 ag=this.dataset.get("metadata").get("sample_names").join("<br/>");this.tiles_div.prepend($("<div/>").html(ag).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(ae,ad,ag){c.call(this,ae,ad,ag);var af=k.get_random_color(),ac=k.get_random_color([af,"#ffffff"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block and sense strand color",type:"color",default_value:af},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{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"}],saved_values:ag.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=(ae.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};p(T.prototype,q.prototype,N.prototype,c.prototype);var d={CompositeTrack:f,DrawableGroup:Q,DiagonalHeatmapTrack:s,FeatureTrack:c,LineTrack:h,ReadTrack:T,VariantTrack:Z,VcfTrack:Z};var o=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 d[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:Q,LineTrack:h,FeatureTrack:c,DiagonalHeatmapTrack:s,ReadTrack:T,VariantTrack:Z,CompositeTrack:f,object_from_template:o}});
\ No newline at end of file
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d static/scripts/packed/viz/visualization.js
--- a/static/scripts/packed/viz/visualization.js
+++ b/static/scripts/packed/viz/visualization.js
@@ -1,1 +1,1 @@
-define(["libs/underscore","mvc/data","viz/trackster/util","utils/config"],function(s,i,l,o){var a=function(u,x,w,v){$.ajax({url:u,data:w,error:function(){alert("Grid failed")},success:function(y){show_modal("Select datasets for new tracks",y,{Cancel:function(){hide_modal()},Add:function(){var z=[];$("input[name=id]:checked,input[name=ldda_ids]:checked").each(function(){var A={data_type:"track_config",hda_ldda:"hda"},B=$(this).val();if($(this).attr("name")!=="id"){A.hda_ldda="ldda"}z[z.length]=$.ajax({url:x+"/"+B,data:A,dataType:"json"})});$.when.apply($,z).then(function(){var A=(arguments[0] instanceof Array?$.map(arguments,function(B){return B[0]}):[arguments[0]]);v(A)});hide_modal()}})}})};var j=function(u){return("isResolved" in u)};var f=function(u){this.default_font=u!==undefined?u:"9px Monaco, Lucida Console, monospace";this.dummy_canvas=this.new_canvas();this.dummy_context=this.dummy_canvas.getContext("2d");this.dummy_context.font=this.default_font;this.char_width_px=this.dummy_context.measureText("A").width;this.patterns={};this.load_pattern("right_strand","/visualization/strand_right.png");this.load_pattern("left_strand","/visualization/strand_left.png");this.load_pattern("right_strand_inv","/visualization/strand_right_inv.png");this.load_pattern("left_strand_inv","/visualization/strand_left_inv.png")};s.extend(f.prototype,{load_pattern:function(u,y){var v=this.patterns,w=this.dummy_context,x=new Image();x.src=galaxy_paths.attributes.image_path+y;x.onload=function(){v[u]=w.createPattern(x,"repeat")}},get_pattern:function(u){return this.patterns[u]},new_canvas:function(){var u=$("<canvas/>")[0];if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(u)}u.manager=this;return u}});var q=Backbone.Model.extend({defaults:{num_elements:20,obj_cache:null,key_ary:null},initialize:function(u){this.clear()},get_elt:function(v){var w=this.attributes.obj_cache,x=this.attributes.key_ary,u=x.indexOf(v);if(u!==-1){if(w[v].stale){x.splice(u,1);delete w[v]}else{this.move_key_to_end(v,u)}}return w[v]},set_elt:function(v,x){var y=this.attributes.obj_cache,z=this.attributes.key_ary,w=this.attributes.num_elements;if(!y[v]){if(z.length>=w){var u=z.shift();delete y[u]}z.push(v)}y[v]=x;return x},move_key_to_end:function(v,u){this.attributes.key_ary.splice(u,1);this.attributes.key_ary.push(v)},clear:function(){this.attributes.obj_cache={};this.attributes.key_ary=[]},size:function(){return this.attributes.key_ary.length},most_recently_added:function(){return this.size()===0?null:this.attributes.key_ary[this.attributes.key_ary.length-1]}});var d=q.extend({defaults:s.extend({},q.prototype.defaults,{dataset:null,genome:null,init_data:null,min_region_size:200,filters_manager:null,data_type:"data",data_mode_compatible:function(u,v){return true},can_subset:function(u){return false}}),initialize:function(u){q.prototype.initialize.call(this);var v=this.get("init_data");if(v){this.add_data(v)}},add_data:function(u){if(this.get("num_elements")<u.length){this.set("num_elements",u.length)}var v=this;s.each(u,function(w){v.set_data(w.region,w)})},data_is_ready:function(){var x=this.get("dataset"),w=$.Deferred(),u=(this.get("data_type")==="raw_data"?"state":this.get("data_type")==="data"?"converted_datasets_state":"error"),v=new l.ServerStateDeferred({ajax_settings:{url:this.get("dataset").url(),data:{hda_ldda:x.get("hda_ldda"),data_type:u},dataType:"json"},interval:5000,success_fn:function(y){return y!=="pending"}});$.when(v.go()).then(function(y){w.resolve(y==="ok"||y==="data")});return w},search_features:function(u){var v=this.get("dataset"),w={query:u,hda_ldda:v.get("hda_ldda"),data_type:"features"};return $.getJSON(v.url(),w)},load_data:function(C,B,v,A){var y=this.get("dataset"),x={data_type:this.get("data_type"),chrom:C.get("chrom"),low:C.get("start"),high:C.get("end"),mode:B,resolution:v,hda_ldda:y.get("hda_ldda")};$.extend(x,A);var E=this.get("filters_manager");if(E){var F=[];var u=E.filters;for(var z=0;z<u.length;z++){F.push(u[z].name)}x.filter_cols=JSON.stringify(F)}var w=this,D=$.getJSON(y.url(),x,function(G){G.region=C;w.set_data(C,G)});this.set_data(C,D);return D},get_data:function(B,A,w,y){var C=this.get_elt(B);if(C&&(j(C)||this.get("data_mode_compatible")(C,A))){return C}var D=this.get("key_ary"),v=this.get("obj_cache"),E,u,z;for(var x=0;x<D.length;x++){E=D[x];u=new g({from_str:E});if(u.contains(B)){z=true;C=v[E];if(j(C)||(this.get("data_mode_compatible")(C,A)&&this.get("can_subset")(C))){this.move_key_to_end(E,x);if(!j(C)){var G=this.subset_entry(C,B);this.set(B,G);C=G}return C}}}if(!z&&B.length()<this.attributes.min_region_size){var F=new g({from_str:this.most_recently_added()});if(!F||(B.get("start")>F.get("start"))){B.set("end",B.get("start")+this.attributes.min_region_size)}else{B.set("start",B.get("end")-this.attributes.min_region_size)}B.set("genome",this.attributes.genome);B.trim()}return this.load_data(B,A,w,y)},set_data:function(v,u){this.set_elt(v,u)},DEEP_DATA_REQ:"deep",BROAD_DATA_REQ:"breadth",get_more_data:function(C,B,x,A,y){var E=this._mark_stale(C);if(!(E&&this.get("data_mode_compatible")(E,B))){console.log("ERROR: problem with getting more data: current data is not compatible");return}var w=C.get("start");if(y===this.DEEP_DATA_REQ){$.extend(A,{start_val:E.data.length+1})}else{if(y===this.BROAD_DATA_REQ){w=(E.max_high?E.max_high:E.data[E.data.length-1][2])+1}}var D=C.copy().set("start",w);var v=this,z=this.load_data(D,B,x,A),u=$.Deferred();this.set_data(C,u);$.when(z).then(function(F){if(F.data){F.data=E.data.concat(F.data);if(F.max_low){F.max_low=E.max_low}if(F.message){F.message=F.message.replace(/[0-9]+/,F.data.length)}}v.set_data(C,F);u.resolve(F)});return u},can_get_more_detailed_data:function(v){var u=this.get_elt(v);return(u.dataset_type==="bigwig"&&u.data.length<8000)},get_more_detailed_data:function(x,z,v,y,w){var u=this._mark_stale(x);if(!u){console.log("ERROR getting more detailed data: no current data");return}if(!w){w={}}if(u.dataset_type==="bigwig"){w.num_samples=1000*y}else{if(u.dataset_type==="summary_tree"){w.level=Math.min(u.level-1,2)}}return this.load_data(x,z,v,w)},_mark_stale:function(v){var u=this.get_elt(v);if(!u){console.log("ERROR: no data to mark as stale: ",this.get("dataset"),v.toString())}u.stale=true;return u},get_genome_wide_data:function(u){var w=this,y=true,x=s.map(u.get("chroms_info").chrom_info,function(A){var z=w.get_elt(new g({chrom:A.chrom,start:0,end:A.len}));if(!z){y=false}return z});if(y){return x}var v=$.Deferred();$.getJSON(this.get("dataset").url(),{data_type:"genome_data"},function(z){w.add_data(z.data);v.resolve(z.data)});return v},subset_entry:function(w,x){var u={bigwig:function(y,z){return s.filter(y,function(A){return A[0]>=z.get("start")&&A[0]<=z.get("end")})},refseq:function(z,A){var B=A.get("start")-w.region.get("start"),y=w.data.length-(w.region.get("end")-A.get("end"));return w.data.slice(B,y)}};var v=w.data;if(!w.region.same(x)&&w.dataset_type in u){v=u[w.dataset_type](w.data,x)}return{region:x,data:v,dataset_type:w.dataset_type}},get_elt:function(u){return q.prototype.get_elt.call(this,u.toString())},set_elt:function(v,u){return q.prototype.set_elt.call(this,v.toString(),u)}});var p=d.extend({initialize:function(u){var v=new Backbone.Model();v.urlRoot=u.data_url;this.set("dataset",v)},load_data:function(w,x,u,v){return(w.length()<=100000?d.prototype.load_data.call(this,w,x,u,v):{data:null,region:w})}});var c=Backbone.Model.extend({defaults:{name:null,key:null,chroms_info:null},initialize:function(u){this.id=u.dbkey},get_chroms_info:function(){return this.attributes.chroms_info.chrom_info},get_chrom_region:function(u){var v=s.find(this.get_chroms_info(),function(w){return w.chrom===u});return new g({chrom:v.chrom,end:v.len})},get_chrom_len:function(u){return s.find(this.get_chroms_info(),function(v){return v.chrom===u}).len}});var g=Backbone.RelationalModel.extend({defaults:{chrom:null,start:0,end:0,genome:null},same:function(u){return this.attributes.chrom===u.get("chrom")&&this.attributes.start===u.get("start")&&this.attributes.end===u.get("end")},initialize:function(v){if(v.from_str){var x=v.from_str.split(":"),w=x[0],u=x[1].split("-");this.set({chrom:w,start:parseInt(u[0],10),end:parseInt(u[1],10)})}},copy:function(){return new g({chrom:this.get("chrom"),start:this.get("start"),end:this.get("end")})},length:function(){return this.get("end")-this.get("start")},toString:function(){return this.get("chrom")+":"+this.get("start")+"-"+this.get("end")},toJSON:function(){return{chrom:this.get("chrom"),start:this.get("start"),end:this.get("end")}},compute_overlap:function(B){var v=this.get("chrom"),A=B.get("chrom"),z=this.get("start"),x=B.get("start"),y=this.get("end"),w=B.get("end"),u;if(v&&A&&v!==A){return g.overlap_results.DIF_CHROMS}if(z<x){if(y<x){u=g.overlap_results.BEFORE}else{if(y<w){u=g.overlap_results.OVERLAP_START}else{u=g.overlap_results.CONTAINS}}}else{if(z>x){if(z>w){u=g.overlap_results.AFTER}else{if(y<=w){u=g.overlap_results.CONTAINED_BY}else{u=g.overlap_results.OVERLAP_END}}}else{u=(y>=w?g.overlap_results.CONTAINS:g.overlap_results.CONTAINED_BY)}}return u},trim:function(u){if(this.attributes.start<0){this.attributes.start=0}if(this.attributes.genome){var v=this.attributes.genome.get_chrom_len(this.attributes.chrom);if(this.attributes.end>v){this.attributes.end=v-1}}return this},contains:function(u){return this.compute_overlap(u)===g.overlap_results.CONTAINS},overlaps:function(u){return s.intersection([this.compute_overlap(u)],[g.overlap_results.DIF_CHROMS,g.overlap_results.BEFORE,g.overlap_results.AFTER]).length===0}},{overlap_results:{DIF_CHROMS:1000,BEFORE:1001,CONTAINS:1002,OVERLAP_START:1003,OVERLAP_END:1004,CONTAINED_BY:1005,AFTER:1006}});var m=Backbone.Collection.extend({model:g});var e=Backbone.RelationalModel.extend({defaults:{region:null,note:""},relations:[{type:Backbone.HasOne,key:"region",relatedModel:g}]});var r=Backbone.Collection.extend({model:e});var t=i.Dataset.extend({initialize:function(u){this.set("id",u.dataset_id);this.set("config",o.ConfigSettingCollection.from_config_dict(u.prefs));this.get("config").add([{key:"name",value:this.get("name")},{key:"color"}]);var v=this.get("preloaded_data");if(v){v=v.data}else{v=[]}this.set("data_manager",new d({dataset:this,init_data:v}))}});var n=Backbone.RelationalModel.extend({defaults:{title:"",type:""},url:galaxy_paths.get("visualization_url"),save:function(){return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(this)}})}});var k=n.extend({defaults:s.extend({},n.prototype.defaults,{dbkey:"",tracks:null,bookmarks:null,viewport:null}),relations:[{type:Backbone.HasMany,key:"tracks",relatedModel:t}],add_tracks:function(u){this.get("tracks").add(u)}});var b=Backbone.Model.extend({});var h=Backbone.Router.extend({initialize:function(v){this.view=v.view;this.route(/([\w]+)$/,"change_location");this.route(/([\w]+\:[\d,]+-[\d,]+)$/,"change_location");var u=this;u.view.on("navigate",function(w){u.navigate(w)})},change_location:function(u){this.view.go_to(u)}});return{BackboneTrack:t,BrowserBookmark:e,BrowserBookmarkCollection:r,Cache:q,CanvasManager:f,Genome:c,GenomeDataManager:d,GenomeRegion:g,GenomeRegionCollection:m,GenomeVisualization:k,GenomeReferenceDataManager:p,TrackBrowserRouter:h,TrackConfig:b,Visualization:n,select_datasets:a}});
\ No newline at end of file
+define(["libs/underscore","mvc/data","viz/trackster/util","utils/config"],function(s,i,l,o){var a=function(u,x,w,v){$.ajax({url:u,data:w,error:function(){alert("Grid failed")},success:function(y){show_modal("Select datasets for new tracks",y,{Cancel:function(){hide_modal()},Add:function(){var z=[];$("input[name=id]:checked,input[name=ldda_ids]:checked").each(function(){var A={data_type:"track_config",hda_ldda:"hda"},B=$(this).val();if($(this).attr("name")!=="id"){A.hda_ldda="ldda"}z[z.length]=$.ajax({url:x+"/"+B,data:A,dataType:"json"})});$.when.apply($,z).then(function(){var A=(arguments[0] instanceof Array?$.map(arguments,function(B){return B[0]}):[arguments[0]]);v(A)});hide_modal()}})}})};var j=function(u){return("isResolved" in u)};var f=function(u){this.default_font=u!==undefined?u:"9px Monaco, Lucida Console, monospace";this.dummy_canvas=this.new_canvas();this.dummy_context=this.dummy_canvas.getContext("2d");this.dummy_context.font=this.default_font;this.char_width_px=this.dummy_context.measureText("A").width;this.patterns={};this.load_pattern("right_strand","/visualization/strand_right.png");this.load_pattern("left_strand","/visualization/strand_left.png");this.load_pattern("right_strand_inv","/visualization/strand_right_inv.png");this.load_pattern("left_strand_inv","/visualization/strand_left_inv.png")};s.extend(f.prototype,{load_pattern:function(u,y){var v=this.patterns,w=this.dummy_context,x=new Image();x.src=galaxy_paths.attributes.image_path+y;x.onload=function(){v[u]=w.createPattern(x,"repeat")}},get_pattern:function(u){return this.patterns[u]},new_canvas:function(){var u=$("<canvas/>")[0];if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(u)}u.manager=this;return u}});var q=Backbone.Model.extend({defaults:{num_elements:20,obj_cache:null,key_ary:null},initialize:function(u){this.clear()},get_elt:function(v){var w=this.attributes.obj_cache,x=this.attributes.key_ary,u=x.indexOf(v);if(u!==-1){if(w[v].stale){x.splice(u,1);delete w[v]}else{this.move_key_to_end(v,u)}}return w[v]},set_elt:function(v,x){var y=this.attributes.obj_cache,z=this.attributes.key_ary,w=this.attributes.num_elements;if(!y[v]){if(z.length>=w){var u=z.shift();delete y[u]}z.push(v)}y[v]=x;return x},move_key_to_end:function(v,u){this.attributes.key_ary.splice(u,1);this.attributes.key_ary.push(v)},clear:function(){this.attributes.obj_cache={};this.attributes.key_ary=[]},size:function(){return this.attributes.key_ary.length},most_recently_added:function(){return this.size()===0?null:this.attributes.key_ary[this.attributes.key_ary.length-1]}});var d=q.extend({defaults:s.extend({},q.prototype.defaults,{dataset:null,genome:null,init_data:null,min_region_size:200,filters_manager:null,data_type:"data",data_mode_compatible:function(u,v){return true},can_subset:function(u){return false}}),initialize:function(u){q.prototype.initialize.call(this);var v=this.get("init_data");if(v){this.add_data(v)}},add_data:function(u){if(this.get("num_elements")<u.length){this.set("num_elements",u.length)}var v=this;s.each(u,function(w){v.set_data(w.region,w)})},data_is_ready:function(){var x=this.get("dataset"),w=$.Deferred(),u=(this.get("data_type")==="raw_data"?"state":this.get("data_type")==="data"?"converted_datasets_state":"error"),v=new l.ServerStateDeferred({ajax_settings:{url:this.get("dataset").url(),data:{hda_ldda:x.get("hda_ldda"),data_type:u},dataType:"json"},interval:5000,success_fn:function(y){return y!=="pending"}});$.when(v.go()).then(function(y){w.resolve(y==="ok"||y==="data")});return w},search_features:function(u){var v=this.get("dataset"),w={query:u,hda_ldda:v.get("hda_ldda"),data_type:"features"};return $.getJSON(v.url(),w)},load_data:function(C,B,v,A){var y=this.get("dataset"),x={data_type:this.get("data_type"),chrom:C.get("chrom"),low:C.get("start"),high:C.get("end"),mode:B,resolution:v,hda_ldda:y.get("hda_ldda")};$.extend(x,A);var E=this.get("filters_manager");if(E){var F=[];var u=E.filters;for(var z=0;z<u.length;z++){F.push(u[z].name)}x.filter_cols=JSON.stringify(F)}var w=this,D=$.getJSON(y.url(),x,function(G){G.region=C;w.set_data(C,G)});this.set_data(C,D);return D},get_data:function(B,A,w,y){var C=this.get_elt(B);if(C&&(j(C)||this.get("data_mode_compatible")(C,A))){return C}var D=this.get("key_ary"),v=this.get("obj_cache"),E,u,z;for(var x=0;x<D.length;x++){E=D[x];u=new g({from_str:E});if(u.contains(B)){z=true;C=v[E];if(j(C)||(this.get("data_mode_compatible")(C,A)&&this.get("can_subset")(C))){this.move_key_to_end(E,x);if(!j(C)){var G=this.subset_entry(C,B);this.set(B,G);C=G}return C}}}if(!z&&B.length()<this.attributes.min_region_size){var F=new g({from_str:this.most_recently_added()});if(!F||(B.get("start")>F.get("start"))){B.set("end",B.get("start")+this.attributes.min_region_size)}else{B.set("start",B.get("end")-this.attributes.min_region_size)}B.set("genome",this.attributes.genome);B.trim()}return this.load_data(B,A,w,y)},set_data:function(v,u){this.set_elt(v,u)},DEEP_DATA_REQ:"deep",BROAD_DATA_REQ:"breadth",get_more_data:function(C,B,x,A,y){var E=this._mark_stale(C);if(!(E&&this.get("data_mode_compatible")(E,B))){console.log("ERROR: problem with getting more data: current data is not compatible");return}var w=C.get("start");if(y===this.DEEP_DATA_REQ){$.extend(A,{start_val:E.data.length+1})}else{if(y===this.BROAD_DATA_REQ){w=(E.max_high?E.max_high:E.data[E.data.length-1][2])+1}}var D=C.copy().set("start",w);var v=this,z=this.load_data(D,B,x,A),u=$.Deferred();this.set_data(C,u);$.when(z).then(function(F){if(F.data){F.data=E.data.concat(F.data);if(F.max_low){F.max_low=E.max_low}if(F.message){F.message=F.message.replace(/[0-9]+/,F.data.length)}}v.set_data(C,F);u.resolve(F)});return u},can_get_more_detailed_data:function(v){var u=this.get_elt(v);return(u.dataset_type==="bigwig"&&u.data.length<8000)},get_more_detailed_data:function(x,z,v,y,w){var u=this._mark_stale(x);if(!u){console.log("ERROR getting more detailed data: no current data");return}if(!w){w={}}if(u.dataset_type==="bigwig"){w.num_samples=1000*y}return this.load_data(x,z,v,w)},_mark_stale:function(v){var u=this.get_elt(v);if(!u){console.log("ERROR: no data to mark as stale: ",this.get("dataset"),v.toString())}u.stale=true;return u},get_genome_wide_data:function(u){var w=this,y=true,x=s.map(u.get("chroms_info").chrom_info,function(A){var z=w.get_elt(new g({chrom:A.chrom,start:0,end:A.len}));if(!z){y=false}return z});if(y){return x}var v=$.Deferred();$.getJSON(this.get("dataset").url(),{data_type:"genome_data"},function(z){w.add_data(z.data);v.resolve(z.data)});return v},subset_entry:function(w,x){var u={bigwig:function(y,z){return s.filter(y,function(A){return A[0]>=z.get("start")&&A[0]<=z.get("end")})},refseq:function(z,A){var B=A.get("start")-w.region.get("start"),y=w.data.length-(w.region.get("end")-A.get("end"));return w.data.slice(B,y)}};var v=w.data;if(!w.region.same(x)&&w.dataset_type in u){v=u[w.dataset_type](w.data,x)}return{region:x,data:v,dataset_type:w.dataset_type}},get_elt:function(u){return q.prototype.get_elt.call(this,u.toString())},set_elt:function(v,u){return q.prototype.set_elt.call(this,v.toString(),u)}});var p=d.extend({initialize:function(u){var v=new Backbone.Model();v.urlRoot=u.data_url;this.set("dataset",v)},load_data:function(w,x,u,v){return(w.length()<=100000?d.prototype.load_data.call(this,w,x,u,v):{data:null,region:w})}});var c=Backbone.Model.extend({defaults:{name:null,key:null,chroms_info:null},initialize:function(u){this.id=u.dbkey},get_chroms_info:function(){return this.attributes.chroms_info.chrom_info},get_chrom_region:function(u){var v=s.find(this.get_chroms_info(),function(w){return w.chrom===u});return new g({chrom:v.chrom,end:v.len})},get_chrom_len:function(u){return s.find(this.get_chroms_info(),function(v){return v.chrom===u}).len}});var g=Backbone.RelationalModel.extend({defaults:{chrom:null,start:0,end:0,genome:null},same:function(u){return this.attributes.chrom===u.get("chrom")&&this.attributes.start===u.get("start")&&this.attributes.end===u.get("end")},initialize:function(v){if(v.from_str){var x=v.from_str.split(":"),w=x[0],u=x[1].split("-");this.set({chrom:w,start:parseInt(u[0],10),end:parseInt(u[1],10)})}},copy:function(){return new g({chrom:this.get("chrom"),start:this.get("start"),end:this.get("end")})},length:function(){return this.get("end")-this.get("start")},toString:function(){return this.get("chrom")+":"+this.get("start")+"-"+this.get("end")},toJSON:function(){return{chrom:this.get("chrom"),start:this.get("start"),end:this.get("end")}},compute_overlap:function(B){var v=this.get("chrom"),A=B.get("chrom"),z=this.get("start"),x=B.get("start"),y=this.get("end"),w=B.get("end"),u;if(v&&A&&v!==A){return g.overlap_results.DIF_CHROMS}if(z<x){if(y<x){u=g.overlap_results.BEFORE}else{if(y<w){u=g.overlap_results.OVERLAP_START}else{u=g.overlap_results.CONTAINS}}}else{if(z>x){if(z>w){u=g.overlap_results.AFTER}else{if(y<=w){u=g.overlap_results.CONTAINED_BY}else{u=g.overlap_results.OVERLAP_END}}}else{u=(y>=w?g.overlap_results.CONTAINS:g.overlap_results.CONTAINED_BY)}}return u},trim:function(u){if(this.attributes.start<0){this.attributes.start=0}if(this.attributes.genome){var v=this.attributes.genome.get_chrom_len(this.attributes.chrom);if(this.attributes.end>v){this.attributes.end=v-1}}return this},contains:function(u){return this.compute_overlap(u)===g.overlap_results.CONTAINS},overlaps:function(u){return s.intersection([this.compute_overlap(u)],[g.overlap_results.DIF_CHROMS,g.overlap_results.BEFORE,g.overlap_results.AFTER]).length===0}},{overlap_results:{DIF_CHROMS:1000,BEFORE:1001,CONTAINS:1002,OVERLAP_START:1003,OVERLAP_END:1004,CONTAINED_BY:1005,AFTER:1006}});var m=Backbone.Collection.extend({model:g});var e=Backbone.RelationalModel.extend({defaults:{region:null,note:""},relations:[{type:Backbone.HasOne,key:"region",relatedModel:g}]});var r=Backbone.Collection.extend({model:e});var t=i.Dataset.extend({initialize:function(u){this.set("id",u.dataset_id);this.set("config",o.ConfigSettingCollection.from_config_dict(u.prefs));this.get("config").add([{key:"name",value:this.get("name")},{key:"color"}]);var v=this.get("preloaded_data");if(v){v=v.data}else{v=[]}this.set("data_manager",new d({dataset:this,init_data:v}))}});var n=Backbone.RelationalModel.extend({defaults:{title:"",type:""},url:galaxy_paths.get("visualization_url"),save:function(){return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(this)}})}});var k=n.extend({defaults:s.extend({},n.prototype.defaults,{dbkey:"",tracks:null,bookmarks:null,viewport:null}),relations:[{type:Backbone.HasMany,key:"tracks",relatedModel:t}],add_tracks:function(u){this.get("tracks").add(u)}});var b=Backbone.Model.extend({});var h=Backbone.Router.extend({initialize:function(v){this.view=v.view;this.route(/([\w]+)$/,"change_location");this.route(/([\w]+\:[\d,]+-[\d,]+)$/,"change_location");var u=this;u.view.on("navigate",function(w){u.navigate(w)})},change_location:function(u){this.view.go_to(u)}});return{BackboneTrack:t,BrowserBookmark:e,BrowserBookmarkCollection:r,Cache:q,CanvasManager:f,Genome:c,GenomeDataManager:d,GenomeRegion:g,GenomeRegionCollection:m,GenomeVisualization:k,GenomeReferenceDataManager:p,TrackBrowserRouter:h,TrackConfig:b,Visualization:n,select_datasets:a}});
\ No newline at end of file
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d templates/webapps/galaxy/visualization/scatterplot.mako
--- a/templates/webapps/galaxy/visualization/scatterplot.mako
+++ b/templates/webapps/galaxy/visualization/scatterplot.mako
@@ -222,9 +222,9 @@
<script type="text/javascript">
$(function(){
- var hda = ${h.to_json_string( hda )},
- historyID = '${historyID}',
- querySettings = ${h.to_json_string( kwargs )},
+ var hda = ${h.to_json_string( hda.get_api_value() )},
+ historyID = '${trans.security.encode_id( hda.history.id )}',
+ querySettings = ${h.to_json_string( query_args )},
chartConfig = _.extend( querySettings, {
containerSelector : '#chart',
//TODO: move to ScatterplotControlForm.initialize
@@ -250,8 +250,8 @@
<%def name="body()"><!--dataset info--><div id="chart-header" class="header">
- <h2 class="title">Scatterplot of '${hda['name']}'</h2>
- <p class="subtitle">${hda['misc_info']}</p>
+ <h2 class="title">Scatterplot of '${hda.name}'</h2>
+ <p class="subtitle">${hda.info}</p></div><div id="scatterplot" class="scatterplot-control-form"></div></%def>
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d templates/webapps/galaxy/visualization/v_fwork_test.mako
--- /dev/null
+++ b/templates/webapps/galaxy/visualization/v_fwork_test.mako
@@ -0,0 +1,132 @@
+<%inherit file="/base.mako"/>
+<%def name="title()">
+${visualization_name}
+</%def>
+
+
+<%def name="stylesheets()">
+${parent.stylesheets()}
+${h.css(
+ "base",
+)}
+
+<style type="text/css">
+/*TODO: use/move into base.less*/
+* { margin: 0px; padding: 0px; }
+</style>
+
+</%def>
+
+<%def name="process_hda( hda )">
+<%
+ hda_dict = hda.get_api_value()
+ hda_dict[ 'id' ] = trans.security.encode_id( hda_dict[ 'id' ] )
+ hda_dict[ 'history_id' ] = trans.security.encode_id( hda_dict[ 'history_id' ] )
+ del hda_dict[ 'peek' ]
+ return hda_dict
+%>
+</%def>
+
+<%def name="javascripts()">
+${parent.javascripts()}
+<script type="text/javascript">
+$(function(){
+
+//var data = {
+// title : 'shared with user visualization',
+// type : 'test',
+// slug : 'shared',
+// annotation : 'a visualization shared with a specific user',
+// config : {
+// x : 10,
+// y : 10
+// }
+// };
+
+//var creationPromise = jQuery.ajax( '/api/visualizations', {
+// type : 'POST',
+// contentType : 'application/json',
+// data : JSON.stringify( data )
+// });
+//creationPromise.success(function(){
+// console.debug( 'success' );
+//});
+//creationPromise.error(function(){
+// console.debug( 'error' );
+//});
+
+
+});
+</script>
+</%def>
+
+<%def name="print_var( name, var )">
+%if var is not None:
+ <% t = str( type( var ) )[1:-1] %>
+ <p>${name}: ${t}, ${var}</p>
+%else:
+ <p>No ${name}</p>
+%endif
+</%def>
+
+<%def name="body()">
+ <%
+ import pprint
+ print self
+ print self.context
+ pprint.pprint( self.context.kwargs, indent=4 )
+ %>
+ <%
+ vars_to_print = [
+ ( 'default', default ),
+ ( 'string', string ),
+ ( 'boolean', boolean ),
+ ( 'integer', integer ),
+ ( 'float', float ),
+ ( 'json', json ),
+ ]
+ %>
+ %for name, var in vars_to_print:
+ ${print_var( name, var )}
+ %endfor
+
+ %if visualization:
+ <h1>${visualization.title}</h1>
+ <p>id: ${trans.security.encode_id( visualization.id )}</p>
+ <p>dbkey: ${visualization.dbkey}</p>
+ <p>config:
+ <pre>${h.to_json_string( visualization.latest_revision.config, sort_keys=True, indent=( 4 * ' ' ) )}</pre>
+ </p>
+ %endif
+
+ %if dataset:
+ <h1>${dataset.name}</h1>
+ <p>id: ${trans.security.encode_id( dataset.id )}</p>
+ <p>history id: ${trans.security.encode_id( dataset.history.id )}</p>
+ <pre>
+ ${h.to_json_string( process_hda( dataset ), sort_keys=True, indent=( 4 * ' ' ) )}
+ </pre>
+ %endif
+
+ %if dataset_instance:
+ <h1>${dataset_instance.name}</h1>
+ <p>id: ${trans.security.encode_id( dataset_instance.id )}</p>
+ %if hda_ldda == 'hda':
+ <p>history id: ${trans.security.encode_id( dataset_instance.history.id )}</p>
+ <pre>
+ ${h.to_json_string( process_hda( dataset_instance ), sort_keys=True, indent=( 4 * ' ' ) )}
+ </pre>
+ %else:
+ <p>(LibraryDatasetDatasetAssociation)</p>
+ %endif
+ %endif
+
+ %if query_args:
+ <ul>
+ %for key, val in query_args.items():
+ <li>${key} : ${val}</li>
+ %endfor
+ </ul>
+ %endif
+
+</%def>
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d universe_wsgi.ini.sample
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -171,6 +171,10 @@
# Galaxy.
#datatypes_config_file = datatypes_conf.xml
+# Visualizations config file, defines what visualizations apply to particular data and how to pass them
+# the necessary parameters
+#visualizations_conf_path = visualizations_conf.xml
+
# Each job is given a unique empty directory as its current working directory.
# This option defines in what parent directory those directories will be
# created.
diff -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae -r ffc6ff4fe6dfef48d6440c1cdb910661010f559d visualizations_conf.xml.sample
--- /dev/null
+++ b/visualizations_conf.xml.sample
@@ -0,0 +1,292 @@
+<?xml version="1.0"?>
+<!--
+ This is the xml file to edit to add new visualizations to the framework.
+ NOTE!: this is a work in progress!
+
+ Note: also that visualizations that fail to parse in visualizations/registry will
+ produce an error in the server log, but otherwise will be skipped and not available.
+ If you can't find your visualization in the UI, check the server logs for errors
+ during start up.
+-->
+
+<!-- .......................................................................... DTD -->
+<!-- runnable on NIX with xmllint -->
+<!DOCTYPE visualizations [
+ <!-- 0 or more visualizations -->
+ <!ELEMENT visualizations (visualization*)>
+ <!-- each visualization must have a template (all other elements are optional) -->
+ <!ELEMENT visualization (data_sources*,params*,template_root*,template,link_text*,render_location*)>
+ <!-- visualization name (e.g. 'trackster', 'scatterplot', etc.) is required -->
+ <!ATTLIST visualization
+ name CDATA #REQUIRED
+ >
+
+ <!ELEMENT data_sources (data_source*)>
+ <!-- data sources are elements that describe what objects (HDAs, LDDAs, Job, User, etc.)
+ are applicable to a visualization. Often these are used to fetch applicable links
+ to the visualizations that use them.
+ -->
+ <!ELEMENT data_source (model_class,(test|to_param)*)>
+ <!ELEMENT model_class (#PCDATA)>
+ <!-- model_class is currently the class name of the object you want to make a visualization
+ applicable to (e.g. HistoryDatasetAssociation). Currently only classes in galaxy.model
+ can be used.
+ REQUIRED and currently limited to: 'HistoryDatasetAssociation', 'LibraryDatasetDatasetAssociation'
+ -->
+ <!ELEMENT test (#PCDATA)>
+ <!-- tests help define what conditions the visualization can be applied to the model_class/target.
+ Currently, all tests are OR'd and there is no logical grouping. Tests are run in order.
+ (text): the text of this element is what the given target will be compared to (REQUIRED)
+ type: what type of test to run (e.g. when the target is an HDA the test will often be of type 'isinstance'
+ and test whether the HDA's datatype isinstace of a class)
+ DEFAULT: string comparison.
+ test_attr: what attribute of the target object should be used in the test. For instance, 'datatype'
+ will attempt to get the HDA.datatype from a target HDA. If the given object doesn't have
+ that attribute the test will fail (with no error). test_attr can be dot separated attributes,
+ looking up each in turn. For example, if the target was a history, one could access the
+ history.user.email by setting test_attr to 'user.email' (why you would want that, I don't know)
+ DEFAULT: to comparing the object itself (and not any of it's attributes)
+ result_type: if the result (the text of the element mentioned above) needs to be parsed into
+ something other than a string, result_type will tell the registry how to do this. E.g.
+ if result_type is 'datatype' the registry will assume the text is a datatype class name
+ and parse it into the proper class before the test (often 'isinstance') is run.
+ DEFAULT: no parsing (result should be a string)
+ -->
+ <!ATTLIST test
+ type CDATA #IMPLIED
+ test_attr CDATA #IMPLIED
+ result_type CDATA #IMPLIED
+ >
+
+ <!ELEMENT to_param (#PCDATA)>
+ <!-- to_param tells the registry how to parse the data_source into a query string param.
+ For example, HDA data_sources can set param_to text to 'dataset_id' and param_attr to 'id' and the
+ the target HDA (if it passes the tests) will be passed as "dataset_id=HDA.id"
+ (text): the query string param key this source will be parsed into (e.g. dataset_id)
+ REQUIRED
+ param_attr: the attribute of the data_source object to use as the value in the query string param.
+ E.g. param_attr='id' for an HDA data_source would use the (encoded) id.
+ NOTE: a to_param MUST have either a param_attr or assign
+ assign: you can use this to directly assign a value to a query string's param. E.g. if the
+ data_source is a LDDA we can set 'hda_or_ldda=ldda' using assign='ldda'.
+ NOTE: a to_param MUST have either a param_attr or assign
+ -->
+ <!ATTLIST to_param
+ param_attr CDATA #IMPLIED
+ assign CDATA #IMPLIED
+ >
+
+ <!ELEMENT params ((param|param_modifier)*)>
+ <!-- params describe what data will be sent to a visualization template and
+ how to convert them from a query string in a URL into variables usable in a template.
+ For example,
+ param_modifiers are a special class of parameters that modify other params
+ (e.g. hda_ldda can be 'hda' or 'ldda' and modifies/informs dataset_id to fetch an HDA or LDDA)
+ -->
+ <!ELEMENT param (#PCDATA)>
+ <!-- param tells the registry how to parse the query string param back into a resource/data_source.
+ For example, if a query string has "dataset_id=NNN" and the type is 'dataset', the registry
+ will attempt to fetch the hda with id of NNN from the database and pass it to the template.
+ (text): the query string param key this source will be parsed from (e.g. dataset_id)
+ REQUIRED
+ type: the type of the resource.
+ Can be: str (DEFAULT), bool, int, float, json, visualization, dbkey, dataset, or hda_ldda.
+ default: if a param is not passed on the query string (and is not required) OR the given param
+ fails to parse, this value is used instead.
+ DEFAULT: None
+ required: set this to true if the param is required for the template. Rendering will with an error
+ if the param hasn't been sent.
+ DEFAULT: false
+ csv: set this to true if the param is a comma separated list. The registry will attempt to
+ parse each value as the given type and send the result as a list to the template.
+ DEFAULT: false
+ constrain_to: (currently unused) constain a param to a set of values, error if not valid.
+ DEFAULT: don't constrain
+ var_name_in_template: a new name for the resource/variable to use in the template. E.g. an initial
+ query string param key might be 'dataset_id' in the URL, the registry parses it into an HDA,
+ and if var_name_in_template is set to 'hda', the template will be able to access the HDA
+ with the variable name 'hda' (as in hda.title).
+ DEFAULT: keep the original query string name
+ -->
+ <!ATTLIST param
+ type CDATA #IMPLIED
+ default CDATA #IMPLIED
+ required CDATA #IMPLIED
+ csv CDATA #IMPLIED
+ constrain_to CDATA #IMPLIED
+ var_name_in_template CDATA #IMPLIED
+ >
+ <!-- param_modifiers are the same as param but have a REQUIRED 'modifies' attribute.
+ 'modifies' must point to the param name (the text part of param element) that it will modify.
+ E.g. <param_modifier modifies="dataset_id">hda_ldda</param_modifier>
+ -->
+ <!ELEMENT param_modifier (#PCDATA)>
+ <!ATTLIST param_modifier
+ modifies CDATA #REQUIRED
+ type CDATA #IMPLIED
+ default CDATA #IMPLIED
+ required CDATA #IMPLIED
+ csv CDATA #IMPLIED
+ constrain_to CDATA #IMPLIED
+ var_name_in_template CDATA #IMPLIED
+ >
+
+ <!-- template_root: the directory to search for the template relative to templates/webapps/galaxy
+ (optional) DEFAULT: visualizations
+ -->
+ <!ELEMENT template_root (#PCDATA)>
+ <!-- template: the template used to render the visualization. REQUIRED -->
+ <!ELEMENT template (#PCDATA)>
+ <!-- link_text: the text component of an html anchor displayed when the registry builds the link information -->
+ <!ELEMENT link_text (#PCDATA)>
+ <!-- render_location: used as the target attribute of the link to the visualization.
+ Can be 'galaxy_main', '_top', '_blank'. DEFAULT: 'galaxy_main'
+ -->
+ <!-- TODO: rename -> render_target -->
+ <!ELEMENT render_location (#PCDATA)>
+]>
+
+<!-- .......................................................................... configuration xml -->
+<visualizations>
+ <visualization name="trackster">
+ <!--not tested yet -->
+ <data_sources>
+ <data_source>
+ <model_class>HistoryDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="hda">hda_ldda</to_param>
+ <to_param param_attr="dbkey">dbkey</to_param>
+ </data_source>
+ <data_source>
+ <model_class>LibraryDatasetDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="ldda">hda_ldda</to_param>
+ </data_source>
+ </data_sources>
+ <params>
+ <param type="visualization">id</param>
+ <param type="dataset">dataset_id</param>
+ <param type="genome_region">genome_region</param>
+ <param type="dbkey">dbkey</param>
+ </params>
+ <template_root>tracks</template_root>
+ <template>browser.mako</template>
+ <render_location>_top</render_location>
+ </visualization>
+
+ <visualization name="circster">
+ <data_sources>
+ <data_source>
+ <model_class>HistoryDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="hda">hda_ldda</to_param>
+ </data_source>
+ <data_source>
+ <model_class>LibraryDatasetDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="ldda">hda_ldda</to_param>
+ </data_source>
+ </data_sources>
+ <params>
+ <param type="visualization">id</param>
+ <param type="hda_or_ldda">dataset_id</param>
+ <param_modifier type="string" modifies="dataset_id">hda_ldda</param_modifier>
+ <param type="dbkey">dbkey</param>
+ </params>
+ <template>circster.mako</template>
+ <render_location>_top</render_location>
+ </visualization>
+
+ <!--
+ <visualization name="sweepster">
+ <data_sources>
+ <data_source>
+ <model_class>HistoryDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="hda">hda_ldda</to_param>
+ </data_source>
+ <data_source>
+ <model_class>LibraryDatasetDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="ldda">hda_ldda</to_param>
+ </data_source>
+ </data_sources>
+ <params>
+ <param type="visualization" var_name_in_template="viz">visualization</param>
+ <param type="hda_or_ldda" var_name_in_template="dataset">dataset_id</param>
+ <param_modifier type="string" modifies="dataset_id">hda_ldda</param_modifier>
+ </params>
+ <template>sweepster.mako</template>
+ <render_location>_top</render_location>
+ </visualization>
+ -->
+
+ <visualization name="phyloviz">
+ <data_sources>
+ <data_source>
+ <model_class>HistoryDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Newick</test>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Nexus</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ </data_source>
+ </data_sources>
+ <params>
+ <param type="dataset" var_name_in_template="hda" required="true">dataset_id</param>
+ <param type="integer" default="0">tree_index</param>
+ </params>
+ <template>phyloviz.mako</template>
+ <render_location>_top</render_location>
+ </visualization>
+
+ <visualization name="scatterplot">
+ <data_sources>
+ <data_source>
+ <model_class>HistoryDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">tabular.Tabular</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ </data_source>
+ </data_sources>
+ <params>
+ <param type="dataset" var_name_in_template="hda" required="true">dataset_id</param>
+ </params>
+ <template>scatterplot.mako</template>
+ </visualization>
+
+ <!--
+ <visualization name="test">
+ <data_sources>
+ <data_source>
+ <model_class>HistoryDatasetAssociation</model_class>
+ <test type="isinstance" test_attr="datatype" result_type="datatype">data.Data</test>
+ <to_param param_attr="id">dataset_id</to_param>
+ <to_param assign="bler">string</to_param>
+ <to_param assign="False">boolean</to_param>
+ <to_param assign="-5">integer</to_param>
+ <to_param assign="3.14">float</to_param>
+ <to_param assign="{}">json</to_param>
+ </data_source>
+ </data_sources>
+ <params>
+ <param>default</param>
+ <param type="str">string</param>
+ <param type="bool">boolean</param>
+ <param type="int">integer</param>
+ <param type="float">float</param>
+ <param type="json">json</param>
+ <param type="str" required="true">string</param>
+ <param type="visualization" var_name_in_template="visualization">visualization_id</param>
+ <param type="dataset" var_name_in_template="dataset">dataset_id</param>
+ <param type="hda_or_ldda">dataset_instance</param>
+ <param_modifier type="str" modifies="dataset_instance">hda_ldda</param_modifier>
+ </params>
+ <template>v_fwork_test.mako</template>
+ </visualization>
+ -->
+
+</visualizations>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Modify the upload_directory and upload_tar methods to handle complex and simple repository dependencies without changeset revisions specified.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/718f2f51ed01/
Changeset: 718f2f51ed01
User: Dave Bouvier
Date: 2013-05-17 18:50:47
Summary: Modify the upload_directory and upload_tar methods to handle complex and simple repository dependencies without changeset revisions specified.
Affected #: 1 file
diff -r 9d234e34bf3edcc7d31a0105cf4eef6393185d9a -r 718f2f51ed019319a0406b5dd7d679be8f0c35ae 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
@@ -117,7 +117,7 @@
ok, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed = \
self.upload_tar( trans, repository, tar, uploaded_file, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert )
elif uploaded_directory:
- ok,message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed = \
+ ok, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed = \
self.upload_directory( trans, repository, uploaded_directory, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert )
else:
if ( isgzip or isbz2 ) and uncompress_file:
@@ -263,6 +263,19 @@
break
else:
undesirable_files_removed += 1
+ uploaded_file_name = os.path.abspath( os.path.join( root, uploaded_file ) )
+ if os.path.split( uploaded_file_name )[ -1 ] == 'repository_dependencies.xml':
+ # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately.
+ altered, root = commit_util.handle_repository_dependencies_definition( trans, uploaded_file_name )
+ if altered:
+ tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ shutil.move( tmp_filename, uploaded_file_name )
+ elif os.path.split( uploaded_file_name )[ -1 ] == 'tool_dependencies.xml':
+ # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately.
+ altered, root = commit_util.handle_tool_dependencies_definition( trans, uploaded_file_name )
+ if altered:
+ tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ shutil.move( tmp_filename, uploaded_file_name )
if ok:
repo_path = os.path.join( full_path, relative_path )
repo_basedir = os.path.normpath( os.path.join( repo_path, os.path.pardir ) )
@@ -311,6 +324,20 @@
tar.extractall( path=full_path )
tar.close()
uploaded_file.close()
+ for filename in filenames_in_archive:
+ uploaded_file_name = os.path.join( full_path, filename )
+ if os.path.split( uploaded_file_name )[ -1 ] == 'repository_dependencies.xml':
+ # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately.
+ altered, root = commit_util.handle_repository_dependencies_definition( trans, uploaded_file_name )
+ if altered:
+ tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ shutil.move( tmp_filename, uploaded_file_name )
+ elif os.path.split( uploaded_file_name )[ -1 ] == 'tool_dependencies.xml':
+ # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately.
+ altered, root = commit_util.handle_tool_dependencies_definition( trans, uploaded_file_name )
+ if altered:
+ tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ shutil.move( tmp_filename, uploaded_file_name )
return commit_util.handle_directory_changes( trans,
repository,
full_path,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jgoecks: Trackster: enable composite feature tracks. When feature/read tracks are composited, coverage data for each track is composited together.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9d234e34bf3e/
Changeset: 9d234e34bf3e
User: jgoecks
Date: 2013-05-17 18:06:14
Summary: Trackster: enable composite feature tracks. When feature/read tracks are composited, coverage data for each track is composited together.
Affected #: 1 file
diff -r 7aee9615091381ba6b4f037fa46d226f2d89d5a4 -r 9d234e34bf3edcc7d31a0105cf4eef6393185d9a static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -5,13 +5,6 @@
var extend = _.extend;
-/**
- * Helper to determine if object is jQuery deferred.
- */
-var is_deferred = function ( d ) {
- return ( 'isResolved' in d );
-};
-
// ---- Web UI specific utilities ----
/**
@@ -179,7 +172,10 @@
DATA_LOADING = "Loading data...",
DATA_OK = "Ready for display",
TILE_CACHE_SIZE = 10,
- DATA_CACHE_SIZE = 20;
+ DATA_CACHE_SIZE = 20,
+
+ // Numerical/continuous data display modes.
+ CONTINUOUS_DATA_MODES = ["Histogram", "Line", "Filled", "Intensity"];
/**
* Round a number to a given number of decimal places.
@@ -829,13 +825,6 @@
* Replace group with a single composite track that includes all group's tracks.
*/
show_composite_track: function() {
- // Create composite track name.
- var drawables_names = [];
- for (var i = 0; i < this.drawables.length; i++) {
- drawables_names.push(this.drawables[i].name);
- }
-
- // Replace this group with composite track.
var composite_track = new CompositeTrack(this.view, this.view, {
name: this.name,
drawables: this.drawables
@@ -3048,7 +3037,7 @@
return !(tile instanceof LineTrackTile);
});
- if (tiles.length !== non_line_track_tiles.length) {
+ if (non_line_track_tiles.length > 0 && non_line_track_tiles.length < tiles.length) {
// Clear because this is set when drawing.
this.max_height_px = 0;
var track = this;
@@ -3113,10 +3102,13 @@
// Function that returns data/Deferreds needed to draw tile.
var get_tile_data = function() {
+ // HACK: if display mode (mode) is in continuous data modes, data mode must be coverage to get coverage data.
+ var data_mode = (_.find(CONTINUOUS_DATA_MODES, function(m) { return m === mode; }) ? "Coverage" : mode);
+
// Map drawable object to data needed for drawing.
var tile_data = _.map(drawables, function(d) {
// Get the track data/promise.
- return d.data_manager.get_data(region, mode, resolution, track.data_url_extra_params);
+ return d.data_manager.get_data(region, data_mode, resolution, track.data_url_extra_params);
});
// Get reference data/promise.
@@ -3399,9 +3391,11 @@
});
/**
- * A tiled track composed of multiple other tracks.
+ * A tiled track composed of multiple other tracks. Composite tracks only work with
+ * bigwig data for now.
*/
var CompositeTrack = function(view, container, obj_dict) {
+ this.display_modes = CONTINUOUS_DATA_MODES;
TiledTrack.call(this, view, container, obj_dict);
// Init drawables; each drawable is a copy so that config/preferences
@@ -3420,12 +3414,13 @@
}
this.enabled = true;
}
-
- // HACK: modes should be static class vars for most tracks and should update as
- // needed for CompositeTracks
- if (this.drawables.length !== 0) {
- this.set_display_modes(this.drawables[0].display_modes, this.drawables[0].mode);
- }
+
+ // Set all feature tracks to use Coverage mode.
+ _.each(this.drawables, function(d) {
+ if (d instanceof FeatureTrack || d instanceof ReadTrack) {
+ d.change_mode("Coverage");
+ }
+ });
this.update_icons();
@@ -3530,34 +3525,21 @@
TiledTrack.prototype.before_draw.call(this);
//
- // Set min, max for LineTracks to be largest min, max.
+ // Set min, max for tracks to be largest min, max.
//
// Get smallest min, biggest max.
- var
- min = Number.MAX_VALUE,
- max = -min,
- track;
- for (var i = 0; i < this.drawables.length; i++) {
- track = this.drawables[i];
- if (track instanceof LineTrack) {
- if (track.prefs.min_value < min) {
- min = track.prefs.min_value;
- }
- if (track.prefs.max_value > max) {
- max = track.prefs.max_value;
- }
- }
- }
+ var min = _.min(_.map(this.drawables, function(d) { return d.prefs.min_value; })),
+ max = _.max(_.map(this.drawables, function(d) { return d.prefs.max_value; }));
+
this.prefs.min_value = min;
this.prefs.max_value = max;
// Set all tracks to smallest min, biggest max.
- for (var i = 0; i < this.drawables.length; i++) {
- track = this.drawables[i];
- track.prefs.min_value = min;
- track.prefs.max_value = max;
- }
+ _.each(this.drawables, function(d) {
+ d.prefs.min_value = min;
+ d.prefs.max_value = max;
+ });
},
/**
@@ -3681,7 +3663,7 @@
*/
var LineTrack = function (view, container, obj_dict) {
var track = this;
- this.display_modes = ["Histogram", "Line", "Filled", "Intensity"];
+ this.display_modes = CONTINUOUS_DATA_MODES;
this.mode = "Histogram";
TiledTrack.call(this, view, container, obj_dict);
@@ -4040,7 +4022,7 @@
// If data is line track data, draw line track tile.
if (result.dataset_type === 'bigwig') {
- return this._draw_line_track_tile(result, ctx, 'Histogram', resolution, region, w_scale);
+ return this._draw_line_track_tile(result, ctx, mode, resolution, region, w_scale);
}
// Handle row-by-row tracks
@@ -4297,7 +4279,9 @@
{ 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: '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' }
],
saved_values: obj_dict.prefs,
onchange: function() {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dan: Add ToolShed metadata comparison method for Data Managers.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7aee96150913/
Changeset: 7aee96150913
User: dan
Date: 2013-05-17 17:26:00
Summary: Add ToolShed metadata comparison method for Data Managers.
Affected #: 1 file
diff -r 0481cf4b6cc3793902612af45cf0323de5dd3db6 -r 7aee9615091381ba6b4f037fa46d226f2d89d5a4 lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -75,6 +75,7 @@
ancestor_repository_dependencies = ancestor_repository_dependencies_dict.get( 'repository_dependencies', [] )
ancestor_tool_dependencies = ancestor_metadata_dict.get( 'tool_dependencies', {} )
ancestor_workflows = ancestor_metadata_dict.get( 'workflows', [] )
+ ancestor_data_manager = ancestor_metadata_dict.get( 'data_manager', {} )
current_datatypes = current_metadata_dict.get( 'datatypes', [] )
current_tools = current_metadata_dict.get( 'tools', [] )
current_guids = [ tool_dict[ 'guid' ] for tool_dict in current_tools ]
@@ -84,6 +85,7 @@
current_repository_dependencies = current_repository_dependencies_dict.get( 'repository_dependencies', [] )
current_tool_dependencies = current_metadata_dict.get( 'tool_dependencies', {} )
current_workflows = current_metadata_dict.get( 'workflows', [] )
+ current_data_manager = current_metadata_dict.get( 'data_manager', {} )
# Handle case where no metadata exists for either changeset.
no_datatypes = not ancestor_datatypes and not current_datatypes
no_readme_files = not ancestor_readme_files and not current_readme_files
@@ -92,7 +94,8 @@
no_tool_dependencies = not ancestor_tool_dependencies and not current_tool_dependencies
no_tools = not ancestor_guids and not current_guids
no_workflows = not ancestor_workflows and not current_workflows
- if no_datatypes and no_readme_files and no_repository_dependencies and no_tool_dependencies and no_tools and no_workflows:
+ no_data_manager = not ancestor_data_manager and not current_data_manager
+ if no_datatypes and no_readme_files and no_repository_dependencies and no_tool_dependencies and no_tools and no_workflows and no_data_manager:
return 'no metadata'
# Uncomment the following if we decide that README files should affect how installable repository revisions are defined. See the NOTE in the
# compare_readme_files() method.
@@ -101,20 +104,25 @@
tool_dependency_comparison = compare_tool_dependencies( ancestor_tool_dependencies, current_tool_dependencies )
workflow_comparison = compare_workflows( ancestor_workflows, current_workflows )
datatype_comparison = compare_datatypes( ancestor_datatypes, current_datatypes )
+ data_manager_comparison = compare_data_manager( ancestor_data_manager, current_data_manager )
# Handle case where all metadata is the same.
+ # TODO: these values, ('equal', etc), should be abstracted out to constants
if ancestor_guids == current_guids and \
repository_dependency_comparison == 'equal' and \
tool_dependency_comparison == 'equal' and \
workflow_comparison == 'equal' and \
- datatype_comparison == 'equal':
+ datatype_comparison == 'equal' and \
+ data_manager_comparison == 'equal':
return 'equal'
# Handle case where ancestor metadata is a subset of current metadata.
# readme_file_is_subset = readme_file_comparision in [ 'equal', 'subset' ]
+ # TODO: this list [ 'equal', 'subset' ] should be created once
repository_dependency_is_subset = repository_dependency_comparison in [ 'equal', 'subset' ]
tool_dependency_is_subset = tool_dependency_comparison in [ 'equal', 'subset' ]
workflow_dependency_is_subset = workflow_comparison in [ 'equal', 'subset' ]
datatype_is_subset = datatype_comparison in [ 'equal', 'subset' ]
- if repository_dependency_is_subset and tool_dependency_is_subset and workflow_dependency_is_subset and datatype_is_subset:
+ datamanager_is_subset = data_manager_comparison in [ 'equal', 'subset' ]
+ if repository_dependency_is_subset and tool_dependency_is_subset and workflow_dependency_is_subset and datatype_is_subset and datamanager_is_subset:
is_subset = True
for guid in ancestor_guids:
if guid not in current_guids:
@@ -124,6 +132,21 @@
return 'subset'
return 'not equal and not subset'
+def compare_data_manager( ancestor_metadata, current_metadata ):
+ """Determine if ancestor_metadata is the same as or a subset of current_metadata for data_managers."""
+ def __data_manager_dict_to_tuple_list( metadata_dict ):
+ # we do not check tool_guid or tool conf file name
+ return set( sorted( [ ( name, tuple( sorted( value.get( 'data_tables', [] ) ) ), value.get( 'guid' ), value.get( 'version' ), value.get( 'name' ), value.get( 'id' ) ) for name, value in metadata_dict.iteritems() ] ) )
+ # only compare valid entries, any invalid entries are ignored
+ ancestor_metadata = __data_manager_dict_to_tuple_list( ancestor_metadata.get( 'data_managers', {} ) )
+ current_metadata = __data_manager_dict_to_tuple_list( current_metadata.get( 'data_managers', {} ) )
+ # use set comparisons
+ if ancestor_metadata.issubset( current_metadata ):
+ if ancestor_metadata == current_metadata:
+ return 'equal'
+ return 'subset'
+ return 'not equal and not subset'
+
def compare_datatypes( ancestor_datatypes, current_datatypes ):
"""Determine if ancestor_datatypes is the same as or a subset of current_datatypes."""
# Each datatype dict looks something like: {"dtype": "galaxy.datatypes.images:Image", "extension": "pdf", "mimetype": "application/pdf"}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dan: Fix for setting data manager version.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0481cf4b6cc3/
Changeset: 0481cf4b6cc3
User: dan
Date: 2013-05-17 17:21:00
Summary: Fix for setting data manager version.
Affected #: 1 file
diff -r 412d5434a559d1d49ebcd2b24b357f466aaf688b -r 0481cf4b6cc3793902612af45cf0323de5dd3db6 lib/galaxy/tools/data_manager/manager.py
--- a/lib/galaxy/tools/data_manager/manager.py
+++ b/lib/galaxy/tools/data_manager/manager.py
@@ -112,6 +112,7 @@
self.declared_id = elem.get( 'id', None )
self.guid = elem.get( 'guid', None )
path = elem.get( 'tool_file', None )
+ self.version = elem.get( 'version', self.version )
tool_guid = None
if path is None:
tool_elem = elem.find( 'tool' )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dan: Fix for commit_util.UNDESIRABLE_DIRS in lib/galaxy/webapps/tool_shed/controllers/upload.py.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/412d5434a559/
Changeset: 412d5434a559
User: dan
Date: 2013-05-17 16:47:30
Summary: Fix for commit_util.UNDESIRABLE_DIRS in lib/galaxy/webapps/tool_shed/controllers/upload.py.
Affected #: 1 file
diff -r 9c06caa86e2ac73f439e9b148fb200e036a06ea2 -r 412d5434a559d1d49ebcd2b24b357f466aaf688b 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
@@ -257,7 +257,7 @@
ok = os.path.basename( uploaded_file ) not in commit_util.UNDESIRABLE_FILES
if ok:
for file_path_item in relative_path.split( '/' ):
- if file_path_item in COMMIT_UTIL.UNDESIRABLE_DIRS:
+ if file_path_item in commit_util.UNDESIRABLE_DIRS:
undesirable_dirs_removed += 1
ok = False
break
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fixes for committing to a tool shed repository - bug introduced when the tool shed uplaod controller was refactored.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9c06caa86e2a/
Changeset: 9c06caa86e2a
User: greg
Date: 2013-05-17 13:30:53
Summary: Fixes for committing to a tool shed repository - bug introduced when the tool shed uplaod controller was refactored.
Affected #: 2 files
diff -r c499e581b0f603528b66df0efe7e0e656a8e16f9 -r 9c06caa86e2ac73f439e9b148fb200e036a06ea2 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
@@ -23,10 +23,6 @@
log = logging.getLogger( __name__ )
-undesirable_dirs = [ '.hg', '.svn', '.git', '.cvs' ]
-undesirable_files = [ '.hg_archival.txt', 'hgrc', '.DS_Store' ]
-CHUNK_SIZE = 2**20 # 1Mb
-
class UploadController( BaseUIController ):
@@ -258,10 +254,10 @@
for root, dirs, files in os.walk( uploaded_directory ):
for uploaded_file in files:
relative_path = os.path.normpath( os.path.join( os.path.relpath( root, uploaded_directory ), uploaded_file ) )
- ok = os.path.basename( uploaded_file ) not in undesirable_files
+ ok = os.path.basename( uploaded_file ) not in commit_util.UNDESIRABLE_FILES
if ok:
for file_path_item in relative_path.split( '/' ):
- if file_path_item in undesirable_dirs:
+ if file_path_item in COMMIT_UTIL.UNDESIRABLE_DIRS:
undesirable_dirs_removed += 1
ok = False
break
@@ -300,10 +296,10 @@
full_path = os.path.abspath( repo_dir )
filenames_in_archive = []
for tarinfo_obj in tar.getmembers():
- ok = os.path.basename( tarinfo_obj.name ) not in undesirable_files
+ ok = os.path.basename( tarinfo_obj.name ) not in commit_util.UNDESIRABLE_FILES
if ok:
for file_path_item in tarinfo_obj.name.split( '/' ):
- if file_path_item in undesirable_dirs:
+ if file_path_item in commit_util.UNDESIRABLE_DIRS:
undesirable_dirs_removed += 1
ok = False
break
diff -r c499e581b0f603528b66df0efe7e0e656a8e16f9 -r 9c06caa86e2ac73f439e9b148fb200e036a06ea2 lib/tool_shed/util/commit_util.py
--- a/lib/tool_shed/util/commit_util.py
+++ b/lib/tool_shed/util/commit_util.py
@@ -16,6 +16,10 @@
log = logging.getLogger( __name__ )
+UNDESIRABLE_DIRS = [ '.hg', '.svn', '.git', '.cvs' ]
+UNDESIRABLE_FILES = [ '.hg_archival.txt', 'hgrc', '.DS_Store' ]
+CHUNK_SIZE = 2**20 # 1Mb
+
def check_archive( archive ):
for member in archive.getmembers():
# Allow regular files and directories only
@@ -113,11 +117,11 @@
# We have a repository that is not new (it contains files), so discover those files that are in the repository, but not in the uploaded archive.
for root, dirs, files in os.walk( full_path ):
if root.find( '.hg' ) < 0 and root.find( 'hgrc' ) < 0:
- for undesirable_dir in undesirable_dirs:
+ for undesirable_dir in UNDESIRABLE_DIRS:
if undesirable_dir in dirs:
dirs.remove( undesirable_dir )
undesirable_dirs_removed += 1
- for undesirable_file in undesirable_files:
+ for undesirable_file in UNDESIRABLE_FILES:
if undesirable_file in files:
files.remove( undesirable_file )
undesirable_files_removed += 1
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Refactor the tool shed upload controller.
by commits-noreply@bitbucket.org 17 May '13
by commits-noreply@bitbucket.org 17 May '13
17 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c499e581b0f6/
Changeset: c499e581b0f6
User: greg
Date: 2013-05-17 03:45:54
Summary: Refactor the tool shed upload controller.
Affected #: 2 files
diff -r 35de5a8a928bf63fd5de3d1ec066097602acf235 -r c499e581b0f603528b66df0efe7e0e656a8e16f9 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
@@ -1,7 +1,6 @@
import logging
import os
import shutil
-import sys
import tarfile
import tempfile
import urllib
@@ -9,9 +8,8 @@
from galaxy import util
from galaxy import web
from galaxy.datatypes import checkers
-from galaxy.util import json
-
import tool_shed.util.shed_util_common as suc
+from tool_shed.util import commit_util
from tool_shed.util import metadata_util
from tool_shed.util import repository_dependency_util
from tool_shed.util import tool_dependency_util
@@ -32,278 +30,6 @@
class UploadController( BaseUIController ):
- def check_archive( self, archive ):
- for member in archive.getmembers():
- # Allow regular files and directories only
- if not ( member.isdir() or member.isfile() or member.islnk() ):
- message = "Uploaded archives can only include regular directories and files (no symbolic links, devices, etc). Offender: %s" % str( member )
- return False, message
- for item in [ '.hg', '..', '/' ]:
- if member.name.startswith( item ):
- message = "Uploaded archives cannot contain .hg directories, absolute filenames starting with '/', or filenames with two dots '..'."
- return False, message
- if member.name in [ 'hgrc' ]:
- message = "Uploaded archives cannot contain hgrc files."
- return False, message
- return True, ''
-
- def check_file_contents_for_email_alerts( self, trans ):
- """
- See if any admin users have chosen to receive email alerts when a repository is updated. If so, the file contents of the update must be
- checked for inappropriate content.
- """
- admin_users = trans.app.config.get( "admin_users", "" ).split( "," )
- for repository in trans.sa_session.query( trans.model.Repository ) \
- .filter( trans.model.Repository.table.c.email_alerts != None ):
- email_alerts = json.from_json_string( repository.email_alerts )
- for user_email in email_alerts:
- if user_email in admin_users:
- return True
- return False
-
- def check_file_content_for_html_and_images( self, file_path ):
- message = ''
- if checkers.check_html( file_path ):
- message = 'The file "%s" contains HTML content.\n' % str( file_path )
- elif checkers.check_image( file_path ):
- message = 'The file "%s" contains image content.\n' % str( file_path )
- return message
-
- def create_and_write_tmp_file( self, text ):
- fh = tempfile.NamedTemporaryFile( 'wb' )
- tmp_filename = fh.name
- fh.close()
- fh = open( tmp_filename, 'wb' )
- fh.write( '<?xml version="1.0"?>\n' )
- fh.write( text )
- fh.close()
- return tmp_filename
-
- def get_upload_point( self, repository, **kwd ):
- upload_point = kwd.get( 'upload_point', None )
- if upload_point is not None:
- # The value of upload_point will be something like: database/community_files/000/repo_12/1.bed
- if os.path.exists( upload_point ):
- if os.path.isfile( upload_point ):
- # Get the parent directory
- upload_point, not_needed = os.path.split( upload_point )
- # Now the value of uplaod_point will be something like: database/community_files/000/repo_12/
- upload_point = upload_point.split( 'repo_%d' % repository.id )[ 1 ]
- if upload_point:
- upload_point = upload_point.lstrip( '/' )
- upload_point = upload_point.rstrip( '/' )
- # Now the value of uplaod_point will be something like: /
- if upload_point == '/':
- upload_point = None
- else:
- # Must have been an error selecting something that didn't exist, so default to repository root
- upload_point = None
- return upload_point
-
- def handle_bz2( self, repository, uploaded_file_name ):
- fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_bunzip2_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
- bzipped_file = bz2.BZ2File( uploaded_file_name, 'rb' )
- while 1:
- try:
- chunk = bzipped_file.read( CHUNK_SIZE )
- except IOError:
- os.close( fd )
- os.remove( uncompressed )
- log.exception( 'Problem uncompressing bz2 data "%s": %s' % ( uploaded_file_name, str( e ) ) )
- return
- if not chunk:
- break
- os.write( fd, chunk )
- os.close( fd )
- bzipped_file.close()
- shutil.move( uncompressed, uploaded_file_name )
-
- def handle_directory_changes( self, trans, repository, full_path, filenames_in_archive, remove_repo_files_not_in_tar, new_repo_alert, commit_message,
- undesirable_dirs_removed, undesirable_files_removed ):
- repo_dir = repository.repo_path( trans.app )
- repo = hg.repository( suc.get_configured_ui(), repo_dir )
- content_alert_str = ''
- files_to_remove = []
- filenames_in_archive = [ os.path.join( full_path, name ) for name in filenames_in_archive ]
- if remove_repo_files_not_in_tar and not repository.is_new( trans.app ):
- # We have a repository that is not new (it contains files), so discover those files that are in the repository, but not in the uploaded archive.
- for root, dirs, files in os.walk( full_path ):
- if root.find( '.hg' ) < 0 and root.find( 'hgrc' ) < 0:
- for undesirable_dir in undesirable_dirs:
- if undesirable_dir in dirs:
- dirs.remove( undesirable_dir )
- undesirable_dirs_removed += 1
- for undesirable_file in undesirable_files:
- if undesirable_file in files:
- files.remove( undesirable_file )
- undesirable_files_removed += 1
- for name in files:
- full_name = os.path.join( root, name )
- if full_name not in filenames_in_archive:
- files_to_remove.append( full_name )
- for repo_file in files_to_remove:
- # Remove files in the repository (relative to the upload point) that are not in the uploaded archive.
- try:
- commands.remove( repo.ui, repo, repo_file, force=True )
- except Exception, e:
- log.debug( "Error removing files using the mercurial API, so trying a different approach, the error was: %s" % str( e ))
- relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
- repo.dirstate.remove( relative_selected_file )
- repo.dirstate.write()
- absolute_selected_file = os.path.abspath( selected_file )
- if os.path.isdir( absolute_selected_file ):
- try:
- os.rmdir( absolute_selected_file )
- except OSError, e:
- # The directory is not empty.
- pass
- elif os.path.isfile( absolute_selected_file ):
- os.remove( absolute_selected_file )
- dir = os.path.split( absolute_selected_file )[0]
- try:
- os.rmdir( dir )
- except OSError, e:
- # The directory is not empty.
- pass
- # See if any admin users have chosen to receive email alerts when a repository is
- # updated. If so, check every uploaded file to ensure content is appropriate.
- check_contents = self.check_file_contents_for_email_alerts( trans )
- for filename_in_archive in filenames_in_archive:
- # Check file content to ensure it is appropriate.
- if check_contents and os.path.isfile( filename_in_archive ):
- content_alert_str += self.check_file_content_for_html_and_images( filename_in_archive )
- commands.add( repo.ui, repo, filename_in_archive )
- if filename_in_archive.endswith( 'tool_data_table_conf.xml.sample' ):
- # Handle the special case where a tool_data_table_conf.xml.sample file is being uploaded by parsing the file and adding new entries
- # to the in-memory trans.app.tool_data_tables dictionary.
- error, message = tool_util.handle_sample_tool_data_table_conf_file( trans.app, filename_in_archive )
- if error:
- return False, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
- commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
- admin_only = len( repository.downloadable_revisions ) != 1
- suc.handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
- return True, '', files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
-
- def handle_gzip( self, repository, uploaded_file_name ):
- fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_gunzip_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
- gzipped_file = gzip.GzipFile( uploaded_file_name, 'rb' )
- while 1:
- try:
- chunk = gzipped_file.read( CHUNK_SIZE )
- except IOError, e:
- os.close( fd )
- os.remove( uncompressed )
- log.exception( 'Problem uncompressing gz data "%s": %s' % ( uploaded_file_name, str( e ) ) )
- return
- if not chunk:
- break
- os.write( fd, chunk )
- os.close( fd )
- gzipped_file.close()
- shutil.move( uncompressed, uploaded_file_name )
-
- def handle_repository_dependencies_definition( self, trans, repository_dependencies_config ):
- altered = False
- try:
- # Make sure we're looking at a valid repository_dependencies.xml file.
- tree = util.parse_xml( repository_dependencies_config )
- root = tree.getroot()
- except Exception, e:
- error_message = "Error parsing %s in handle_repository_dependencies_definition: " % str( repository_dependencies_config )
- log.exception( error_message )
- return False, None
- if root.tag == 'repositories':
- for index, elem in enumerate( root ):
- # <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
- populated, elem = self.handle_repository_dependency_elem( trans, elem )
- if populated:
- root[ index ] = elem
- if not altered:
- altered = True
- return altered, root
- return False, None
-
- def handle_repository_dependency_elem( self, trans, elem ):
- # <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
- populated = False
- name = elem.get( 'name' )
- owner = elem.get( 'owner' )
- changeset_revision = elem.get( 'changeset_revision' )
- if not changeset_revision:
- # Populate the changeset_revision attribute with the latest installable metadata revision for the defined repository.
- # We use the latest installable revision instead of the latest metadata revision to ensure that the contents of the
- # revision are valid.
- repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
- if repository:
- repo_dir = repository.repo_path( trans.app )
- repo = hg.repository( suc.get_configured_ui(), repo_dir )
- lastest_installable_changeset_revision = suc.get_latest_downloadable_changeset_revision( trans, repository, repo )
- if lastest_installable_changeset_revision != suc.INITIAL_CHANGELOG_HASH:
- elem.attrib[ 'changeset_revision' ] = lastest_installable_changeset_revision
- populated = True
- return populated, elem
-
- def handle_tool_dependencies_definition( self, trans, tool_dependencies_config ):
- altered = False
- try:
- # Make sure we're looking at a valid tool_dependencies.xml file.
- tree = util.parse_xml( tool_dependencies_config )
- root = tree.getroot()
- except Exception, e:
- error_message = "Error parsing %s in handle_tool_dependencies_definition: " % str( tool_dependencies_config )
- log.exception( error_message )
- return False, None
- if root.tag == 'tool_dependency':
- for root_index, root_elem in enumerate( root ):
- # <package name="eigen" version="2.0.17">
- if root_elem.tag == 'package':
- package_altered = False
- for package_index, package_elem in enumerate( root_elem ):
- if package_elem.tag == 'repository':
- # <repository name="package_eigen_2_0" owner="test" changeset_revision="09eb05087cd0" prior_installation_required="True" />
- populated, repository_elem = self.handle_repository_dependency_elem( trans, package_elem )
- if populated:
- root_elem[ package_index ] = repository_elem
- package_altered = True
- if not altered:
- altered = True
-
- elif package_elem.tag == 'install':
- # <install version="1.0">
- for actions_index, actions_elem in enumerate( package_elem ):
- for action_index, action_elem in enumerate( actions_elem ):
- action_type = action_elem.get( 'type' )
- if action_type == 'set_environment_for_install':
- # <action type="set_environment_for_install">
- # <repository name="package_eigen_2_0" owner="test" changeset_revision="09eb05087cd0">
- # <package name="eigen" version="2.0.17" />
- # </repository>
- # </action>
- for repo_index, repo_elem in enumerate( action_elem ):
- populated, repository_elem = self.handle_repository_dependency_elem( trans, repo_elem )
- if populated:
- action_elem[ repo_index ] = repository_elem
- package_altered = True
- if not altered:
- altered = True
- if package_altered:
- actions_elem[ action_index ] = action_elem
- if package_altered:
- root_elem[ actions_index ] = actions_elem
-
- if package_altered:
- root[ root_index ] = root_elem
- return altered, root
- return False, None
-
- def uncompress( self, repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 ):
- if isgzip:
- self.handle_gzip( repository, uploaded_file_name )
- return uploaded_file_filename.rstrip( '.gz' )
- if isbz2:
- self.handle_bz2( repository, uploaded_file_name )
- return uploaded_file_filename.rstrip( '.bz2' )
-
@web.expose
@web.require_login( 'upload', use_panels=True )
def upload( self, trans, **kwd ):
@@ -319,7 +45,7 @@
uncompress_file = util.string_as_bool( kwd.get( 'uncompress_file', 'true' ) )
remove_repo_files_not_in_tar = util.string_as_bool( kwd.get( 'remove_repo_files_not_in_tar', 'true' ) )
uploaded_file = None
- upload_point = self.get_upload_point( repository, **kwd )
+ upload_point = commit_util.get_upload_point( repository, **kwd )
tip = repository.tip( trans.app )
file_data = kwd.get( 'file_data', '' )
url = kwd.get( 'url', '' )
@@ -399,7 +125,7 @@
self.upload_directory( trans, repository, uploaded_directory, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert )
else:
if ( isgzip or isbz2 ) and uncompress_file:
- uploaded_file_filename = self.uncompress( repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 )
+ uploaded_file_filename = commit_util.uncompress( repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 )
if upload_point is not None:
full_path = os.path.abspath( os.path.join( repo_dir, upload_point, uploaded_file_filename ) )
else:
@@ -407,18 +133,18 @@
# Move some version of the uploaded file to the load_point within the repository hierarchy.
if uploaded_file_filename in [ 'repository_dependencies.xml' ]:
# Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately.
- altered, root = self.handle_repository_dependencies_definition( trans, uploaded_file_name )
+ altered, root = commit_util.handle_repository_dependencies_definition( trans, uploaded_file_name )
if altered:
- tmp_filename = self.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
shutil.move( tmp_filename, full_path )
else:
shutil.move( uploaded_file_name, full_path )
elif uploaded_file_filename in [ 'tool_dependencies.xml' ]:
# Inspect the contents of the file to see if it defines a complex repository dependency definition whose changeset_revision values
# are missing and if so, set them appropriately.
- altered, root = self.handle_tool_dependencies_definition( trans, uploaded_file_name )
+ altered, root = commit_util.handle_tool_dependencies_definition( trans, uploaded_file_name )
if altered:
- tmp_filename = self.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
shutil.move( tmp_filename, full_path )
else:
shutil.move( uploaded_file_name, full_path )
@@ -426,9 +152,9 @@
shutil.move( uploaded_file_name, full_path )
# See if any admin users have chosen to receive email alerts when a repository is updated. If so, check every uploaded file to ensure
# content is appropriate.
- check_contents = self.check_file_contents_for_email_alerts( trans )
+ check_contents = commit_util.check_file_contents_for_email_alerts( trans )
if check_contents and os.path.isfile( full_path ):
- content_alert_str = self.check_file_content_for_html_and_images( full_path )
+ content_alert_str = commit_util.check_file_content_for_html_and_images( full_path )
else:
content_alert_str = ''
commands.add( repo.ui, repo, full_path )
@@ -553,8 +279,8 @@
os.remove( repo_path )
shutil.move( os.path.join( uploaded_directory, relative_path ), repo_path )
filenames_in_archive.append( relative_path )
- return self.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 )
+ return commit_util.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 )
def upload_tar( self, trans, repository, tar, uploaded_file, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert ):
# Upload a tar archive of files.
@@ -562,7 +288,7 @@
repo = hg.repository( suc.get_configured_ui(), repo_dir )
undesirable_dirs_removed = 0
undesirable_files_removed = 0
- ok, message = self.check_archive( tar )
+ ok, message = commit_util.check_archive( tar )
if not ok:
tar.close()
uploaded_file.close()
@@ -589,12 +315,12 @@
tar.extractall( path=full_path )
tar.close()
uploaded_file.close()
- return self.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 )
+ return commit_util.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 )
diff -r 35de5a8a928bf63fd5de3d1ec066097602acf235 -r c499e581b0f603528b66df0efe7e0e656a8e16f9 lib/tool_shed/util/commit_util.py
--- /dev/null
+++ b/lib/tool_shed/util/commit_util.py
@@ -0,0 +1,289 @@
+import logging
+import os
+import shutil
+import tempfile
+from galaxy import util
+from galaxy.datatypes import checkers
+from galaxy.util import json
+import tool_shed.util.shed_util_common as suc
+from tool_shed.util import tool_util
+
+from galaxy import eggs
+eggs.require( 'mercurial' )
+from mercurial import commands
+from mercurial import hg
+from mercurial import ui
+
+log = logging.getLogger( __name__ )
+
+def check_archive( archive ):
+ for member in archive.getmembers():
+ # Allow regular files and directories only
+ if not ( member.isdir() or member.isfile() or member.islnk() ):
+ message = "Uploaded archives can only include regular directories and files (no symbolic links, devices, etc). Offender: %s" % str( member )
+ return False, message
+ for item in [ '.hg', '..', '/' ]:
+ if member.name.startswith( item ):
+ message = "Uploaded archives cannot contain .hg directories, absolute filenames starting with '/', or filenames with two dots '..'."
+ return False, message
+ if member.name in [ 'hgrc' ]:
+ message = "Uploaded archives cannot contain hgrc files."
+ return False, message
+ return True, ''
+
+def check_file_contents_for_email_alerts( trans ):
+ """
+ See if any admin users have chosen to receive email alerts when a repository is updated. If so, the file contents of the update must be
+ checked for inappropriate content.
+ """
+ admin_users = trans.app.config.get( "admin_users", "" ).split( "," )
+ for repository in trans.sa_session.query( trans.model.Repository ) \
+ .filter( trans.model.Repository.table.c.email_alerts != None ):
+ email_alerts = json.from_json_string( repository.email_alerts )
+ for user_email in email_alerts:
+ if user_email in admin_users:
+ return True
+ return False
+
+def check_file_content_for_html_and_images( file_path ):
+ message = ''
+ if checkers.check_html( file_path ):
+ message = 'The file "%s" contains HTML content.\n' % str( file_path )
+ elif checkers.check_image( file_path ):
+ message = 'The file "%s" contains image content.\n' % str( file_path )
+ return message
+
+def create_and_write_tmp_file( text ):
+ fh = tempfile.NamedTemporaryFile( 'wb' )
+ tmp_filename = fh.name
+ fh.close()
+ fh = open( tmp_filename, 'wb' )
+ fh.write( '<?xml version="1.0"?>\n' )
+ fh.write( text )
+ fh.close()
+ return tmp_filename
+
+def get_upload_point( repository, **kwd ):
+ upload_point = kwd.get( 'upload_point', None )
+ if upload_point is not None:
+ # The value of upload_point will be something like: database/community_files/000/repo_12/1.bed
+ if os.path.exists( upload_point ):
+ if os.path.isfile( upload_point ):
+ # Get the parent directory
+ upload_point, not_needed = os.path.split( upload_point )
+ # Now the value of uplaod_point will be something like: database/community_files/000/repo_12/
+ upload_point = upload_point.split( 'repo_%d' % repository.id )[ 1 ]
+ if upload_point:
+ upload_point = upload_point.lstrip( '/' )
+ upload_point = upload_point.rstrip( '/' )
+ # Now the value of uplaod_point will be something like: /
+ if upload_point == '/':
+ upload_point = None
+ else:
+ # Must have been an error selecting something that didn't exist, so default to repository root
+ upload_point = None
+ return upload_point
+
+def handle_bz2( repository, uploaded_file_name ):
+ fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_bunzip2_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
+ bzipped_file = bz2.BZ2File( uploaded_file_name, 'rb' )
+ while 1:
+ try:
+ chunk = bzipped_file.read( CHUNK_SIZE )
+ except IOError:
+ os.close( fd )
+ os.remove( uncompressed )
+ log.exception( 'Problem uncompressing bz2 data "%s": %s' % ( uploaded_file_name, str( e ) ) )
+ return
+ if not chunk:
+ break
+ os.write( fd, chunk )
+ os.close( fd )
+ bzipped_file.close()
+ shutil.move( uncompressed, uploaded_file_name )
+
+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 ):
+ repo_dir = repository.repo_path( trans.app )
+ repo = hg.repository( suc.get_configured_ui(), repo_dir )
+ content_alert_str = ''
+ files_to_remove = []
+ filenames_in_archive = [ os.path.join( full_path, name ) for name in filenames_in_archive ]
+ if remove_repo_files_not_in_tar and not repository.is_new( trans.app ):
+ # We have a repository that is not new (it contains files), so discover those files that are in the repository, but not in the uploaded archive.
+ for root, dirs, files in os.walk( full_path ):
+ if root.find( '.hg' ) < 0 and root.find( 'hgrc' ) < 0:
+ for undesirable_dir in undesirable_dirs:
+ if undesirable_dir in dirs:
+ dirs.remove( undesirable_dir )
+ undesirable_dirs_removed += 1
+ for undesirable_file in undesirable_files:
+ if undesirable_file in files:
+ files.remove( undesirable_file )
+ undesirable_files_removed += 1
+ for name in files:
+ full_name = os.path.join( root, name )
+ if full_name not in filenames_in_archive:
+ files_to_remove.append( full_name )
+ for repo_file in files_to_remove:
+ # Remove files in the repository (relative to the upload point) that are not in the uploaded archive.
+ try:
+ commands.remove( repo.ui, repo, repo_file, force=True )
+ except Exception, e:
+ log.debug( "Error removing files using the mercurial API, so trying a different approach, the error was: %s" % str( e ))
+ relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
+ repo.dirstate.remove( relative_selected_file )
+ repo.dirstate.write()
+ absolute_selected_file = os.path.abspath( selected_file )
+ if os.path.isdir( absolute_selected_file ):
+ try:
+ os.rmdir( absolute_selected_file )
+ except OSError, e:
+ # The directory is not empty.
+ pass
+ elif os.path.isfile( absolute_selected_file ):
+ os.remove( absolute_selected_file )
+ dir = os.path.split( absolute_selected_file )[0]
+ try:
+ os.rmdir( dir )
+ except OSError, e:
+ # The directory is not empty.
+ pass
+ # See if any admin users have chosen to receive email alerts when a repository is
+ # updated. If so, check every uploaded file to ensure content is appropriate.
+ check_contents = check_file_contents_for_email_alerts( trans )
+ for filename_in_archive in filenames_in_archive:
+ # Check file content to ensure it is appropriate.
+ if check_contents and os.path.isfile( filename_in_archive ):
+ content_alert_str += check_file_content_for_html_and_images( filename_in_archive )
+ commands.add( repo.ui, repo, filename_in_archive )
+ if filename_in_archive.endswith( 'tool_data_table_conf.xml.sample' ):
+ # Handle the special case where a tool_data_table_conf.xml.sample file is being uploaded by parsing the file and adding new entries
+ # to the in-memory trans.app.tool_data_tables dictionary.
+ error, message = tool_util.handle_sample_tool_data_table_conf_file( trans.app, filename_in_archive )
+ if error:
+ return False, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
+ commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
+ admin_only = len( repository.downloadable_revisions ) != 1
+ suc.handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
+ return True, '', files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
+
+def handle_gzip( repository, uploaded_file_name ):
+ fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_gunzip_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
+ gzipped_file = gzip.GzipFile( uploaded_file_name, 'rb' )
+ while 1:
+ try:
+ chunk = gzipped_file.read( CHUNK_SIZE )
+ except IOError, e:
+ os.close( fd )
+ os.remove( uncompressed )
+ log.exception( 'Problem uncompressing gz data "%s": %s' % ( uploaded_file_name, str( e ) ) )
+ return
+ if not chunk:
+ break
+ os.write( fd, chunk )
+ os.close( fd )
+ gzipped_file.close()
+ shutil.move( uncompressed, uploaded_file_name )
+
+def handle_repository_dependencies_definition( trans, repository_dependencies_config ):
+ altered = False
+ try:
+ # Make sure we're looking at a valid repository_dependencies.xml file.
+ tree = util.parse_xml( repository_dependencies_config )
+ root = tree.getroot()
+ except Exception, e:
+ error_message = "Error parsing %s in handle_repository_dependencies_definition: " % str( repository_dependencies_config )
+ log.exception( error_message )
+ return False, None
+ if root.tag == 'repositories':
+ for index, elem in enumerate( root ):
+ # <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
+ populated, elem = handle_repository_dependency_elem( trans, elem )
+ if populated:
+ root[ index ] = elem
+ if not altered:
+ altered = True
+ return altered, root
+ return False, None
+
+def handle_repository_dependency_elem( trans, elem ):
+ # <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
+ populated = False
+ name = elem.get( 'name' )
+ owner = elem.get( 'owner' )
+ changeset_revision = elem.get( 'changeset_revision' )
+ if not changeset_revision:
+ # Populate the changeset_revision attribute with the latest installable metadata revision for the defined repository.
+ # We use the latest installable revision instead of the latest metadata revision to ensure that the contents of the
+ # revision are valid.
+ repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
+ if repository:
+ repo_dir = repository.repo_path( trans.app )
+ repo = hg.repository( suc.get_configured_ui(), repo_dir )
+ lastest_installable_changeset_revision = suc.get_latest_downloadable_changeset_revision( trans, repository, repo )
+ if lastest_installable_changeset_revision != suc.INITIAL_CHANGELOG_HASH:
+ elem.attrib[ 'changeset_revision' ] = lastest_installable_changeset_revision
+ populated = True
+ return populated, elem
+
+def handle_tool_dependencies_definition( trans, tool_dependencies_config ):
+ altered = False
+ try:
+ # Make sure we're looking at a valid tool_dependencies.xml file.
+ tree = util.parse_xml( tool_dependencies_config )
+ root = tree.getroot()
+ except Exception, e:
+ error_message = "Error parsing %s in handle_tool_dependencies_definition: " % str( tool_dependencies_config )
+ log.exception( error_message )
+ return False, None
+ if root.tag == 'tool_dependency':
+ for root_index, root_elem in enumerate( root ):
+ # <package name="eigen" version="2.0.17">
+ if root_elem.tag == 'package':
+ package_altered = False
+ for package_index, package_elem in enumerate( root_elem ):
+ if package_elem.tag == 'repository':
+ # <repository name="package_eigen_2_0" owner="test" changeset_revision="09eb05087cd0" prior_installation_required="True" />
+ populated, repository_elem = handle_repository_dependency_elem( trans, package_elem )
+ if populated:
+ root_elem[ package_index ] = repository_elem
+ package_altered = True
+ if not altered:
+ altered = True
+
+ elif package_elem.tag == 'install':
+ # <install version="1.0">
+ for actions_index, actions_elem in enumerate( package_elem ):
+ for action_index, action_elem in enumerate( actions_elem ):
+ action_type = action_elem.get( 'type' )
+ if action_type == 'set_environment_for_install':
+ # <action type="set_environment_for_install">
+ # <repository name="package_eigen_2_0" owner="test" changeset_revision="09eb05087cd0">
+ # <package name="eigen" version="2.0.17" />
+ # </repository>
+ # </action>
+ for repo_index, repo_elem in enumerate( action_elem ):
+ populated, repository_elem = handle_repository_dependency_elem( trans, repo_elem )
+ if populated:
+ action_elem[ repo_index ] = repository_elem
+ package_altered = True
+ if not altered:
+ altered = True
+ if package_altered:
+ actions_elem[ action_index ] = action_elem
+ if package_altered:
+ root_elem[ actions_index ] = actions_elem
+
+ if package_altered:
+ root[ root_index ] = root_elem
+ return altered, root
+ return False, None
+
+def uncompress( repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 ):
+ if isgzip:
+ handle_gzip( repository, uploaded_file_name )
+ return uploaded_file_filename.rstrip( '.gz' )
+ if isbz2:
+ handle_bz2( repository, uploaded_file_name )
+ return uploaded_file_filename.rstrip( '.bz2' )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Eliminate the requirement for repository dependency tag sets to include a changeset_revision attribute.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/35de5a8a928b/
Changeset: 35de5a8a928b
User: greg
Date: 2013-05-16 22:57:10
Summary: Eliminate the requirement for repository dependency tag sets to include a changeset_revision attribute.
Here is an example of what used to be required:
<repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
Now the following is supported:
<repository name="molecule_datatypes" owner="test" />
When the changeset_revision is missing, the tool shed uplad process will automatically determine the latest installable changeset revsion for the repository
defined by the name and owner tag attributes and populate the tag with the changeset discovered changeset revision value.
Caveat emptor:
0) I wanted to get this changeset committed before the next stable branch is created on Monday, 5/20/13, so that it would be included in the next Galaxy release.
By definition, anything I do to this new feature from now on will be a bug fix. ;) This featue should be fully functional within the next week.
1) This currently only works when uplaoding a single file named repository_dependencies.xml or tool_dependencies.xml. It does not yet work when files with this name
are include in a tarball or are uploaded using the ftp or hg clone options in the tool shed upload form.
2) This feature is not yet fully tested, but doesn't break anything I've tried (all tool shed functional tests pass). I've tested it with both simple repository
dependency definitions and complex repository dependency definitions. I've also tested it with the recently supported <action type="set_environment_for_install">
tag set.
Affected #: 3 files
diff -r 03c0f4d30880d3286e0ccc61c3f2554343c8c53e -r 35de5a8a928bf63fd5de3d1ec066097602acf235 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
@@ -32,6 +32,21 @@
class UploadController( BaseUIController ):
+ def check_archive( self, archive ):
+ for member in archive.getmembers():
+ # Allow regular files and directories only
+ if not ( member.isdir() or member.isfile() or member.islnk() ):
+ message = "Uploaded archives can only include regular directories and files (no symbolic links, devices, etc). Offender: %s" % str( member )
+ return False, message
+ for item in [ '.hg', '..', '/' ]:
+ if member.name.startswith( item ):
+ message = "Uploaded archives cannot contain .hg directories, absolute filenames starting with '/', or filenames with two dots '..'."
+ return False, message
+ if member.name in [ 'hgrc' ]:
+ message = "Uploaded archives cannot contain hgrc files."
+ return False, message
+ return True, ''
+
def check_file_contents_for_email_alerts( self, trans ):
"""
See if any admin users have chosen to receive email alerts when a repository is updated. If so, the file contents of the update must be
@@ -54,32 +69,266 @@
message = 'The file "%s" contains image content.\n' % str( file_path )
return message
+ def create_and_write_tmp_file( self, text ):
+ fh = tempfile.NamedTemporaryFile( 'wb' )
+ tmp_filename = fh.name
+ fh.close()
+ fh = open( tmp_filename, 'wb' )
+ fh.write( '<?xml version="1.0"?>\n' )
+ fh.write( text )
+ fh.close()
+ return tmp_filename
+
+ def get_upload_point( self, repository, **kwd ):
+ upload_point = kwd.get( 'upload_point', None )
+ if upload_point is not None:
+ # The value of upload_point will be something like: database/community_files/000/repo_12/1.bed
+ if os.path.exists( upload_point ):
+ if os.path.isfile( upload_point ):
+ # Get the parent directory
+ upload_point, not_needed = os.path.split( upload_point )
+ # Now the value of uplaod_point will be something like: database/community_files/000/repo_12/
+ upload_point = upload_point.split( 'repo_%d' % repository.id )[ 1 ]
+ if upload_point:
+ upload_point = upload_point.lstrip( '/' )
+ upload_point = upload_point.rstrip( '/' )
+ # Now the value of uplaod_point will be something like: /
+ if upload_point == '/':
+ upload_point = None
+ else:
+ # Must have been an error selecting something that didn't exist, so default to repository root
+ upload_point = None
+ return upload_point
+
+ def handle_bz2( self, repository, uploaded_file_name ):
+ fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_bunzip2_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
+ bzipped_file = bz2.BZ2File( uploaded_file_name, 'rb' )
+ while 1:
+ try:
+ chunk = bzipped_file.read( CHUNK_SIZE )
+ except IOError:
+ os.close( fd )
+ os.remove( uncompressed )
+ log.exception( 'Problem uncompressing bz2 data "%s": %s' % ( uploaded_file_name, str( e ) ) )
+ return
+ if not chunk:
+ break
+ os.write( fd, chunk )
+ os.close( fd )
+ bzipped_file.close()
+ shutil.move( uncompressed, uploaded_file_name )
+
+ def handle_directory_changes( self, trans, repository, full_path, filenames_in_archive, remove_repo_files_not_in_tar, new_repo_alert, commit_message,
+ undesirable_dirs_removed, undesirable_files_removed ):
+ repo_dir = repository.repo_path( trans.app )
+ repo = hg.repository( suc.get_configured_ui(), repo_dir )
+ content_alert_str = ''
+ files_to_remove = []
+ filenames_in_archive = [ os.path.join( full_path, name ) for name in filenames_in_archive ]
+ if remove_repo_files_not_in_tar and not repository.is_new( trans.app ):
+ # We have a repository that is not new (it contains files), so discover those files that are in the repository, but not in the uploaded archive.
+ for root, dirs, files in os.walk( full_path ):
+ if root.find( '.hg' ) < 0 and root.find( 'hgrc' ) < 0:
+ for undesirable_dir in undesirable_dirs:
+ if undesirable_dir in dirs:
+ dirs.remove( undesirable_dir )
+ undesirable_dirs_removed += 1
+ for undesirable_file in undesirable_files:
+ if undesirable_file in files:
+ files.remove( undesirable_file )
+ undesirable_files_removed += 1
+ for name in files:
+ full_name = os.path.join( root, name )
+ if full_name not in filenames_in_archive:
+ files_to_remove.append( full_name )
+ for repo_file in files_to_remove:
+ # Remove files in the repository (relative to the upload point) that are not in the uploaded archive.
+ try:
+ commands.remove( repo.ui, repo, repo_file, force=True )
+ except Exception, e:
+ log.debug( "Error removing files using the mercurial API, so trying a different approach, the error was: %s" % str( e ))
+ relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
+ repo.dirstate.remove( relative_selected_file )
+ repo.dirstate.write()
+ absolute_selected_file = os.path.abspath( selected_file )
+ if os.path.isdir( absolute_selected_file ):
+ try:
+ os.rmdir( absolute_selected_file )
+ except OSError, e:
+ # The directory is not empty.
+ pass
+ elif os.path.isfile( absolute_selected_file ):
+ os.remove( absolute_selected_file )
+ dir = os.path.split( absolute_selected_file )[0]
+ try:
+ os.rmdir( dir )
+ except OSError, e:
+ # The directory is not empty.
+ pass
+ # See if any admin users have chosen to receive email alerts when a repository is
+ # updated. If so, check every uploaded file to ensure content is appropriate.
+ check_contents = self.check_file_contents_for_email_alerts( trans )
+ for filename_in_archive in filenames_in_archive:
+ # Check file content to ensure it is appropriate.
+ if check_contents and os.path.isfile( filename_in_archive ):
+ content_alert_str += self.check_file_content_for_html_and_images( filename_in_archive )
+ commands.add( repo.ui, repo, filename_in_archive )
+ if filename_in_archive.endswith( 'tool_data_table_conf.xml.sample' ):
+ # Handle the special case where a tool_data_table_conf.xml.sample file is being uploaded by parsing the file and adding new entries
+ # to the in-memory trans.app.tool_data_tables dictionary.
+ error, message = tool_util.handle_sample_tool_data_table_conf_file( trans.app, filename_in_archive )
+ if error:
+ return False, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
+ commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
+ admin_only = len( repository.downloadable_revisions ) != 1
+ suc.handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
+ return True, '', files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
+
+ def handle_gzip( self, repository, uploaded_file_name ):
+ fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_gunzip_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
+ gzipped_file = gzip.GzipFile( uploaded_file_name, 'rb' )
+ while 1:
+ try:
+ chunk = gzipped_file.read( CHUNK_SIZE )
+ except IOError, e:
+ os.close( fd )
+ os.remove( uncompressed )
+ log.exception( 'Problem uncompressing gz data "%s": %s' % ( uploaded_file_name, str( e ) ) )
+ return
+ if not chunk:
+ break
+ os.write( fd, chunk )
+ os.close( fd )
+ gzipped_file.close()
+ shutil.move( uncompressed, uploaded_file_name )
+
+ def handle_repository_dependencies_definition( self, trans, repository_dependencies_config ):
+ altered = False
+ try:
+ # Make sure we're looking at a valid repository_dependencies.xml file.
+ tree = util.parse_xml( repository_dependencies_config )
+ root = tree.getroot()
+ except Exception, e:
+ error_message = "Error parsing %s in handle_repository_dependencies_definition: " % str( repository_dependencies_config )
+ log.exception( error_message )
+ return False, None
+ if root.tag == 'repositories':
+ for index, elem in enumerate( root ):
+ # <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
+ populated, elem = self.handle_repository_dependency_elem( trans, elem )
+ if populated:
+ root[ index ] = elem
+ if not altered:
+ altered = True
+ return altered, root
+ return False, None
+
+ def handle_repository_dependency_elem( self, trans, elem ):
+ # <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
+ populated = False
+ name = elem.get( 'name' )
+ owner = elem.get( 'owner' )
+ changeset_revision = elem.get( 'changeset_revision' )
+ if not changeset_revision:
+ # Populate the changeset_revision attribute with the latest installable metadata revision for the defined repository.
+ # We use the latest installable revision instead of the latest metadata revision to ensure that the contents of the
+ # revision are valid.
+ repository = suc.get_repository_by_name_and_owner( trans.app, name, owner )
+ if repository:
+ repo_dir = repository.repo_path( trans.app )
+ repo = hg.repository( suc.get_configured_ui(), repo_dir )
+ lastest_installable_changeset_revision = suc.get_latest_downloadable_changeset_revision( trans, repository, repo )
+ if lastest_installable_changeset_revision != suc.INITIAL_CHANGELOG_HASH:
+ elem.attrib[ 'changeset_revision' ] = lastest_installable_changeset_revision
+ populated = True
+ return populated, elem
+
+ def handle_tool_dependencies_definition( self, trans, tool_dependencies_config ):
+ altered = False
+ try:
+ # Make sure we're looking at a valid tool_dependencies.xml file.
+ tree = util.parse_xml( tool_dependencies_config )
+ root = tree.getroot()
+ except Exception, e:
+ error_message = "Error parsing %s in handle_tool_dependencies_definition: " % str( tool_dependencies_config )
+ log.exception( error_message )
+ return False, None
+ if root.tag == 'tool_dependency':
+ for root_index, root_elem in enumerate( root ):
+ # <package name="eigen" version="2.0.17">
+ if root_elem.tag == 'package':
+ package_altered = False
+ for package_index, package_elem in enumerate( root_elem ):
+ if package_elem.tag == 'repository':
+ # <repository name="package_eigen_2_0" owner="test" changeset_revision="09eb05087cd0" prior_installation_required="True" />
+ populated, repository_elem = self.handle_repository_dependency_elem( trans, package_elem )
+ if populated:
+ root_elem[ package_index ] = repository_elem
+ package_altered = True
+ if not altered:
+ altered = True
+
+ elif package_elem.tag == 'install':
+ # <install version="1.0">
+ for actions_index, actions_elem in enumerate( package_elem ):
+ for action_index, action_elem in enumerate( actions_elem ):
+ action_type = action_elem.get( 'type' )
+ if action_type == 'set_environment_for_install':
+ # <action type="set_environment_for_install">
+ # <repository name="package_eigen_2_0" owner="test" changeset_revision="09eb05087cd0">
+ # <package name="eigen" version="2.0.17" />
+ # </repository>
+ # </action>
+ for repo_index, repo_elem in enumerate( action_elem ):
+ populated, repository_elem = self.handle_repository_dependency_elem( trans, repo_elem )
+ if populated:
+ action_elem[ repo_index ] = repository_elem
+ package_altered = True
+ if not altered:
+ altered = True
+ if package_altered:
+ actions_elem[ action_index ] = action_elem
+ if package_altered:
+ root_elem[ actions_index ] = actions_elem
+
+ if package_altered:
+ root[ root_index ] = root_elem
+ return altered, root
+ return False, None
+
+ def uncompress( self, repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 ):
+ if isgzip:
+ self.handle_gzip( repository, uploaded_file_name )
+ return uploaded_file_filename.rstrip( '.gz' )
+ if isbz2:
+ self.handle_bz2( repository, uploaded_file_name )
+ return uploaded_file_filename.rstrip( '.bz2' )
+
@web.expose
@web.require_login( 'upload', use_panels=True )
def upload( self, trans, **kwd ):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- commit_message = util.restore_text( params.get( 'commit_message', 'Uploaded' ) )
- category_ids = util.listify( params.get( 'category_id', '' ) )
+ message = kwd.get( 'message', '' )
+ status = kwd.get( 'status', 'done' )
+ commit_message = kwd.get( 'commit_message', 'Uploaded' )
+ category_ids = util.listify( kwd.get( 'category_id', '' ) )
categories = suc.get_categories( trans )
- repository_id = params.get( 'repository_id', '' )
+ repository_id = kwd.get( 'repository_id', '' )
repository = suc.get_repository_in_tool_shed( trans, repository_id )
repo_dir = repository.repo_path( trans.app )
repo = hg.repository( suc.get_configured_ui(), repo_dir )
- uncompress_file = util.string_as_bool( params.get( 'uncompress_file', 'true' ) )
- remove_repo_files_not_in_tar = util.string_as_bool( params.get( 'remove_repo_files_not_in_tar', 'true' ) )
+ uncompress_file = util.string_as_bool( kwd.get( 'uncompress_file', 'true' ) )
+ remove_repo_files_not_in_tar = util.string_as_bool( kwd.get( 'remove_repo_files_not_in_tar', 'true' ) )
uploaded_file = None
- upload_point = self.__get_upload_point( repository, **kwd )
+ upload_point = self.get_upload_point( repository, **kwd )
tip = repository.tip( trans.app )
- file_data = params.get( 'file_data', '' )
- url = params.get( 'url', '' )
+ file_data = kwd.get( 'file_data', '' )
+ url = kwd.get( 'url', '' )
# Part of the upload process is sending email notification to those that have registered to
# receive them. One scenario occurs when the first change set is produced for the repository.
# See the suc.handle_email_alerts() method for the definition of the scenarios.
new_repo_alert = repository.is_new( trans.app )
uploaded_directory = None
- if params.get( 'upload_button', False ):
+ if kwd.get( 'upload_button', False ):
if file_data == '' and url == '':
message = 'No files were entered on the upload form.'
status = 'error'
@@ -155,10 +404,28 @@
full_path = os.path.abspath( os.path.join( repo_dir, upload_point, uploaded_file_filename ) )
else:
full_path = os.path.abspath( os.path.join( repo_dir, uploaded_file_filename ) )
- # Move the uploaded file to the load_point within the repository hierarchy.
- shutil.move( uploaded_file_name, full_path )
- # See if any admin users have chosen to receive email alerts when a repository is
- # updated. If so, check every uploaded file to ensure content is appropriate.
+ # Move some version of the uploaded file to the load_point within the repository hierarchy.
+ if uploaded_file_filename in [ 'repository_dependencies.xml' ]:
+ # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately.
+ altered, root = self.handle_repository_dependencies_definition( trans, uploaded_file_name )
+ if altered:
+ tmp_filename = self.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ shutil.move( tmp_filename, full_path )
+ else:
+ shutil.move( uploaded_file_name, full_path )
+ elif uploaded_file_filename in [ 'tool_dependencies.xml' ]:
+ # Inspect the contents of the file to see if it defines a complex repository dependency definition whose changeset_revision values
+ # are missing and if so, set them appropriately.
+ altered, root = self.handle_tool_dependencies_definition( trans, uploaded_file_name )
+ if altered:
+ tmp_filename = self.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) )
+ shutil.move( tmp_filename, full_path )
+ else:
+ shutil.move( uploaded_file_name, full_path )
+ else:
+ shutil.move( uploaded_file_name, full_path )
+ # See if any admin users have chosen to receive email alerts when a repository is updated. If so, check every uploaded file to ensure
+ # content is appropriate.
check_contents = self.check_file_contents_for_email_alerts( trans )
if check_contents and os.path.isfile( full_path ):
content_alert_str = self.check_file_content_for_html_and_images( full_path )
@@ -264,7 +531,7 @@
filenames_in_archive = []
for root, dirs, files in os.walk( uploaded_directory ):
for uploaded_file in files:
- relative_path = os.path.normpath(os.path.join(os.path.relpath(root, uploaded_directory), uploaded_file))
+ relative_path = os.path.normpath( os.path.join( os.path.relpath( root, uploaded_directory ), uploaded_file ) )
ok = os.path.basename( uploaded_file ) not in undesirable_files
if ok:
for file_path_item in relative_path.split( '/' ):
@@ -275,19 +542,19 @@
else:
undesirable_files_removed += 1
if ok:
- repo_path = os.path.join(full_path, relative_path)
- repo_basedir = os.path.normpath(os.path.join(repo_path, os.path.pardir))
- if not os.path.exists(repo_basedir):
- os.makedirs(repo_basedir)
- if os.path.exists(repo_path):
- if os.path.isdir(repo_path):
- shutil.rmtree(repo_path)
+ repo_path = os.path.join( full_path, relative_path )
+ repo_basedir = os.path.normpath( os.path.join( repo_path, os.path.pardir ) )
+ if not os.path.exists( repo_basedir ):
+ os.makedirs( repo_basedir )
+ if os.path.exists( repo_path ):
+ if os.path.isdir( repo_path ):
+ shutil.rmtree( repo_path )
else:
- os.remove(repo_path)
- shutil.move(os.path.join(uploaded_directory, relative_path), repo_path)
+ os.remove( repo_path )
+ shutil.move( os.path.join( uploaded_directory, relative_path ), repo_path )
filenames_in_archive.append( relative_path )
- return self.__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)
+ return self.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 )
def upload_tar( self, trans, repository, tar, uploaded_file, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert ):
# Upload a tar archive of files.
@@ -295,7 +562,7 @@
repo = hg.repository( suc.get_configured_ui(), repo_dir )
undesirable_dirs_removed = 0
undesirable_files_removed = 0
- ok, message = self.__check_archive( tar )
+ ok, message = self.check_archive( tar )
if not ok:
tar.close()
uploaded_file.close()
@@ -322,159 +589,12 @@
tar.extractall( path=full_path )
tar.close()
uploaded_file.close()
- return self.__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 )
-
- def __handle_directory_changes( self, trans, repository, full_path, filenames_in_archive, remove_repo_files_not_in_tar, new_repo_alert, commit_message,
- undesirable_dirs_removed, undesirable_files_removed ):
- repo_dir = repository.repo_path( trans.app )
- repo = hg.repository( suc.get_configured_ui(), repo_dir )
- content_alert_str = ''
- files_to_remove = []
- filenames_in_archive = [ os.path.join( full_path, name ) for name in filenames_in_archive ]
- if remove_repo_files_not_in_tar and not repository.is_new( trans.app ):
- # We have a repository that is not new (it contains files), so discover
- # those files that are in the repository, but not in the uploaded archive.
- for root, dirs, files in os.walk( full_path ):
- if root.find( '.hg' ) < 0 and root.find( 'hgrc' ) < 0:
- for undesirable_dir in undesirable_dirs:
- if undesirable_dir in dirs:
- dirs.remove( undesirable_dir )
- undesirable_dirs_removed += 1
- for undesirable_file in undesirable_files:
- if undesirable_file in files:
- files.remove( undesirable_file )
- undesirable_files_removed += 1
- for name in files:
- full_name = os.path.join( root, name )
- if full_name not in filenames_in_archive:
- files_to_remove.append( full_name )
- for repo_file in files_to_remove:
- # Remove files in the repository (relative to the upload point) that are not in the uploaded archive.
- try:
- commands.remove( repo.ui, repo, repo_file, force=True )
- except Exception, e:
- log.debug( "Error removing files using the mercurial API, so trying a different approach, the error was: %s" % str( e ))
- relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
- repo.dirstate.remove( relative_selected_file )
- repo.dirstate.write()
- absolute_selected_file = os.path.abspath( selected_file )
- if os.path.isdir( absolute_selected_file ):
- try:
- os.rmdir( absolute_selected_file )
- except OSError, e:
- # The directory is not empty
- pass
- elif os.path.isfile( absolute_selected_file ):
- os.remove( absolute_selected_file )
- dir = os.path.split( absolute_selected_file )[0]
- try:
- os.rmdir( dir )
- except OSError, e:
- # The directory is not empty
- pass
- # See if any admin users have chosen to receive email alerts when a repository is
- # updated. If so, check every uploaded file to ensure content is appropriate.
- check_contents = self.check_file_contents_for_email_alerts( trans )
- for filename_in_archive in filenames_in_archive:
- # Check file content to ensure it is appropriate.
- if check_contents and os.path.isfile( filename_in_archive ):
- content_alert_str += self.check_file_content_for_html_and_images( filename_in_archive )
- commands.add( repo.ui, repo, filename_in_archive )
- if filename_in_archive.endswith( 'tool_data_table_conf.xml.sample' ):
- # Handle the special case where a tool_data_table_conf.xml.sample file is being uploaded by parsing the file and adding new entries
- # to the in-memory trans.app.tool_data_tables dictionary.
- error, message = tool_util.handle_sample_tool_data_table_conf_file( trans.app, filename_in_archive )
- if error:
- return False, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
- commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
- admin_only = len( repository.downloadable_revisions ) != 1
- suc.handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
- return True, '', files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
-
- def uncompress( self, repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 ):
- if isgzip:
- self.__handle_gzip( repository, uploaded_file_name )
- return uploaded_file_filename.rstrip( '.gz' )
- if isbz2:
- self.__handle_bz2( repository, uploaded_file_name )
- return uploaded_file_filename.rstrip( '.bz2' )
-
- def __handle_gzip( self, repository, uploaded_file_name ):
- fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_gunzip_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
- gzipped_file = gzip.GzipFile( uploaded_file_name, 'rb' )
- while 1:
- try:
- chunk = gzipped_file.read( CHUNK_SIZE )
- except IOError, e:
- os.close( fd )
- os.remove( uncompressed )
- log.exception( 'Problem uncompressing gz data "%s": %s' % ( uploaded_file_name, str( e ) ) )
- return
- if not chunk:
- break
- os.write( fd, chunk )
- os.close( fd )
- gzipped_file.close()
- shutil.move( uncompressed, uploaded_file_name )
-
- def __handle_bz2( self, repository, uploaded_file_name ):
- fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_bunzip2_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
- bzipped_file = bz2.BZ2File( uploaded_file_name, 'rb' )
- while 1:
- try:
- chunk = bzipped_file.read( CHUNK_SIZE )
- except IOError:
- os.close( fd )
- os.remove( uncompressed )
- log.exception( 'Problem uncompressing bz2 data "%s": %s' % ( uploaded_file_name, str( e ) ) )
- return
- if not chunk:
- break
- os.write( fd, chunk )
- os.close( fd )
- bzipped_file.close()
- shutil.move( uncompressed, uploaded_file_name )
-
- def __get_upload_point( self, repository, **kwd ):
- upload_point = kwd.get( 'upload_point', None )
- if upload_point is not None:
- # The value of upload_point will be something like: database/community_files/000/repo_12/1.bed
- if os.path.exists( upload_point ):
- if os.path.isfile( upload_point ):
- # Get the parent directory
- upload_point, not_needed = os.path.split( upload_point )
- # Now the value of uplaod_point will be something like: database/community_files/000/repo_12/
- upload_point = upload_point.split( 'repo_%d' % repository.id )[ 1 ]
- if upload_point:
- upload_point = upload_point.lstrip( '/' )
- upload_point = upload_point.rstrip( '/' )
- # Now the value of uplaod_point will be something like: /
- if upload_point == '/':
- upload_point = None
- else:
- # Must have been an error selecting something that didn't exist, so default to repository root
- upload_point = None
- return upload_point
-
- def __check_archive( self, archive ):
- for member in archive.getmembers():
- # Allow regular files and directories only
- if not ( member.isdir() or member.isfile() or member.islnk() ):
- message = "Uploaded archives can only include regular directories and files (no symbolic links, devices, etc). Offender: %s" % str( member )
- return False, message
- for item in [ '.hg', '..', '/' ]:
- if member.name.startswith( item ):
- message = "Uploaded archives cannot contain .hg directories, absolute filenames starting with '/', or filenames with two dots '..'."
- return False, message
- if member.name in [ 'hgrc' ]:
- message = "Uploaded archives cannot contain hgrc files."
- return False, message
- return True, ''
+ return self.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 )
diff -r 03c0f4d30880d3286e0ccc61c3f2554343c8c53e -r 35de5a8a928bf63fd5de3d1ec066097602acf235 lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -1283,6 +1283,13 @@
new_repository_dependencies_metadata = metadata_dict.get( 'repository_dependencies', None )
if new_repository_dependencies_metadata:
new_repository_dependencies = metadata_dict[ 'repository_dependencies' ][ 'repository_dependencies' ]
+ # TODO: We used to include the following here to handle the case where repository dependency definitions were deleted. However,
+ # this erroneously returned True in cases where is should not have done so. This usually occurred where multiple single files
+ # were uploaded when a single tarball should have been. We need to implement support for handling deleted repository dependency
+ # definitions so that we can guarantee reproducibility, but we need to do it in a way that is better than the following.
+ # for new_repository_dependency in new_repository_dependencies:
+ # if new_repository_dependency not in saved_repository_dependencies:
+ # return True
# The saved metadata must be a subset of the new metadata.
for saved_repository_dependency in saved_repository_dependencies:
if saved_repository_dependency not in new_repository_dependencies:
@@ -1314,6 +1321,13 @@
saved_tool_dependencies = metadata[ 'tool_dependencies' ]
new_tool_dependencies = metadata_dict.get( 'tool_dependencies', None )
if new_tool_dependencies:
+ # TODO: We used to include the following here to handle the case where tool dependency definitions were deleted. However,
+ # this erroneously returned True in cases where is should not have done so. This usually occurred where multiple single files
+ # were uploaded when a single tarball should have been. We need to implement support for handling deleted tool dependency
+ # definitions so that we can guarantee reproducibility, but we need to do it in a way that is better than the following.
+ # for new_tool_dependency in new_tool_dependencies:
+ # if new_tool_dependency not in saved_tool_dependencies:
+ # return True
# The saved metadata must be a subset of the new metadata.
for saved_tool_dependency in saved_tool_dependencies:
if saved_tool_dependency not in new_tool_dependencies:
diff -r 03c0f4d30880d3286e0ccc61c3f2554343c8c53e -r 35de5a8a928bf63fd5de3d1ec066097602acf235 lib/tool_shed/util/shed_util_common.py
--- a/lib/tool_shed/util/shed_util_common.py
+++ b/lib/tool_shed/util/shed_util_common.py
@@ -492,6 +492,13 @@
"""Get a tool shed repository record from the Galaxy database defined by the id."""
return trans.sa_session.query( trans.model.ToolShedRepository ).get( trans.security.decode_id( id ) )
+def get_latest_downloadable_changeset_revision( trans, repository, repo ):
+ repository_tip = repository.tip( trans.app )
+ repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), repository_tip )
+ if repository_metadata and repository_metadata.downloadable:
+ return repository_tip
+ return get_previous_metadata_changeset_revision( repository, repo, repository_tip, downloadable=True )
+
def get_named_tmpfile_from_ctx( ctx, filename, dir ):
"""Return a named temporary file created from a specified file with a given name included in a repository changeset revision."""
filename = strip_path( filename )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Only display the tool test results folder if a repository has been installed and tested.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/03c0f4d30880/
Changeset: 03c0f4d30880
User: Dave Bouvier
Date: 2013-05-16 21:55:01
Summary: Only display the tool test results folder if a repository has been installed and tested.
Affected #: 1 file
diff -r 487b92cec404614ddf29b6dfb36b6b47ba6fc086 -r 03c0f4d30880d3286e0ccc61c3f2554343c8c53e lib/galaxy/webapps/tool_shed/util/container_util.py
--- a/lib/galaxy/webapps/tool_shed/util/container_util.py
+++ b/lib/galaxy/webapps/tool_shed/util/container_util.py
@@ -799,8 +799,11 @@
containers_dict[ 'valid_tools' ] = valid_tools_root_folder
# Tool test results container.
if tool_test_results:
- folder_id, tool_test_results_root_folder = build_tool_test_results_folder( trans, folder_id, tool_test_results, time_last_tested=time_last_tested )
- containers_dict[ 'tool_test_results' ] = tool_test_results_root_folder
+ # Only create and populate this folder if there are actual tool test results to display, since the
+ # display of the 'Test environment' folder by itself can be misleading.
+ if 'passed_tests' in tool_test_results or 'failed_tests' in tool_test_results or 'installation_errors' in tool_test_results:
+ folder_id, tool_test_results_root_folder = build_tool_test_results_folder( trans, folder_id, tool_test_results, time_last_tested=time_last_tested )
+ containers_dict[ 'tool_test_results' ] = tool_test_results_root_folder
# Workflows container.
if metadata:
if 'workflows' in metadata:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Add tool shed functional test temporary directory to .hgignore.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/487b92cec404/
Changeset: 487b92cec404
User: Dave Bouvier
Date: 2013-05-16 21:50:58
Summary: Add tool shed functional test temporary directory to .hgignore.
Affected #: 1 file
diff -r 1001212761bbd65fc3b3a69a2ae57ffa257f29f3 -r 487b92cec404614ddf29b6dfb36b6b47ba6fc086 .hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -81,6 +81,7 @@
# Test output
run_functional_tests.html
+test/tool_shed/tmp/*
# Project files
*.kpf
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Additional exception handling when testing repositories that fail installation before a row is created in the Galaxy database.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/99565a21b772/
Changeset: 99565a21b772
User: Dave Bouvier
Date: 2013-05-16 21:22:18
Summary: Additional exception handling when testing repositories that fail installation before a row is created in the Galaxy database.
Affected #: 1 file
diff -r 23e0fd64cfd4f985aafc8499e123167b28cfe8a0 -r 99565a21b77259aa8ad3a470e49de877fe57f2c8 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
@@ -650,7 +650,8 @@
repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( name, owner, changeset_revision )
except:
log.exception( 'Error getting installed repository.' )
- continue
+ success = False
+ pass
# If the installation succeeds, configure and run functional tests for this repository. This is equivalent to
# sh run_functional_tests.sh -installed
if success:
@@ -921,7 +922,11 @@
else:
# Even if the repository failed to install, execute the uninstall method, in case a dependency did succeed.
log.debug( 'Uninstalling repository %s', repository_info_dict[ 'name' ] )
- repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( name, owner, changeset_revision )
+ try:
+ repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( name, owner, changeset_revision )
+ except:
+ log.exception( 'Unable to uninstall, no installed repository found.' )
+ continue
test_result = dict( tool_shed=repository.tool_shed,
name=repository.name,
owner=repository.owner,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Backward compatibility fix for installing repositories with repository dependencies.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/23e0fd64cfd4/
Changeset: 23e0fd64cfd4
User: Dave Bouvier
Date: 2013-05-16 21:18:39
Summary: Backward compatibility fix for installing repositories with repository dependencies.
Affected #: 1 file
diff -r 477979aa944f14ab7189aa51e67168da333f0840 -r 23e0fd64cfd4f985aafc8499e123167b28cfe8a0 lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -1087,7 +1087,7 @@
name = repository_elem.get( 'name' )
owner = repository_elem.get( 'owner' )
changeset_revision = repository_elem.get( 'changeset_revision' )
- prior_installation_required = repository_elem.get( 'prior_installation_required', False )
+ prior_installation_required = str( repository_elem.get( 'prior_installation_required', False ) )
repository_dependency_tup = [ toolshed, name, owner, changeset_revision, prior_installation_required ]
user = None
repository = None
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fixes for determining if a new metadata revision is required when inspecting tool shed repository dependency definitions.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/477979aa944f/
Changeset: 477979aa944f
User: greg
Date: 2013-05-16 17:55:41
Summary: Fixes for determining if a new metadata revision is required when inspecting tool shed repository dependency definitions.
Affected #: 2 files
diff -r 311cf2ae1623289551b0b568975e9840d46097a2 -r 477979aa944f14ab7189aa51e67168da333f0840 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
@@ -1,15 +1,27 @@
-import sys, os, shutil, logging, tarfile, tempfile, urllib
+import logging
+import os
+import shutil
+import sys
+import tarfile
+import tempfile
+import urllib
from galaxy.web.base.controller import BaseUIController
-from galaxy import web, util
+from galaxy import util
+from galaxy import web
from galaxy.datatypes import checkers
from galaxy.util import json
import tool_shed.util.shed_util_common as suc
-from tool_shed.util import metadata_util, repository_dependency_util, tool_dependency_util, tool_util
+from tool_shed.util import metadata_util
+from tool_shed.util import repository_dependency_util
+from tool_shed.util import tool_dependency_util
+from tool_shed.util import tool_util
from galaxy import eggs
-eggs.require('mercurial')
-from mercurial import hg, ui, commands
+eggs.require( 'mercurial' )
+from mercurial import commands
+from mercurial import hg
+from mercurial import ui
log = logging.getLogger( __name__ )
@@ -17,7 +29,9 @@
undesirable_files = [ '.hg_archival.txt', 'hgrc', '.DS_Store' ]
CHUNK_SIZE = 2**20 # 1Mb
+
class UploadController( BaseUIController ):
+
def check_file_contents_for_email_alerts( self, trans ):
"""
See if any admin users have chosen to receive email alerts when a repository is updated. If so, the file contents of the update must be
@@ -31,6 +45,7 @@
if user_email in admin_users:
return True
return False
+
def check_file_content_for_html_and_images( self, file_path ):
message = ''
if checkers.check_html( file_path ):
@@ -38,6 +53,7 @@
elif checkers.check_image( file_path ):
message = 'The file "%s" contains image content.\n' % str( file_path )
return message
+
@web.expose
@web.require_login( 'upload', use_panels=True )
def upload( self, trans, **kwd ):
@@ -235,6 +251,7 @@
remove_repo_files_not_in_tar=remove_repo_files_not_in_tar,
message=message,
status=status )
+
def upload_directory( self, trans, repository, uploaded_directory, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert ):
repo_dir = repository.repo_path( trans.app )
repo = hg.repository( suc.get_configured_ui(), repo_dir )
@@ -269,7 +286,9 @@
os.remove(repo_path)
shutil.move(os.path.join(uploaded_directory, relative_path), repo_path)
filenames_in_archive.append( relative_path )
- return self.__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)
+ return self.__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)
+
def upload_tar( self, trans, repository, tar, uploaded_file, upload_point, remove_repo_files_not_in_tar, commit_message, new_repo_alert ):
# Upload a tar archive of files.
repo_dir = repository.repo_path( trans.app )
@@ -303,8 +322,18 @@
tar.extractall( path=full_path )
tar.close()
uploaded_file.close()
- return self.__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)
- def __handle_directory_changes( self, trans, repository, full_path, filenames_in_archive, remove_repo_files_not_in_tar, new_repo_alert, commit_message, undesirable_dirs_removed, undesirable_files_removed ):
+ return self.__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 )
+
+ def __handle_directory_changes( self, trans, repository, full_path, filenames_in_archive, remove_repo_files_not_in_tar, new_repo_alert, commit_message,
+ undesirable_dirs_removed, undesirable_files_removed ):
repo_dir = repository.repo_path( trans.app )
repo = hg.repository( suc.get_configured_ui(), repo_dir )
content_alert_str = ''
@@ -369,6 +398,7 @@
admin_only = len( repository.downloadable_revisions ) != 1
suc.handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
return True, '', files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
+
def uncompress( self, repository, uploaded_file_name, uploaded_file_filename, isgzip, isbz2 ):
if isgzip:
self.__handle_gzip( repository, uploaded_file_name )
@@ -376,6 +406,7 @@
if isbz2:
self.__handle_bz2( repository, uploaded_file_name )
return uploaded_file_filename.rstrip( '.bz2' )
+
def __handle_gzip( self, repository, uploaded_file_name ):
fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_gunzip_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
gzipped_file = gzip.GzipFile( uploaded_file_name, 'rb' )
@@ -393,6 +424,7 @@
os.close( fd )
gzipped_file.close()
shutil.move( uncompressed, uploaded_file_name )
+
def __handle_bz2( self, repository, uploaded_file_name ):
fd, uncompressed = tempfile.mkstemp( prefix='repo_%d_upload_bunzip2_' % repository.id, dir=os.path.dirname( uploaded_file_name ), text=False )
bzipped_file = bz2.BZ2File( uploaded_file_name, 'rb' )
@@ -410,6 +442,7 @@
os.close( fd )
bzipped_file.close()
shutil.move( uncompressed, uploaded_file_name )
+
def __get_upload_point( self, repository, **kwd ):
upload_point = kwd.get( 'upload_point', None )
if upload_point is not None:
@@ -430,6 +463,7 @@
# Must have been an error selecting something that didn't exist, so default to repository root
upload_point = None
return upload_point
+
def __check_archive( self, archive ):
for member in archive.getmembers():
# Allow regular files and directories only
diff -r 311cf2ae1623289551b0b568975e9840d46097a2 -r 477979aa944f14ab7189aa51e67168da333f0840 lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -1088,7 +1088,7 @@
owner = repository_elem.get( 'owner' )
changeset_revision = repository_elem.get( 'changeset_revision' )
prior_installation_required = repository_elem.get( 'prior_installation_required', False )
- repository_dependency_tup = ( toolshed, name, owner, changeset_revision, prior_installation_required )
+ repository_dependency_tup = [ toolshed, name, owner, changeset_revision, prior_installation_required ]
user = None
repository = None
if app.name == 'galaxy':
@@ -1284,11 +1284,8 @@
if new_repository_dependencies_metadata:
new_repository_dependencies = metadata_dict[ 'repository_dependencies' ][ 'repository_dependencies' ]
# The saved metadata must be a subset of the new metadata.
- for new_repository_dependency in new_repository_dependencies:
- if new_repository_dependency not in saved_repository_dependencies:
- return True
- for saved_repository_dependency_metadata in saved_repository_dependencies:
- if saved_repository_dependency_metadata not in new_repository_dependencies:
+ for saved_repository_dependency in saved_repository_dependencies:
+ if saved_repository_dependency not in new_repository_dependencies:
return True
return False
else:
@@ -1318,9 +1315,6 @@
new_tool_dependencies = metadata_dict.get( 'tool_dependencies', None )
if new_tool_dependencies:
# The saved metadata must be a subset of the new metadata.
- for new_tool_dependency in new_tool_dependencies:
- if new_tool_dependency not in saved_tool_dependencies:
- return True
for saved_tool_dependency in saved_tool_dependencies:
if saved_tool_dependency not in new_tool_dependencies:
return True
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Add exception handling for edge case when a repository record is not created by the install method.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/311cf2ae1623/
Changeset: 311cf2ae1623
User: Dave Bouvier
Date: 2013-05-16 05:51:39
Summary: Add exception handling for edge case when a repository record is not created by the install method.
Affected #: 1 file
diff -r 53d3c967d373c3b0589a31a4e400b01786cea091 -r 311cf2ae1623289551b0b568975e9840d46097a2 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
@@ -646,7 +646,11 @@
repository_status[ 'passed_tests' ] = []
repository_status[ 'failed_tests' ] = []
repository_status[ 'installation_errors' ] = dict( current_repository=[], repository_dependencies=[], tool_dependencies=[] )
- repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( name, owner, changeset_revision )
+ try:
+ repository = test_db_util.get_installed_repository_by_name_owner_changeset_revision( name, owner, changeset_revision )
+ except:
+ log.exception( 'Error getting installed repository.' )
+ continue
# If the installation succeeds, configure and run functional tests for this repository. This is equivalent to
# sh run_functional_tests.sh -installed
if success:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Add the ability for a repository owner to check a "Skip automated testing of tools in this revision" checkbox on the Manage repository page. If checked, the tool shed automated test framework will skip testing the tools in that revision.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/53d3c967d373/
Changeset: 53d3c967d373
User: greg
Date: 2013-05-16 02:15:11
Summary: Add the ability for a repository owner to check a "Skip automated testing of tools in this revision" checkbox on the Manage repository page. If checked, the tool shed automated test framework will skip testing the tools in that revision.
Affected #: 2 files
diff -r f2e183a522d8b1d08ff293f23cce254cc4597721 -r 53d3c967d373c3b0589a31a4e400b01786cea091 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
@@ -341,7 +341,7 @@
message += '<ul>'
message += '<li>you are authorized to update them</li>'
message += '<li>the latest installable revision contains at least 1 tool</li>'
- message += '<li>the latest installable revision has <b>Skip tool tests</b> checked</li>'
+ message += '<li>the latest installable revision has <b>Skip automated testing of tools in this revision</b> checked</li>'
message += '</ul>'
kwd[ 'message' ] = message
kwd[ 'status' ] = 'warning'
@@ -649,7 +649,7 @@
message = 'This list contains repositories that match the following criteria:<br>'
message += '<ul>'
message += '<li>the latest installable revision contains at least 1 tool</li>'
- message += '<li>the latest installable revision has <b>Skip tool tests</b> checked</li>'
+ message += '<li>the latest installable revision has <b>Skip automated testing of tools in this revision</b> checked</li>'
message += '</ul>'
kwd[ 'message' ] = message
kwd[ 'status' ] = 'warning'
@@ -1976,29 +1976,31 @@
@web.expose
@web.require_login( "manage repository" )
def manage_repository( self, trans, id, **kwd ):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
+ message = util.restore_text( kwd.get( 'message', '' ) )
+ status = kwd.get( 'status', 'done' )
repository = suc.get_repository_in_tool_shed( trans, id )
repo_dir = repository.repo_path( trans.app )
repo = hg.repository( suc.get_configured_ui(), repo_dir )
- repo_name = util.restore_text( params.get( 'repo_name', repository.name ) )
- changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip( trans.app ) ) )
- description = util.restore_text( params.get( 'description', repository.description ) )
- long_description = util.restore_text( params.get( 'long_description', repository.long_description ) )
+ repo_name = util.restore_text( kwd.get( 'repo_name', repository.name ) )
+ changeset_revision = util.restore_text( kwd.get( 'changeset_revision', repository.tip( trans.app ) ) )
+ description = util.restore_text( kwd.get( 'description', repository.description ) )
+ long_description = util.restore_text( kwd.get( 'long_description', repository.long_description ) )
avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model )
- display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
- alerts = params.get( 'alerts', '' )
+ display_reviews = util.string_as_bool( kwd.get( 'display_reviews', False ) )
+ alerts = kwd.get( 'alerts', '' )
alerts_checked = CheckboxField.is_checked( alerts )
- category_ids = util.listify( params.get( 'category_id', '' ) )
+ skip_tool_tests = kwd.get( 'skip_tool_tests', '' )
+ skip_tool_tests_checked = CheckboxField.is_checked( skip_tool_tests )
+ skip_tool_tests_comment = kwd.get( 'skip_tool_tests_comment', '' )
+ category_ids = util.listify( kwd.get( 'category_id', '' ) )
if repository.email_alerts:
email_alerts = json.from_json_string( repository.email_alerts )
else:
email_alerts = []
- allow_push = params.get( 'allow_push', '' )
+ allow_push = kwd.get( 'allow_push', '' )
error = False
user = trans.user
- if params.get( 'edit_repository_button', False ):
+ if kwd.get( 'edit_repository_button', False ):
flush_needed = False
# TODO: add a can_manage in the security agent.
if not ( user.email == repository.user.email or trans.user_is_admin() ):
@@ -2034,7 +2036,32 @@
trans.sa_session.add( repository )
trans.sa_session.flush()
message += "The repository information has been updated."
- elif params.get( 'manage_categories_button', False ):
+ elif kwd.get( 'skip_tool_tests_button', False ):
+ repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
+ skip_tool_test = repository_metadata.skip_tool_tests
+ if skip_tool_test:
+ # Handle the mapper behavior.
+ skip_tool_test = skip_tool_test[ 0 ]
+ if skip_tool_tests_checked:
+ if skip_tool_test:
+ comment = skip_tool_test.comment
+ if comment != skip_tool_tests_comment:
+ skip_tool_test.comment = skip_tool_tests_comment
+ trans.sa_session.add( skip_tool_test )
+ trans.sa_session.flush()
+ else:
+ skip_tool_test = trans.model.SkipToolTest( repository_metadata_id=repository_metadata.id,
+ initial_changeset_revision=changeset_revision,
+ comment=skip_tool_tests_comment )
+ trans.sa_session.add( skip_tool_test )
+ trans.sa_session.flush()
+ message = "Tools in this revision will be tested by the automated test framework."
+ else:
+ if skip_tool_test:
+ trans.sa_session.delete( skip_tool_test )
+ trans.sa_session.flush()
+ message = "Tools in this revision will not be tested by the automated test framework."
+ elif kwd.get( 'manage_categories_button', False ):
flush_needed = False
# Delete all currently existing categories.
for rca in repository.categories:
@@ -2043,14 +2070,14 @@
if category_ids:
# Create category associations
for category_id in category_ids:
- category = trans.sa_session.query(model.Category).get( trans.security.decode_id( category_id ) )
+ category = trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( category_id ) )
rca = trans.app.model.RepositoryCategoryAssociation( repository, category )
trans.sa_session.add( rca )
trans.sa_session.flush()
message = "The repository information has been updated."
- elif params.get( 'user_access_button', False ):
+ elif kwd.get( 'user_access_button', False ):
if allow_push not in [ 'none' ]:
- remove_auth = params.get( 'remove_auth', '' )
+ remove_auth = kwd.get( 'remove_auth', '' )
if remove_auth:
usernames = ''
else:
@@ -2062,7 +2089,7 @@
usernames = ','.join( usernames )
repository.set_allow_push( trans.app, usernames, remove_auth=remove_auth )
message = "The repository information has been updated."
- elif params.get( 'receive_email_alerts_button', False ):
+ elif kwd.get( 'receive_email_alerts_button', False ):
flush_needed = False
if alerts_checked:
if user.email not in email_alerts:
@@ -2097,6 +2124,7 @@
repository_metadata = None
metadata = None
is_malicious = False
+ skip_tool_test = None
repository_dependencies = None
if changeset_revision != suc.INITIAL_CHANGELOG_HASH:
repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
@@ -2114,6 +2142,11 @@
metadata = repository_metadata.metadata
is_malicious = repository_metadata.malicious
if repository_metadata:
+ skip_tool_test = repository_metadata.skip_tool_tests
+ if skip_tool_test:
+ # Handle the mapper behavior.
+ skip_tool_test = skip_tool_test[ 0 ]
+ skip_tool_tests_checked = True
metadata = repository_metadata.metadata
# Get a dictionary of all repositories upon which the contents of the current repository_metadata record depend.
repository_dependencies = \
@@ -2136,6 +2169,7 @@
message += malicious_error
status = 'error'
malicious_check_box = CheckboxField( 'malicious', checked=is_malicious )
+ skip_tool_tests_check_box = CheckboxField( 'skip_tool_tests', checked=skip_tool_tests_checked )
categories = suc.get_categories( trans )
selected_categories = [ rca.category_id for rca in repository.categories ]
containers_dict = container_util.build_repository_containers_for_tool_shed( trans, repository, changeset_revision, repository_dependencies, repository_metadata )
@@ -2159,6 +2193,8 @@
display_reviews=display_reviews,
num_ratings=num_ratings,
alerts_check_box=alerts_check_box,
+ skip_tool_tests_check_box=skip_tool_tests_check_box,
+ skip_tool_test=skip_tool_test,
malicious_check_box=malicious_check_box,
message=message,
status=status )
diff -r f2e183a522d8b1d08ff293f23cce254cc4597721 -r 53d3c967d373c3b0589a31a4e400b01786cea091 templates/webapps/tool_shed/repository/manage_repository.mako
--- a/templates/webapps/tool_shed/repository/manage_repository.mako
+++ b/templates/webapps/tool_shed/repository/manage_repository.mako
@@ -58,6 +58,11 @@
can_view_change_log = not is_new
+ if repository_metadata and repository_metadata.includes_tools:
+ includes_tools = True
+ else:
+ includes_tools = False
+
if changeset_revision_is_repository_tip:
tip_str = 'repository tip'
sharable_link_label = 'Sharable link to this repository:'
@@ -193,6 +198,36 @@
</div></div>
${render_repository_items( metadata, containers_dict, can_set_metadata=True )}
+%if includes_tools:
+ <p/>
+ <div class="toolForm">
+ <div class="toolFormTitle">Automated tool tests</div>
+ <div class="toolFormBody">
+ <form name="skip_tool_tests" id="skip_tool_tests" action="${h.url_for( controller='repository', action='manage_repository', id=trans.security.encode_id( repository.id ), changeset_revision=repository_metadata.changeset_revision )}" method="post" >
+ <div class="form-row">
+ <label>Skip automated testing of tools in this revision:</label>
+ ${skip_tool_tests_check_box.get_html()}
+ <div class="toolParamHelp" style="clear: both;">
+ Check the box and click <b>Save</b> to skip automated testing of the tools in this revision.
+ </div>
+ </div>
+ <div style="clear: both"></div>
+ <div class="form-row">
+ <label>Reason for skipping automated testing:</label>
+ %if skip_tool_test:
+ <pre><textarea name="skip_tool_tests_comment" rows="3" cols="80">${skip_tool_test.comment | h}</textarea></pre>
+ %else:
+ <textarea name="skip_tool_tests_comment" rows="3" cols="80"></textarea>
+ %endif
+ </div>
+ <div style="clear: both"></div>
+ <div class="form-row">
+ <input type="submit" name="skip_tool_tests_button" value="Save"/>
+ </div>
+ </form>
+ </div>
+ </div>
+%endif
<p/><div class="toolForm"><div class="toolFormTitle">Manage categories</div>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fixes for determing if a new repository_metadata record is required for a tool shed repository.
by commits-noreply@bitbucket.org 16 May '13
by commits-noreply@bitbucket.org 16 May '13
16 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f2e183a522d8/
Changeset: f2e183a522d8
User: greg
Date: 2013-05-16 02:06:44
Summary: Fixes for determing if a new repository_metadata record is required for a tool shed repository.
Affected #: 2 files
diff -r 590f27390d0dca446fe804a99ba819663653207e -r f2e183a522d8b1d08ff293f23cce254cc4597721 lib/tool_shed/util/metadata_util.py
--- a/lib/tool_shed/util/metadata_util.py
+++ b/lib/tool_shed/util/metadata_util.py
@@ -300,18 +300,17 @@
skip_tool_test = suc.get_skip_tool_test_by_changeset_revision( trans, changeset_hash )
if skip_tool_test:
# We found a skip_tool_test record associated with the changeset_revision, so see if it has a valid repository_revision.
- try:
- repository_revision = skip_tool_test.repository_revision
+ repository_revision = suc.get_repository_metadata_by_id( trans, trans.security.encode_id( repository_metadata.id ) )
+ if repository_revision:
# The skip_tool_test record is associated with a valid repository_metadata record, so proceed.
continue
- except:
- # We found a skip_tool_test record that is associated with an invalid repository_metadata record, so update it to point to
- # the newly created repository_metadata record. In some special cases there may be multiple skip_tool_test records that
- # require updating, so we won't break here, we'll continue to inspect the rest of the changelog up to the received
- # changeset_revision.
- skip_tool_test.repository_revision = repository_metadata
- trans.sa_session.add( skip_tool_test )
- trans.sa_session.flush()
+ # We found a skip_tool_test record that is associated with an invalid repository_metadata record, so update it to point to
+ # the newly created repository_metadata record. In some special cases there may be multiple skip_tool_test records that
+ # require updating, so we won't break here, we'll continue to inspect the rest of the changelog up to the received
+ # changeset_revision.
+ skip_tool_test.repository_metadata_id = repository_metadata.id
+ trans.sa_session.add( skip_tool_test )
+ trans.sa_session.flush()
if changeset_hash == changeset_revision:
# Proceed no further than the received changeset_revision.
break
@@ -1285,16 +1284,19 @@
if new_repository_dependencies_metadata:
new_repository_dependencies = metadata_dict[ 'repository_dependencies' ][ 'repository_dependencies' ]
# The saved metadata must be a subset of the new metadata.
- for new_repository_dependency_metadata in new_repository_dependencies:
- if new_repository_dependency_metadata not in saved_repository_dependencies:
+ for new_repository_dependency in new_repository_dependencies:
+ if new_repository_dependency not in saved_repository_dependencies:
return True
for saved_repository_dependency_metadata in saved_repository_dependencies:
if saved_repository_dependency_metadata not in new_repository_dependencies:
return True
+ return False
else:
# The repository_dependencies.xml file must have been deleted, so create a new repository_metadata record so we always have
# access to the deleted file.
return True
+ else:
+ return False
else:
if 'repository_dependencies' in metadata_dict:
# There is no saved repository metadata, so we need to create a new repository_metadata record.
@@ -1316,16 +1318,19 @@
new_tool_dependencies = metadata_dict.get( 'tool_dependencies', None )
if new_tool_dependencies:
# The saved metadata must be a subset of the new metadata.
- for new_repository_dependency_metadata in new_tool_dependencies:
- if new_repository_dependency_metadata not in saved_tool_dependencies:
+ for new_tool_dependency in new_tool_dependencies:
+ if new_tool_dependency not in saved_tool_dependencies:
return True
- for saved_repository_dependency_metadata in saved_tool_dependencies:
- if saved_repository_dependency_metadata not in new_tool_dependencies:
+ for saved_tool_dependency in saved_tool_dependencies:
+ if saved_tool_dependency not in new_tool_dependencies:
return True
+ return False
else:
# The tool_dependencies.xml file must have been deleted, so create a new repository_metadata record so we always have
# access to the deleted file.
return True
+ else:
+ return False
else:
# We have repository metadata that does not include metadata for any tool dependencies in the repository, so we can update
# the existing repository metadata.
@@ -1367,6 +1372,7 @@
for new_tool_metadata_dict in metadata_dict[ 'tools' ]:
if new_tool_metadata_dict[ 'id' ] not in saved_tool_ids:
return True
+ return False
else:
# The new metadata includes tools, but the stored metadata does not, so we can update the stored metadata.
return False
diff -r 590f27390d0dca446fe804a99ba819663653207e -r f2e183a522d8b1d08ff293f23cce254cc4597721 lib/tool_shed/util/shed_util_common.py
--- a/lib/tool_shed/util/shed_util_common.py
+++ b/lib/tool_shed/util/shed_util_common.py
@@ -724,6 +724,9 @@
return all_metadata_records[ 0 ]
return None
+def get_repository_metadata_by_id( trans, id ):
+ return trans.sa_session.query( trans.model.RepositoryMetadata ).get( trans.security.decode_id( id ) )
+
def get_repository_owner( cleaned_repository_url ):
"""Gvien a "cleaned" repository clone URL, return the owner of the repository."""
items = cleaned_repository_url.split( '/repos/' )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/582a0194b5c5/
Changeset: 582a0194b5c5
User: dannon
Date: 2013-05-16 00:47:30
Summary: Library API: import cleanup (fixes incorrect util import)
Affected #: 1 file
diff -r a87623b5f358a570718afc07d7a714269220db61 -r 582a0194b5c55994e0f961da7b762d0a8779ce2f lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -1,13 +1,12 @@
"""
API operations on a library.
"""
-import logging, os, string, shutil, urllib, re, socket
-from cgi import escape, FieldStorage
-from galaxy import util, datatypes, jobs, web, util
-from galaxy.web.base.controller import *
-from galaxy.util.sanitize_html import sanitize_html
-from galaxy.model.orm import *
-from paste.httpexceptions import *
+import logging
+from galaxy import util
+from galaxy import web
+from galaxy.model.orm import and_, not_, or_
+from galaxy.web.base.controller import BaseAPIController, url_for
+from paste.httpexceptions import HTTPBadRequest, HTTPForbidden
log = logging.getLogger( __name__ )
https://bitbucket.org/galaxy/galaxy-central/commits/f301a34b2ca6/
Changeset: f301a34b2ca6
User: dannon
Date: 2013-05-16 00:47:52
Summary: Remove unused params from library.show
Affected #: 1 file
diff -r 582a0194b5c55994e0f961da7b762d0a8779ce2f -r f301a34b2ca6ae3c8ab842161e32a5c2d6dd458b lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -56,7 +56,6 @@
log.debug( "LibraryContentsController.show: enter" )
library_id = id
deleted = util.string_as_bool( deleted )
- params = util.Params( kwd )
try:
decoded_library_id = trans.security.decode_id( library_id )
except TypeError:
https://bitbucket.org/galaxy/galaxy-central/commits/590f27390d0d/
Changeset: 590f27390d0d
User: dannon
Date: 2013-05-16 00:48:44
Summary: Library Contents API: import cleanup, remove import *'s.
Affected #: 1 file
diff -r f301a34b2ca6ae3c8ab842161e32a5c2d6dd458b -r 590f27390d0dca446fe804a99ba819663653207e lib/galaxy/webapps/galaxy/api/library_contents.py
--- a/lib/galaxy/webapps/galaxy/api/library_contents.py
+++ b/lib/galaxy/webapps/galaxy/api/library_contents.py
@@ -1,13 +1,10 @@
"""
API operations on the contents of a library.
"""
-import logging, os, string, shutil, urllib, re, socket
-from cgi import escape, FieldStorage
-from galaxy import util, datatypes, jobs, web, util
-from galaxy.web.base.controller import *
-from galaxy.util.sanitize_html import sanitize_html
-from galaxy.model.orm import *
+import logging
+from galaxy import web
from galaxy.model import ExtendedMetadata, ExtendedMetadataIndex
+from galaxy.web.base.controller import BaseAPIController, HTTPBadRequest, url_for, UsesLibraryMixin, UsesLibraryMixinItems
log = logging.getLogger( __name__ )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jgoecks: Viz framework: (a) add sam-to-bigwig converter and (b) Trackster bug fixes.
by commits-noreply@bitbucket.org 15 May '13
by commits-noreply@bitbucket.org 15 May '13
15 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/a87623b5f358/
Changeset: a87623b5f358
User: jgoecks
Date: 2013-05-15 22:15:12
Summary: Viz framework: (a) add sam-to-bigwig converter and (b) Trackster bug fixes.
Affected #: 2 files
diff -r 00ecd6a50591b108d3af3a2c6fd6f9389217d363 -r a87623b5f358a570718afc07d7a714269220db61 lib/galaxy/datatypes/converters/sam_to_bigwig_converter.xml
--- /dev/null
+++ b/lib/galaxy/datatypes/converters/sam_to_bigwig_converter.xml
@@ -0,0 +1,20 @@
+<tool id="CONVERTER_sam_to_bigwig_0" name="Convert SAM to BigWig" version="1.0.0" hidden="true">
+ <command>
+ samtools view -bh $input | bedtools genomecov -bg -split -ibam stdin -g $chromInfo
+
+ ## Streaming the bedgraph file to wigToBigWig is fast but very memory intensive; hence, this
+ ## should only be used on systems with large RAM.
+ ## | wigToBigWig stdin $chromInfo $output
+
+ ## This can be used anywhere.
+ > temp.bg ; bedGraphToBigWig temp.bg $chromInfo $output
+ </command>
+ <inputs>
+ <param format="bam" name="input" type="data" label="Choose BAM file"/>
+ </inputs>
+ <outputs>
+ <data format="bigwig" name="output"/>
+ </outputs>
+ <help>
+ </help>
+</tool>
diff -r 00ecd6a50591b108d3af3a2c6fd6f9389217d363 -r a87623b5f358a570718afc07d7a714269220db61 static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -3026,7 +3026,7 @@
num_cols: 12,
on_finish: function(new_val) {
$(".bs-tooltip").remove();
- var new_val = parseFloat(new_val);
+ var new_val = round(parseFloat(new_val), 3);
track.prefs[ pref_name ] = (!isNaN(new_val) ? new_val : null);
on_change();
},
@@ -3134,14 +3134,14 @@
track.tile_cache.set_elt(key, tile_drawn);
$.when.apply($, get_tile_data()).then( function() {
var tile_data = get_tile_data(),
- tracks_data = tile_data.slice(0, tile_data.length - 1),
- seq_data = tile_data[tile_data.length - 1];
-
+ tracks_data = tile_data,
+ seq_data;
+
// If sequence data is available, subset to get only data in region.
- if (seq_data) {
- seq_data = view.reference_track.data_manager.subset_entry(seq_data, region);
+ if (view.reference_track) {
+ seq_data = view.reference_track.data_manager.subset_entry(tile_data.pop(), region);
}
-
+
// Get drawing modes, heights for all tracks.
var drawing_modes = [],
drawing_heights = [];
@@ -3733,8 +3733,8 @@
*/
postdraw_actions: function(tiles, width, w_scale, clear_after) {
// Add min, max labels.
- this._add_yaxis_label('max', round(this.prefs.max_value, 3), 'max_value');
- this._add_yaxis_label('min', round(this.prefs.min_value, 3), 'min_value');
+ this._add_yaxis_label('max');
+ this._add_yaxis_label('min');
}
});
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
5 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8d97d7e9e50b/
Changeset: 8d97d7e9e50b
User: dan
Date: 2013-05-15 22:03:48
Summary: Fix imports in lib/galaxy/webapps/reports/controllers/root.py
Affected #: 1 file
diff -r 2c3d59e8341e42117537a6d81760801191240a92 -r 8d97d7e9e50b5d2e0d3cbd62b9bf92d4217f562c lib/galaxy/webapps/reports/controllers/root.py
--- a/lib/galaxy/webapps/reports/controllers/root.py
+++ b/lib/galaxy/webapps/reports/controllers/root.py
@@ -1,4 +1,4 @@
-from galaxy.web.base.controller import *
+from galaxy.web.base.controller import BaseUIController, web
import logging
log = logging.getLogger( __name__ )
https://bitbucket.org/galaxy/galaxy-central/commits/87b4c2c7b04c/
Changeset: 87b4c2c7b04c
User: dan
Date: 2013-05-15 22:04:04
Summary: Fix imports in lib/galaxy/webapps/reports/controllers/sample_tracking.py
Affected #: 1 file
diff -r 8d97d7e9e50b5d2e0d3cbd62b9bf92d4217f562c -r 87b4c2c7b04c33fa0d89c6b9ca1d799e18df5efc lib/galaxy/webapps/reports/controllers/sample_tracking.py
--- a/lib/galaxy/webapps/reports/controllers/sample_tracking.py
+++ b/lib/galaxy/webapps/reports/controllers/sample_tracking.py
@@ -1,10 +1,10 @@
import calendar, operator, os, socket
-from datetime import *
+from datetime import datetime, date, timedelta
from time import mktime, strftime, localtime
-from galaxy.web.base.controller import *
+from galaxy.web.base.controller import BaseUIController, web
from galaxy import model, util
from galaxy.web.framework.helpers import time_ago, iff, grids
-from galaxy.model.orm import *
+from galaxy.model.orm import and_, not_, or_
import pkg_resources
pkg_resources.require( "SQLAlchemy >= 0.4" )
import sqlalchemy as sa
https://bitbucket.org/galaxy/galaxy-central/commits/2f18df41a411/
Changeset: 2f18df41a411
User: dan
Date: 2013-05-15 22:05:38
Summary: Fix imports in lib/galaxy/webapps/reports/controllers/workflows.py
Affected #: 1 file
diff -r 87b4c2c7b04c33fa0d89c6b9ca1d799e18df5efc -r 2f18df41a411db2cfa876135b1e2cbeee063b73f lib/galaxy/webapps/reports/controllers/workflows.py
--- a/lib/galaxy/webapps/reports/controllers/workflows.py
+++ b/lib/galaxy/webapps/reports/controllers/workflows.py
@@ -1,10 +1,10 @@
import calendar, operator, os, socket
-from datetime import *
+from datetime import datetime, date, timedelta
from time import mktime, strftime, localtime
-from galaxy.web.base.controller import *
+from galaxy.web.base.controller import BaseUIController, web
from galaxy import model, util
from galaxy.web.framework.helpers import time_ago, iff, grids
-from galaxy.model.orm import *
+from galaxy.model.orm import and_, not_, or_
import pkg_resources
pkg_resources.require( "SQLAlchemy >= 0.4" )
import sqlalchemy as sa
https://bitbucket.org/galaxy/galaxy-central/commits/c75d043c9d4b/
Changeset: c75d043c9d4b
User: dan
Date: 2013-05-15 22:08:07
Summary: Fix imports in lib/galaxy/webapps/reports/controllers/users.py
Affected #: 1 file
diff -r 2f18df41a411db2cfa876135b1e2cbeee063b73f -r c75d043c9d4bcdf006f4e0b69387e0ee35e354ea lib/galaxy/webapps/reports/controllers/users.py
--- a/lib/galaxy/webapps/reports/controllers/users.py
+++ b/lib/galaxy/webapps/reports/controllers/users.py
@@ -1,9 +1,10 @@
-from datetime import *
+from datetime import datetime, date, timedelta
from time import strftime
import calendar, operator
-from galaxy.web.base.controller import *
+from galaxy.web.base.controller import BaseUIController, web
import galaxy.model
-from galaxy.model.orm import *
+from galaxy import util
+from galaxy.model.orm import and_, not_, or_
import pkg_resources
pkg_resources.require( "SQLAlchemy >= 0.4" )
import sqlalchemy as sa
https://bitbucket.org/galaxy/galaxy-central/commits/00ecd6a50591/
Changeset: 00ecd6a50591
User: dan
Date: 2013-05-15 22:10:07
Summary: Fix imports in lib/galaxy/webapps/reports/controllers/system.py
Affected #: 1 file
diff -r c75d043c9d4bcdf006f4e0b69387e0ee35e354ea -r 00ecd6a50591b108d3af3a2c6fd6f9389217d363 lib/galaxy/webapps/reports/controllers/system.py
--- a/lib/galaxy/webapps/reports/controllers/system.py
+++ b/lib/galaxy/webapps/reports/controllers/system.py
@@ -1,8 +1,8 @@
import operator, os
from datetime import datetime, timedelta
-from galaxy.web.base.controller import *
-from galaxy import model
-from galaxy.model.orm import *
+from galaxy.web.base.controller import BaseUIController, web
+from galaxy import model, util
+from galaxy.model.orm import and_, not_, or_, desc
import logging
log = logging.getLogger( __name__ )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dan: Fix import * in webapps/reports/controllers/jobs.py. Was causing util from galaxy to be overwritten with util from galaxy.model.orm.
by commits-noreply@bitbucket.org 15 May '13
by commits-noreply@bitbucket.org 15 May '13
15 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/2c3d59e8341e/
Changeset: 2c3d59e8341e
User: dan
Date: 2013-05-15 21:59:44
Summary: Fix import * in webapps/reports/controllers/jobs.py. Was causing util from galaxy to be overwritten with util from galaxy.model.orm.
Affected #: 1 file
diff -r b8c951a4e56b7e0cdc8cd176dd96521c2e0eb764 -r 2c3d59e8341e42117537a6d81760801191240a92 lib/galaxy/webapps/reports/controllers/jobs.py
--- a/lib/galaxy/webapps/reports/controllers/jobs.py
+++ b/lib/galaxy/webapps/reports/controllers/jobs.py
@@ -1,10 +1,10 @@
import calendar, operator, os, socket
-from datetime import *
+from datetime import datetime, date, timedelta
from time import mktime, strftime, localtime
-from galaxy.web.base.controller import *
+from galaxy.web.base.controller import BaseUIController, web
from galaxy import model, util
from galaxy.web.framework.helpers import time_ago, iff, grids
-from galaxy.model.orm import *
+from galaxy.model.orm import and_, not_, or_
import pkg_resources
pkg_resources.require( "SQLAlchemy >= 0.4" )
import sqlalchemy as sa
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dan: Add missing message_box_* config elements to Reports app. Broken in cb0432cfcc8a.
by commits-noreply@bitbucket.org 15 May '13
by commits-noreply@bitbucket.org 15 May '13
15 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b8c951a4e56b/
Changeset: b8c951a4e56b
User: dan
Date: 2013-05-15 21:44:58
Summary: Add missing message_box_* config elements to Reports app. Broken in cb0432cfcc8a.
Affected #: 1 file
diff -r 52b6923d713b0c205cdc8c970e49f4b606710056 -r b8c951a4e56b7e0cdc8cd176dd96521c2e0eb764 lib/galaxy/webapps/reports/config.py
--- a/lib/galaxy/webapps/reports/config.py
+++ b/lib/galaxy/webapps/reports/config.py
@@ -35,6 +35,10 @@
self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) )
self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'False' ) )
self.brand = kwargs.get( 'brand', None )
+ # Configuration for the message box directly below the masthead.
+ self.message_box_visible = kwargs.get( 'message_box_visible', False )
+ self.message_box_content = kwargs.get( 'message_box_content', None )
+ self.message_box_class = kwargs.get( 'message_box_class', 'info' )
self.wiki_url = kwargs.get( 'wiki_url', 'http://wiki.g2.bx.psu.edu/FrontPage' )
self.blog_url = kwargs.get( 'blog_url', None )
self.screencasts_url = kwargs.get( 'screencasts_url', None )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: Dave Bouvier: Fix incorrect setting of the do_not_test flag when a repository has installation errors.
by commits-noreply@bitbucket.org 15 May '13
by commits-noreply@bitbucket.org 15 May '13
15 May '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/52b6923d713b/
Changeset: 52b6923d713b
User: Dave Bouvier
Date: 2013-05-15 21:25:48
Summary: Fix incorrect setting of the do_not_test flag when a repository has installation errors.
Affected #: 1 file
diff -r 89b5f6866eeb2fcb955d4e773d5b5328dd214610 -r 52b6923d713b0c205cdc8c970e49f4b606710056 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
@@ -790,9 +790,8 @@
error_message=dependency.error_message )
repository_status[ 'installation_errors' ][ 'repository_dependencies' ].append( test_result )
# 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[ 'do_not_test' ] = str( set_do_not_test )
+ params[ 'do_not_test' ] = 'false'
params[ 'test_install_error' ] = 'true'
register_test_result( galaxy_tool_shed_url,
metadata_revision_id,
@@ -925,10 +924,9 @@
changeset_revision=repository.changeset_revision,
error_message=repository.error_message )
repository_status[ 'installation_errors' ][ 'repository_dependencies' ].append( test_result )
- set_do_not_test = not is_latest_downloadable_revision( galaxy_tool_shed_url, repository_info_dict )
params[ 'tools_functionally_correct' ] = 'false'
params[ 'test_install_error' ] = 'true'
- params[ 'do_not_test' ] = str( set_do_not_test )
+ params[ 'do_not_test' ] = 'false'
register_test_result( galaxy_tool_shed_url,
metadata_revision_id,
repository_status,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0