1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/50c65739cd1a/ changeset: 50c65739cd1a user: carlfeberhard date: 2013-01-18 22:32:05 summary: api/history_contents, index: handle hda_dict building exceptions by appending an error-state hda_dict (instead of 'fail-on-first'); history panel: handle bootstrapped and ajax hda api exceptions more gracefully, show hda api exceptions as hdas in the error state. affected #: 4 files diff -r 34e42ae792320b1ee2471f11f341ac55445f52e0 -r 50c65739cd1af36f48672a980db378783dffcdd5 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 @@ -60,8 +60,20 @@ # 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 ) ) + #TODO: curr. ordered by history, change to order from ids list + encoded_hda_id = trans.security.encode_id( hda.id ) + if encoded_hda_id in ids: + #TODO: share code with show + try: + rval.append( get_hda_dict( trans, history, hda, for_editing=True ) ) + + except Exception, exc: + # don't fail entire list if hda err's, record and move on + # (making sure http recvr knows it's err'd) + trans.response.status = 500 + log.error( "Error in history API at listing contents " + + "with history %s, hda %s: %s", history_id, encoded_hda_id, str( exc ) ) + rval.append( self._exception_as_hda_dict( trans, encoded_hda_id, exc ) ) else: # if no ids passed, return a _SUMMARY_ of _all_ datasets in the history @@ -69,12 +81,14 @@ rval.append( self._summary_hda_dict( trans, history_id, hda ) ) except Exception, e: - rval = "Error in history API at listing contents" + # for errors that are not specific to one hda (history lookup or summary list) + rval = "Error in history API at listing contents: " + str( e ) log.error( rval + ": %s, %s" % ( type( e ), str( e ) ) ) trans.response.status = 500 return rval + #TODO: move to model or Mixin def _summary_hda_dict( self, trans, history_id, hda ): """ Returns a dictionary based on the HDA in .. _summary form:: @@ -94,6 +108,26 @@ 'url' : url_for( 'history_content', history_id=history_id, id=encoded_id, ), } + #TODO: move to model or Mixin + def _exception_as_hda_dict( self, trans, hda_id, exception ): + """ + Returns a dictionary for an HDA that raised an exception when it's + dictionary was being built. + { + 'id' : < the encoded dataset id >, + 'type' : < name of the dataset >, + 'url' : < api url to retrieve this datasets full data >, + } + """ + return { + 'id' : hda_id, + 'state' : trans.app.model.Dataset.states.ERROR, + 'visible' : True, + 'misc_info' : str( exception ), + 'misc_blurb': 'Failed to retrieve dataset information.', + 'error' : str( exception ) + } + @web.expose_api def show( self, trans, id, history_id, **kwd ): """ diff -r 34e42ae792320b1ee2471f11f341ac55445f52e0 -r 50c65739cd1af36f48672a980db378783dffcdd5 static/scripts/mvc/dataset/hda-base.js --- a/static/scripts/mvc/dataset/hda-base.js +++ b/static/scripts/mvc/dataset/hda-base.js @@ -445,7 +445,7 @@ if( !this.model.get( 'purged' ) ){ parent.append( $( '<div>' + this.model.get( 'misc_blurb' ) + '</div>' ) ); } - parent.append( ( _l( 'An error occurred running this job' ) + ': ' + parent.append( ( _l( 'An error occurred with this dataset' ) + ': ' + '<i>' + $.trim( this.model.get( 'misc_info' ) ) + '</i>' ) ); parent.append( this._render_primaryActionButtons( this.defaultPrimaryActionButtonRenderers.concat([ this._render_downloadButton ]) diff -r 34e42ae792320b1ee2471f11f341ac55445f52e0 -r 50c65739cd1af36f48672a980db378783dffcdd5 static/scripts/mvc/history/history-model.js --- a/static/scripts/mvc/history/history-model.js +++ b/static/scripts/mvc/history/history-model.js @@ -55,9 +55,17 @@ this.hdas = new HDACollection(); // if we've got hdas passed in the constructor, load them and set up updates if needed - if( initialHdas && initialHdas.length ){ - this.hdas.reset( initialHdas ); - this.checkForUpdates(); + if( initialHdas ){ + if( _.isArray( initialHdas ) ){ + this.hdas.reset( initialHdas ); + this.checkForUpdates(); + + // handle errors in initialHdas + //TODO: errors from the api shouldn't be plain strings... + //TODO: remove when mappers and hda_dict are unified (or move to alt history) + } else if( _.isString( initialHdas ) && ( initialHdas.match( /error/i ) ) ){ + alert( _l( 'Error loading bootstrapped history' ) + ':\n' + initialHdas ); + } } // events @@ -163,7 +171,7 @@ // send the changed ids (if any) to dataset collection to have them fetch their own model changes if( changedIds.length ){ - history.updateHdas( changedIds ); + history.fetchHdaUpdates( changedIds ); } // set up to keep pulling if this history in run/queue state @@ -193,16 +201,37 @@ * If it's not in the collection, addHdas will be used to create it. * @param {String[]} hdaIds an array of the encoded ids of the hdas to get from the server. */ - updateHdas : function( hdaIds ){ + fetchHdaUpdates : function( hdaIds ){ //TODO:?? move to collection? still need proper url var history = this; jQuery.ajax({ url : this.url() + '/contents?' + jQuery.param({ ids : hdaIds.join(',') }), + /** + * @inner + */ error : function( xhr, status, error ){ - var msg = 'ERROR updating hdas from api history contents:'; - history.log( msg, hdaIds, xhr, status, error ); - alert( msg + hdaIds.join(',') ); + if( ( xhr.readyState === 0 ) && ( xhr.status === 0 ) ){ return; } + + var errorJson = JSON.parse( xhr.responseText ); + if( _.isArray( errorJson ) ){ + + // just for debugging/reporting purposes + var grouped = _.groupBy( errorJson, function( hdaData ){ + if( _.has( hdaData, 'error' ) ){ return 'errored'; } + return 'ok'; + }); + history.log( 'fetched, errored datasets:', grouped.errored ); + + // the server already formats the error'd hdas in proper hda-model form + // so we're good to send these on + history.updateHdas( errorJson ); + + } else { + var msg = _l( 'ERROR updating hdas from api history contents' ) + ': '; + history.log( msg, hdaIds, xhr, status, error, errorJSON ); + alert( msg + hdaIds.join(',') ); + } }, /** when the proper models for the requested ids are returned, @@ -210,30 +239,41 @@ * @inner */ success : function( hdaDataList, status, xhr ){ - history.log( history + '.updateHdas, success:', hdaDataList, status, xhr ); - //TODO: compile new models to be added in one go - var hdasToAdd = []; - - _.each( hdaDataList, function( hdaData, index ){ - var existingModel = history.hdas.get( hdaData.id ); - // if this model exists already, update it - if( existingModel ){ - history.log( 'found existing model in list for id ' + hdaData.id + ', updating...:' ); - existingModel.set( hdaData ); - - // if this model is new and isn't in the hda collection, cache it to be created - } else { - history.log( 'NO existing model for id ' + hdaData.id + ', creating...:' ); - hdasToAdd.push( hdaData ); - } - }); - if( hdasToAdd.length ){ - history.addHdas( hdasToAdd ); - } + history.log( history + '.fetchHdaUpdates, success:', status, xhr ); + history.updateHdas( hdaDataList ); } }); }, + /** Update the models in the hdas collection from the data given. + * If a model exists in the collection, set will be used with the new data. + * If it's not in the collection, addHdas will be used to create it. + * @param {Object[]} hdaDataList an array of the model data used to update. + */ + updateHdas : function( hdaDataList ){ + var history = this, + // models/views that need to be created + hdasToAdd = []; + history.log( history + '.updateHdas:', hdaDataList ); + + _.each( hdaDataList, function( hdaData, index ){ + var existingModel = history.hdas.get( hdaData.id ); + // if this model exists already, update it + if( existingModel ){ + history.log( 'found existing model in list for id ' + hdaData.id + ', updating...:' ); + existingModel.set( hdaData ); + + // if this model is new and isn't in the hda collection, cache it to be created + } else { + history.log( 'NO existing model for id ' + hdaData.id + ', creating...:' ); + hdasToAdd.push( hdaData ); + } + }); + if( hdasToAdd.length ){ + history.addHdas( hdasToAdd ); + } + }, + /** Add multiple hda models to the hdas collection from an array of hda data. */ addHdas : function( hdaDataList ){ diff -r 34e42ae792320b1ee2471f11f341ac55445f52e0 -r 50c65739cd1af36f48672a980db378783dffcdd5 templates/root/alternate_history.mako --- a/templates/root/alternate_history.mako +++ b/templates/root/alternate_history.mako @@ -191,13 +191,19 @@ ##TODO: api/web controllers should use common code, and this section should call that code <%def name="get_history( id )"><% - return trans.webapp.api_controllers[ 'histories' ].show( trans, trans.security.encode_id( id ) ) + history_json = trans.webapp.api_controllers[ 'histories' ].show( trans, trans.security.encode_id( id ) ) + #assert isinstance( history, dict ), ( + # 'Bootstrapped history was expecting a dictionary: %s' %( str( history ) ) ) + return history_json %></%def><%def name="get_current_user()"><% - return trans.webapp.api_controllers[ 'users' ].show( trans, 'current' ) + user_json = trans.webapp.api_controllers[ 'users' ].show( trans, 'current' ) + #assert isinstance( hdaDataList, list ), ( + # 'Bootstrapped current user was expecting a dictionary: %s' %( str( user ) ) ) + return user_json %></%def> @@ -207,10 +213,10 @@ if not hdas: return '[]' # rather just use the history.id (wo the datasets), but... - return trans.webapp.api_controllers[ 'history_contents' ].index( - #trans, trans.security.encode_id( history_id ), + hda_json = trans.webapp.api_controllers[ 'history_contents' ].index( trans, trans.security.encode_id( history_id ), ids=( ','.join([ trans.security.encode_id( hda.id ) for hda in hdas ]) ) ) + return hda_json %></%def> 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.