7 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/74f946b09dd0/ changeset: 74f946b09dd0 user: carlfeberhard date: 2012-11-05 22:42:16 summary: util/debugging.py: add stack trace formatter affected #: 1 file diff -r 9d6a61f060d359a0289b3163990f6a8ba122d253 -r 74f946b09dd06341a02d009a81c00e25dd5dfb59 lib/galaxy/util/debugging.py --- /dev/null +++ b/lib/galaxy/util/debugging.py @@ -0,0 +1,29 @@ + +import inspect +import pprint + +import logging +log = logging.getLogger( __name__ ) + +def stack_trace_string( max_depth=None, line_format="{index}:{file}:{function}:{line}" ): + """ + Returns a string representation of the current stack. + + :param depth: positive integer to control how many levels of the stack are + returned. max_depth=None returns the entire stack (default). + """ + stack_list = [] + for index, caller in enumerate( inspect.stack() ): + # don't include this function + if index == 0: continue + if max_depth and index > max_depth: break + + caller_data = { + 'index' : str( index ), + 'file' : caller[1], + 'function' : caller[3], + 'line' : caller[2] + } + stack_list.append( line_format.format( **caller_data ) ) + + return '\n'.join( stack_list ) https://bitbucket.org/galaxy/galaxy-central/changeset/472a8f0ef7f5/ changeset: 472a8f0ef7f5 user: carlfeberhard date: 2012-11-05 22:45:10 summary: fix to model/__init__: was importing sys from galaxy.web.form_builder.* affected #: 1 file diff -r 74f946b09dd06341a02d009a81c00e25dd5dfb59 -r 472a8f0ef7f5517d20cecaa8a0a65d952fc7d35e lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -18,7 +18,7 @@ from galaxy.model.item_attrs import UsesAnnotations, APIItem from sqlalchemy.orm import object_session from sqlalchemy.sql.expression import func -import os.path, os, errno, codecs, operator, socket, pexpect, logging, time, shutil +import sys, os.path, os, errno, codecs, operator, socket, pexpect, logging, time, shutil if sys.version_info[:2] < ( 2, 5 ): from sets import Set as set https://bitbucket.org/galaxy/galaxy-central/changeset/613df4d4ca7d/ changeset: 613df4d4ca7d user: carlfeberhard date: 2012-11-05 22:47:14 summary: comments & docs for QuotaAgent.get_percent affected #: 1 file diff -r 472a8f0ef7f5517d20cecaa8a0a65d952fc7d35e -r 613df4d4ca7d41c240311991022148dedb2e620d lib/galaxy/quota/__init__.py --- a/lib/galaxy/quota/__init__.py +++ b/lib/galaxy/quota/__init__.py @@ -122,20 +122,26 @@ dqa = self.model.DefaultQuotaAssociation( default_type, quota ) self.sa_session.add( dqa ) self.sa_session.flush() + def get_percent( self, trans=None, user=False, history=False, usage=False, quota=False ): + """ + Return the percentage of any storage quota applicable to the user/transaction. + """ + # if trans passed, use it to get the user, history (instead of/override vals passed) if trans: user = trans.user history = trans.history + # if quota wasn't passed, attempt to get the quota if quota is False: quota = self.get_quota( user ) + # return none if no applicable quotas or quotas disabled if quota is None: return None + # get the usage, if it wasn't passed if usage is False: usage = self.get_usage( trans, user, history ) - percent = int( float( usage ) / quota * 100 ) - if percent > 100: - percent = 100 - return percent + return min( ( int( float( usage ) / quota * 100 ), 100 ) ) + def set_entity_quota_associations( self, quotas=[], users=[], groups=[], delete_existing_assocs=True ): for quota in quotas: if delete_existing_assocs: https://bitbucket.org/galaxy/galaxy-central/changeset/c57bcb1d78fc/ changeset: c57bcb1d78fc user: carlfeberhard date: 2012-11-05 22:49:58 summary: api/history_contents: allow anon-user to query own, current history; improve doc string affected #: 1 file diff -r 613df4d4ca7d41c240311991022148dedb2e620d -r c57bcb1d78fcb49e8b856a0c4e28d59626e3403f lib/galaxy/webapps/galaxy/api/history_contents.py --- a/lib/galaxy/webapps/galaxy/api/history_contents.py +++ b/lib/galaxy/webapps/galaxy/api/history_contents.py @@ -26,51 +26,100 @@ def index( self, trans, history_id, ids=None, **kwd ): """ GET /api/histories/{encoded_history_id}/contents - Displays a collection (list) of history contents + Displays a collection (list) of history contents (HDAs) + + :param history_id: an encoded id string of the `History` to search + :param ids: (optional) a comma separated list of encoded `HDA` ids + + If Ids is not given, index returns a list of *summary* json objects for + every `HDA` associated with the given `history_id`. + See _summary_hda_dict. + + If ids is given, index returns a *more complete* json object for each + HDA in the ids list. + + Note: Anonymous users are allowed to get their current history contents + (generally useful for browser UI access of the api) """ rval = [] try: - history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True ) + # get the history, if anon user and requesting current history - allow it + if( ( trans.user == None ) + and ( history_id == trans.security.encode_id( trans.history.id ) ) ): + #TODO:?? is secure? + history = trans.history - # if no ids passed, return a _SUMMARY_ of _all_ datasets in the history - if not ids: - for dataset in history.datasets: - api_type = "file" - encoded_id = trans.security.encode_id( dataset.id ) - # build the summary - rval.append( dict( id = encoded_id, - type = api_type, - name = dataset.name, - url = url_for( 'history_content', history_id=history_id, id=encoded_id, ) ) ) + # otherwise, check permissions for the history first + else: + history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True ) - # if ids, return _FULL_ data (as show) for each id passed - #NOTE: this might not be the best form (passing all info), - # but we(I?) need an hda collection with full data somewhere + # build the return hda data list + if ids: + # if ids, return _FULL_ data (as show) for each id passed + #NOTE: this might not be the best form (passing all info), + # but we(I?) need an hda collection with full data somewhere + ids = ids.split( ',' ) + for hda in history.datasets: + if trans.security.encode_id( hda.id ) in ids: + rval.append( get_hda_dict( trans, history, hda, for_editing=True ) ) + else: - ids = ids.split( ',' ) - for id in ids: - hda = self.get_history_dataset_association( trans, history, id, - check_ownership=True, check_accessible=True, check_state=False ) - rval.append( get_hda_dict( trans, history, hda, for_editing=True ) ) + # if no ids passed, return a _SUMMARY_ of _all_ datasets in the history + for hda in history.datasets: + rval.append( self._summary_hda_dict( trans, history_id, hda ) ) except Exception, e: rval = "Error in history API at listing contents" - log.error( rval + ": %s" % str(e) ) + log.error( rval + ": %s, %s" % ( type( e ), str( e ) ) ) trans.response.status = 500 + return rval + def _summary_hda_dict( self, trans, history_id, hda ): + """ + Returns a dictionary based on the HDA in .. _summary form:: + { + 'id' : < the encoded dataset id >, + 'name' : < currently only returns 'file' >, + 'type' : < name of the dataset >, + 'url' : < api url to retrieve this datasets full data >, + } + """ + api_type = "file" + encoded_id = trans.security.encode_id( hda.id ) + return { + 'id' : encoded_id, + 'name' : hda.name, + 'type' : api_type, + 'url' : url_for( 'history_content', history_id=history_id, id=encoded_id, ), + } + @web.expose_api def show( self, trans, id, history_id, **kwd ): """ GET /api/histories/{encoded_history_id}/contents/{encoded_content_id} Displays information about a history content (dataset). + + """ hda_dict = {} try: - history = self.get_history( trans, history_id, - check_ownership=True, check_accessible=True, deleted=False ) - hda = self.get_history_dataset_association( trans, history, id, - check_ownership=True, check_accessible=True ) + # for anon users: + #TODO: check login_required? + #TODO: this isn't actually most_recently_used (as defined in histories) + if( ( trans.user == None ) + and ( history_id == trans.security.encode_id( trans.history.id ) ) ): + history = trans.history + #TODO: dataset/hda by id (from history) OR check_ownership for anon user + hda = self.get_history_dataset_association( trans, history, id, + check_ownership=False, check_accessible=True ) + + else: + history = self.get_history( trans, history_id, + check_ownership=True, check_accessible=True, deleted=False ) + hda = self.get_history_dataset_association( trans, history, id, + check_ownership=True, check_accessible=True ) + hda_dict = get_hda_dict( trans, history, hda, for_editing=True ) except Exception, e: @@ -112,7 +161,7 @@ return "Not implemented." -# move these into model?? hell if I know...doesn't seem like the urls should go here +#TODO: move these into model def get_hda_dict( trans, history, hda, for_editing ): hda_dict = hda.get_api_value( view='element' ) @@ -146,13 +195,14 @@ if meta_files: hda_dict[ 'meta_files' ] = meta_files - #hda_dict[ 'display_apps' ] = get_display_apps( trans, hda ) + hda_dict[ 'display_apps' ] = get_display_apps( trans, hda ) hda_dict[ 'visualizations' ] = hda.get_visualizations() hda_dict[ 'peek' ] = to_unicode( hda.display_peek() ) return hda_dict def get_display_apps( trans, hda ): + #TODO: make more straightforward (somehow) display_apps = [] def get_display_app_url( display_app_link, hda, trans ): @@ -165,7 +215,6 @@ app_name=urllib.quote_plus( display_app_link.display_application.id ), link_name=urllib.quote_plus( display_app_link.id ) ) - for display_app in hda.get_display_applications( trans ).itervalues(): app_links = [] for display_app_link in display_app.links.itervalues(): https://bitbucket.org/galaxy/galaxy-central/changeset/7942fb2a20c5/ changeset: 7942fb2a20c5 user: carlfeberhard date: 2012-11-05 22:51:58 summary: api/users.show: added current as viable id to display json for trans.user; show, index: added key 'auota_percent' to json, returns null if no quota on user, percent otherwise affected #: 1 file diff -r c57bcb1d78fcb49e8b856a0c4e28d59626e3403f -r 7942fb2a20c5bd4b66f02e5f58335fc1b74cb040 lib/galaxy/webapps/galaxy/api/users.py --- a/lib/galaxy/webapps/galaxy/api/users.py +++ b/lib/galaxy/webapps/galaxy/api/users.py @@ -26,6 +26,7 @@ # only admins can see deleted users if not trans.user_is_admin(): return [] + else: route = 'user' query = query.filter( trans.app.model.User.table.c.deleted == False ) @@ -33,9 +34,13 @@ if not trans.user_is_admin(): item = trans.user.get_api_value( value_mapper={ 'id': trans.security.encode_id } ) item['url'] = url_for( route, id=item['id'] ) + item['quota_percent'] = trans.app.quota_agent.get_percent( trans=trans ) return [item] + for user in query: item = user.get_api_value( value_mapper={ 'id': trans.security.encode_id } ) + #TODO: move into api_values + item['quota_percent'] = trans.app.quota_agent.get_percent( trans=trans ) item['url'] = url_for( route, id=item['id'] ) rval.append( item ) return rval @@ -50,20 +55,36 @@ """ deleted = util.string_as_bool( deleted ) try: + # user is requesting data about themselves if id == "current": - user = trans.user + # ...and is anonymous - return usage and quota (if any) + if not trans.user: + item = self.anon_user_api_value( trans ) + return item + + # ...and is logged in - return full + else: + user = trans.user else: user = self.get_user( trans, id, deleted=deleted ) + + # check that the user is requesting themselves (and they aren't del'd) unless admin if not trans.user_is_admin(): assert trans.user == user assert not user.deleted + except: if trans.user_is_admin(): raise else: raise HTTPBadRequest( detail='Invalid user id ( %s ) specified' % id ) + item = user.get_api_value( view='element', value_mapper={ 'id': trans.security.encode_id, 'total_disk_usage': float } ) + #TODO: move into api_values (needs trans, tho - can we do that with api_keys/@property??) + #TODO: works with other users (from admin)?? + item['quota_percent'] = trans.app.quota_agent.get_percent( trans=trans ) + return item @web.expose_api @@ -93,3 +114,16 @@ @web.expose_api def undelete( self, trans, **kwd ): raise HTTPNotImplemented() + + #TODO: move to more basal, common resource than this + def anon_user_api_value( self, trans ): + """ + Returns data for an anonymous user, truncated to only usage and quota_percent + """ + usage = trans.app.quota_agent.get_usage( trans ) + percent = trans.app.quota_agent.get_percent( trans=trans, usage=usage ) + return { + 'total_disk_usage' : int( usage ), + 'nice_total_disk_usage' : util.nice_size( usage ), + 'quota_percent' : percent + } https://bitbucket.org/galaxy/galaxy-central/changeset/24ce9866b5af/ changeset: 24ce9866b5af user: carlfeberhard date: 2012-11-05 22:56:28 summary: added user backbone model; added quota meter view on user model affected #: 5 files diff -r 7942fb2a20c5bd4b66f02e5f58335fc1b74cb040 -r 24ce9866b5af82325d2047b5d6c346ceae64c041 static/scripts/mvc/user/user-model.js --- /dev/null +++ b/static/scripts/mvc/user/user-model.js @@ -0,0 +1,56 @@ +var User = BaseModel.extend( LoggableMixin ).extend({ + //logger : console, + + defaults : { + id : null, + username : "(anonymous user)", + email : "", + total_disk_usage : 0, + nice_total_disk_usage : "0 bytes" + }, + + initialize : function( data ){ + this.log( 'User.initialize:', data ); + + this.on( 'loaded', function( model, resp ){ this.log( this + ' has loaded:', model, resp ); }); + this.on( 'change', function( model, data ){ this.log( this + ' has changed:', model, data.changes ); }); + }, + + urlRoot : 'api/users', + loadFromApi : function( idOrCurrent, options ){ + idOrCurrent = idOrCurrent || User.CURRENT_ID_STR; + options = options || {}; + var model = this, + userFn = options.success; + options.success = function( model, response ){ + model.trigger( 'loaded', model, response ); + if( userFn ){ userFn( model, response ); } + }; + if( idOrCurrent === User.CURRENT_ID_STR ){ + options.url = this.urlRoot + '/' + User.CURRENT_ID_STR; + } + return BaseModel.prototype.fetch.call( this, options ); + }, + + toString : function(){ + var userInfo = [ this.get( 'username' ) ]; + if( this.get( 'id' ) ){ + userInfo.unshift( this.get( 'id' ) ); + userInfo.push( this.get( 'email' ) ); + } + return 'User(' + userInfo.join( ':' ) + ')'; + } +}); +User.CURRENT_ID_STR = 'current'; + +User.getCurrentUserFromApi = function( options ){ + var currentUser = new User(); + currentUser.loadFromApi( User.CURRENT_ID_STR, options ); + return currentUser; +}; + +var UserCollection = Backbone.Collection.extend( LoggableMixin ).extend({ + model : User, + logger : console, + urlRoot : 'api/users' +}); diff -r 7942fb2a20c5bd4b66f02e5f58335fc1b74cb040 -r 24ce9866b5af82325d2047b5d6c346ceae64c041 static/scripts/mvc/user/user-quotameter.js --- /dev/null +++ b/static/scripts/mvc/user/user-quotameter.js @@ -0,0 +1,118 @@ +// strange view that spans two frames: renders to two separate elements based on a User's disk usage: +// a quota/usage bar (curr. masthead), and +// an over-quota message (curr. history panel) + +// for now, keep the view in the history panel (where the message is), but render ALSO to the masthead + +var UserQuotaMeter = BaseView.extend( LoggableMixin ).extend({ + logger : console, + + options : { + warnAtPercent : 85, + errorAtPercent : 100, + + // the quota/usage bar is in the masthead + meterDocument : window.top.document, + containerSelector : '.quota-meter-container', + meterSelector : '#quota-meter', + barSelector : '#quota-meter-bar', + textSelector : '#quota-meter-text', + + // the quota message currently displays in the history panel + msgDocument : ( top.frames.galaxy_history )?( top.frames.galaxy_history.document ) + :( top.document ), + msgSelector : '#quota-message-container', + + warnClass : 'quota-meter-bar-warn', + errorClass : 'quota-meter-bar-error', + usageTemplate : 'Using <%= nice_total_disk_usage %>', + quotaTemplate : 'Using <%= quota_percent %>%', + meterTemplate : '', // see where I'm going? + animationSpeed : 'fast' + }, + + initialize : function( options ){ + this.log( this + '.initialize:', options ); + + _.extend( this.options, options ); + + //this.bind( 'all', function( event, data ){ this.log( this + ' event:', event, data ); }, this ); + this.model.bind( 'change:quota_percent change:total_disk_usage', this.render, this ); + }, + + update : function( options ){ + this.log( this + ' updating user data...', options ); + this.model.loadFromApi( this.model.get( 'id' ), options ); + return this; + }, + + isOverQuota : function(){ + return ( this.model.get( 'quota_percent' ) !== null + && this.model.get( 'quota_percent' ) >= this.options.errorAtPercent ); + }, + + _render_quota : function(){ + var modelJson = this.model.toJSON(), + //prevPercent = this.model.previous( 'quota_percent' ), + percent = modelJson.quota_percent, + meter = $( UserQuotaMeter.templates.quota( modelJson ) ); + //this.log( this + '.rendering quota, percent:', percent, 'meter:', meter ); + + // OVER QUOTA: color the quota bar and show the quota error message + if( this.isOverQuota() ){ + //this.log( '\t over quota' ); + meter.addClass( 'progress-danger' ); + meter.find( '#quota-meter-text' ).css( 'color', 'white' ); + //TODO: only trigger event if state has changed + this.trigger( 'quota:over', modelJson ); + + // APPROACHING QUOTA: color the quota bar + } else if( percent >= this.options.warnAtPercent ){ + //this.log( '\t approaching quota' ); + meter.addClass( 'progress-warning' ); + //TODO: only trigger event if state has changed + this.trigger( 'quota:under quota:under:approaching', modelJson ); + + // otherwise, hide/don't use the msg box + } else { + meter.addClass( 'progress-success' ); + //TODO: only trigger event if state has changed + this.trigger( 'quota:under quota:under:ok', modelJson ); + } + return meter; + }, + + _render_usage : function(){ + var usage = $( UserQuotaMeter.templates.usage( this.model.toJSON() ) ); + this.log( this + '.rendering usage:', usage ); + return usage; + }, + + render : function(){ + //this.log( this + '.rendering' ); + var meterHtml = null; + + // no quota on server ('quota_percent' === null (can be valid at 0)), show usage instead + this.log( this + '.model.quota_percent:', this.model.get( 'quota_percent' ) ); + if( ( this.model.get( 'quota_percent' ) === null ) + || ( this.model.get( 'quota_percent' ) === undefined ) ){ + meterHtml = this._render_usage(); + + // otherwise, render percent of quota (and warning, error) + } else { + meterHtml = this._render_quota(); + } + + this.$el.html( meterHtml ); + //this.log( this + '.$el:', this.$el ); + return this; + }, + + toString : function(){ + return 'UserQuotaMeter(' + this.model + ')'; + } +}); +UserQuotaMeter.templates = { + quota : Handlebars.templates[ 'template-user-quotaMeter-quota' ], + usage : Handlebars.templates[ 'template-user-quotaMeter-usage' ] +}; diff -r 7942fb2a20c5bd4b66f02e5f58335fc1b74cb040 -r 24ce9866b5af82325d2047b5d6c346ceae64c041 static/scripts/templates/compiled/template-user-quotaMeter-quota.js --- /dev/null +++ b/static/scripts/templates/compiled/template-user-quotaMeter-quota.js @@ -0,0 +1,19 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['template-user-quotaMeter-quota'] = template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; + var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression; + + + buffer += "<div id=\"quota-meter\" class=\"quota-meter progress\">\n <div id=\"quota-meter-bar\" class=\"quota-meter-bar bar\" style=\"width: "; + foundHelper = helpers.quota_percent; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.quota_percent; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "%\"></div>\n "; + buffer += "\n <div id=\"quota-meter-text\" class=\"quota-meter-text\"style=\"top: 6px\">\n Using "; + foundHelper = helpers.quota_percent; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.quota_percent; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "%\n </div>\n</div>"; + return buffer;}); +})(); \ No newline at end of file diff -r 7942fb2a20c5bd4b66f02e5f58335fc1b74cb040 -r 24ce9866b5af82325d2047b5d6c346ceae64c041 static/scripts/templates/compiled/template-user-quotaMeter-usage.js --- /dev/null +++ b/static/scripts/templates/compiled/template-user-quotaMeter-usage.js @@ -0,0 +1,14 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['template-user-quotaMeter-usage'] = template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; + var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression; + + + buffer += "\n<div id=\"quota-meter\" class=\"quota-meter\" style=\"background-color: transparent\">\n <div id=\"quota-meter-text\" class=\"quota-meter-text\" style=\"top: 6px; color: white\">\n Using "; + foundHelper = helpers.nice_total_disk_usage; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.nice_total_disk_usage; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\n </div>\n</div>"; + return buffer;}); +})(); \ No newline at end of file diff -r 7942fb2a20c5bd4b66f02e5f58335fc1b74cb040 -r 24ce9866b5af82325d2047b5d6c346ceae64c041 static/scripts/templates/ui-templates.html --- /dev/null +++ b/static/scripts/templates/ui-templates.html @@ -0,0 +1,18 @@ +<script type="text/template" class="template-history" id="template-user-quotaMeter-quota"> +<div id="quota-meter" class="quota-meter progress"> + <div id="quota-meter-bar" class="quota-meter-bar bar" style="width: {{quota_percent}}%"></div> + {{! TODO: remove the hardcoded style }} + <div id="quota-meter-text" class="quota-meter-text"style="top: 6px"> + Using {{ quota_percent }}% + </div> +</div> +</script> + +<script type="text/template" class="template-history" id="template-user-quotaMeter-usage"> +{{! TODO: remove the hardcoded styles }} +<div id="quota-meter" class="quota-meter" style="background-color: transparent"> + <div id="quota-meter-text" class="quota-meter-text" style="top: 6px; color: white"> + Using {{ nice_total_disk_usage }} + </div> +</div> +</script> https://bitbucket.org/galaxy/galaxy-central/changeset/b996950ffcf2/ changeset: b996950ffcf2 user: carlfeberhard date: 2012-11-05 22:59:20 summary: (alt)history: incorporate quota meter; bugifxes affected #: 4 files diff -r 24ce9866b5af82325d2047b5d6c346ceae64c041 -r b996950ffcf2342f1211dc49e6ca75c4dfd7050f static/scripts/mvc/history.js --- a/static/scripts/mvc/history.js +++ b/static/scripts/mvc/history.js @@ -6,19 +6,44 @@ 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 - from mako template: + 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:final, 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) - BUG: anon user, broken updater (upload) - added check_state to UsesHistoryDatasetAssocMixin - BUG: anon user - BUG: historyItem, error'd ds show display, download? + ??: still happening? - from loadFromAPI: + 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:final' 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 @@ -40,16 +65,16 @@ HDACollection, meta_files, display_apps, etc. - break this file up - localize all - ?: render url templates on init or render? - ?: history, annotation won't accept unicode 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 @@ -62,6 +87,7 @@ 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 @@ -118,7 +144,7 @@ // based on trans.user (is_admin or security_agent.can_access_dataset( <user_roles>, hda.dataset )) accessible : false, - // this needs to be removed (it is a function of the view type (e.g. HDAForEditingView)) + //TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView)) for_editing : true }, @@ -139,8 +165,14 @@ this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE ); } - this.on( 'change', function( event, x, y, z ){ - this.log( this + ' has changed:', event, x, y, z ); + //this.on( 'change', function( currModel, changedList ){ + // this.log( this + ' has changed:', currModel, changedList ); + //}); + this.on( 'change:state', function( currModel, newState ){ + this.log( this + ' has changed state:', currModel, newState ); + if( this.inFinalState() ){ + this.trigger( 'state:final', currModel, newState, this.previous( 'state' ) ); + } }); }, @@ -148,15 +180,6 @@ return ( this.get( 'deleted' ) || this.get( 'purged' ) ); }, - // roughly can_edit from history_common.mako - not deleted or purged = editable - isEditable : function(){ - return ( - //this.get( 'for_editing' ) - //&& !( this.get( 'deleted' ) || this.get( 'purged' ) )?? - !this.isDeletedOrPurged() - ); - }, - // 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 ){ @@ -174,11 +197,15 @@ // 'final' states are states where no processing (for the ds) is left to do on the server inFinalState : function(){ + var state = this.get( 'state' ); return ( - ( this.get( 'state' ) === HistoryDatasetAssociation.STATES.OK ) - || ( this.get( 'state' ) === HistoryDatasetAssociation.STATES.FAILED_METADATA ) - || ( this.get( 'state' ) === HistoryDatasetAssociation.STATES.EMPTY ) - || ( this.get( 'state' ) === HistoryDatasetAssociation.STATES.ERROR ) + ( 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 ) ); }, @@ -199,24 +226,32 @@ //------------------------------------------------------------------------------ HistoryDatasetAssociation.STATES = { - NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states - NEW : 'new', UPLOAD : 'upload', QUEUED : 'queued', RUNNING : 'running', + SETTING_METADATA : 'setting_metadata', + + NEW : 'new', OK : 'ok', EMPTY : 'empty', - ERROR : 'error', + + FAILED_METADATA : 'failed_metadata', + NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states DISCARDED : 'discarded', - SETTING_METADATA : 'setting_metadata', - FAILED_METADATA : 'failed_metadata' + ERROR : 'error' }; //============================================================================== var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({ model : HistoryDatasetAssociation, - logger : console, + //logger : console, + + initialize : function(){ + //this.bind( 'all', function( x, y, z ){ + // console.info( this + '', x, y, z ); + //}); + }, // return the ids of every hda in this collection ids : function(){ @@ -225,9 +260,7 @@ // return an HDA collection containing every 'shown' hda based on show_deleted/hidden getVisible : function( show_deleted, show_hidden ){ - return new HDACollection( - this.filter( function( item ){ return item.isVisible( 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> ] @@ -243,6 +276,17 @@ return stateLists; }, + // returns the id of every hda still running (not in a final state) + running : function(){ + var idList = []; + this.each( function( item ){ + if( !item.inFinalState() ){ + idList.push( item.get( 'id' ) ); + } + }); + return idList; + }, + // update (fetch -> render) the hdas with the ids given update : function( ids ){ this.log( this + 'update:', ids ); @@ -307,13 +351,13 @@ this.checkForUpdates(); } - this.on( 'change', function( event, x, y, z ){ - this.log( this + ' has changed:', event, x, y, z ); - }); + //this.on( 'change', function( currModel, changedList ){ + // this.log( this + ' has changed:', currModel, changedList ); + //}); }, // get data via the api (alternative to sending options,hdas to initialize) - loadFromAPI : function( historyId, callback ){ + loadFromApi : function( historyId, callback ){ var history = this; // fetch the history AND the user (mainly to see if they're logged in at this point) @@ -353,43 +397,13 @@ // 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 - this.stateFromStateIds(); - if( ( this.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING ) - || ( this.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){ + // boiling it down on the client to running/not + if( this.hdas.running().length ){ this.stateUpdater(); } return this; }, - // sets history state based on current hdas' states - // ported from api/histories.traverse (with placement of error state changed) - stateFromStateIds : function(){ - var stateIdLists = this.hdas.getStateLists(); - this.attributes.state_ids = stateIdLists; - - //TODO: make this more concise - if( ( stateIdLists.running.length > 0 ) - || ( stateIdLists.upload.length > 0 ) - || ( stateIdLists.setting_metadata.length > 0 ) ){ - this.set( 'state', HistoryDatasetAssociation.STATES.RUNNING ); - - } else if( stateIdLists.queued.length > 0 ){ - this.set( 'state', HistoryDatasetAssociation.STATES.QUEUED ); - - } else if( ( stateIdLists.error.length > 0 ) - || ( stateIdLists.failed_metadata.length > 0 ) ){ - this.set( 'state', HistoryDatasetAssociation.STATES.ERROR ); - - } else if( stateIdLists.ok.length === this.hdas.length ){ - this.set( 'state', HistoryDatasetAssociation.STATES.OK ); - - } else { - throw( this + '.stateFromStateDetails: unable to determine ' - + 'history state from hda states: ' + this.get( 'state_ids' ) ); - } - 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(){ @@ -407,6 +421,7 @@ history.set( response ); history.log( 'current history state:', history.get( 'state' ), '(was)', oldState ); + //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 = []; @@ -451,7 +466,7 @@ // view for HistoryDatasetAssociation model above // uncomment this out see log messages - logger : console, + //logger : console, tagName : "div", className : "historyItemContainer", @@ -537,6 +552,9 @@ view.$el.append( itemWrapper ).fadeIn( 'fast', function(){ view.log( view + ' rendered:', view.$el ); view.trigger( 'rendered' ); + if( view.model.inFinalState() ){ + view.trigger( 'rendered:final' ); + } }); }); return this; @@ -697,7 +715,7 @@ var modelData = _.extend( this.model.toJSON(), { urls: this.urls } ); // if there's no dbkey and it's editable : pass a flag to the template to render a link to editing in the '?' if( this.model.get( 'metadata_dbkey' ) === '?' - && this.model.isEditable() ){ + && !this.model.isDeletedOrPurged() ){ _.extend( modelData, { dbkey_unknown_and_editable : true }); } return HDAView.templates.hdaSummary( modelData ); @@ -1290,7 +1308,7 @@ var HistoryView = BaseView.extend( LoggableMixin ).extend({ // uncomment this out see log messages - logger : console, + //logger : console, // direct attachment to existing element el : 'body.historyPage', @@ -1369,7 +1387,7 @@ // 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 ) ); - historyView.$el.find( '.tooltip' ).tooltip(); + newRender.find( '.tooltip' ).tooltip(); // render hda views (if any) if( !this.model.hdas.length @@ -1394,6 +1412,7 @@ //TODO: ideally, these would be set up before the fade in (can't because of async save text) historyView.setUpBehaviours(); + historyView.trigger( 'rendered' ); next(); }); @@ -1410,7 +1429,7 @@ visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden ); // only render the shown hdas - visibleHdas.each( function( hda ){ + _.each( visibleHdas, function( hda ){ var hdaId = hda.get( 'id' ), expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId ); historyView.hdaViews[ hdaId ] = new HDAView({ @@ -1428,21 +1447,28 @@ // set up HistoryView->HDAView listeners setUpHdaListeners : function( hdaView ){ - var history = this; + var historyView = this; // use storage to maintain a list of hdas whose bodies are expanded hdaView.bind( 'toggleBodyVisibility', function( id, visible ){ if( visible ){ - history.storage.get( 'expandedHdas' ).set( id, true ); + historyView.storage.get( 'expandedHdas' ).set( id, true ); } else { - history.storage.get( 'expandedHdas' ).deleteKey( id ); + historyView.storage.get( 'expandedHdas' ).deleteKey( id ); } }); + + // rendering listeners + //hdaView.bind( 'rendered', function(){}); + hdaView.bind( 'rendered:final', function(){ historyView.trigger( 'hda:rendered:final' ); }); }, // 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() { @@ -1463,6 +1489,20 @@ this.urls.annotate, "new_annotation", 18, true, 4 ); }, + //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' @@ -1470,7 +1510,7 @@ // collapse all hda bodies hideAllHdaBodies : function(){ - _.each( this.itemViews, function( item ){ + _.each( this.hdaViews, function( item ){ item.toggleBodyVisibility( null, false ); }); this.storage.set( 'expandedHdas', {} ); diff -r 24ce9866b5af82325d2047b5d6c346ceae64c041 -r b996950ffcf2342f1211dc49e6ca75c4dfd7050f static/scripts/templates/compiled/template-history-historyPanel.js --- a/static/scripts/templates/compiled/template-history-historyPanel.js +++ b/static/scripts/templates/compiled/template-history-historyPanel.js @@ -16,42 +16,52 @@ function program3(depth0,data) { + var buffer = "", stack1, foundHelper; + buffer += "\n <div id=\"history-name\" style=\"margin-right: 50px;\" class=\"tooltip\"\n title=\"You must be logged in to edit your history name\">"; + foundHelper = helpers.name; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "</div>\n "; + return buffer;} + +function program5(depth0,data) { + return "refresh";} -function program5(depth0,data) { +function program7(depth0,data) { return "collapse all";} -function program7(depth0,data) { +function program9(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\n <a id=\"history-tag\" title=\""; foundHelper = helpers.local; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(8, program8, data)}); } - else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(8, program8, data)}); } - if(stack1 || stack1 === 0) { buffer += stack1; } - buffer += "\"\n class=\"icon-button tags tooltip\" target=\"galaxy_main\" href=\"javascript:void(0)\"></a>\n <a id=\"history-annotate\" title=\""; - foundHelper = helpers.local; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(10, program10, data)}); } else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(10, program10, data)}); } if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\"\n class=\"icon-button tags tooltip\" target=\"galaxy_main\" href=\"javascript:void(0)\"></a>\n <a id=\"history-annotate\" title=\""; + foundHelper = helpers.local; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(12, program12, data)}); } + else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(12, program12, data)}); } + if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\"\n class=\"icon-button annotate tooltip\" target=\"galaxy_main\" href=\"javascript:void(0)\"></a>\n "; return buffer;} -function program8(depth0,data) { +function program10(depth0,data) { return "Edit history tags";} -function program10(depth0,data) { +function program12(depth0,data) { return "Edit history annotation";} -function program12(depth0,data) { +function program14(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\n <a href=\""; @@ -60,18 +70,18 @@ stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.local; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(13, program13, data)}); } + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(15, program15, data)}); } else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(13, program13, data)}); } + if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(15, program15, data)}); } if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "</a>\n "; return buffer;} -function program13(depth0,data) { +function program15(depth0,data) { return "hide deleted";} -function program15(depth0,data) { +function program17(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\n <a href=\""; @@ -80,52 +90,52 @@ stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.local; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(16, program16, data)}); } + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(18, program18, data)}); } else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(16, program16, data)}); } + if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(18, program18, data)}); } if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "</a>\n "; return buffer;} -function program16(depth0,data) { +function program18(depth0,data) { return "hide hidden";} -function program18(depth0,data) { +function program20(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\n"; foundHelper = helpers.warningmessagesmall; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(19, program19, data)}); } + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(21, program21, data)}); } else { stack1 = depth0.warningmessagesmall; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.warningmessagesmall) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(19, program19, data)}); } + if (!helpers.warningmessagesmall) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(21, program21, data)}); } if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n"; return buffer;} -function program19(depth0,data) { +function program21(depth0,data) { var stack1, foundHelper; foundHelper = helpers.local; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(20, program20, data)}); } + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(22, program22, data)}); } else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(20, program20, data)}); } + if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(22, program22, data)}); } if(stack1 || stack1 === 0) { return stack1; } else { return ''; }} -function program20(depth0,data) { +function program22(depth0,data) { return "You are currently viewing a deleted history!";} -function program22(depth0,data) { +function program24(depth0,data) { var buffer = "", stack1; buffer += "\n <div id=\"history-tag-area\" style=\"display: none\">\n <strong>Tags:</strong>\n <div class=\"tag-elt\"></div>\n </div>\n\n <div id=\"history-annotation-area\" style=\"display: none\">\n <strong>Annotation / Notes:</strong>\n <div id=\"history-annotation-container\">\n <div id=\"history-annotation\" class=\"tooltip editable-text\" title=\"Click to edit annotation\">\n "; stack1 = depth0.annotation; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(25, program25, data),fn:self.program(23, program23, data)}); + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(27, program27, data),fn:self.program(25, program25, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n </div>\n </div>\n </div>\n "; return buffer;} -function program23(depth0,data) { +function program25(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\n "; @@ -135,12 +145,12 @@ buffer += escapeExpression(stack1) + "\n "; return buffer;} -function program25(depth0,data) { +function program27(depth0,data) { return "\n <em>Describe or add notes to history</em>\n ";} -function program27(depth0,data) { +function program29(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\n<div id=\"message-container\">\n <div class=\""; @@ -154,11 +164,6 @@ buffer += escapeExpression(stack1) + "\n </div><br />\n</div>\n"; return buffer;} -function program29(depth0,data) { - - - return "\n <div id=\"quota-message\" class=\"errormessage\">\n You are over your disk quota. Tool execution is on hold until your disk usage drops below your allocated quota.\n </div>\n <br/>\n ";} - function program31(depth0,data) { @@ -171,13 +176,14 @@ else { stack1 = depth0.nice_size; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</div>\n "; stack1 = depth0.user; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)}); + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.email; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n </div> \n</div>\n<div style=\"clear: both;\"></div>\n\n<div id=\"top-links\" class=\"historyLinks\">\n <a title=\""; foundHelper = helpers.local; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(3, program3, data)}); } + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)}); } else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(3, program3, data)}); } + if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)}); } if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\" class=\"icon-button arrow-circle tooltip\" href=\""; stack1 = depth0.urls; @@ -185,42 +191,40 @@ stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "\"></a>\n <a title='"; foundHelper = helpers.local; - if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)}); } + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},inverse:self.noop,fn:self.program(7, program7, data)}); } else { stack1 = depth0.local; stack1 = typeof stack1 === functionType ? stack1() : stack1; } - if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)}); } + if (!helpers.local) { stack1 = blockHelperMissing.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(7, program7, data)}); } if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "' id=\"history-collapse-all\"\n class='icon-button toggle tooltip' href='javascript:void(0);'></a>\n <div style=\"width: 40px; float: right; white-space: nowrap;\">\n "; stack1 = depth0.user; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(7, program7, data)}); + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.email; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(9, program9, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n </div>\n</div>\n<div style=\"clear: both;\"></div>\n\n"; buffer += "\n<div class=\"historyLinks\">\n "; stack1 = depth0.show_deleted; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(12, program12, data)}); + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(14, program14, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n "; stack1 = depth0.show_hidden; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(15, program15, data)}); + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(17, program17, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n</div>\n\n"; stack1 = depth0.deleted; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(18, program18, data)}); + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(20, program20, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n\n"; buffer += "\n"; buffer += "\n<div style=\"margin: 0px 5px 10px 5px\">\n\n "; stack1 = depth0.user; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(22, program22, data)}); + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.email; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(24, program24, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n</div>\n\n"; stack1 = depth0.message; - stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(27, program27, data)}); - if(stack1 || stack1 === 0) { buffer += stack1; } - buffer += "\n\n<div id=\"quota-message-container\">\n "; - stack1 = depth0.over_quota; stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(29, program29, data)}); if(stack1 || stack1 === 0) { buffer += stack1; } - buffer += "\n</div>\n\n<div id=\""; + buffer += "\n\n<div id=\"quota-message-container\" style=\"display: none\">\n <div id=\"quota-message\" class=\"errormessage\">\n You are over your disk quota. Tool execution is on hold until your disk usage drops below your allocated quota.\n </div>\n</div>\n\n<div id=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } diff -r 24ce9866b5af82325d2047b5d6c346ceae64c041 -r b996950ffcf2342f1211dc49e6ca75c4dfd7050f static/scripts/templates/history-templates.html --- a/static/scripts/templates/history-templates.html +++ b/static/scripts/templates/history-templates.html @@ -110,9 +110,12 @@ <div id="history-name-container" style="position: relative;"> {{! TODO: factor out conditional css }} <div id="history-size" style="position: absolute; top: 3px; right: 0px;">{{nice_size}}</div> - {{#if user}} + {{#if user.email}} <div id="history-name" style="margin-right: 50px;" class="tooltip editable-text" title="Click to rename history">{{name}}</div> + {{else}} + <div id="history-name" style="margin-right: 50px;" class="tooltip" + title="You must be logged in to edit your history name">{{name}}</div> {{/if}} </div></div> @@ -123,7 +126,7 @@ <a title='{{#local}}collapse all{{/local}}' id="history-collapse-all" class='icon-button toggle tooltip' href='javascript:void(0);'></a><div style="width: 40px; float: right; white-space: nowrap;"> - {{#if user}} + {{#if user.email}} <a id="history-tag" title="{{#local}}Edit history tags{{/local}}" class="icon-button tags tooltip" target="galaxy_main" href="javascript:void(0)"></a><a id="history-annotate" title="{{#local}}Edit history annotation{{/local}}" @@ -151,7 +154,7 @@ {{! TODO: move inline styles out }} <div style="margin: 0px 5px 10px 5px"> - {{#if user}} + {{#if user.email}} <div id="history-tag-area" style="display: none"><strong>Tags:</strong><div class="tag-elt"></div> @@ -180,13 +183,10 @@ </div> {{/if}} -<div id="quota-message-container"> - {{#if over_quota}} +<div id="quota-message-container" style="display: none"><div id="quota-message" class="errormessage"> You are over your disk quota. Tool execution is on hold until your disk usage drops below your allocated quota. </div> - <br/> - {{/if}} </div><div id="{{id}}-datasets" class="history-datasets-list"></div> diff -r 24ce9866b5af82325d2047b5d6c346ceae64c041 -r b996950ffcf2342f1211dc49e6ca75c4dfd7050f templates/root/alternate_history.mako --- a/templates/root/alternate_history.mako +++ b/templates/root/alternate_history.mako @@ -90,10 +90,13 @@ hda_class_name = 'HistoryDatasetAssociation' encoded_id_template = '<%= id %>' - username_template = '<%= username %>' + #username_template = '<%= username %>' hda_ext_template = '<%= file_ext %>' meta_type_template = '<%= file_type %>' + display_app_name_template = '<%= name %>' + display_app_link_template = '<%= link %>' + url_dict = { # ................................................................ warning message links 'purge' : h.url_for( controller='dataset', action='purge', @@ -108,6 +111,7 @@ # ................................................................ title actions (display, edit, delete), 'display' : h.url_for( controller='dataset', action='display', dataset_id=encoded_id_template, preview=True, filename='' ), + #TODO: #'user_display_url' : h.url_for( controller='dataset', action='display_by_username_and_slug', # username=username_template, slug=encoded_id_template, filename='' ), 'edit' : h.url_for( controller='dataset', action='edit', @@ -141,8 +145,9 @@ item_class=hda_class_name, item_id=encoded_id_template ), }, 'annotation' : { + #TODO: needed? doesn't look like this is used (unless graceful degradation) #'annotate_url' : h.url_for( controller='dataset', action='annotate', - # id=encoded_id_template ), # doesn't look like this is used (unless graceful degradation) + # id=encoded_id_template ), 'get' : h.url_for( controller='dataset', action='get_annotation_async', id=encoded_id_template ), 'set' : h.url_for( controller='/dataset', action='annotate_async', @@ -167,33 +172,24 @@ <%def name="get_current_user()"><% - if not trans.user: - return '{}' - return trans.webapp.api_controllers[ 'users' ].show( - trans, trans.security.encode_id( trans.user.id ) ) + return trans.webapp.api_controllers[ 'users' ].show( trans, 'current' ) %></%def><%def name="get_hdas( history_id, hdas )"><% + #BUG: one inaccessible dataset will error entire list + if not hdas: return '[]' # rather just use the history.id (wo the datasets), but... - #BUG: one inaccessible dataset will error entire list return trans.webapp.api_controllers[ 'history_contents' ].index( + #trans, trans.security.encode_id( history_id ), trans, trans.security.encode_id( history_id ), ids=( ','.join([ trans.security.encode_id( hda.id ) for hda in hdas ]) ) ) %></%def> -<%def name="print_visualizations( datasets )"> -<% - for dataset in datasets: - print trans.security.encode_id( dataset.id ) - print dataset.get_visualizations() - -%> -</%def> ## ----------------------------------------------------------------------------- <%def name="javascripts()"> @@ -220,13 +216,17 @@ "template-history-annotationArea", "template-history-displayApps", - "template-history-historyPanel" + "template-history-historyPanel", + + "template-user-quotaMeter-quota", + "template-user-quotaMeter-usage" )} ##TODO: fix: curr hasta be _after_ h.templates - move somehow ${h.js( - "mvc/history" - ##"mvc/tags", "mvc/annotations" + "mvc/history", + ##"mvc/tags", "mvc/annotations", + "mvc/user/user-model", "mvc/user/user-quotameter" )} <script type="text/javascript"> @@ -243,31 +243,65 @@ 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' ); } } - // Navigate to a dataset. - console.debug( 'getting data' ); + + // load initial data in this page - since we're already sending it... 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 ); + console.debug( 'user:', user ); + console.debug( 'history:', history ); + console.debug( 'hdas:', hdas ); - // i don't like this, but user authentication changes views/behaviour + // i don't like this relationship, but user authentication changes views/behaviour history.user = user; 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 ); - var glx_history_view = new HistoryView({ model: glx_history, urlTemplates: galaxy_paths.attributes }).render(); + glx_history.logger = console; + var glx_history_view = new HistoryView({ + model: glx_history, + urlTemplates: galaxy_paths.attributes, + 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 ); + //glx_history.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 ); + //window.currUser.logger = console; + window.quotaMeter = new UserQuotaMeter({ model: currUser, el: $( top.document ).find( '.quota-meter-container' ) }); + window.quotaMeter.render(); + //window.quotaMeter.logger = console; + + // 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 ); + // 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(); + } + }); + + // update the quota meter when any hda reaches a 'final' state + //NOTE: use an anon function or update will be passed the hda and think it's the options param + glx_history_view.on( 'hda:rendered:final', function(){ window.quotaMeter.update({}) }, window.quotaMeter ) + + if( console && console.debug ){ window.user = top.user = user; window._history = top._history = history; 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.