mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-25 00:12:08 +08:00
Merge.
This commit is contained in:
@@ -150,6 +150,9 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
/** fn to call when the collection is created (scoped to this) */
|
||||
this.oncreate = attributes.oncreate;
|
||||
|
||||
/** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */
|
||||
this.autoscrollDist = attributes.autoscrollDist || 24;
|
||||
|
||||
/** is the unpaired panel shown? */
|
||||
this.unpairedPanelHidden = false;
|
||||
/** is the paired panel shown? */
|
||||
@@ -873,7 +876,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
filterChoice( '_1', '_2' ),
|
||||
filterChoice( '_R1', '_R2' ),
|
||||
'</div>'
|
||||
].join(''), {}));
|
||||
].join(''))({}));
|
||||
|
||||
return this.$( selector ).popover({
|
||||
container : '.collection-creator',
|
||||
@@ -1025,7 +1028,6 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
|
||||
/** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/
|
||||
_showAlert : function( message, alertClass ){
|
||||
console.debug( 'alert:', message, alertClass );
|
||||
alertClass = alertClass || 'alert-danger';
|
||||
this.$( '.main-help' ).hide();
|
||||
this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()
|
||||
@@ -1305,8 +1307,10 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
/** rename a pair when the pair name is clicked */
|
||||
_clickPairName : function( ev ){
|
||||
ev.stopPropagation();
|
||||
var $control = $( ev.currentTarget ),
|
||||
pair = this.paired[ $control.parent().parent().index() / 2 ],
|
||||
var $name = $( ev.currentTarget ),
|
||||
$pair = $name.parent().parent(),
|
||||
index = $pair.index( '.dataset.paired' ),
|
||||
pair = this.paired[ index ],
|
||||
response = prompt( 'Enter a new name for the pair:', pair.name );
|
||||
if( response ){
|
||||
pair.name = response;
|
||||
@@ -1314,7 +1318,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
// when adding/removing extensions
|
||||
//TODO: kinda hacky
|
||||
pair.customizedName = true;
|
||||
$control.text( pair.name );
|
||||
$name.text( pair.name );
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1322,7 +1326,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
_clickUnpair : function( ev ){
|
||||
//if( !ev.currentTarget ){ return true; }
|
||||
//TODO: this is a hack bc each paired rev now has two elems (dataset, button)
|
||||
var pairIndex = Math.floor( $( ev.currentTarget ).index() / 2 );
|
||||
var pairIndex = Math.floor( $( ev.currentTarget ).index( '.unpair-btn' ) );
|
||||
//this.debug( 'pair:', pairIndex );
|
||||
//TODO: animate
|
||||
this._unpair( this.paired[ pairIndex ] );
|
||||
@@ -1340,27 +1344,41 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
//console.debug( '_dragoverPairedColumns:', ev );
|
||||
ev.preventDefault();
|
||||
|
||||
var $list = this.$( '.paired-columns .column-datasets' ),
|
||||
offset = $list.offset();
|
||||
var $list = this.$( '.paired-columns .column-datasets' );
|
||||
this._checkForAutoscroll( $list, ev.originalEvent.clientY );
|
||||
//console.debug( ev.originalEvent.clientX, ev.originalEvent.clientY );
|
||||
var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );
|
||||
//console.debug( ev.originalEvent.clientX - offset.left, ev.originalEvent.clientY - offset.top );
|
||||
|
||||
$( '.paired-drop-placeholder' ).remove();
|
||||
var $placeholder = $( '<div class="paired-drop-placeholder"></div>')
|
||||
var $placeholder = $( '<div class="paired-drop-placeholder"></div>' );
|
||||
if( !$nearest.size() ){
|
||||
$list.append( $placeholder );
|
||||
} else {
|
||||
$nearest.before( $placeholder );
|
||||
}
|
||||
},
|
||||
|
||||
/** If the mouse is near enough to the list's top or bottom, scroll the list */
|
||||
_checkForAutoscroll : function( $element, y ){
|
||||
var AUTOSCROLL_SPEED = 2;
|
||||
var offset = $element.offset(),
|
||||
scrollTop = $element.scrollTop(),
|
||||
upperDist = y - offset.top,
|
||||
lowerDist = ( offset.top + $element.outerHeight() ) - y;
|
||||
//console.debug( '_checkForAutoscroll:', scrollTop, upperDist, lowerDist );
|
||||
if( upperDist >= 0 && upperDist < this.autoscrollDist ){
|
||||
$element.scrollTop( scrollTop - AUTOSCROLL_SPEED );
|
||||
} else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){
|
||||
$element.scrollTop( scrollTop + AUTOSCROLL_SPEED );
|
||||
}
|
||||
},
|
||||
|
||||
/** get the nearest *previous* paired dataset PairView based on the mouse's Y coordinate.
|
||||
* If the y is at the end of the list, return an empty jQuery object.
|
||||
*/
|
||||
_getNearestPairedDatasetLi : function( y ){
|
||||
var WIGGLE = 4,
|
||||
lis = this.$( '.paired-columns .column-datasets li' ).toArray();
|
||||
//TODO: better way?
|
||||
for( var i=0; i<lis.length; i++ ){
|
||||
var $li = $( lis[i] ),
|
||||
top = $li.offset().top,
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
//==============================================================================
|
||||
/*
|
||||
TODO:
|
||||
?? superclass dataloader, subclass lazydataloader??
|
||||
|
||||
*/
|
||||
//==============================================================================
|
||||
/**
|
||||
* Object to progressively load JSON data from a REST url, delaying some time between loading chunks
|
||||
*
|
||||
* Will load size amount of data every delay ms, starting at start and ending at total.
|
||||
*
|
||||
* NOTE: Data from ajax loading is aggregated in a list, with one element for each ajax response.
|
||||
* It's up to the calling code to combine the results in a meaningful, correct way.
|
||||
*
|
||||
* example:
|
||||
* var loader = new scatterplot.LazyDataLoader({
|
||||
* //logger : console,
|
||||
* url : ( apiDatasetsURL + '/' + hda.id + '?data_type=raw_data'
|
||||
* + '&columns=[10,14]' ),
|
||||
* total : hda.metadata_data_lines,
|
||||
* size : 500,
|
||||
*
|
||||
* initialize : function( config ){
|
||||
* // ... do some stuff
|
||||
* },
|
||||
*
|
||||
* buildUrl : function( start, size ){
|
||||
* // change the formation of start, size in query string
|
||||
* return loader.url + '&' + jQuery.param({
|
||||
* start_val: start,
|
||||
* max_vals: size
|
||||
* });
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // you can use events
|
||||
* $( loader ).bind( 'error', function( event, xhr, status, error ){
|
||||
* alert( loader + ' ERROR:' + status + '\n' + error );
|
||||
* // bail out...
|
||||
* });
|
||||
* $( loader ).bind( 'loaded.new', function( event, response ){
|
||||
* console.info( 'new data available:', event, response );
|
||||
* // ... do stuff with new data
|
||||
* });
|
||||
* $( loader ).bind( 'complete', function( event, allDataArray, total ){
|
||||
* console.info( 'final load complete:', event, allDataArray, total );
|
||||
* // ... do stuff with all data
|
||||
* });
|
||||
*
|
||||
* // ...or use a callback called when all data is loaded
|
||||
* loader.load( function( dataArray ){ console.debug( 'FINISHED!', x, y, z ); } );
|
||||
*/
|
||||
function LazyDataLoader( config ){
|
||||
// for now assume:
|
||||
// get, async, and params sent via url query string
|
||||
// we want json
|
||||
// we know the size of the data on the server beforehand
|
||||
var loader = this,
|
||||
// events to trigger when new or all data has been loaded
|
||||
// new batches of data (including last). Will be sent: the ajax response data, start value, and size
|
||||
LOADED_NEW_EVENT = 'loaded.new',
|
||||
// all data has been loaded: the final loader's data array and the total
|
||||
LOADED_ALL_EVENT = 'complete';
|
||||
// error from ajax
|
||||
ERROR_EVENT = 'error';
|
||||
|
||||
jQuery.extend( loader, LoggableMixin );
|
||||
jQuery.extend( loader, {
|
||||
|
||||
//NOTE: the next two need to be sent in config (required)
|
||||
// total size of data on server
|
||||
total : undefined,
|
||||
// url of service to get the data
|
||||
url : undefined,
|
||||
|
||||
// holds the interval id for the current load delay
|
||||
currentIntervalId : undefined,
|
||||
|
||||
// each load call will add an element to this array
|
||||
// it's the responsibility of the code using this to combine them properly
|
||||
data : [],
|
||||
// ms btwn recursive loads
|
||||
delay : 4000,
|
||||
// starting line, element, whatever
|
||||
start : 0,
|
||||
// size to fetch per load
|
||||
size : 4000,
|
||||
|
||||
// loader init func: extends loader with config and calls config.init if there
|
||||
//@param {object} config : object containing variables to override (or additional)
|
||||
initialize : function( config ){
|
||||
jQuery.extend( loader, config );
|
||||
|
||||
// call the custom initialize function if any
|
||||
// only dangerous if the user tries LazyDataLoader.prototype.init
|
||||
if( config.hasOwnProperty( 'initialize' ) ){
|
||||
config.initialize.call( loader, config );
|
||||
}
|
||||
this.log( this + ' initialized:', loader );
|
||||
},
|
||||
|
||||
// returns query string formatted start and size (for the next fetch) appended to the loader.url
|
||||
//OVERRIDE: to change how params are passed, param names, etc.
|
||||
//@param {int} start : the line/row/datum indicating where in the dataset the next load begins
|
||||
//@param {int} size : the number of lines/rows/data to get on the next load
|
||||
buildUrl : function( start, size ){
|
||||
// currently VERY SPECIFIC to using data_providers.py start_val, max_vals params
|
||||
return this.url + '&' + jQuery.param({
|
||||
start_val: start,
|
||||
max_vals: size
|
||||
});
|
||||
},
|
||||
|
||||
//OVERRIDE: to handle ajax errors differently
|
||||
ajaxErrorFn : function( xhr, status, error ){
|
||||
console.error( 'ERROR fetching data:', error );
|
||||
},
|
||||
|
||||
// converters passed to the jQuery ajax call for data type parsing
|
||||
//OVERRIDE: to provide custom parsing
|
||||
converters : {
|
||||
'* text' : window.String,
|
||||
'text html' : true,
|
||||
'text xml' : jQuery.parseXML,
|
||||
'text json' : jQuery.parseJSON
|
||||
},
|
||||
|
||||
// interface to begin load (and first recursive call)
|
||||
//@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data
|
||||
load : function( callback ){
|
||||
this.log( this + '.load' );
|
||||
|
||||
// ensure necessary stuff
|
||||
if( !loader.url ){ throw( loader + ' requires a url' ); }
|
||||
|
||||
if( this.total === null ){
|
||||
this.log( '\t total is null (will load all)' );
|
||||
} else {
|
||||
this.log( '\t total:', this.total );
|
||||
}
|
||||
//if( !loader.total ){ throw( loader + ' requires a total (total size of the data)' ); }
|
||||
|
||||
//FIRST RECURSION: start
|
||||
var startingSize = loader.size;
|
||||
if( ( loader.total !== null ) && ( loader.total < loader.size ) ){
|
||||
startingSize = loader.total;
|
||||
}
|
||||
loader.log( loader + '\t beginning recursion' );
|
||||
loadHelper( loader.start, startingSize );
|
||||
|
||||
//FIRST, SUBSEQUENT RECURSION function
|
||||
function loadHelper( start, size ){
|
||||
loader.log( loader + '.loadHelper, start:', start, 'size:', size );
|
||||
var url = loader.buildUrl( start, size );
|
||||
loader.log( '\t url:', url );
|
||||
|
||||
jQuery.ajax({
|
||||
url : loader.buildUrl( start, size ),
|
||||
converters : loader.converters,
|
||||
dataType : 'json',
|
||||
|
||||
error : function( xhr, status, error ){
|
||||
loader.log( '\t ajax error, status:', status, 'error:', error );
|
||||
if( loader.currentIntervalId ){
|
||||
clearInterval( loader.currentIntervalId );
|
||||
}
|
||||
$( loader ).trigger( ERROR_EVENT, [ status, error ] );
|
||||
loader.ajaxErrorFn( xhr, status, error );
|
||||
},
|
||||
|
||||
success : function( response ){
|
||||
loader.log( '\t ajax success, response:', response, 'next:', next, 'remainder:', remainder );
|
||||
|
||||
if( response !== null ){
|
||||
// store the response as is in a new element
|
||||
//TODO:?? store start, size as well?
|
||||
loader.data.push( response );
|
||||
|
||||
//TODO: these might not be the best way to split this up
|
||||
// fire the first load event (if this is the first batch) AND partial
|
||||
$( loader ).trigger( LOADED_NEW_EVENT, [ response, start, size ] );
|
||||
|
||||
//RECURSION:
|
||||
var next = start + size,
|
||||
remainder = loader.size;
|
||||
|
||||
if( loader.total !== null ){
|
||||
remainder = Math.min( loader.total - next, loader.size );
|
||||
}
|
||||
loader.log( '\t next recursion, start:', next, 'size:', remainder );
|
||||
|
||||
// if we haven't gotten everything yet, so set up for next recursive call and set the timer
|
||||
if( loader.total === null || remainder > 0 ){
|
||||
loader.currentIntervalId = setTimeout(
|
||||
function(){ loadHelper( next, remainder ); },
|
||||
loader.delay
|
||||
);
|
||||
loader.log( '\t currentIntervalId:', loader.currentIntervalId );
|
||||
|
||||
// otherwise (base-case), don't do anything
|
||||
} else {
|
||||
loadFinished();
|
||||
}
|
||||
|
||||
} else { //response === null --> base-case, server sez nuthin left
|
||||
loadFinished();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//HANDLE BASE-CASE, LAST RECURSION
|
||||
function loadFinished(){
|
||||
loader.log( loader + '.loadHelper, has finished:', loader.data );
|
||||
$( loader ).trigger( LOADED_ALL_EVENT, [ loader.data, loader.total ] );
|
||||
if( callback ){ callback( loader.data ); }
|
||||
}
|
||||
},
|
||||
|
||||
toString : function(){ return 'LazyDataLoader'; }
|
||||
});
|
||||
|
||||
loader.initialize( config );
|
||||
return loader;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@@ -14,6 +14,7 @@ from datetime import datetime
|
||||
|
||||
from galaxy.exceptions import ObjectNotFound
|
||||
from galaxy.util import umask_fix_perms, string_as_bool
|
||||
from galaxy.util import string_as_bool
|
||||
from galaxy.util.directory_hash import directory_hash_id
|
||||
from galaxy.util.sleeper import Sleeper
|
||||
from .s3_multipart_upload import multipart_upload
|
||||
@@ -77,6 +78,7 @@ class S3ObjectStore(ObjectStore):
|
||||
b_xml = config_xml.findall('bucket')[0]
|
||||
self.bucket = b_xml.get('name')
|
||||
self.use_rr = string_as_bool(b_xml.get('use_reduced_redundancy', "False"))
|
||||
self.max_chunk_size = int(b_xml.get('max_chunk_size', 250))
|
||||
cn_xml = config_xml.findall('connection')
|
||||
if not cn_xml:
|
||||
cn_xml = {}
|
||||
@@ -84,11 +86,24 @@ class S3ObjectStore(ObjectStore):
|
||||
cn_xml = cn_xml[0]
|
||||
self.host = cn_xml.get('host', None)
|
||||
self.port = int(cn_xml.get('port', 6000))
|
||||
self.is_secure = cn_xml.get('is_secure', True)
|
||||
self.multipart = string_as_bool(cn_xml.get('multipart', 'True'))
|
||||
self.is_secure = string_as_bool(cn_xml.get('is_secure', 'True'))
|
||||
self.conn_path = cn_xml.get('conn_path', '/')
|
||||
c_xml = config_xml.findall('cache')[0]
|
||||
self.cache_size = float(c_xml.get('size', -1))
|
||||
self.cache_path = c_xml.get('path')
|
||||
|
||||
# for multipart upload
|
||||
self.s3server = {'access_key': self.access_key,
|
||||
'secret_key': self.secret_key,
|
||||
'is_secure': self.is_secure,
|
||||
'max_chunk_size': self.max_chunk_size,
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'use_rr': self.use_rr,
|
||||
'conn_path': self.conn_path}
|
||||
|
||||
|
||||
except Exception:
|
||||
# Toss it back up after logging, we can't continue loading at this point.
|
||||
log.exception("Malformed ObjectStore Configuration XML -- unable to continue")
|
||||
@@ -328,14 +343,14 @@ class S3ObjectStore(ObjectStore):
|
||||
start_time = datetime.now()
|
||||
log.debug("Pushing cache file '%s' of size %s bytes to key '%s'" % (source_file, os.path.getsize(source_file), rel_path))
|
||||
mb_size = os.path.getsize(source_file) / 1e6
|
||||
if mb_size < 10 or type(self) == SwiftObjectStore:
|
||||
if mb_size < 10 or (not self.multipart):
|
||||
self.transfer_progress = 0 # Reset transfer progress counter
|
||||
key.set_contents_from_filename(source_file,
|
||||
reduced_redundancy=self.use_rr,
|
||||
cb=self._transfer_cb,
|
||||
num_cb=10)
|
||||
else:
|
||||
multipart_upload(self.bucket, key.name, source_file, mb_size, self.access_key, self.secret_key, use_rr=self.use_rr)
|
||||
multipart_upload(self.s3server, self.bucket, key.name, source_file, mb_size)
|
||||
end_time = datetime.now()
|
||||
log.debug("Pushed cache file '%s' to key '%s' (%s bytes transfered in %s sec)" % (source_file, rel_path, os.path.getsize(source_file), end_time - start_time))
|
||||
return True
|
||||
|
||||
@@ -34,13 +34,23 @@ def map_wrap(f):
|
||||
return wrapper
|
||||
|
||||
|
||||
def mp_from_ids(mp_id, mp_keyname, mp_bucketname, aws_access_key_id, aws_secret_access_key):
|
||||
def mp_from_ids(s3server, mp_id, mp_keyname, mp_bucketname):
|
||||
"""Get the multipart upload from the bucket and multipart IDs.
|
||||
|
||||
This allows us to reconstitute a connection to the upload
|
||||
from within multiprocessing functions.
|
||||
"""
|
||||
conn = S3Connection(aws_access_key_id, aws_secret_access_key)
|
||||
if s3server['host']:
|
||||
conn = boto.connect_s3(aws_access_key_id=s3server['access_key'],
|
||||
aws_secret_access_key=s3server['secret_key'],
|
||||
is_secure=s3server['is_secure'],
|
||||
host=s3server['host'],
|
||||
port=s3server['port'],
|
||||
calling_format=boto.s3.connection.OrdinaryCallingFormat(),
|
||||
path=s3server['conn_path'])
|
||||
else:
|
||||
conn = S3Connection(s3server['access_key'], s3server['secret_key'])
|
||||
|
||||
bucket = conn.lookup(mp_bucketname)
|
||||
mp = boto.s3.multipart.MultiPartUpload(bucket)
|
||||
mp.key_name = mp_keyname
|
||||
@@ -49,33 +59,36 @@ def mp_from_ids(mp_id, mp_keyname, mp_bucketname, aws_access_key_id, aws_secret_
|
||||
|
||||
|
||||
@map_wrap
|
||||
def transfer_part(mp_id, mp_keyname, mp_bucketname, i, part, aws_access_key_id, aws_secret_access_key):
|
||||
def transfer_part(s3server, mp_id, mp_keyname, mp_bucketname, i, part):
|
||||
"""Transfer a part of a multipart upload. Designed to be run in parallel.
|
||||
"""
|
||||
mp = mp_from_ids(mp_id, mp_keyname, mp_bucketname, aws_access_key_id, aws_secret_access_key)
|
||||
mp = mp_from_ids(s3server, mp_id, mp_keyname, mp_bucketname)
|
||||
with open(part) as t_handle:
|
||||
mp.upload_part_from_file(t_handle, i + 1)
|
||||
os.remove(part)
|
||||
|
||||
|
||||
def multipart_upload(bucket, s3_key_name, tarball, mb_size, aws_access_key_id, aws_secret_access_key, use_rr=True):
|
||||
def multipart_upload(s3server, bucket, s3_key_name, tarball, mb_size):
|
||||
"""Upload large files using Amazon's multipart upload functionality.
|
||||
"""
|
||||
cores = multiprocessing.cpu_count()
|
||||
|
||||
def split_file(in_file, mb_size, split_num=5):
|
||||
prefix = os.path.join(os.path.dirname(in_file),
|
||||
"%sS3PART" % (os.path.basename(s3_key_name)))
|
||||
# Split chunks so they are 5MB < chunk < 250MB
|
||||
split_size = int(max(min(mb_size / (split_num * 2.0), 250), 5))
|
||||
"%sS3PART" % (os.path.basename(s3_key_name)))
|
||||
max_chunk = s3server['max_chunk_size']
|
||||
# Split chunks so they are 5MB < chunk < 250MB(max_chunk_size)
|
||||
split_size = int(max(min(mb_size / (split_num * 2.0), max_chunk), 5))
|
||||
if not os.path.exists("%saa" % prefix):
|
||||
cl = ["split", "-b%sm" % split_size, in_file, prefix]
|
||||
subprocess.check_call(cl)
|
||||
return sorted(glob.glob("%s*" % prefix))
|
||||
|
||||
mp = bucket.initiate_multipart_upload(s3_key_name, reduced_redundancy=use_rr)
|
||||
mp = bucket.initiate_multipart_upload(s3_key_name,
|
||||
reduced_redundancy=s3server['use_rr'])
|
||||
|
||||
with multimap(cores) as pmap:
|
||||
for _ in pmap(transfer_part, ((mp.id, mp.key_name, mp.bucket_name, i, part, aws_access_key_id, aws_secret_access_key)
|
||||
for _ in pmap(transfer_part, ((s3server, mp.id, mp.key_name, mp.bucket_name, i, part)
|
||||
for (i, part) in
|
||||
enumerate(split_file(tarball, mb_size, cores)))):
|
||||
pass
|
||||
|
||||
@@ -150,6 +150,9 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
/** fn to call when the collection is created (scoped to this) */
|
||||
this.oncreate = attributes.oncreate;
|
||||
|
||||
/** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */
|
||||
this.autoscrollDist = attributes.autoscrollDist || 24;
|
||||
|
||||
/** is the unpaired panel shown? */
|
||||
this.unpairedPanelHidden = false;
|
||||
/** is the paired panel shown? */
|
||||
@@ -873,7 +876,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
filterChoice( '_1', '_2' ),
|
||||
filterChoice( '_R1', '_R2' ),
|
||||
'</div>'
|
||||
].join(''), {}));
|
||||
].join(''))({}));
|
||||
|
||||
return this.$( selector ).popover({
|
||||
container : '.collection-creator',
|
||||
@@ -1025,7 +1028,6 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
|
||||
/** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/
|
||||
_showAlert : function( message, alertClass ){
|
||||
console.debug( 'alert:', message, alertClass );
|
||||
alertClass = alertClass || 'alert-danger';
|
||||
this.$( '.main-help' ).hide();
|
||||
this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()
|
||||
@@ -1305,8 +1307,10 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
/** rename a pair when the pair name is clicked */
|
||||
_clickPairName : function( ev ){
|
||||
ev.stopPropagation();
|
||||
var $control = $( ev.currentTarget ),
|
||||
pair = this.paired[ $control.parent().parent().index() / 2 ],
|
||||
var $name = $( ev.currentTarget ),
|
||||
$pair = $name.parent().parent(),
|
||||
index = $pair.index( '.dataset.paired' ),
|
||||
pair = this.paired[ index ],
|
||||
response = prompt( 'Enter a new name for the pair:', pair.name );
|
||||
if( response ){
|
||||
pair.name = response;
|
||||
@@ -1314,7 +1318,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
// when adding/removing extensions
|
||||
//TODO: kinda hacky
|
||||
pair.customizedName = true;
|
||||
$control.text( pair.name );
|
||||
$name.text( pair.name );
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1322,7 +1326,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
_clickUnpair : function( ev ){
|
||||
//if( !ev.currentTarget ){ return true; }
|
||||
//TODO: this is a hack bc each paired rev now has two elems (dataset, button)
|
||||
var pairIndex = Math.floor( $( ev.currentTarget ).index() / 2 );
|
||||
var pairIndex = Math.floor( $( ev.currentTarget ).index( '.unpair-btn' ) );
|
||||
//this.debug( 'pair:', pairIndex );
|
||||
//TODO: animate
|
||||
this._unpair( this.paired[ pairIndex ] );
|
||||
@@ -1340,27 +1344,41 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
|
||||
//console.debug( '_dragoverPairedColumns:', ev );
|
||||
ev.preventDefault();
|
||||
|
||||
var $list = this.$( '.paired-columns .column-datasets' ),
|
||||
offset = $list.offset();
|
||||
var $list = this.$( '.paired-columns .column-datasets' );
|
||||
this._checkForAutoscroll( $list, ev.originalEvent.clientY );
|
||||
//console.debug( ev.originalEvent.clientX, ev.originalEvent.clientY );
|
||||
var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );
|
||||
//console.debug( ev.originalEvent.clientX - offset.left, ev.originalEvent.clientY - offset.top );
|
||||
|
||||
$( '.paired-drop-placeholder' ).remove();
|
||||
var $placeholder = $( '<div class="paired-drop-placeholder"></div>')
|
||||
var $placeholder = $( '<div class="paired-drop-placeholder"></div>' );
|
||||
if( !$nearest.size() ){
|
||||
$list.append( $placeholder );
|
||||
} else {
|
||||
$nearest.before( $placeholder );
|
||||
}
|
||||
},
|
||||
|
||||
/** If the mouse is near enough to the list's top or bottom, scroll the list */
|
||||
_checkForAutoscroll : function( $element, y ){
|
||||
var AUTOSCROLL_SPEED = 2;
|
||||
var offset = $element.offset(),
|
||||
scrollTop = $element.scrollTop(),
|
||||
upperDist = y - offset.top,
|
||||
lowerDist = ( offset.top + $element.outerHeight() ) - y;
|
||||
//console.debug( '_checkForAutoscroll:', scrollTop, upperDist, lowerDist );
|
||||
if( upperDist >= 0 && upperDist < this.autoscrollDist ){
|
||||
$element.scrollTop( scrollTop - AUTOSCROLL_SPEED );
|
||||
} else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){
|
||||
$element.scrollTop( scrollTop + AUTOSCROLL_SPEED );
|
||||
}
|
||||
},
|
||||
|
||||
/** get the nearest *previous* paired dataset PairView based on the mouse's Y coordinate.
|
||||
* If the y is at the end of the list, return an empty jQuery object.
|
||||
*/
|
||||
_getNearestPairedDatasetLi : function( y ){
|
||||
var WIGGLE = 4,
|
||||
lis = this.$( '.paired-columns .column-datasets li' ).toArray();
|
||||
//TODO: better way?
|
||||
for( var i=0; i<lis.length; i++ ){
|
||||
var $li = $( lis[i] ),
|
||||
top = $li.offset().top,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:4000,start:0,size:4000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){console.error("ERROR fetching data:",f)},converters:{"* text":window.String,"text html":true,"text xml":jQuery.parseXML,"text json":jQuery.parseJSON},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),converters:a.converters,dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a};
|
||||
@@ -1,228 +0,0 @@
|
||||
//==============================================================================
|
||||
/*
|
||||
TODO:
|
||||
?? superclass dataloader, subclass lazydataloader??
|
||||
|
||||
*/
|
||||
//==============================================================================
|
||||
/**
|
||||
* Object to progressively load JSON data from a REST url, delaying some time between loading chunks
|
||||
*
|
||||
* Will load size amount of data every delay ms, starting at start and ending at total.
|
||||
*
|
||||
* NOTE: Data from ajax loading is aggregated in a list, with one element for each ajax response.
|
||||
* It's up to the calling code to combine the results in a meaningful, correct way.
|
||||
*
|
||||
* example:
|
||||
* var loader = new scatterplot.LazyDataLoader({
|
||||
* //logger : console,
|
||||
* url : ( apiDatasetsURL + '/' + hda.id + '?data_type=raw_data'
|
||||
* + '&columns=[10,14]' ),
|
||||
* total : hda.metadata_data_lines,
|
||||
* size : 500,
|
||||
*
|
||||
* initialize : function( config ){
|
||||
* // ... do some stuff
|
||||
* },
|
||||
*
|
||||
* buildUrl : function( start, size ){
|
||||
* // change the formation of start, size in query string
|
||||
* return loader.url + '&' + jQuery.param({
|
||||
* start_val: start,
|
||||
* max_vals: size
|
||||
* });
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // you can use events
|
||||
* $( loader ).bind( 'error', function( event, xhr, status, error ){
|
||||
* alert( loader + ' ERROR:' + status + '\n' + error );
|
||||
* // bail out...
|
||||
* });
|
||||
* $( loader ).bind( 'loaded.new', function( event, response ){
|
||||
* console.info( 'new data available:', event, response );
|
||||
* // ... do stuff with new data
|
||||
* });
|
||||
* $( loader ).bind( 'complete', function( event, allDataArray, total ){
|
||||
* console.info( 'final load complete:', event, allDataArray, total );
|
||||
* // ... do stuff with all data
|
||||
* });
|
||||
*
|
||||
* // ...or use a callback called when all data is loaded
|
||||
* loader.load( function( dataArray ){ console.debug( 'FINISHED!', x, y, z ); } );
|
||||
*/
|
||||
function LazyDataLoader( config ){
|
||||
// for now assume:
|
||||
// get, async, and params sent via url query string
|
||||
// we want json
|
||||
// we know the size of the data on the server beforehand
|
||||
var loader = this,
|
||||
// events to trigger when new or all data has been loaded
|
||||
// new batches of data (including last). Will be sent: the ajax response data, start value, and size
|
||||
LOADED_NEW_EVENT = 'loaded.new',
|
||||
// all data has been loaded: the final loader's data array and the total
|
||||
LOADED_ALL_EVENT = 'complete';
|
||||
// error from ajax
|
||||
ERROR_EVENT = 'error';
|
||||
|
||||
jQuery.extend( loader, LoggableMixin );
|
||||
jQuery.extend( loader, {
|
||||
|
||||
//NOTE: the next two need to be sent in config (required)
|
||||
// total size of data on server
|
||||
total : undefined,
|
||||
// url of service to get the data
|
||||
url : undefined,
|
||||
|
||||
// holds the interval id for the current load delay
|
||||
currentIntervalId : undefined,
|
||||
|
||||
// each load call will add an element to this array
|
||||
// it's the responsibility of the code using this to combine them properly
|
||||
data : [],
|
||||
// ms btwn recursive loads
|
||||
delay : 4000,
|
||||
// starting line, element, whatever
|
||||
start : 0,
|
||||
// size to fetch per load
|
||||
size : 4000,
|
||||
|
||||
// loader init func: extends loader with config and calls config.init if there
|
||||
//@param {object} config : object containing variables to override (or additional)
|
||||
initialize : function( config ){
|
||||
jQuery.extend( loader, config );
|
||||
|
||||
// call the custom initialize function if any
|
||||
// only dangerous if the user tries LazyDataLoader.prototype.init
|
||||
if( config.hasOwnProperty( 'initialize' ) ){
|
||||
config.initialize.call( loader, config );
|
||||
}
|
||||
this.log( this + ' initialized:', loader );
|
||||
},
|
||||
|
||||
// returns query string formatted start and size (for the next fetch) appended to the loader.url
|
||||
//OVERRIDE: to change how params are passed, param names, etc.
|
||||
//@param {int} start : the line/row/datum indicating where in the dataset the next load begins
|
||||
//@param {int} size : the number of lines/rows/data to get on the next load
|
||||
buildUrl : function( start, size ){
|
||||
// currently VERY SPECIFIC to using data_providers.py start_val, max_vals params
|
||||
return this.url + '&' + jQuery.param({
|
||||
start_val: start,
|
||||
max_vals: size
|
||||
});
|
||||
},
|
||||
|
||||
//OVERRIDE: to handle ajax errors differently
|
||||
ajaxErrorFn : function( xhr, status, error ){
|
||||
console.error( 'ERROR fetching data:', error );
|
||||
},
|
||||
|
||||
// converters passed to the jQuery ajax call for data type parsing
|
||||
//OVERRIDE: to provide custom parsing
|
||||
converters : {
|
||||
'* text' : window.String,
|
||||
'text html' : true,
|
||||
'text xml' : jQuery.parseXML,
|
||||
'text json' : jQuery.parseJSON
|
||||
},
|
||||
|
||||
// interface to begin load (and first recursive call)
|
||||
//@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data
|
||||
load : function( callback ){
|
||||
this.log( this + '.load' );
|
||||
|
||||
// ensure necessary stuff
|
||||
if( !loader.url ){ throw( loader + ' requires a url' ); }
|
||||
|
||||
if( this.total === null ){
|
||||
this.log( '\t total is null (will load all)' );
|
||||
} else {
|
||||
this.log( '\t total:', this.total );
|
||||
}
|
||||
//if( !loader.total ){ throw( loader + ' requires a total (total size of the data)' ); }
|
||||
|
||||
//FIRST RECURSION: start
|
||||
var startingSize = loader.size;
|
||||
if( ( loader.total !== null ) && ( loader.total < loader.size ) ){
|
||||
startingSize = loader.total;
|
||||
}
|
||||
loader.log( loader + '\t beginning recursion' );
|
||||
loadHelper( loader.start, startingSize );
|
||||
|
||||
//FIRST, SUBSEQUENT RECURSION function
|
||||
function loadHelper( start, size ){
|
||||
loader.log( loader + '.loadHelper, start:', start, 'size:', size );
|
||||
var url = loader.buildUrl( start, size );
|
||||
loader.log( '\t url:', url );
|
||||
|
||||
jQuery.ajax({
|
||||
url : loader.buildUrl( start, size ),
|
||||
converters : loader.converters,
|
||||
dataType : 'json',
|
||||
|
||||
error : function( xhr, status, error ){
|
||||
loader.log( '\t ajax error, status:', status, 'error:', error );
|
||||
if( loader.currentIntervalId ){
|
||||
clearInterval( loader.currentIntervalId );
|
||||
}
|
||||
$( loader ).trigger( ERROR_EVENT, [ status, error ] );
|
||||
loader.ajaxErrorFn( xhr, status, error );
|
||||
},
|
||||
|
||||
success : function( response ){
|
||||
loader.log( '\t ajax success, response:', response, 'next:', next, 'remainder:', remainder );
|
||||
|
||||
if( response !== null ){
|
||||
// store the response as is in a new element
|
||||
//TODO:?? store start, size as well?
|
||||
loader.data.push( response );
|
||||
|
||||
//TODO: these might not be the best way to split this up
|
||||
// fire the first load event (if this is the first batch) AND partial
|
||||
$( loader ).trigger( LOADED_NEW_EVENT, [ response, start, size ] );
|
||||
|
||||
//RECURSION:
|
||||
var next = start + size,
|
||||
remainder = loader.size;
|
||||
|
||||
if( loader.total !== null ){
|
||||
remainder = Math.min( loader.total - next, loader.size );
|
||||
}
|
||||
loader.log( '\t next recursion, start:', next, 'size:', remainder );
|
||||
|
||||
// if we haven't gotten everything yet, so set up for next recursive call and set the timer
|
||||
if( loader.total === null || remainder > 0 ){
|
||||
loader.currentIntervalId = setTimeout(
|
||||
function(){ loadHelper( next, remainder ); },
|
||||
loader.delay
|
||||
);
|
||||
loader.log( '\t currentIntervalId:', loader.currentIntervalId );
|
||||
|
||||
// otherwise (base-case), don't do anything
|
||||
} else {
|
||||
loadFinished();
|
||||
}
|
||||
|
||||
} else { //response === null --> base-case, server sez nuthin left
|
||||
loadFinished();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//HANDLE BASE-CASE, LAST RECURSION
|
||||
function loadFinished(){
|
||||
loader.log( loader + '.loadHelper, has finished:', loader.data );
|
||||
$( loader ).trigger( LOADED_ALL_EVENT, [ loader.data, loader.total ] );
|
||||
if( callback ){ callback( loader.data ); }
|
||||
}
|
||||
},
|
||||
|
||||
toString : function(){ return 'LazyDataLoader'; }
|
||||
});
|
||||
|
||||
loader.initialize( config );
|
||||
return loader;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Reference in New Issue
Block a user