commit/galaxy-central: carlfeberhard: History panel: remove from iframe, general improvements; pack scripts
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/4f1c25230dd0/ Changeset: 4f1c25230dd0 User: carlfeberhard Date: 2013-10-21 22:26:47 Summary: History panel: remove from iframe, general improvements; pack scripts Affected #: 28 files diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -633,11 +633,11 @@ # if a tool declares 'force_history_refresh' in its xml, when the hda -> ready, reload the history panel # expensive - if( ( hda.state in [ 'running', 'queued' ] ) - and ( hda.creating_job and hda.creating_job.tool_id ) ): - tool_used = trans.app.toolbox.get_tool( hda.creating_job.tool_id ) - if tool_used and tool_used.force_history_refresh: - hda_dict[ 'force_history_refresh' ] = True + #if( ( hda.state in [ 'running', 'queued' ] ) + #and ( hda.creating_job and hda.creating_job.tool_id ) ): + # tool_used = trans.app.toolbox.get_tool( hda.creating_job.tool_id ) + # if tool_used and tool_used.force_history_refresh: + # hda_dict[ 'force_history_refresh' ] = True return trans.security.encode_dict_ids( hda_dict ) @@ -653,13 +653,14 @@ 'accessible': False }) - def get_hda_dict_with_error( self, trans, hda, error_msg='' ): + def get_hda_dict_with_error( self, trans, hda=None, history_id=None, id=None, error_msg='Error' ): return trans.security.encode_dict_ids({ - 'id' : hda.id, - 'history_id': hda.history.id, - 'hid' : hda.hid, - 'name' : hda.name, - 'error' : error_msg + 'id' : hda.id if hda else id, + 'history_id': hda.history.id if hda else history_id, + 'hid' : hda.hid if hda else '(unknown)', + 'name' : hda.name if hda else '(unknown)', + 'error' : error_msg, + 'state' : trans.model.Dataset.states.NEW }) def get_display_apps( self, trans, hda ): diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a lib/galaxy/webapps/galaxy/api/histories.py --- a/lib/galaxy/webapps/galaxy/api/histories.py +++ b/lib/galaxy/webapps/galaxy/api/histories.py @@ -97,6 +97,9 @@ # Most recent active history for user sessions, not deleted history = trans.user.galaxy_sessions[0].histories[-1].history + elif history_id == "current": + history = trans.get_history( create=True ) + else: history = self.get_history( trans, history_id, check_ownership=False, check_accessible=True, deleted=deleted ) @@ -110,7 +113,7 @@ except Exception, e: msg = "Error in history API at showing history detail: %s" % ( str( e ) ) - log.exception( msg, exc_info=True ) + log.exception( e ) trans.response.status = 500 return msg @@ -174,7 +177,8 @@ trans.sa_session.add( new_history ) trans.sa_session.flush() - item = new_history.to_dict(view='element', value_mapper={'id':trans.security.encode_id}) + #item = new_history.to_dict(view='element', value_mapper={'id':trans.security.encode_id}) + item = self.get_history_dict( trans, new_history ) item['url'] = url_for( 'history', id=item['id'] ) #TODO: possibly default to True here - but favor explicit for now (and backwards compat) diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a 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 @@ -59,22 +59,21 @@ encoded_hda_id = trans.security.encode_id( hda.id ) if encoded_hda_id in ids: #TODO: share code with show - try: - hda_dict = self.get_hda_dict( trans, hda ) - hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda ) - hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda ) - rval.append( hda_dict ) - - except Exception, exc: - # don't fail entire list if hda err's, record and move on - log.error( "Error in history API at listing contents with history %s, hda %s: (%s) %s", - history_id, encoded_hda_id, type( exc ), str( exc ), exc_info=True ) - rval.append( self.get_hda_dict_with_error( trans, hda, str( exc ) ) ) + rval.append( self._detailed_hda_dict( trans, hda ) ) # if no ids passed, return a _SUMMARY_ of _all_ datasets in the history else: + details = kwd.get( 'details', None ) or [] + if details and details != 'all': + details = util.listify( details ) + for hda in history.datasets: - rval.append( self._summary_hda_dict( trans, history_id, hda ) ) + encoded_hda_id = trans.security.encode_id( hda.id ) + if( ( encoded_hda_id in details ) + or ( details == 'all' ) ): + rval.append( self._detailed_hda_dict( trans, hda ) ) + else: + rval.append( self._summary_hda_dict( trans, history_id, hda ) ) except Exception, e: # for errors that are not specific to one hda (history lookup or summary list) @@ -85,7 +84,7 @@ return rval #TODO: move to model or Mixin - def _summary_hda_dict( self, trans, history_id, hda ): + def _summary_hda_dict( self, trans, encoded_history_id, hda ): """ Returns a dictionary based on the HDA in summary form:: { @@ -99,11 +98,32 @@ encoded_id = trans.security.encode_id( hda.id ) return { 'id' : encoded_id, + 'history_id' : encoded_history_id, 'name' : hda.name, 'type' : api_type, - 'url' : url_for( 'history_content', history_id=history_id, id=encoded_id, ), + 'state' : hda.state, + 'deleted': hda.deleted, + 'visible': hda.visible, + 'hid' : hda.hid, + 'url' : url_for( 'history_content', history_id=encoded_history_id, id=encoded_id, ), } + def _detailed_hda_dict( self, trans, hda ): + """ + Detailed dictionary of hda values. + """ + try: + hda_dict = self.get_hda_dict( trans, hda ) + hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda ) + hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda ) + return hda_dict + + except Exception, exc: + # catch error here - returning a briefer hda_dict with an error attribute + log.exception( "Error in history API at listing contents with history %s, hda %s: (%s) %s", + hda.history_id, hda.id, type( exc ), str( exc ) ) + return self.get_hda_dict_with_error( trans, hda=hda, error_msg=str( exc ) ) + @web.expose_api_anonymous def show( self, trans, id, history_id, **kwd ): """ @@ -244,7 +264,7 @@ trans.response.status = 501 return - @web.expose_api + @web.expose_api_anonymous def update( self, trans, history_id, id, payload, **kwd ): """ update( self, trans, history_id, id, payload, **kwd ) @@ -269,10 +289,28 @@ #TODO: PUT /api/histories/{encoded_history_id} payload = { rating: rating } (w/ no security checks) changed = {} try: - hda = self.get_dataset( trans, id, - check_ownership=True, check_accessible=True, check_state=True ) - # validation handled here and some parsing, processing, and conversion - payload = self._validate_and_parse_update_payload( payload ) + # anon user + if trans.user == None: + if history_id != trans.security.encode_id( trans.history.id ): + trans.response.status = 401 + return { 'error': 'Anonymous users cannot edit histories other than their current history' } + + anon_allowed_payload = {} + if 'deleted' in payload: + anon_allowed_payload[ 'deleted' ] = payload[ 'deleted' ] + if 'visible' in payload: + anon_allowed_payload[ 'visible' ] = payload[ 'visible' ] + + payload = self._validate_and_parse_update_payload( anon_allowed_payload ) + hda = self.get_dataset( trans, id, check_ownership=False, check_accessible=False, check_state=True ) + if hda.history != trans.history: + trans.response.status = 401 + return { 'error': 'Anonymous users cannot edit datasets outside their current history' } + + else: + payload = self._validate_and_parse_update_payload( payload ) + hda = self.get_dataset( trans, id, check_ownership=True, check_accessible=True, check_state=True ) + # additional checks here (security, etc.) changed = self.set_hda_from_dict( trans, hda, payload ) diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a lib/galaxy/webapps/galaxy/controllers/root.py --- a/lib/galaxy/webapps/galaxy/controllers/root.py +++ b/lib/galaxy/webapps/galaxy/controllers/root.py @@ -5,7 +5,7 @@ import os import urllib -from paste.httpexceptions import HTTPNotFound +from paste.httpexceptions import HTTPNotFound, HTTPBadGateway from galaxy import web from galaxy.web import url_for @@ -30,7 +30,8 @@ @web.expose def index(self, trans, id=None, tool_id=None, mode=None, workflow_id=None, m_c=None, m_a=None, **kwd): - """Called on the root url to display the main Galaxy page. + """ + Called on the root url to display the main Galaxy page. """ return trans.fill_template( "root/index.mako", tool_id=tool_id, @@ -99,63 +100,75 @@ show_deleted=string_as_bool( show_deleted ), show_hidden=string_as_bool( show_hidden ) ) - @web.expose - def history( self, trans, as_xml=False, show_deleted=None, show_hidden=None, hda_id=None, **kwd ): - """Display the current history, creating a new history if necessary. - - NOTE: No longer accepts "id" or "template" options for security reasons. - """ - if as_xml: - return self.history_as_xml( trans, - show_deleted=string_as_bool( show_deleted ), show_hidden=string_as_bool( show_hidden ) ) - - # get all datasets server-side, client-side will get flags and render appropriately - show_deleted = string_as_bool_or_none( show_deleted ) - show_purged = show_deleted - show_hidden = string_as_bool_or_none( show_hidden ) - params = Params( kwd ) - message = params.get( 'message', '' ) - #TODO: ugh... - message = message if message != 'None' else '' - status = params.get( 'status', 'done' ) - - if trans.app.config.require_login and not trans.user: - return trans.fill_template( '/no_access.mako', message = 'Please log in to access Galaxy histories.' ) - - def err_msg( where=None ): - where = where if where else 'getting the history data from the server' - err_msg = ( 'An error occurred %s. ' - + 'Please contact a Galaxy administrator if the problem persists.' ) %( where ) - return err_msg, 'error' - + def _get_current_history_data( self, trans ): history_dictionary = {} hda_dictionaries = [] + try: history = trans.get_history( create=True ) hdas = self.get_history_datasets( trans, history, show_deleted=True, show_hidden=True, show_purged=True ) for hda in hdas: + hda_dict = {} try: - hda_dictionaries.append( self.get_hda_dict( trans, hda ) ) + hda_dict = self.get_hda_dict( trans, hda ) except Exception, exc: # don't fail entire list if hda err's, record and move on log.error( 'Error bootstrapping hda %d: %s', hda.id, str( exc ), exc_info=True ) - hda_dictionaries.append( self.get_hda_dict_with_error( trans, hda, str( exc ) ) ) + hda_dict = self.get_hda_dict_with_error( trans, hda, str( exc ) ) + + hda_dictionaries.append( hda_dict ) # re-use the hdas above to get the history data... history_dictionary = self.get_history_dict( trans, history, hda_dictionaries=hda_dictionaries ) except Exception, exc: user_id = str( trans.user.id ) if trans.user else '(anonymous)' - log.error( 'Error bootstrapping history for user %s: %s', user_id, str( exc ), exc_info=True ) - message, status = err_msg() + log.exception( 'Error bootstrapping history for user %s: %s', user_id, str( exc ) ) + message = ( 'An error occurred getting the history data from the server. ' + + 'Please contact a Galaxy administrator if the problem persists.' ) history_dictionary[ 'error' ] = message - return trans.stream_template_mako( "root/history.mako", - history_json = to_json_string( history_dictionary ), hda_json = to_json_string( hda_dictionaries ), - show_deleted=show_deleted, show_hidden=show_hidden, hda_id=hda_id, log=log, message=message, status=status ) + return { + 'history' : history_dictionary, + 'hdas' : hda_dictionaries + } + + @web.expose + def history( self, trans, as_xml=False, show_deleted=None, show_hidden=None, **kwd ): + """ + Display the current history in it's own page or as xml. + """ + if as_xml: + return self.history_as_xml( trans, + show_deleted=string_as_bool( show_deleted ), show_hidden=string_as_bool( show_hidden ) ) + + if trans.app.config.require_login and not trans.user: + return trans.fill_template( '/no_access.mako', message = 'Please log in to access Galaxy histories.' ) + + # get all datasets server-side, client-side will get flags and render appropriately + show_deleted = string_as_bool_or_none( show_deleted ) + show_purged = show_deleted + show_hidden = string_as_bool_or_none( show_hidden ) + + history_dictionary = {} + hda_dictionaries = [] + try: + history_data = self._get_current_history_data( trans ) + history_dictionary = history_data[ 'history' ] + hda_dictionaries = history_data[ 'hdas' ] + + except Exception, exc: + user_id = str( trans.user.id ) if trans.user else '(anonymous)' + log.exception( 'Error bootstrapping history for user %s: %s', user_id, str( exc ) ) + history_dictionary[ 'error' ] = ( 'An error occurred getting the history data from the server. ' + + 'Please contact a Galaxy administrator if the problem persists.' ) + + return trans.fill_template_mako( "root/history.mako", + history = history_dictionary, hdas = hda_dictionaries, + show_deleted=show_deleted, show_hidden=show_hidden ) ## ---- Dataset display / editing ---------------------------------------- @web.expose @@ -503,7 +516,22 @@ return rval @web.expose - def generate_error( self, trans ): + def generate_error( self, trans, code=500 ): """Raises an exception (debugging). """ + trans.response.status = code raise Exception( "Fake error!" ) + + @web.json + def generate_json_error( self, trans, code=500 ): + """Raises an exception (debugging). + """ + try: + code = int( code ) + except Exception, exc: + code = 500 + + if code == 502: + raise HTTPBadGateway() + trans.response.status = code + return { 'error': 'Fake error!' } diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a lib/galaxy/webapps/galaxy/controllers/tool_runner.py --- a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py +++ b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py @@ -325,4 +325,5 @@ <p><b>Please do not use your browser\'s "stop" or "reload" buttons until the upload is complete, or it may be interrupted.</b></p><p>You may safely continue to use Galaxy while the upload is in progress. Using "stop" and "reload" on pages other than Galaxy is also safe.</p> """ + #return trans.show_message( msg, refresh_frames=[ 'history' ] ) return trans.show_message( msg ) diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a static/scripts/mvc/base-mvc.js --- a/static/scripts/mvc/base-mvc.js +++ b/static/scripts/mvc/base-mvc.js @@ -7,31 +7,30 @@ name: null, hidden: false }, - + show: function() { this.set("hidden", false); }, - + hide: function() { this.set("hidden", true); }, - + is_visible: function() { return !this.attributes.hidden; } }); - /** * Base view that handles visibility based on model's hidden attribute. */ var BaseView = Backbone.View.extend({ - + initialize: function() { this.model.on("change:hidden", this.update_visible, this); this.update_visible(); }, - + update_visible: function() { if( this.model.attributes.hidden ){ this.$el.hide(); @@ -120,7 +119,8 @@ // ~constants for the current engine var STORAGE_ENGINE = sessionStorage, STORAGE_ENGINE_GETTER = function sessionStorageGet( key ){ - return JSON.parse( this.getItem( key ) ); + var item = this.getItem( key ); + return ( item !== null )?( JSON.parse( this.getItem( key ) ) ):( null ); }, STORAGE_ENGINE_SETTER = function sessionStorageSet( key, val ){ return this.setItem( key, JSON.stringify( val ) ); @@ -182,7 +182,7 @@ } //??: more readable to make another class? - var returnedStorage = {}; + var returnedStorage = {}, // attempt to get starting data from engine... data = STORAGE_ENGINE_GETTER.call( STORAGE_ENGINE, storageKey ); @@ -216,3 +216,63 @@ return returnedStorage; }; + + +//============================================================================== +function LoadingIndicator( $where ){ + var self = this, + $indicator; + + function setPosition(){ + // even tho pos is 'fixed' - give illusion of width 100% and margin by manually setting width, offset + var padding = 4, + width = $indicator.parent().width() || $where.width(), + offset = $indicator.parent().offset() || $where.offset(); + + $indicator.outerWidth( width - ( padding * 2 ) ); + // have to use css top, left and not offset (wont work when indicator is hidden) + $indicator.css({ top: offset.top + padding + 'px' , left: offset.left + padding + 'px' }); + } + + function render(){ + var $spinner = $( '<span class="fa-icon-spinner fa-icon-spin fa-icon-large"></span>') + .css({ 'color' : 'grey', 'font-size' : '16px' }); + var $message = $( '<i>loading...</i>' ) + .css({ 'color' : 'grey', 'margin-left' : '8px' }); + $indicator = $( '<div/>' ).addClass( 'loading-indicator' ) + .css({ + 'position' : 'fixed', + 'padding' : '4px', + 'text-align' : 'center', + 'background-color' : 'white', + 'opacity' : '0.85', + 'border-radius' : '3px' + }) + .append( $spinner, $message ) + //NOTE: insert as sibling to $where + .insertBefore( $where ); + setPosition(); + return $indicator.hide(); + } + + self.show = function( speed, callback ){ + speed = speed || 'fast'; + setPosition(); + $indicator.fadeIn( speed, callback ); + // not using full fadeOut allows using scroll to still work + //$whatIsLoading.fadeTo( speed, 0.0001, callback ); + return self; + }; + + self.hide = function( speed, callback ){ + speed = speed || 'fast'; + //$whatIsLoading.fadeTo( speed, 1.0, function(){ + // if( callback ){ callback(); } + //}); + $indicator.fadeOut( speed, callback ); + return self; + }; + $indicator = render(); + return self; +} + diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a static/scripts/mvc/dataset/hda-base.js --- a/static/scripts/mvc/dataset/hda-base.js +++ b/static/scripts/mvc/dataset/hda-base.js @@ -1,7 +1,6 @@ -//define([ -// "../mvc/base-mvc" -//], function(){ - +define([ + "mvc/dataset/hda-model" +], function( hdaModel ){ /* global Backbone, LoggableMixin, HistoryDatasetAssociation, HDABaseView */ //============================================================================== /** @class Read only view for HistoryDatasetAssociation. @@ -22,6 +21,8 @@ tagName : "div", className : "historyItemContainer", + fxSpeed : 'fast', + // ......................................................................... SET UP /** Set up the view, cache url templates, bind listeners * @param {Object} attributes @@ -38,33 +39,20 @@ this._render_showParamsButton ]; - // cache urlTemplates (gen. provided by GalaxyPaths) to urls - if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); } - this.urlTemplates = attributes.urlTemplates; - /** is the body of this hda view expanded/not. */ this.expanded = attributes.expanded || false; + this._setUpListeners(); + }, + _setUpListeners : function(){ // re-rendering on any model changes - this.model.bind( 'change', function( model, options ){ - // if more than the display apps have changed: render everything - var nonDisplayAppChanges = _.omit( this.model.changedAttributes(), 'display_apps', 'display_types' ); - if( _.keys( nonDisplayAppChanges ).length ){ - this.render(); + this.model.on( 'change', this.render, this ); - // if just the display links, and it's already expanded: render the links only - } else { - if( this.expanded ){ - this._render_displayApps(); - } - } - }, this ); - - //this.bind( 'all', function( event ){ + //this.on( 'all', function( event ){ // this.log( event ); //}, this ); }, - + // ......................................................................... RENDER MAIN /** Render this HDA, set up ui. * @fires rendered:ready when rendered and NO running HDAs @@ -86,9 +74,8 @@ // handle that here by removing previous view's tooltips this.$el.find("[title]").tooltip( "destroy" ); - /** web controller urls for functions relating to this hda. - * These are rendered from urlTemplates using the model data. */ - this.urls = this._renderUrls( this.urlTemplates, this.model.toJSON() ); + /** web controller urls for functions relating to this hda. */ + this.urls = this.model.urls(); itemWrapper .addClass( 'historyItemWrapper' ).addClass( 'historyItem' ) @@ -104,9 +91,9 @@ itemWrapper.append( this.body ); // transition... - this.$el.fadeOut( 'fast', function(){ + this.$el.fadeOut( this.fxSpeed, function(){ view.$el.children().remove(); - view.$el.append( itemWrapper ).fadeIn( 'fast', function(){ + view.$el.append( itemWrapper ).fadeIn( view.fxSpeed, function(){ view.log( view + ' rendered:', view.$el ); var renderedEventName = 'rendered'; @@ -121,55 +108,6 @@ return this; }, - /** render the urls for this hda using the model data and the url templates from initialize. - * @param {Object} urlTemplates a map (or nested map) of underscore templates (currently, anyhoo) - * @param {Object} modelJson data from the model - * @returns {Object} the templated urls - */ - _renderUrls : function( urlTemplates, modelJson ){ - var hdaView = this, - urls = {}; - _.each( urlTemplates, function( urlTemplateOrObj, urlKey ){ - // object == nested templates: recurse - if( _.isObject( urlTemplateOrObj ) ){ - urls[ urlKey ] = hdaView._renderUrls( urlTemplateOrObj, modelJson ); - - // string == template: - } else { - // meta_down load is a special case (see renderMetaDownloadUrls) - //TODO: should be a better (gen.) way to handle this case - if( urlKey === 'meta_download' ){ - urls[ urlKey ] = hdaView._renderMetaDownloadUrls( urlTemplateOrObj, modelJson ); - - } else { - try { - urls[ urlKey ] = _.template( urlTemplateOrObj, modelJson ); - } catch( Error ){ - throw( hdaView + '._renderUrls error: ' + Error + - '\n rendering:' + urlTemplateOrObj + - '\n with ' + JSON.stringify( modelJson ) ); - } - } - } - }); - return urls; - }, - - /** there can be more than one meta_file (e.g. bam index) to download, - * so return a list of url and file_type for each - * @param {Object} urlTemplate underscore templates for meta download urls - * @param {Object} modelJson data from the model - * @returns {Object} url and filetype for each meta file - */ - _renderMetaDownloadUrls : function( urlTemplate, modelJson ){ - return _.map( modelJson.meta_files, function( meta_file ){ - return { - url : _.template( urlTemplate, { id: modelJson.id, file_type: meta_file.file_type }), - file_type : meta_file.file_type - }; - }); - }, - /** set up js behaviors, event handlers for elements within the given container * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el) */ @@ -218,8 +156,8 @@ _render_displayButton : function(){ // don't show display if not viewable or not accessible // (do show if in error, running) - if( ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) - || ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.NEW ) + if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) + || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW ) || ( !this.model.get( 'accessible' ) ) ){ this.displayButton = null; return null; @@ -237,7 +175,7 @@ displayBtnData.title = _l( 'Cannot display datasets removed from disk' ); // disable if still uploading - } else if( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.UPLOAD ){ + } else if( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.UPLOAD ){ displayBtnData.enabled = false; displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' ); @@ -406,40 +344,40 @@ body.html( '' ); //TODO: not a fan of this dispatch switch( this.model.get( 'state' ) ){ - case HistoryDatasetAssociation.STATES.NEW : + case hdaModel.HistoryDatasetAssociation.STATES.NEW : this._render_body_new( body ); break; - case HistoryDatasetAssociation.STATES.NOT_VIEWABLE : + case hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE : this._render_body_not_viewable( body ); break; - case HistoryDatasetAssociation.STATES.UPLOAD : + case hdaModel.HistoryDatasetAssociation.STATES.UPLOAD : this._render_body_uploading( body ); break; - case HistoryDatasetAssociation.STATES.PAUSED: + case hdaModel.HistoryDatasetAssociation.STATES.PAUSED: this._render_body_paused( body ); break; - case HistoryDatasetAssociation.STATES.QUEUED : + case hdaModel.HistoryDatasetAssociation.STATES.QUEUED : this._render_body_queued( body ); break; - case HistoryDatasetAssociation.STATES.RUNNING : + case hdaModel.HistoryDatasetAssociation.STATES.RUNNING : this._render_body_running( body ); break; - case HistoryDatasetAssociation.STATES.ERROR : + case hdaModel.HistoryDatasetAssociation.STATES.ERROR : this._render_body_error( body ); break; - case HistoryDatasetAssociation.STATES.DISCARDED : + case hdaModel.HistoryDatasetAssociation.STATES.DISCARDED : this._render_body_discarded( body ); break; - case HistoryDatasetAssociation.STATES.SETTING_METADATA : + case hdaModel.HistoryDatasetAssociation.STATES.SETTING_METADATA : this._render_body_setting_metadata( body ); break; - case HistoryDatasetAssociation.STATES.EMPTY : + case hdaModel.HistoryDatasetAssociation.STATES.EMPTY : this._render_body_empty( body ); break; - case HistoryDatasetAssociation.STATES.FAILED_METADATA : + case hdaModel.HistoryDatasetAssociation.STATES.FAILED_METADATA : this._render_body_failed_metadata( body ); break; - case HistoryDatasetAssociation.STATES.OK : + case hdaModel.HistoryDatasetAssociation.STATES.OK : this._render_body_ok( body ); break; default: @@ -591,27 +529,44 @@ * @fires body-expanded when a body has been expanded * @fires body-collapsed when a body has been collapsed */ - toggleBodyVisibility : function( event, expanded ){ + toggleBodyVisibility : function( event, expand ){ var hdaView = this; - this.expanded = ( expanded === undefined )?( !this.body.is( ':visible' ) ):( expanded ); - //this.log( 'toggleBodyVisibility, expanded:', expanded, '$body:', $body ); + expand = ( expand === undefined )?( !this.body.is( ':visible' ) ):( expand ); + if( expand ){ + if( this.model.inReadyState() && !this.model.hasDetails() ){ + var xhr = this.model.fetch(); + xhr.done( function( model ){ + hdaView.expandBody(); + }); + } else { + this.expandBody(); + } + } else { + this.collapseBody(); + } + }, - if( this.expanded ){ - hdaView._render_body_html( hdaView.body ); - this.body.slideDown( 'fast', function(){ - hdaView.trigger( 'body-expanded', hdaView.model.get( 'id' ) ); - }); - } else { - this.body.slideUp( 'fast', function(){ - hdaView.trigger( 'body-collapsed', hdaView.model.get( 'id' ) ); - }); - } + expandBody : function(){ + var hdaView = this; + hdaView._render_body_html( hdaView.body ); + this.body.slideDown( hdaView.fxSpeed, function(){ + hdaView.expanded = true; + hdaView.trigger( 'body-expanded', hdaView.model.get( 'id' ) ); + }); + }, + + collapseBody : function(){ + var hdaView = this; + this.body.slideUp( hdaView.fxSpeed, function(){ + hdaView.expanded = false; + hdaView.trigger( 'body-collapsed', hdaView.model.get( 'id' ) ); + }); }, // ......................................................................... DELETION remove : function( callback ){ var hdaView = this; - this.$el.fadeOut( 'fast', function(){ + this.$el.fadeOut( hdaView.fxSpeed, function(){ hdaView.$el.remove(); hdaView.off(); if( callback ){ callback(); } @@ -638,6 +593,6 @@ }; //============================================================================== -//return { -// HDABaseView : HDABaseView, -//};}); +return { + HDABaseView : HDABaseView, +};}); diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a static/scripts/mvc/dataset/hda-edit.js --- a/static/scripts/mvc/dataset/hda-edit.js +++ b/static/scripts/mvc/dataset/hda-edit.js @@ -1,6 +1,7 @@ -//define([ -// "../mvc/base-mvc" -//], function(){ +define([ + "mvc/dataset/hda-model", + "mvc/dataset/hda-base" +], function( hdaModel, hdaBase ){ //============================================================================== /** @class Editing view for HistoryDatasetAssociation. * @name HDAEditView @@ -10,7 +11,7 @@ * @borrows LoggableMixin#log as #log * @constructs */ -var HDAEditView = HDABaseView.extend( LoggableMixin ).extend( +var HDAEditView = hdaBase.HDABaseView.extend( LoggableMixin ).extend( /** @lends HDAEditView.prototype */{ // ......................................................................... SET UP @@ -22,7 +23,8 @@ * @see HDABaseView#initialize */ initialize : function( attributes ){ - HDABaseView.prototype.initialize.call( this, attributes ); + hdaBase.HDABaseView.prototype.initialize.call( this, attributes ); + this.hasUser = attributes.hasUser; /** list of rendering functions for the default, primary icon-buttons. */ this.defaultPrimaryActionButtonRenderers = [ @@ -37,33 +39,8 @@ * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el) */ _setUpBehaviors : function( $container ){ - //TODO: ideally this would be a DELETE call to the api - // using purge async for now - HDABaseView.prototype._setUpBehaviors.call( this, $container ); - - // use purge_async with an ajax call - var hdaView = this, - purge_url = this.urls.purge, - purge_link = $container.find( '#historyItemPurger-' + this.model.get( 'id' ) ); - if( purge_link ){ - //TODO: remove href from template - purge_link.attr( 'href', [ "javascript", "void(0)" ].join( ':' ) ); - purge_link.click( function( event ){ - //TODO??: confirm? - var xhr = jQuery.ajax( purge_url ); - xhr.success( function( message, status, responseObj ){ - hdaView.model.set( 'purged', true ); - hdaView.trigger( 'purged', hdaView ); - }); - xhr.error( function( error, status, message ){ - //TODO: Exception messages are hidden within error page - //!NOTE: that includes the 'Removal of datasets by users is not allowed in this Galaxy instance.' - view.trigger( 'error', - hdaView, xhr, { url: purge_url }, _l( "Unable to purge this dataset" ) ); - }); - }); - } - //TODO: same with undelete_async + hdaBase.HDABaseView.prototype._setUpBehaviors.call( this, $container ); + //var hdaView = this; }, // ......................................................................... RENDER WARNINGS @@ -74,7 +51,7 @@ */ _render_warnings : function(){ // jQ errs on building dom with whitespace - if there are no messages, trim -> '' - return $( jQuery.trim( HDABaseView.templates.messages( + return $( jQuery.trim( hdaBase.HDABaseView.templates.messages( _.extend( this.model.toJSON(), { urls: this.urls } ) ))); }, @@ -101,9 +78,9 @@ // don't show edit while uploading, in-accessible // DO show if in error (ala previous history panel) //TODO??: not viewable/accessible are essentially the same (not viewable set from accessible) - if( ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.NEW ) - || ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.UPLOAD ) - || ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) + if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW ) + || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.UPLOAD ) + || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) || ( !this.model.get( 'accessible' ) ) ){ this.editButton = null; return null; @@ -138,44 +115,26 @@ _render_deleteButton : function(){ // don't show delete if... //TODO??: not viewable/accessible are essentially the same (not viewable set from accessible) - if( ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.NEW ) - || ( this.model.get( 'state' ) === HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) + if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW ) + || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) || ( !this.model.get( 'accessible' ) ) ){ this.deleteButton = null; return null; } var self = this, + id = 'historyItemDeleter-' + self.model.get( 'id' ), delete_url = self.urls[ 'delete' ], deleteBtnData = { - title : _l( 'Delete' ), - href : delete_url, - id : 'historyItemDeleter-' + this.model.get( 'id' ), - icon_class : 'delete', - on_click : function() { - // Delete the dataset on the server and update HDA + view depending on success/failure. - // FIXME: when HDA-delete is implemented in the API, can call set(), then save directly - // on the model. - $.ajax({ - url: delete_url, - type: 'POST', - error: function() { - // Something went wrong, so show HDA again. - // TODO: an error notification would be good. - self.trigger( 'error', self, null, null, { url: delete_url }, _l( 'deletion failed' ) ); - self.$el.show(); - }, - success: function() { - // FIXME: setting model attribute causes re-rendering, which is unnecessary. - //self.$el.remove(); - - self.model.set({ deleted: true }); - } - }); - - // Return false so that anchor action (page reload) does not happen. - //return false; - } + title : _l( 'Delete' ), + href : delete_url, + id : id, + icon_class : 'delete', + on_click : function() { + // ...bler... tooltips being left behind in DOM (hover out never called on deletion) + self.$el.find( '.menu-button.delete' ).trigger( 'mouseout' ); + self.model[ 'delete' ](); + } }; if( this.model.get( 'deleted' ) || this.model.get( 'purged' ) ){ deleteBtnData = { @@ -202,7 +161,7 @@ //TODO: use HDABaseView and select/replace base on this switch _.extend( modelData, { dbkey_unknown_and_editable : true }); } - return HDABaseView.templates.hdaSummary( modelData ); + return hdaBase.HDABaseView.templates.hdaSummary( modelData ); }, // ......................................................................... primary actions @@ -210,7 +169,7 @@ * @returns {jQuery} rendered DOM */ _render_errButton : function(){ - if( this.model.get( 'state' ) !== HistoryDatasetAssociation.STATES.ERROR ){ + if( this.model.get( 'state' ) !== hdaModel.HistoryDatasetAssociation.STATES.ERROR ){ this.errButton = null; return null; } @@ -378,8 +337,7 @@ //TODO: these should be a sub-MV _render_tagButton : function(){ //TODO: check for User - if( !( this.model.hasData() ) - || ( !this.urls.tags.get ) ){ + if( !this.hasUser || !this.urls.tags.get ){ this.tagButton = null; return null; } @@ -399,8 +357,7 @@ //TODO: these should be a sub-MV _render_annotateButton : function(){ //TODO: check for User - if( !( this.model.hasData() ) - || ( !this.urls.annotation.get ) ){ + if( !this.hasUser || !this.urls.annotation.get ){ this.annotateButton = null; return null; } @@ -417,8 +374,8 @@ /** Render area to display tags. * @returns {jQuery} rendered DOM */ - //TODO: into sub-MV - //TODO: check for User +//TODO: into sub-MV +//TODO: check for User _render_tagArea : function(){ if( !this.urls.tags.set ){ return null; } //TODO: move to mvc/tags.js @@ -430,8 +387,8 @@ /** Render area to display annotation. * @returns {jQuery} rendered DOM */ - //TODO: into sub-MV - //TODO: check for User +//TODO: into sub-MV +//TODO: check for User _render_annotationArea : function(){ if( !this.urls.annotation.get ){ return null; } //TODO: move to mvc/annotations.js @@ -447,7 +404,7 @@ * @see HDABaseView#_render_body_error */ _render_body_error : function( parent ){ - HDABaseView.prototype._render_body_error.call( this, parent ); + hdaBase.HDABaseView.prototype._render_body_error.call( this, parent ); var primaryActions = parent.find( '#primary-actions-' + this.model.get( 'id' ) ); primaryActions.prepend( this._render_errButton() ); }, @@ -498,11 +455,21 @@ /** event map */ events : { 'click .historyItemTitle' : 'toggleBodyVisibility', + 'click .historyItemUndelete' : function( ev ){ this.model.undelete(); return false; }, + 'click .historyItemUnhide' : function( ev ){ this.model.unhide(); return false; }, + 'click .historyItemPurge' : 'confirmPurge', + 'click a.icon-button.tags' : 'loadAndDisplayTags', 'click a.icon-button.annotate' : 'loadAndDisplayAnnotation' }, // ......................................................................... STATE CHANGES / MANIPULATION + confirmPurge : function _confirmPurge( ev ){ + //TODO: confirm dialog + this.model.purge({ url: this.urls.purge }); + return false; + }, + /** Find the tag area and, if initial: load the html (via ajax) for displaying them; otherwise, unhide/hide */ //TODO: into sub-MV @@ -523,30 +490,29 @@ url: this.urls.tags.get, error: function( xhr, status, error ){ view.log( "Tagging failed", xhr, status, error ); - view.trigger( 'error', view, xhr, { url: view.urls.tags.get }, _l( "Tagging failed" ) ); + view.trigger( 'error', view, xhr, {}, _l( "Tagging failed" ) ); }, success: function(tag_elt_html) { tagElt.html(tag_elt_html); tagElt.find("[title]").tooltip(); - tagArea.slideDown("fast"); + tagArea.slideDown( view.fxSpeed ); } }); } else { // Tag element is filled; show. - tagArea.slideDown("fast"); + tagArea.slideDown( view.fxSpeed ); } } else { // Hide. - tagArea.slideUp("fast"); + tagArea.slideUp( view.fxSpeed ); } return false; }, /** Find the annotation area and, if initial: load the html (via ajax) for displaying them; otherwise, unhide/hide */ - //TODO: into sub-MV loadAndDisplayAnnotation : function( event ){ - //TODO: this is a drop in from history.mako - should use MV as well +//TODO: this is a drop in from history.mako - should use MV as well this.log( this + '.loadAndDisplayAnnotation', event ); var view = this, annotationArea = this.$el.find( '.annotation-area' ), @@ -559,10 +525,9 @@ // Need to fill annotation element. $.ajax({ url: this.urls.annotation.get, - error: function( xhr, status, message ){ - view.log( "Annotation failed", xhr, status, message ); - view.trigger( 'error', - view, xhr, { url: view.urls.annotation.get }, _l( "Annotation failed" ) ); + error: function(){ + view.log( "Annotation failed", xhr, status, error ); + view.trigger( 'error', view, xhr, {}, _l( "Annotation failed" ) ); }, success: function( htmlFromAjax ){ if( htmlFromAjax === "" ){ @@ -576,16 +541,16 @@ setAnnotationUrl, "new_annotation", 18, true, 4 ); - annotationArea.slideDown("fast"); + annotationArea.slideDown( view.fxSpeed ); } }); } else { - annotationArea.slideDown("fast"); + annotationArea.slideDown( view.fxSpeed ); } } else { // Hide. - annotationArea.slideUp("fast"); + annotationArea.slideUp( view.fxSpeed ); } return false; }, @@ -712,6 +677,6 @@ //============================================================================== -//return { -// HDAView : HDAView, -//};}); +return { + HDAEditView : HDAEditView, +};}); diff -r 6882fa888d10b469524669325a547a2c0f6c7e41 -r 4f1c25230dd009efd1f04f8d7a74d83733f8b75a static/scripts/mvc/dataset/hda-model.js --- a/static/scripts/mvc/dataset/hda-model.js +++ b/static/scripts/mvc/dataset/hda-model.js @@ -1,6 +1,5 @@ -//define([ -// "../mvc/base-mvc" -//], function(){ +define([ +], function(){ //============================================================================== /** @class (HDA) model for a Galaxy dataset * related to a history. @@ -34,6 +33,13 @@ name : '(unnamed dataset)', // one of HistoryDatasetAssociation.STATES state : 'new', + + deleted : false, + visible : true, + + accessible : true, + purged : false, + // sniffed datatype (sam, tabular, bed, etc.) data_type : null, // size in bytes @@ -44,37 +50,67 @@ meta_files : [], misc_blurb : '', - misc_info : '', - - deleted : false, - purged : false, - visible : true, - accessible : true + misc_info : '' }, /** fetch location of this history in the api */ urlRoot: 'api/histories/', url : function(){ - //TODO: get this via url router return this.urlRoot + this.get( 'history_id' ) + '/contents/' + this.get( 'id' ); - //TODO: this breaks on save() }, + urls : function(){ + var id = this.get( 'id' ); + if( !id ){ return {}; } + var urls = { + 'delete' : '/datasets/' + id + '/delete_async', + 'purge' : '/datasets/' + id + '/purge_async', + 'unhide' : '/datasets/' + id + '/unhide', + 'undelete' : '/datasets/' + id + '/undelete', + + 'display' : '/datasets/' + id + '/display/?preview=True', + 'download' : '/datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ), + 'edit' : '/datasets/' + id + '/edit', + 'report_error': '/dataset/errors?id=' + id, + 'rerun' : '/tool_runner/rerun?id=' + id, + 'show_params': '/datasets/' + id + '/show_params', + 'visualization': '/visualization', + + 'annotation': { 'get': '/dataset/get_annotation_async?id=' + id, + 'set': '/dataset/annotate_async?id=' + id }, + 'tags' : { 'get': '/tag/get_tagging_elt_async?item_id=' + id + '&item_class=HistoryDatasetAssociation', + 'set': '/tag/retag?item_id=' + id + '&item_class=HistoryDatasetAssociation' } + }; + //'meta_download': '/dataset/get_metadata_file?hda_id=%3C%25%3D+id+%25%3E&metadata_name=%3C%25%3D+file_type+%25%3E', + var meta_files = this.get( 'meta_files' ); + if( meta_files ){ + urls.meta_download = _.map( meta_files, function( meta_file ){ + return { + //url : _.template( urlTemplate, { id: modelJson.id, file_type: meta_file.file_type }), + url : '/dataset/get_metadata_file?hda_id=' + id + '&metadata_name=' + meta_file.file_type, + file_type : meta_file.file_type + }; + }); + } + return urls; + }, + /** Set up the model, determine if accessible, bind listeners * @see Backbone.Model#initialize */ - //TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted - initialize : function(){ + initialize : function( data ){ this.log( this + '.initialize', this.attributes ); this.log( '\tparent history_id: ' + this.get( 'history_id' ) ); - // (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE //!! 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 ); } + this._setUpListeners(); + }, + + _setUpListeners : function(){ // 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 ); @@ -82,16 +118,9 @@ this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) ); } }); - - // 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 ); - //}); }, + // ........................................................................ common queries /** Is this hda deleted or purged? */ isDeletedOrPurged : function(){ @@ -117,24 +146,21 @@ return isVisible; }, + hidden : function(){ + return !this.get( 'visible' ); + }, + /** Is this HDA in a 'ready' state; where 'Ready' states are states where no * processing (for the ds) is left to do on the server. - * Currently: NEW, OK, EMPTY, FAILED_METADATA, NOT_VIEWABLE, DISCARDED, - * and ERROR */ inReadyState : function(){ - var state = this.get( 'state' ); - //TODO: to list inclusion test - //TODO: class level readyStates list - return ( - this.isDeletedOrPurged() - || ( 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 ) - ); + var ready = _.contains( HistoryDatasetAssociation.READY_STATES, this.get( 'state' ) ); + return ( this.isDeletedOrPurged() || ready ); + }, + + hasDetails : function(){ + //?? this may not be reliable + return _.has( this.attributes, 'genome_build' ); }, /** Convenience function to match hda.has_data. @@ -144,6 +170,64 @@ return ( this.get( 'file_size' ) > 0 ); }, + // ........................................................................ ajax + 'delete' : function _delete( options ){ + return this.save( { deleted: true }, options ); + }, + undelete : function _undelete( options ){ + return this.save( { deleted: false }, options ); + }, + + hide : function _hide( options ){ + return this.save( { visible: false }, options ); + }, + unhide : function _uhide( options ){ + return this.save( { visible: true }, options ); + }, + + purge : function _purge( options ){ + //TODO: ideally this would be a DELETE call to the api + // using purge async for now + var hda = this, + xhr = jQuery.ajax( options ); + xhr.done( function( message, status, responseObj ){ + hda.set( 'purged', true ); + }); + xhr.fail( function( xhr, status, message ){ + // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.' + // unbury and re-add to xhr + var error = _l( "Unable to purge this dataset" ); + var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users ' + + 'is not allowed in this Galaxy instance' ); + if( xhr.responseJSON && xhr.responseJSON.error ){ + error = xhr.responseJSON.error; + } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){ + error = messageBuriedInUnfortunatelyFormattedError; + } + xhr.responseText = error; + hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } ); + }); + }, + + // ........................................................................ sorting/filtering + searchKeys : [ + 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags' + ], + + search : function( searchFor ){ + var model = this; + searchFor = searchFor.toLowerCase(); + return _.filter( this.searchKeys, function( key ){ + var attr = model.get( key ); + return ( _.isString( attr ) && attr.toLowerCase().indexOf( searchFor ) !== -1 ); + }); + }, + + matches : function( matchesWhat ){ + return !!this.search( matchesWhat ).length; + }, + + // ........................................................................ misc /** String representation */ toString : function(){ @@ -165,8 +249,6 @@ UPLOAD : 'upload', /** the job that will produce the dataset queued in the runner */ QUEUED : 'queued', - /** the job that will produce the dataset paused */ - PAUSED : 'paused', /** the job that will produce the dataset is running */ RUNNING : 'running', /** metadata for the dataset is being discovered/set */ @@ -180,6 +262,8 @@ /** has successfully completed running */ OK : 'ok', + /** the job that will produce the dataset paused */ + PAUSED : 'paused', /** metadata discovery/setting failed or errored (but otherwise ok) */ FAILED_METADATA : 'failed_metadata', /** not accessible to the current user (i.e. due to permissions) */ @@ -190,6 +274,24 @@ ERROR : 'error' }; +HistoryDatasetAssociation.READY_STATES = [ + HistoryDatasetAssociation.STATES.NEW, + HistoryDatasetAssociation.STATES.OK, + HistoryDatasetAssociation.STATES.EMPTY, + HistoryDatasetAssociation.STATES.PAUSED, + HistoryDatasetAssociation.STATES.FAILED_METADATA, + HistoryDatasetAssociation.STATES.NOT_VIEWABLE, + HistoryDatasetAssociation.STATES.DISCARDED, + HistoryDatasetAssociation.STATES.ERROR +]; + +HistoryDatasetAssociation.NOT_READY_STATES = [ + HistoryDatasetAssociation.STATES.UPLOAD, + HistoryDatasetAssociation.STATES.QUEUED, + HistoryDatasetAssociation.STATES.RUNNING, + HistoryDatasetAssociation.STATES.SETTING_METADATA +]; + //============================================================================== /** @class Backbone collection of (HDA) models * @@ -204,83 +306,35 @@ ///** logger used to record this.log messages, commonly set to console */ //// comment this out to suppress log output //logger : console, + urlRoot : '/api/histories', + url : function(){ + return this.urlRoot + '/' + this.historyId + '/contents'; + }, /** Set up. * @see Backbone.Collection#initialize */ - initialize : function(){ - //this.bind( 'all', function( event ){ - // this.log( this + '', arguments ); - //}); + initialize : function( models, options ){ + options = options || {}; + this.historyId = options.historyId; + this._setUpListeners(); }, + _setUpListeners : function(){ + }, + + // ........................................................................ common queries /** Get the ids of every hda in this collection - * @returns array of encoded ids + * @returns array of encoded ids */ ids : function(){ return this.map( function( hda ){ return hda.id; }); }, - /** Get the hda with the given hid - * @param {Int} hid the hid to search for - * @returns {HistoryDatasetAssociation} the hda with the given hid or undefined if not found - */ - getByHid : function( hid ){ - return _.first( this.filter( function( hda ){ return hda.get( 'hid' ) === hid; }) ); - }, - - /** If the given hid is in the collection, return it's index. If not, return the insertion point it would need. - * NOTE: assumes hids are unique and valid - * @param {Int} hid the hid to find or create. If hid is 0, null, undefined: return the last hid + 1 - * @returns the collection index of the existing hda or an insertion point if it doesn't exist - */ - hidToCollectionIndex : function( hid ){ - // if the hid is 0, null, undefined: assume a request for a new hid (return the last index) - if( !hid ){ - return this.models.length; - } - - var endingIndex = this.models.length - 1; - //TODO: prob. more efficient to cycle backwards through these (assuming ordered by hid) - for( var i=endingIndex; i>=0; i-- ){ - var hdaHid = this.at( i ).get( 'hid' ); - //this.log( i, 'hdaHid:', hdaHid ); - if( hdaHid === hid ){ - //this.log( '\t match:', hdaHid, hid, ' returning:', i ); - return i; - } - if( hdaHid < hid ){ - //this.log( '\t past it, returning:', ( i + 1 ) ); - return i + 1; - } - } - return null; - }, - - /** Get every 'shown' hda in this collection based on show_deleted/hidden - * @param {Boolean} show_deleted are we showing deleted hdas? - * @param {Boolean} show_hidden are we showing hidden hdas? - * @returns array of hda models - * @see HistoryDatasetAssociation#isVisible - */ - getVisible : function( show_deleted, show_hidden ){ - return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); }); - }, - - /** For each possible hda state, get an array of all hda ids in that state - * @returns a map of states -> hda ids - * @see HistoryDatasetAssociation#STATES - */ - getStateLists : function(){ - var stateLists = {}; - _.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){ - stateLists[ state ] = []; + notReady : function(){ + return this.filter( function( hda ){ + return !hda.inReadyState(); }); - //NOTE: will err on unknown state - this.each( function( item ){ - stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) ); - }); - return stateLists; }, /** Get the id of every hda in this collection not in a 'ready' state (running). @@ -297,26 +351,55 @@ return idList; }, - /** Update (fetch) the data of the hdas with the given ids. - * @param {String[]} ids an array of hda ids to update - * @returns {HistoryDatasetAssociation[]} hda models that were updated - * @see HistoryDatasetAssociation#fetch + /** Get the hda with the given hid + * @param {Int} hid the hid to search for + * @returns {HistoryDatasetAssociation} the hda with the given hid or undefined if not found */ - update : function( ids ){ - this.log( this + 'update:', ids ); + getByHid : function( hid ){ + return _.first( this.filter( function( hda ){ return hda.get( 'hid' ) === hid; }) ); + }, - if( !( ids && ids.length ) ){ return []; } + /** Get every 'shown' hda in this collection based on show_deleted/hidden + * @param {Boolean} show_deleted are we showing deleted hdas? + * @param {Boolean} show_hidden are we showing hidden hdas? + * @returns array of hda models + * @see HistoryDatasetAssociation#isVisible + */ + getVisible : function( show_deleted, show_hidden ){ + return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); }); + }, - var collection = this, - updatedHdas = null; - _.each( ids, function( id, index ){ - var hda = collection.get( id ); - if( hda ){ - hda.fetch(); - updatedHdas.push( hda ); - } + // ........................................................................ ajax + fetchAllDetails : function(){ + return this.fetch({ data : { details : 'all' } }); + }, + + // ........................................................................ sorting/filtering + matches : function( matchesWhat ){ + return this.filter( function( hda ){ + return hda.matches( matchesWhat ); }); - return updatedHdas; + }, + + // ........................................................................ misc + set : function( models, options ){ + // arrrrrrrrrrrrrrrrrg... + // override to get a correct/smarter merge when incoming data is partial (e.g. stupid backbone) + // w/o this partial models from the server will fill in missing data with model defaults + // and overwrite existing data on the client + // see Backbone.Collection.set and _prepareModel + var collection = this; + models = _.map( models, function( model ){ + var existing = collection.get( model.id ); + if( !existing ){ return model; } + + // merge the models _BEFORE_ calling the superclass version + var merged = existing.toJSON(); + _.extend( merged, model ); + return merged; + }); + // now call superclass when the data is filled + Backbone.Collection.prototype.set.call( this, models, options ); }, /** String representation. */ @@ -326,7 +409,7 @@ }); //============================================================================== -//return { -// HistoryDatasetAssociation : HistoryDatasetAssociation, -// HDACollection : HDACollection, -//};}); +return { + HistoryDatasetAssociation : HistoryDatasetAssociation, + HDACollection : HDACollection +};}); This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
commits-noreply@bitbucket.org