(alt)history: split files up, rework names, add global Galaxy namespace

This commit is contained in:
Carl Eberhard
2012-11-06 15:54:47 -05:00
parent 48d58a8e58
commit b8fb01b2cc
5 changed files with 858 additions and 794 deletions
@@ -1,479 +1,10 @@
//define([
// "../mvc/base-mvc"
//
//], function(){
/* =============================================================================
Backbone.js implementation of history panel
TODO:
bug:
anon, mako:
tooltips not rendered
anno, tags rendered
title editable
bug:
when over quota history is re-rendered, over quota msg is not displayed
bc the quota:over event isn't fired
bc the user state hasn't changed
anon user, mako template init:
bug: rename url seems to be wrong url
currently, adding a dataset (via tool execute, etc.) creates a new dataset and refreshes the page
logged in, mako template:
BUG: am able to start upload even if over quota - 'runs' forever
BUG: from above sit, delete uploading hda - now in state 'discarded'! ...new state to handle
bug: quotaMeter bar rendering square in chrome
BUG: quotaMsg not showing when 100% (on load)
BUG: upload, history size, doesn't change
TODO: on hdas state:ready, update ONLY the size...from what? histories.py? in js?
BUG: imported, shared history with unaccessible dataset errs in historycontents when getting history
(entire history is inaccessible)
??: still happening?
from loadFromApi:
BUG: not showing previous annotations
fixed:
BUG: historyItem, error'd ds show display, download?
FIXED: removed
bug: loading hdas (alt_hist)
FIXED: added anon user api request ( trans.user == None and trans.history.id == requested id )
bug: quota meter not updating on upload/tool run
FIXED: quotaMeter now listens for 'state:ready' from glx_history in alternate_history.mako
bug: use of new HDACollection with event listener in init doesn't die...keeps reporting
FIXED: change getVisible to return an array
BUG: history, broken intial hist state (running, updater, etc.)
??: doesn't seem to happen anymore
BUG: collapse all should remove all expanded from storage
FIXED: hideAllItemBodies now resets storage.expandedItems
BUG: historyItem, shouldn't allow tag, annotate, peek on purged datasets
FIXED: ok state now shows only: info, rerun
BUG: history?, some ids aren't returning encoded...
FIXED:???
BUG: history, showing deleted ds
FIXED
UGH: historyItems have to be decorated with history_ids (api/histories/:history_id/contents/:id)
FIXED by adding history_id to history_contents.show
BUG: history, if hist has err'd ds, hist has perm state 'error', updater on following ds's doesn't run
FIXED by reordering history state from ds' states here and histories
BUG: history, broken annotation on reload (can't get thru api (sets fine, tho))
FIXED: get thru api for now
to relational model?
HDACollection, meta_files, display_apps, etc.
quota mgr
show_deleted/hidden:
use storage
on/off ui
move histview fadein/out in render to app?
don't draw body until it's first expand event
localize all
break this file up
?: render url templates on init or render?
?: history, annotation won't accept unicode
hierarchy:
dataset -> hda
history -> historyForEditing, historyForViewing
display_structured?
btw: get an error'd ds by running fastqc on fastq (when you don't have it installed)
meta:
css/html class/id 'item' -> hda
add classes, ids on empty divs
events (local/ui and otherwise)
list in docs as well
require.js
convert function comments to jsDoc style, complete comments
move inline styles into base.less
watch the magic strings
watch your globals
features:
lineage
hide button
show permissions in info
show shared/sharing status on ds, history
maintain scroll position on refresh (storage?)
selection, multi-select (and actions common to selected (ugh))
searching
sorting, re-shuffling
============================================================================= */
var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend({
// a single HDA model
// uncomment this out see log messages
//logger : console,
defaults : {
// ---these are part of an HDA proper:
// parent (containing) history
history_id : null,
// often used with tagging
model_class : 'HistoryDatasetAssociation',
// index within history (??)
hid : 0,
// ---whereas these are Dataset related/inherited
id : null,
name : '',
// one of HistoryDatasetAssociation.STATES
state : '',
// sniffed datatype (sam, tabular, bed, etc.)
data_type : null,
// size in bytes
file_size : 0,
// array of associated file types (eg. [ 'bam_index', ... ])
meta_files : [],
misc_blurb : '',
misc_info : '',
deleted : false,
purged : false,
// aka. !hidden
visible : false,
// based on trans.user (is_admin or security_agent.can_access_dataset( <user_roles>, hda.dataset ))
accessible : false,
//TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView))
for_editing : true
},
// fetch location of this history in the api
url : function(){
//TODO: get this via url router
return 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );
},
// (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE
//TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted
initialize : function(){
this.log( this + '.initialize', this.attributes );
this.log( '\tparent history_id: ' + this.get( 'history_id' ) );
//!! this state is not in trans.app.model.Dataset.states - set it here -
//TODO: change to server side.
if( !this.get( 'accessible' ) ){
this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE );
}
// if the state has changed and the new state is a ready state, fire an event
this.on( 'change:state', function( currModel, newState ){
this.log( this + ' has changed state:', currModel, newState );
if( this.inReadyState() ){
this.trigger( 'state:ready', this.get( 'id' ), newState, this.previous( 'state' ), currModel );
}
});
// debug on change events
//this.on( 'change', function( currModel, changedList ){
// this.log( this + ' has changed:', currModel, changedList );
//});
//this.bind( 'all', function( event ){
// this.log( this + '', arguments );
//});
},
isDeletedOrPurged : function(){
return ( this.get( 'deleted' ) || this.get( 'purged' ) );
},
// based on show_deleted, show_hidden (gen. from the container control), would this ds show in the list of ds's?
//TODO: too many visibles
isVisible : function( show_deleted, show_hidden ){
var isVisible = true;
if( ( !show_deleted )
&& ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){
isVisible = false;
}
if( ( !show_hidden )
&& ( !this.get( 'visible' ) ) ){
isVisible = false;
}
return isVisible;
},
// 'ready' states are states where no processing (for the ds) is left to do on the server
inReadyState : function(){
var state = this.get( 'state' );
return (
( state === HistoryDatasetAssociation.STATES.NEW )
|| ( state === HistoryDatasetAssociation.STATES.OK )
|| ( state === HistoryDatasetAssociation.STATES.EMPTY )
|| ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA )
|| ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
|| ( state === HistoryDatasetAssociation.STATES.DISCARDED )
|| ( state === HistoryDatasetAssociation.STATES.ERROR )
);
},
// convenience fn to match hda.has_data
hasData : function(){
//TODO:?? is this equivalent to all possible hda.has_data calls?
return ( this.get( 'file_size' ) > 0 );
},
toString : function(){
var nameAndId = this.get( 'id' ) || '';
if( this.get( 'name' ) ){
nameAndId += ':"' + this.get( 'name' ) + '"';
}
return 'HistoryDatasetAssociation(' + nameAndId + ')';
}
});
//------------------------------------------------------------------------------
HistoryDatasetAssociation.STATES = {
UPLOAD : 'upload',
QUEUED : 'queued',
RUNNING : 'running',
SETTING_METADATA : 'setting_metadata',
NEW : 'new',
OK : 'ok',
EMPTY : 'empty',
FAILED_METADATA : 'failed_metadata',
NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states
DISCARDED : 'discarded',
ERROR : 'error'
};
//==============================================================================
var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({
model : HistoryDatasetAssociation,
//logger : console,
initialize : function(){
//this.bind( 'all', function( event ){
// this.log( this + '', arguments );
//});
},
// return the ids of every hda in this collection
ids : function(){
return this.map( function( item ){ return item.id; });
},
// return an HDA collection containing every 'shown' hda based on show_deleted/hidden
getVisible : function( show_deleted, show_hidden ){
return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); });
},
// get a map where <possible hda state> : [ <list of hda ids in that state> ]
getStateLists : function(){
var stateLists = {};
_.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){
stateLists[ state ] = [];
});
//NOTE: will err on unknown state
this.each( function( item ){
stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) );
});
return stateLists;
},
// returns the id of every hda still running (not in a ready state)
running : function(){
var idList = [];
this.each( function( item ){
if( !item.inReadyState() ){
idList.push( item.get( 'id' ) );
}
});
return idList;
},
// update (fetch -> render) the hdas with the ids given
update : function( ids ){
this.log( this + 'update:', ids );
if( !( ids && ids.length ) ){ return; }
var collection = this;
_.each( ids, function( id, index ){
var historyItem = collection.get( id );
historyItem.fetch();
});
},
toString : function(){
return ( 'HDACollection(' + this.ids().join(',') + ')' );
}
});
//==============================================================================
var History = BaseModel.extend( LoggableMixin ).extend({
//TODO: bind change events from items and collection to this (itemLengths, states)
// uncomment this out see log messages
//logger : console,
// values from api (may need more)
defaults : {
id : '',
name : '',
state : '',
//TODO: wire these to items (or this)
show_deleted : false,
show_hidden : false,
diskSize : 0,
deleted : false,
tags : [],
annotation : null,
//TODO: quota msg and message? how to get those over the api?
message : null,
quotaMsg : false
},
url : function(){
// api location of history resource
//TODO: hardcoded
return 'api/histories/' + this.get( 'id' );
},
initialize : function( initialSettings, initialHdas ){
this.log( this + ".initialize:", initialSettings, initialHdas );
this.hdas = new HDACollection();
// if we've got hdas passed in the constructor, load them and set up updates if needed
if( initialHdas && initialHdas.length ){
this.hdas.reset( initialHdas );
this.checkForUpdates();
}
//this.on( 'change', function( currModel, changedList ){
// this.log( this + ' has changed:', currModel, changedList );
//});
//this.bind( 'all', function( event ){
// this.log( this + '', arguments );
//});
},
// get data via the api (alternative to sending options,hdas to initialize)
loadFromApi : function( historyId, callback ){
var history = this;
// fetch the history AND the user (mainly to see if they're logged in at this point)
history.attributes.id = historyId;
//TODO:?? really? fetch user here?
jQuery.when( jQuery.ajax( 'api/users/current' ), history.fetch()
).then( function( userResponse, historyResponse ){
//console.warn( 'fetched user, history: ', userResponse, historyResponse );
history.attributes.user = userResponse[0]; //? meh.
history.log( history );
}).then( function(){
// ...then the hdas (using contents?ids=...)
jQuery.ajax( history.url() + '/contents?' + jQuery.param({
ids : history.itemIdsFromStateIds().join( ',' )
// reset the collection to the hdas returned
})).success( function( hdas ){
//console.warn( 'fetched hdas' );
history.hdas.reset( hdas );
history.checkForUpdates();
callback();
});
});
},
// reduce the state_ids map of hda id lists -> a single list of ids
//...ugh - seems roundabout; necessary because the history doesn't have a straightforward list of ids
// (and history_contents/index curr returns a summary only)
hdaIdsFromStateIds : function(){
return _.reduce( _.values( this.get( 'state_ids' ) ), function( reduction, currIdList ){
return reduction.concat( currIdList );
});
},
// get the history's state from it's cummulative ds states, delay + update if needed
checkForUpdates : function( datasets ){
// get overall History state from collection, run updater if History has running/queued hdas
// boiling it down on the client to running/not
if( this.hdas.running().length ){
this.stateUpdater();
}
return this;
},
// update this history, find any hda's running/queued, update ONLY those that have changed states,
// set up to run this again in some interval of time
stateUpdater : function(){
var history = this,
oldState = this.get( 'state' ),
// state ids is a map of every possible hda state, each containing a list of ids for hdas in that state
oldStateIds = this.get( 'state_ids' );
// pull from the history api
//TODO: fetch?
jQuery.ajax( 'api/histories/' + this.get( 'id' )
).success( function( response ){
//this.log( 'historyApiRequest, response:', response );
history.set( response );
history.log( 'current history state:', history.get( 'state' ),
'(was)', oldState,
'new size:', history.get( 'nice_size' ) );
//TODO: revisit this - seems too elaborate, need something straightforward
// for each state, check for the difference between old dataset states and new
// the goal here is to check ONLY those datasets that have changed states (not all datasets)
var changedIds = [];
_.each( _.keys( response.state_ids ), function( state ){
var diffIds = _.difference( response.state_ids[ state ], oldStateIds[ state ] );
// aggregate those changed ids
changedIds = changedIds.concat( diffIds );
});
// send the changed ids (if any) to dataset collection to have them fetch their own model changes
if( changedIds.length ){
history.hdas.update( changedIds );
}
// set up to keep pulling if this history in run/queue state
//TODO: magic number here
if( ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING )
|| ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){
setTimeout( function(){
history.stateUpdater();
}, 4000 );
}
}).error( function( xhr, status, error ){
if( console && console.warn ){
console.warn( 'Error getting history updates from the server:', xhr, status, error );
}
alert( 'Error getting history updates from the server.\n' + error );
});
},
toString : function(){
var nameString = ( this.get( 'name' ) )?
( ',' + this.get( 'name' ) ) : ( '' );
return 'History(' + this.get( 'id' ) + nameString + ')';
}
});
//==============================================================================
/** View for editing (working with - as opposed to viewing/read-only) an hda
*
*/
var HDAView = BaseView.extend( LoggableMixin ).extend({
//??TODO: add alias in initialize this.hda = this.model?
// view for HistoryDatasetAssociation model above
@@ -1325,287 +856,7 @@ function create_trackster_action_fn(vis_url, dataset_params, dbkey) {
};
}
//==============================================================================
// view for the HDACollection (as per current right hand panel)
var HistoryView = BaseView.extend( LoggableMixin ).extend({
// uncomment this out see log messages
//logger : console,
// direct attachment to existing element
el : 'body.historyPage',
// init with the model, urlTemplates, set up storage, bind HDACollection events
//NOTE: this will create or load PersistantStorage keyed under 'HistoryView.<id>'
//pre: you'll need to pass in the urlTemplates (urlTemplates : { history : {...}, hda : {...} })
initialize : function( attributes ){
this.log( this + '.initialize:', attributes );
// set up url templates
//TODO: prob. better to put this in class scope (as the handlebars templates), but...
// they're added to GalaxyPaths on page load (after this file is loaded)
if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); }
if( !attributes.urlTemplates.history ){ throw( 'HDAView needs urlTemplates.history on initialize' ); }
if( !attributes.urlTemplates.hda ){ throw( 'HDAView needs urlTemplates.hda on initialize' ); }
this.urlTemplates = attributes.urlTemplates.history;
this.hdaUrlTemplates = attributes.urlTemplates.hda;
// data that needs to be persistant over page refreshes
// (note the key function which uses the history id as well)
this.storage = new PersistantStorage(
'HistoryView.' + this.model.get( 'id' ),
{ expandedHdas : {} }
);
// bind events from the model's hda collection
//this.model.bind( 'change', this.render, this );
this.model.bind( 'change:nice_size', this.updateHistoryDiskSize, this );
this.model.hdas.bind( 'add', this.add, this );
this.model.hdas.bind( 'reset', this.addAll, this );
this.model.hdas.bind( 'all', this.all, this );
//this.bind( 'all', function(){
// this.log( arguments );
//}, this );
// set up instance vars
this.hdaViews = {};
this.urls = {};
},
add : function( hda ){
//console.debug( 'add.' + this, hda );
//TODO
},
addAll : function(){
//console.debug( 'addAll.' + this );
// re render when all hdas are reset
this.render();
},
all : function( event ){
//console.debug( 'allItemEvents.' + this, event );
//...for which to do the debuggings
},
// render the urls for this view using urlTemplates and the model data
renderUrls : function( modelJson ){
var historyView = this;
historyView.urls = {};
_.each( this.urlTemplates, function( urlTemplate, urlKey ){
historyView.urls[ urlKey ] = _.template( urlTemplate, modelJson );
});
return historyView.urls;
},
// render urls, historyView body, and hdas (if any are shown), fade out, swap, fade in, set up behaviours
render : function(){
var historyView = this,
setUpQueueName = historyView.toString() + '.set-up',
newRender = $( '<div/>' ),
modelJson = this.model.toJSON(),
initialRender = ( this.$el.children().size() === 0 );
//console.debug( this + '.render, initialRender:', initialRender );
// render the urls and add them to the model json
modelJson.urls = this.renderUrls( modelJson );
// render the main template, tooltips
//NOTE: this is done before the items, since item views should handle theirs themselves
newRender.append( HistoryView.templates.historyPanel( modelJson ) );
newRender.find( '.tooltip' ).tooltip();
// render hda views (if any and any shown (show_deleted/hidden)
if( !this.model.hdas.length
|| !this.renderItems( newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){
// if history is empty or no hdas would be rendered, show the empty message
newRender.find( '#emptyHistoryMessage' ).show();
}
// fade out existing, swap with the new, fade in, set up behaviours
$( historyView ).queue( setUpQueueName, function( next ){
historyView.$el.fadeOut( 'fast', function(){ next(); });
});
$( historyView ).queue( setUpQueueName, function( next ){
// swap over from temp div newRender
historyView.$el.html( '' );
historyView.$el.append( newRender.children() );
historyView.$el.fadeIn( 'fast', function(){ next(); });
});
$( historyView ).queue( setUpQueueName, function( next ){
this.log( historyView + ' rendered:', historyView.$el );
//TODO: ideally, these would be set up before the fade in (can't because of async save text)
historyView.setUpBehaviours();
if( initialRender ){
historyView.trigger( 'rendered:initial' );
} else {
historyView.trigger( 'rendered' );
}
next();
});
$( historyView ).dequeue( setUpQueueName );
return this;
},
// set up a view for each item to be shown, init with model and listeners, cache to map ( model.id : view )
renderItems : function( $whereTo ){
this.hdaViews = {};
var historyView = this,
show_deleted = this.model.get( 'show_deleted' ),
show_hidden = this.model.get( 'show_hidden' ),
visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden );
// only render the shown hdas
_.each( visibleHdas, function( hda ){
var hdaId = hda.get( 'id' ),
expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId );
historyView.hdaViews[ hdaId ] = new HDAView({
model : hda,
expanded : expanded,
urlTemplates : historyView.hdaUrlTemplates
});
historyView.setUpHdaListeners( historyView.hdaViews[ hdaId ] );
// render it (NOTE: reverse order, newest on top (prepend))
//TODO: by default send a reverse order list (although this may be more efficient - it's more confusing)
$whereTo.prepend( historyView.hdaViews[ hdaId ].render().$el );
});
return visibleHdas.length;
},
// set up HistoryView->HDAView listeners
setUpHdaListeners : function( hdaView ){
var historyView = this;
// use storage to maintain a list of hdas whose bodies are expanded
hdaView.bind( 'toggleBodyVisibility', function( id, visible ){
if( visible ){
historyView.storage.get( 'expandedHdas' ).set( id, true );
} else {
historyView.storage.get( 'expandedHdas' ).deleteKey( id );
}
});
// rendering listeners
hdaView.bind( 'rendered:ready', function(){ historyView.trigger( 'hda:rendered:ready' ); });
},
// set up js/widget behaviours: tooltips,
//TODO: these should be either sub-MVs, or handled by events
setUpBehaviours : function(){
// anon users shouldn't have access to any of these
if( !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; }
// annotation slide down
var historyAnnotationArea = this.$( '#history-annotation-area' );
this.$( '#history-annotate' ).click( function() {
if ( historyAnnotationArea.is( ":hidden" ) ) {
historyAnnotationArea.slideDown( "fast" );
} else {
historyAnnotationArea.slideUp( "fast" );
}
return false;
});
// title and annotation editable text
//NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable
async_save_text( "history-name-container", "history-name",
this.urls.rename, "new_name", 18 );
async_save_text( "history-annotation-container", "history-annotation",
this.urls.annotate, "new_annotation", 18, true, 4 );
},
// update the history size display (curr. upper right of panel)
updateHistoryDiskSize : function(){
this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) );
},
//TODO: this seems more like a per user message than a history message; IOW, this doesn't belong here
showQuotaMessage : function( userData ){
var msg = this.$el.find( '#quota-message-container' );
//this.log( this + ' showing quota message:', msg, userData );
if( msg.is( ':hidden' ) ){ msg.slideDown( 'fast' ); }
},
//TODO: this seems more like a per user message than a history message
hideQuotaMessage : function( userData ){
var msg = this.$el.find( '#quota-message-container' );
//this.log( this + ' hiding quota message:', msg, userData );
if( !msg.is( ':hidden' ) ){ msg.slideUp( 'fast' ); }
},
events : {
'click #history-collapse-all' : 'hideAllHdaBodies',
'click #history-tag' : 'loadAndDisplayTags'
},
// collapse all hda bodies
hideAllHdaBodies : function(){
_.each( this.hdaViews, function( item ){
item.toggleBodyVisibility( null, false );
});
this.storage.set( 'expandedHdas', {} );
},
// find the tag area and, if initial: (via ajax) load the html for displaying them; otherwise, unhide/hide
//TODO: into sub-MV
loadAndDisplayTags : function( event ){
this.log( this + '.loadAndDisplayTags', event );
var tagArea = this.$el.find( '#history-tag-area' ),
tagElt = tagArea.find( '.tag-elt' );
this.log( '\t tagArea', tagArea, ' tagElt', tagElt );
// Show or hide tag area; if showing tag area and it's empty, fill it
if( tagArea.is( ":hidden" ) ){
if( !jQuery.trim( tagElt.html() ) ){
var view = this;
// Need to fill tag element.
$.ajax({
//TODO: the html from this breaks a couple of times
url: view.urls.tag,
error: function() { alert( "Tagging failed" ); },
success: function(tag_elt_html) {
//view.log( view + ' tag elt html (ajax)', tag_elt_html );
tagElt.html(tag_elt_html);
tagElt.find(".tooltip").tooltip();
tagArea.slideDown("fast");
}
});
} else {
// Tag element already filled: show
tagArea.slideDown("fast");
}
} else {
// Currently shown: Hide
tagArea.slideUp("fast");
}
return false;
},
toString : function(){
var nameString = this.model.get( 'name' ) || '';
return 'HistoryView(' + nameString + ')';
}
});
HistoryView.templates = {
historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
};
//==============================================================================
//return {
// HistoryItem : HistoryItem,
// HDAView : HDAView,
// HistoryCollection : HistoryCollection,
// History : History,
// HistoryView : HistoryView
//};});
+223
View File
@@ -0,0 +1,223 @@
//define([
// "../mvc/base-mvc"
//], function(){
//==============================================================================
/**
*
*/
var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend({
// a single HDA model
// uncomment this out see log messages
//logger : console,
defaults : {
// ---these are part of an HDA proper:
// parent (containing) history
history_id : null,
// often used with tagging
model_class : 'HistoryDatasetAssociation',
// index within history (??)
hid : 0,
// ---whereas these are Dataset related/inherited
id : null,
name : '',
// one of HistoryDatasetAssociation.STATES
state : '',
// sniffed datatype (sam, tabular, bed, etc.)
data_type : null,
// size in bytes
file_size : 0,
// array of associated file types (eg. [ 'bam_index', ... ])
meta_files : [],
misc_blurb : '',
misc_info : '',
deleted : false,
purged : false,
// aka. !hidden
visible : false,
// based on trans.user (is_admin or security_agent.can_access_dataset( <user_roles>, hda.dataset ))
accessible : false,
//TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView))
for_editing : true
},
// fetch location of this history in the api
url : function(){
//TODO: get this via url router
return 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );
},
// (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE
//TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted
initialize : function(){
this.log( this + '.initialize', this.attributes );
this.log( '\tparent history_id: ' + this.get( 'history_id' ) );
//!! this state is not in trans.app.model.Dataset.states - set it here -
//TODO: change to server side.
if( !this.get( 'accessible' ) ){
this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE );
}
// if the state has changed and the new state is a ready state, fire an event
this.on( 'change:state', function( currModel, newState ){
this.log( this + ' has changed state:', currModel, newState );
if( this.inReadyState() ){
this.trigger( 'state:ready', this.get( 'id' ), newState, this.previous( 'state' ), currModel );
}
});
// debug on change events
//this.on( 'change', function( currModel, changedList ){
// this.log( this + ' has changed:', currModel, changedList );
//});
//this.bind( 'all', function( event ){
// this.log( this + '', arguments );
//});
},
isDeletedOrPurged : function(){
return ( this.get( 'deleted' ) || this.get( 'purged' ) );
},
// based on show_deleted, show_hidden (gen. from the container control), would this ds show in the list of ds's?
//TODO: too many visibles
isVisible : function( show_deleted, show_hidden ){
var isVisible = true;
if( ( !show_deleted )
&& ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){
isVisible = false;
}
if( ( !show_hidden )
&& ( !this.get( 'visible' ) ) ){
isVisible = false;
}
return isVisible;
},
// 'ready' states are states where no processing (for the ds) is left to do on the server
inReadyState : function(){
var state = this.get( 'state' );
return (
( state === HistoryDatasetAssociation.STATES.NEW )
|| ( state === HistoryDatasetAssociation.STATES.OK )
|| ( state === HistoryDatasetAssociation.STATES.EMPTY )
|| ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA )
|| ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
|| ( state === HistoryDatasetAssociation.STATES.DISCARDED )
|| ( state === HistoryDatasetAssociation.STATES.ERROR )
);
},
// convenience fn to match hda.has_data
hasData : function(){
//TODO:?? is this equivalent to all possible hda.has_data calls?
return ( this.get( 'file_size' ) > 0 );
},
toString : function(){
var nameAndId = this.get( 'id' ) || '';
if( this.get( 'name' ) ){
nameAndId += ':"' + this.get( 'name' ) + '"';
}
return 'HistoryDatasetAssociation(' + nameAndId + ')';
}
});
//------------------------------------------------------------------------------
HistoryDatasetAssociation.STATES = {
UPLOAD : 'upload',
QUEUED : 'queued',
RUNNING : 'running',
SETTING_METADATA : 'setting_metadata',
NEW : 'new',
OK : 'ok',
EMPTY : 'empty',
FAILED_METADATA : 'failed_metadata',
NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states
DISCARDED : 'discarded',
ERROR : 'error'
};
//==============================================================================
/**
*
*/
var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({
model : HistoryDatasetAssociation,
//logger : console,
initialize : function(){
//this.bind( 'all', function( event ){
// this.log( this + '', arguments );
//});
},
// return the ids of every hda in this collection
ids : function(){
return this.map( function( item ){ return item.id; });
},
// return an HDA collection containing every 'shown' hda based on show_deleted/hidden
getVisible : function( show_deleted, show_hidden ){
return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); });
},
// get a map where <possible hda state> : [ <list of hda ids in that state> ]
getStateLists : function(){
var stateLists = {};
_.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){
stateLists[ state ] = [];
});
//NOTE: will err on unknown state
this.each( function( item ){
stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) );
});
return stateLists;
},
// returns the id of every hda still running (not in a ready state)
running : function(){
var idList = [];
this.each( function( item ){
if( !item.inReadyState() ){
idList.push( item.get( 'id' ) );
}
});
return idList;
},
// update (fetch -> render) the hdas with the ids given
update : function( ids ){
this.log( this + 'update:', ids );
if( !( ids && ids.length ) ){ return; }
var collection = this;
_.each( ids, function( id, index ){
var historyItem = collection.get( id );
historyItem.fetch();
});
},
toString : function(){
return ( 'HDACollection(' + this.ids().join(',') + ')' );
}
});
//==============================================================================
//return {
// HistoryDatasetAssociation : HistoryDatasetAssociation,
// HDACollection : HDACollection,
//};});
+180
View File
@@ -0,0 +1,180 @@
//define([
// "../mvc/base-mvc"
//], function(){
//==============================================================================
/**
*
*/
var History = BaseModel.extend( LoggableMixin ).extend({
//TODO: bind change events from items and collection to this (itemLengths, states)
// uncomment this out see log messages
//logger : console,
// values from api (may need more)
defaults : {
id : '',
name : '',
state : '',
//TODO: wire these to items (or this)
show_deleted : false,
show_hidden : false,
diskSize : 0,
deleted : false,
tags : [],
annotation : null,
//TODO: quota msg and message? how to get those over the api?
message : null,
quotaMsg : false
},
url : function(){
// api location of history resource
//TODO: hardcoded
return 'api/histories/' + this.get( 'id' );
},
initialize : function( initialSettings, initialHdas ){
this.log( this + ".initialize:", initialSettings, initialHdas );
this.hdas = new HDACollection();
// if we've got hdas passed in the constructor, load them and set up updates if needed
if( initialHdas && initialHdas.length ){
this.hdas.reset( initialHdas );
this.checkForUpdates();
}
//this.on( 'change', function( currModel, changedList ){
// this.log( this + ' has changed:', currModel, changedList );
//});
//this.bind( 'all', function( event ){
// this.log( this + '', arguments );
//});
},
// get data via the api (alternative to sending options,hdas to initialize)
loadFromApi : function( historyId, callback ){
var history = this;
// fetch the history AND the user (mainly to see if they're logged in at this point)
history.attributes.id = historyId;
//TODO:?? really? fetch user here?
jQuery.when( jQuery.ajax( 'api/users/current' ), history.fetch()
).then( function( userResponse, historyResponse ){
//console.warn( 'fetched user, history: ', userResponse, historyResponse );
history.attributes.user = userResponse[0]; //? meh.
history.log( history );
}).then( function(){
// ...then the hdas (using contents?ids=...)
jQuery.ajax( history.url() + '/contents?' + jQuery.param({
ids : history.itemIdsFromStateIds().join( ',' )
// reset the collection to the hdas returned
})).success( function( hdas ){
//console.warn( 'fetched hdas' );
history.hdas.reset( hdas );
history.checkForUpdates();
callback();
});
});
},
// reduce the state_ids map of hda id lists -> a single list of ids
//...ugh - seems roundabout; necessary because the history doesn't have a straightforward list of ids
// (and history_contents/index curr returns a summary only)
hdaIdsFromStateIds : function(){
return _.reduce( _.values( this.get( 'state_ids' ) ), function( reduction, currIdList ){
return reduction.concat( currIdList );
});
},
// get the history's state from it's cummulative ds states, delay + update if needed
checkForUpdates : function( datasets ){
// get overall History state from collection, run updater if History has running/queued hdas
// boiling it down on the client to running/not
if( this.hdas.running().length ){
this.stateUpdater();
}
return this;
},
// update this history, find any hda's running/queued, update ONLY those that have changed states,
// set up to run this again in some interval of time
stateUpdater : function(){
var history = this,
oldState = this.get( 'state' ),
// state ids is a map of every possible hda state, each containing a list of ids for hdas in that state
oldStateIds = this.get( 'state_ids' );
// pull from the history api
//TODO: fetch?
jQuery.ajax( 'api/histories/' + this.get( 'id' )
).success( function( response ){
//this.log( 'historyApiRequest, response:', response );
history.set( response );
history.log( 'current history state:', history.get( 'state' ),
'(was)', oldState,
'new size:', history.get( 'nice_size' ) );
//TODO: revisit this - seems too elaborate, need something straightforward
// for each state, check for the difference between old dataset states and new
// the goal here is to check ONLY those datasets that have changed states (not all datasets)
var changedIds = [];
_.each( _.keys( response.state_ids ), function( state ){
var diffIds = _.difference( response.state_ids[ state ], oldStateIds[ state ] );
// aggregate those changed ids
changedIds = changedIds.concat( diffIds );
});
// send the changed ids (if any) to dataset collection to have them fetch their own model changes
if( changedIds.length ){
history.hdas.update( changedIds );
}
// set up to keep pulling if this history in run/queue state
//TODO: magic number here
if( ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING )
|| ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){
setTimeout( function(){
history.stateUpdater();
}, 4000 );
}
}).error( function( xhr, status, error ){
if( console && console.warn ){
console.warn( 'Error getting history updates from the server:', xhr, status, error );
}
alert( 'Error getting history updates from the server.\n' + error );
});
},
toString : function(){
var nameString = ( this.get( 'name' ) )?
( ',' + this.get( 'name' ) ) : ( '' );
return 'History(' + this.get( 'id' ) + nameString + ')';
}
});
//==============================================================================
/** A collection of histories (per user or admin)
* (stub) currently unused
*/
var HistoryCollection = Backbone.Collection.extend( LoggableMixin ).extend({
model : History,
urlRoot : 'api/histories',
logger : console
});
//==============================================================================
//return {
// History : History,
// HistoryCollection : HistoryCollection,
//};});
+380
View File
@@ -0,0 +1,380 @@
//define([
// "../mvc/base-mvc"
//], function(){
/* =============================================================================
Backbone.js implementation of history panel
TODO:
anon user, mako template init:
bug: rename url seems to be wrong url
logged in, mako template:
BUG: meter is not updating RELIABLY on change:nice_size
BUG: am able to start upload even if over quota - 'runs' forever
bug: quotaMeter bar rendering square in chrome
BUG: quotaMsg not showing when 100% (on load)
BUG: imported, shared history with unaccessible dataset errs in historycontents when getting history
(entire history is inaccessible)
??: still happening?
from loadFromApi:
BUG: not showing previous annotations
fixed:
BUG: upload, history size, doesn't change
FIXED: using change:nice_size to trigger re-render of history size
BUG: delete uploading hda - now in state 'discarded'! ...new state to handle
FIXED: handled state
BUG: historyItem, error'd ds show display, download?
FIXED: removed
bug: loading hdas (alt_hist)
FIXED: added anon user api request ( trans.user == None and trans.history.id == requested id )
bug: quota meter not updating on upload/tool run
FIXED: quotaMeter now listens for 'state:ready' from glx_history in alternate_history.mako
bug: use of new HDACollection with event listener in init doesn't die...keeps reporting
FIXED: change getVisible to return an array
BUG: history, broken intial hist state (running, updater, etc.)
??: doesn't seem to happen anymore
BUG: collapse all should remove all expanded from storage
FIXED: hideAllItemBodies now resets storage.expandedItems
BUG: historyItem, shouldn't allow tag, annotate, peek on purged datasets
FIXED: ok state now shows only: info, rerun
BUG: history?, some ids aren't returning encoded...
FIXED:???
BUG: history, showing deleted ds
FIXED
UGH: historyItems have to be decorated with history_ids (api/histories/:history_id/contents/:id)
FIXED by adding history_id to history_contents.show
BUG: history, if hist has err'd ds, hist has perm state 'error', updater on following ds's doesn't run
FIXED by reordering history state from ds' states here and histories
BUG: history, broken annotation on reload (can't get thru api (sets fine, tho))
FIXED: get thru api for now
replication:
show_deleted/hidden:
use storage
on/off ui
move histview fadein/out in render to app?
don't draw body until it's first expand event
localize all
?: render url templates on init or render?
?: history, annotation won't accept unicode
RESTful:
move over webui functions available in api
delete, undelete
update?
currently, adding a dataset (via tool execute, etc.) creates a new dataset and refreshes the page
provide a means to update the panel via js
hierarchy:
to relational model?
HDACollection, meta_files, display_apps, etc.
dataset -> hda
history -> historyForEditing, historyForViewing
display_structured?
meta:
css/html class/id 'item' -> hda
add classes, ids on empty divs
events (local/ui and otherwise)
list in docs as well
require.js
convert function comments to jsDoc style, complete comments
move inline styles into base.less
watch the magic strings
watch your globals
feature creep:
lineage
hide button
show permissions in info
show shared/sharing status on ds, history
maintain scroll position on refresh (storage?)
selection, multi-select (and actions common to selected (ugh))
searching
sorting, re-shuffling
============================================================================= */
/** view for the HDACollection (as per current right hand panel)
*
*/
var HistoryPanel = BaseView.extend( LoggableMixin ).extend({
// uncomment this out see log messages
//logger : console,
// direct attachment to existing element
el : 'body.historyPage',
// init with the model, urlTemplates, set up storage, bind HDACollection events
//NOTE: this will create or load PersistantStorage keyed under 'HistoryView.<id>'
//pre: you'll need to pass in the urlTemplates (urlTemplates : { history : {...}, hda : {...} })
initialize : function( attributes ){
this.log( this + '.initialize:', attributes );
// set up url templates
//TODO: prob. better to put this in class scope (as the handlebars templates), but...
// they're added to GalaxyPaths on page load (after this file is loaded)
if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); }
if( !attributes.urlTemplates.history ){ throw( 'HDAView needs urlTemplates.history on initialize' ); }
if( !attributes.urlTemplates.hda ){ throw( 'HDAView needs urlTemplates.hda on initialize' ); }
this.urlTemplates = attributes.urlTemplates.history;
this.hdaUrlTemplates = attributes.urlTemplates.hda;
// data that needs to be persistant over page refreshes
// (note the key function which uses the history id as well)
this.storage = new PersistantStorage(
'HistoryView.' + this.model.get( 'id' ),
{ expandedHdas : {} }
);
// bind events from the model's hda collection
//this.model.bind( 'change', this.render, this );
this.model.bind( 'change:nice_size', this.updateHistoryDiskSize, this );
this.model.hdas.bind( 'add', this.add, this );
this.model.hdas.bind( 'reset', this.addAll, this );
this.model.hdas.bind( 'all', this.all, this );
//this.bind( 'all', function(){
// this.log( arguments );
//}, this );
// set up instance vars
this.hdaViews = {};
this.urls = {};
},
add : function( hda ){
//console.debug( 'add.' + this, hda );
//TODO
},
addAll : function(){
//console.debug( 'addAll.' + this );
// re render when all hdas are reset
this.render();
},
all : function( event ){
//console.debug( 'allItemEvents.' + this, event );
//...for which to do the debuggings
},
// render the urls for this view using urlTemplates and the model data
renderUrls : function( modelJson ){
var historyView = this;
historyView.urls = {};
_.each( this.urlTemplates, function( urlTemplate, urlKey ){
historyView.urls[ urlKey ] = _.template( urlTemplate, modelJson );
});
return historyView.urls;
},
// render urls, historyView body, and hdas (if any are shown), fade out, swap, fade in, set up behaviours
render : function(){
var historyView = this,
setUpQueueName = historyView.toString() + '.set-up',
newRender = $( '<div/>' ),
modelJson = this.model.toJSON(),
initialRender = ( this.$el.children().size() === 0 );
//console.debug( this + '.render, initialRender:', initialRender );
// render the urls and add them to the model json
modelJson.urls = this.renderUrls( modelJson );
// render the main template, tooltips
//NOTE: this is done before the items, since item views should handle theirs themselves
newRender.append( HistoryPanel.templates.historyPanel( modelJson ) );
newRender.find( '.tooltip' ).tooltip();
// render hda views (if any and any shown (show_deleted/hidden)
if( !this.model.hdas.length
|| !this.renderItems( newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){
// if history is empty or no hdas would be rendered, show the empty message
newRender.find( '#emptyHistoryMessage' ).show();
}
// fade out existing, swap with the new, fade in, set up behaviours
$( historyView ).queue( setUpQueueName, function( next ){
historyView.$el.fadeOut( 'fast', function(){ next(); });
});
$( historyView ).queue( setUpQueueName, function( next ){
// swap over from temp div newRender
historyView.$el.html( '' );
historyView.$el.append( newRender.children() );
historyView.$el.fadeIn( 'fast', function(){ next(); });
});
$( historyView ).queue( setUpQueueName, function( next ){
this.log( historyView + ' rendered:', historyView.$el );
//TODO: ideally, these would be set up before the fade in (can't because of async save text)
historyView.setUpBehaviours();
if( initialRender ){
historyView.trigger( 'rendered:initial' );
} else {
historyView.trigger( 'rendered' );
}
next();
});
$( historyView ).dequeue( setUpQueueName );
return this;
},
// set up a view for each item to be shown, init with model and listeners, cache to map ( model.id : view )
renderItems : function( $whereTo ){
this.hdaViews = {};
var historyView = this,
show_deleted = this.model.get( 'show_deleted' ),
show_hidden = this.model.get( 'show_hidden' ),
visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden );
// only render the shown hdas
_.each( visibleHdas, function( hda ){
var hdaId = hda.get( 'id' ),
expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId );
historyView.hdaViews[ hdaId ] = new HDAView({
model : hda,
expanded : expanded,
urlTemplates : historyView.hdaUrlTemplates
});
historyView.setUpHdaListeners( historyView.hdaViews[ hdaId ] );
// render it (NOTE: reverse order, newest on top (prepend))
//TODO: by default send a reverse order list (although this may be more efficient - it's more confusing)
$whereTo.prepend( historyView.hdaViews[ hdaId ].render().$el );
});
return visibleHdas.length;
},
// set up HistoryView->HDAView listeners
setUpHdaListeners : function( hdaView ){
var historyView = this;
// use storage to maintain a list of hdas whose bodies are expanded
hdaView.bind( 'toggleBodyVisibility', function( id, visible ){
if( visible ){
historyView.storage.get( 'expandedHdas' ).set( id, true );
} else {
historyView.storage.get( 'expandedHdas' ).deleteKey( id );
}
});
// rendering listeners
hdaView.bind( 'rendered:ready', function(){ historyView.trigger( 'hda:rendered:ready' ); });
},
// set up js/widget behaviours: tooltips,
//TODO: these should be either sub-MVs, or handled by events
setUpBehaviours : function(){
// anon users shouldn't have access to any of these
if( !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; }
// annotation slide down
var historyAnnotationArea = this.$( '#history-annotation-area' );
this.$( '#history-annotate' ).click( function() {
if ( historyAnnotationArea.is( ":hidden" ) ) {
historyAnnotationArea.slideDown( "fast" );
} else {
historyAnnotationArea.slideUp( "fast" );
}
return false;
});
// title and annotation editable text
//NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable
async_save_text( "history-name-container", "history-name",
this.urls.rename, "new_name", 18 );
async_save_text( "history-annotation-container", "history-annotation",
this.urls.annotate, "new_annotation", 18, true, 4 );
},
// update the history size display (curr. upper right of panel)
updateHistoryDiskSize : function(){
this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) );
},
//TODO: this seems more like a per user message than a history message; IOW, this doesn't belong here
showQuotaMessage : function( userData ){
var msg = this.$el.find( '#quota-message-container' );
//this.log( this + ' showing quota message:', msg, userData );
if( msg.is( ':hidden' ) ){ msg.slideDown( 'fast' ); }
},
//TODO: this seems more like a per user message than a history message
hideQuotaMessage : function( userData ){
var msg = this.$el.find( '#quota-message-container' );
//this.log( this + ' hiding quota message:', msg, userData );
if( !msg.is( ':hidden' ) ){ msg.slideUp( 'fast' ); }
},
events : {
'click #history-collapse-all' : 'hideAllHdaBodies',
'click #history-tag' : 'loadAndDisplayTags'
},
// collapse all hda bodies
hideAllHdaBodies : function(){
_.each( this.hdaViews, function( item ){
item.toggleBodyVisibility( null, false );
});
this.storage.set( 'expandedHdas', {} );
},
// find the tag area and, if initial: (via ajax) load the html for displaying them; otherwise, unhide/hide
//TODO: into sub-MV
loadAndDisplayTags : function( event ){
this.log( this + '.loadAndDisplayTags', event );
var tagArea = this.$el.find( '#history-tag-area' ),
tagElt = tagArea.find( '.tag-elt' );
this.log( '\t tagArea', tagArea, ' tagElt', tagElt );
// Show or hide tag area; if showing tag area and it's empty, fill it
if( tagArea.is( ":hidden" ) ){
if( !jQuery.trim( tagElt.html() ) ){
var view = this;
// Need to fill tag element.
$.ajax({
//TODO: the html from this breaks a couple of times
url: view.urls.tag,
error: function() { alert( "Tagging failed" ); },
success: function(tag_elt_html) {
//view.log( view + ' tag elt html (ajax)', tag_elt_html );
tagElt.html(tag_elt_html);
tagElt.find(".tooltip").tooltip();
tagArea.slideDown("fast");
}
});
} else {
// Tag element already filled: show
tagArea.slideDown("fast");
}
} else {
// Currently shown: Hide
tagArea.slideUp("fast");
}
return false;
},
toString : function(){
var nameString = this.model.get( 'name' ) || '';
return 'HistoryView(' + nameString + ')';
}
});
//------------------------------------------------------------------------------
HistoryPanel.templates = {
historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
};
//==============================================================================
//return {
// HistoryPanel : HistoryPanel
//};});
+72 -42
View File
@@ -222,97 +222,127 @@ ${h.templates(
"template-user-quotaMeter-usage"
)}
##TODO: fix: curr hasta be _after_ h.templates - move somehow
##TODO: fix: curr hasta be _after_ h.templates bc these use those templates - move somehow
${h.js(
"mvc/history",
"mvc/dataset/hda-model", "mvc/dataset/hda-edit",
"mvc/history/history-model", "mvc/history/history-panel",
##"mvc/tags", "mvc/annotations",
"mvc/user/user-model", "mvc/user/user-quotameter"
)}
<script type="text/javascript">
function galaxyPageSetUp(){
// moving global functions, objects into Galaxy namespace
top.Galaxy = top.Galaxy || {};
// bad idea from memleak standpoint?
top.Galaxy.mainWindow = top.Galaxy.mainWindow || top.frames.galaxy_main;
top.Galaxy.toolWindow = top.Galaxy.toolWindow || top.frames.galaxy_tools;
top.Galaxy.historyWindow = top.Galaxy.historyWindow || top.frames.galaxy_history;
top.Galaxy.$masthead = top.Galaxy.$masthead || $( top.document ).find( 'div#masthead' );
top.Galaxy.$messagebox = top.Galaxy.$messagebox || $( top.document ).find( 'div#messagebox' );
top.Galaxy.$leftPanel = top.Galaxy.$leftPanel || $( top.document ).find( 'div#left' );
top.Galaxy.$centerPanel = top.Galaxy.$centerPanel || $( top.document ).find( 'div#center' );
top.Galaxy.$rightPanel = top.Galaxy.$rightPanel || $( top.document ).find( 'div#right' );
//modals
// other base functions
// global backbone models
top.Galaxy.currUser = top.Galaxy.currUser;
top.Galaxy.currHistoryPanel = top.Galaxy.currHistoryPanel;
top.Galaxy.historyPanels = top.Galaxy.historyPanels || [];
top.Galaxy.paths = galaxy_paths;
window.Galaxy = top.Galaxy;
}
// set js localizable strings
GalaxyLocalization.setLocalizedString( ${ create_localization_json( get_page_localized_strings() ) } );
// add needed controller urls to GalaxyPaths
galaxy_paths.set( 'hda', ${get_hda_url_templates()} );
galaxy_paths.set( 'history', ${get_history_url_templates()} );
//console.debug( 'galaxy_paths:', galaxy_paths );
$(function(){
galaxyPageSetUp();
Galaxy.historyFrame = window;
// ostensibly, this is the App
if( console && console.debug ){
//if( console.clear ){ console.clear(); }
console.debug( 'using backbone.js in history panel' );
console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
top.storage = jQuery.jStorage
}
// load initial data in this page - since we're already sending it...
// LOAD INITIAL DATA IN THIS PAGE - since we're already sending it...
// ...use mako to 'bootstrap' the models
var user = ${ get_current_user() },
history = ${ get_history( history.id ) },
hdas = ${ get_hdas( history.id, datasets ) };
//console.debug( 'user:', user );
//console.debug( 'history:', history );
//console.debug( 'hdas:', hdas );
var currUser = new User( user );
if( !Galaxy.currUser ){ Galaxy.currUser = currUser; }
// i don't like this relationship, but user authentication changes views/behaviour
// add user data to history
// i don't like this history+user relationship, but user authentication changes views/behaviour
history.user = user;
// is page sending in show settings? if so override history's
//TODO: move into historyPanel
history.show_deleted = ${ 'true' if show_deleted else 'false' };
history.show_hidden = ${ 'true' if show_hidden else 'false' };
//console.debug( 'galaxy_paths:', galaxy_paths );
var glx_history = new History( history, hdas );
glx_history.logger = console;
var glx_history_view = new HistoryView({
model: glx_history,
var historyPanel = new HistoryPanel({
model : new History( history, hdas ),
urlTemplates: galaxy_paths.attributes,
logger: console
logger : console
});
glx_history_view.render();
// ...OR load from the api
//var glx_history = new History().setPaths( galaxy_paths ),
// glx_history_view = new HistoryView({ model: glx_history });
//console.warn( 'fetching' );
//glx_history.loadFromApi( pageData.history.id );
historyPanel.render();
if( !Galaxy.currHistoryPanel ){ Galaxy.currHistoryPanel = historyPanel; }
if( !( historyPanel in Galaxy.historyPanels ) ){ Galaxy.historyPanels.unshift( historyPanel ); }
// quota meter is a cross-frame ui element (meter in masthead, over quota message in history)
// ...or LOAD FROM THE API
//historyPanel = new HistoryView({ model: new History().setPaths( galaxy_paths ) });
//historyPanel.loadFromApi( pageData.history.id );
// QUOTA METER is a cross-frame ui element (meter in masthead, over quota message in history)
// create it and join them here for now (via events)
window.currUser = new User( user );
//TODO: this really belongs in the masthead
//window.currUser.logger = console;
window.quotaMeter = new UserQuotaMeter({ model: currUser, el: $( top.document ).find( '.quota-meter-container' ) });
window.quotaMeter.render();
//window.quotaMeter.logger = console;
var quotaMeter = new UserQuotaMeter({
model : currUser,
el : $( top.document ).find( '.quota-meter-container' )
});
//quotaMeter.logger = console; window.quotaMeter = quotaMeter
quotaMeter.render();
// show/hide the 'over quota message' in the history when the meter tells it to
quotaMeter.bind( 'quota:over', glx_history_view.showQuotaMessage, glx_history_view );
quotaMeter.bind( 'quota:under', glx_history_view.hideQuotaMessage, glx_history_view );
quotaMeter.bind( 'quota:over', historyPanel.showQuotaMessage, historyPanel );
quotaMeter.bind( 'quota:under', historyPanel.hideQuotaMessage, historyPanel );
// having to add this to handle re-render of hview while overquota (the above do not fire)
glx_history_view.on( 'rendered', function(){
if( window.quotaMeter.isOverQuota() ){
glx_history_view.showQuotaMessage();
historyPanel.on( 'rendered', function(){
if( quotaMeter.isOverQuota() ){
historyPanel.showQuotaMessage();
}
});
//TODO: this _is_ sent to the page (over_quota)...
// update the quota meter when current history changes size
glx_history.bind( 'change:nice_size', function(){
window.quotaMeter.update()
}, window.quotaMeter );
historyPanel.model.bind( 'change:nice_size', function(){
quotaMeter.update()
}, quotaMeter );
if( console && console.debug ){
window.user = top.user = user;
window._history = top._history = history;
window.hdas = top.hdas = hdas;
window.glx_history = top.glx_history = glx_history;
window.glx_history_view = top.glx_history_view = glx_history_view;
top.storage = jQuery.jStorage
}
return;
});
</script>