PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/mvc/history/history-model.js

https://bitbucket.org/kellrott/galaxy-central
JavaScript | 368 lines | 207 code | 49 blank | 112 comment | 39 complexity | f7d0d3c868932c669d8599fca57f380c MD5 | raw file
Possible License(s): CC-BY-3.0
  1. define([
  2. "mvc/history/history-contents",
  3. "mvc/base-mvc",
  4. "utils/localization"
  5. ], function( HISTORY_CONTENTS, BASE_MVC, _l ){
  6. //==============================================================================
  7. /** @class Model for a Galaxy history resource - both a record of user
  8. * tool use and a collection of the datasets those tools produced.
  9. * @name History
  10. *
  11. * @augments Backbone.Model
  12. * @borrows LoggableMixin#logger as #logger
  13. * @borrows LoggableMixin#log as #log
  14. * @constructs
  15. */
  16. var History = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
  17. /** @lends History.prototype */{
  18. /** logger used to record this.log messages, commonly set to console */
  19. //logger : console,
  20. // values from api (may need more)
  21. defaults : {
  22. model_class : 'History',
  23. id : null,
  24. name : 'Unnamed History',
  25. state : 'new',
  26. diskSize : 0,
  27. deleted : false
  28. },
  29. // ........................................................................ urls
  30. urlRoot: galaxy_config.root + 'api/histories',
  31. // ........................................................................ set up/tear down
  32. /** Set up the model
  33. * @param {Object} historyJSON model data for this History
  34. * @param {Object[]} contentsJSON array of model data for this History's contents (hdas or collections)
  35. * @param {Object} options any extra settings including logger
  36. */
  37. initialize : function( historyJSON, contentsJSON, options ){
  38. options = options || {};
  39. this.logger = options.logger || null;
  40. this.log( this + ".initialize:", historyJSON, contentsJSON, options );
  41. /** HistoryContents collection of the HDAs contained in this history. */
  42. this.log( 'creating history contents:', contentsJSON );
  43. this.contents = new HISTORY_CONTENTS.HistoryContents( contentsJSON || [], { historyId: this.get( 'id' )});
  44. //// if we've got hdas passed in the constructor, load them
  45. //if( contentsJSON && _.isArray( contentsJSON ) ){
  46. // this.log( 'resetting history contents:', contentsJSON );
  47. // this.contents.reset( contentsJSON );
  48. //}
  49. this._setUpListeners();
  50. /** cached timeout id for the dataset updater */
  51. this.updateTimeoutId = null;
  52. // set up update timeout if needed
  53. //this.checkForUpdates();
  54. },
  55. /** set up any event listeners for this history including those to the contained HDAs
  56. * events: error:contents if an error occurred with the contents collection
  57. */
  58. _setUpListeners : function(){
  59. this.on( 'error', function( model, xhr, options, msg, details ){
  60. this.errorHandler( model, xhr, options, msg, details );
  61. });
  62. // hda collection listening
  63. if( this.contents ){
  64. this.listenTo( this.contents, 'error', function(){
  65. this.trigger.apply( this, [ 'error:contents' ].concat( jQuery.makeArray( arguments ) ) );
  66. });
  67. }
  68. // if the model's id changes ('current' or null -> an actual id), update the contents history_id
  69. this.on( 'change:id', function( model, newId ){
  70. if( this.contents ){
  71. this.contents.historyId = newId;
  72. }
  73. }, this );
  74. },
  75. //TODO: see base-mvc
  76. //onFree : function(){
  77. // if( this.contents ){
  78. // this.contents.free();
  79. // }
  80. //},
  81. /** event listener for errors. Generally errors are handled outside this model */
  82. errorHandler : function( model, xhr, options, msg, details ){
  83. // clear update timeout on model err
  84. this.clearUpdateTimeout();
  85. },
  86. // ........................................................................ common queries
  87. /** T/F is this history owned by the current user (Galaxy.currUser)
  88. * Note: that this will return false for an anon user even if the history is theirs.
  89. */
  90. ownedByCurrUser : function(){
  91. // no currUser
  92. if( !Galaxy || !Galaxy.currUser ){
  93. return false;
  94. }
  95. // user is anon or history isn't owned
  96. if( Galaxy.currUser.isAnonymous() || Galaxy.currUser.id !== this.get( 'user_id' ) ){
  97. return false;
  98. }
  99. return true;
  100. },
  101. /** */
  102. contentsCount : function(){
  103. return _.reduce( _.values( this.get( 'state_details' ) ), function( memo, num ){ return memo + num; }, 0 );
  104. },
  105. // ........................................................................ ajax
  106. /** does the contents collection indicate they're still running and need to be updated later?
  107. * delay + update if needed
  108. * @param {Function} onReadyCallback function to run when all contents are in the ready state
  109. * events: ready
  110. */
  111. checkForUpdates : function( onReadyCallback ){
  112. //this.info( 'checkForUpdates' )
  113. // get overall History state from collection, run updater if History has running/queued contents
  114. // boiling it down on the client to running/not
  115. if( this.contents.running().length ){
  116. this.setUpdateTimeout();
  117. } else {
  118. this.trigger( 'ready' );
  119. if( _.isFunction( onReadyCallback ) ){
  120. onReadyCallback.call( this );
  121. }
  122. }
  123. return this;
  124. },
  125. /** create a timeout (after UPDATE_DELAY or delay ms) to refetch the contents. Clear any prev. timeout */
  126. setUpdateTimeout : function( delay ){
  127. delay = delay || History.UPDATE_DELAY;
  128. var history = this;
  129. // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id
  130. this.clearUpdateTimeout();
  131. this.updateTimeoutId = setTimeout( function(){
  132. history.refresh();
  133. }, delay );
  134. return this.updateTimeoutId;
  135. },
  136. /** clear the timeout and the cached timeout id */
  137. clearUpdateTimeout : function(){
  138. if( this.updateTimeoutId ){
  139. clearTimeout( this.updateTimeoutId );
  140. this.updateTimeoutId = null;
  141. }
  142. },
  143. /* update the contents, getting full detailed model data for any whose id is in detailIds
  144. * set up to run this again in some interval of time
  145. * @param {String[]} detailIds list of content ids to get detailed model data for
  146. * @param {Object} options std. backbone fetch options map
  147. */
  148. refresh : function( detailIds, options ){
  149. //this.info( 'refresh:', detailIds, this.contents );
  150. detailIds = detailIds || [];
  151. options = options || {};
  152. var history = this;
  153. // add detailIds to options as CSV string
  154. options.data = options.data || {};
  155. if( detailIds.length ){
  156. options.data.details = detailIds.join( ',' );
  157. }
  158. var xhr = this.contents.fetch( options );
  159. xhr.done( function( models ){
  160. history.checkForUpdates( function(){
  161. // fetch the history inside onReadyCallback in order to recalc history size
  162. this.fetch();
  163. });
  164. });
  165. return xhr;
  166. },
  167. /** Make a copy of this history on the server
  168. * @param {Boolean} current if true, set the copy as the new current history (default: true)
  169. * @param {String} name name of new history (default: none - server sets to: Copy of <current name>)
  170. * @fires copied passed this history and the response JSON from the copy
  171. * @returns {xhr}
  172. */
  173. copy : function( current, name ){
  174. current = ( current !== undefined )?( current ):( true );
  175. if( !this.id ){
  176. throw new Error( 'You must set the history ID before copying it.' );
  177. }
  178. var postData = { history_id : this.id };
  179. if( current ){
  180. postData.current = true;
  181. }
  182. if( name ){
  183. postData.name = name;
  184. }
  185. //TODO:?? all datasets?
  186. var history = this,
  187. xhr = jQuery.post( this.urlRoot, postData );
  188. xhr.done( function( newData ){
  189. history.trigger( 'copied', history, newData );
  190. });
  191. return xhr;
  192. },
  193. // ........................................................................ misc
  194. toString : function(){
  195. return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';
  196. }
  197. });
  198. //------------------------------------------------------------------------------ CLASS VARS
  199. /** When the history has running hdas,
  200. * this is the amount of time between update checks from the server
  201. */
  202. History.UPDATE_DELAY = 4000;
  203. /** Get data for a history then its hdas using a sequential ajax call, return a deferred to receive both */
  204. History.getHistoryData = function getHistoryData( historyId, options ){
  205. options = options || {};
  206. var hdaDetailIds = options.hdaDetailIds || [];
  207. var hdcaDetailIds = options.hdcaDetailIds || [];
  208. //this.debug( 'getHistoryData:', historyId, options );
  209. var df = jQuery.Deferred(),
  210. historyJSON = null;
  211. function getHistory( id ){
  212. // get the history data
  213. return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyId );
  214. }
  215. function isEmpty( historyData ){
  216. // get the number of hdas accrd. to the history
  217. return historyData && historyData.empty;
  218. }
  219. function getHdas( historyData ){
  220. // get the hda data
  221. // if no hdas accrd. to history: return empty immed.
  222. if( isEmpty( historyData ) ){ return []; }
  223. // if there are hdas accrd. to history: get those as well
  224. if( _.isFunction( hdaDetailIds ) ){
  225. hdaDetailIds = hdaDetailIds( historyData );
  226. }
  227. if( _.isFunction( hdcaDetailIds ) ){
  228. hdcaDetailIds = hdcaDetailIds( historyData );
  229. }
  230. var data = {};
  231. if( hdaDetailIds.length ) {
  232. data.dataset_details = hdaDetailIds.join( ',' );
  233. }
  234. if( hdcaDetailIds.length ) {
  235. // for symmetry, not actually used by backend of consumed
  236. // by frontend.
  237. data.dataset_collection_details = hdcaDetailIds.join( ',' );
  238. }
  239. return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyData.id + '/contents', { data: data });
  240. }
  241. // getting these concurrently is 400% slower (sqlite, local, vanilla) - so:
  242. // chain the api calls - getting history first then hdas
  243. var historyFn = options.historyFn || getHistory,
  244. hdaFn = options.hdaFn || getHdas;
  245. // chain ajax calls: get history first, then hdas
  246. var historyXHR = historyFn( historyId );
  247. historyXHR.done( function( json ){
  248. // set outer scope var here for use below
  249. historyJSON = json;
  250. df.notify({ status: 'history data retrieved', historyJSON: historyJSON });
  251. });
  252. historyXHR.fail( function( xhr, status, message ){
  253. // call reject on the outer deferred to allow its fail callback to run
  254. df.reject( xhr, 'loading the history' );
  255. });
  256. var hdaXHR = historyXHR.then( hdaFn );
  257. hdaXHR.then( function( hdaJSON ){
  258. df.notify({ status: 'dataset data retrieved', historyJSON: historyJSON, hdaJSON: hdaJSON });
  259. // we've got both: resolve the outer scope deferred
  260. df.resolve( historyJSON, hdaJSON );
  261. });
  262. hdaXHR.fail( function( xhr, status, message ){
  263. // call reject on the outer deferred to allow its fail callback to run
  264. df.reject( xhr, 'loading the datasets', { history: historyJSON } );
  265. });
  266. return df;
  267. };
  268. //==============================================================================
  269. /** @class A collection of histories (per user).
  270. * (stub) currently unused.
  271. */
  272. var HistoryCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
  273. /** @lends HistoryCollection.prototype */{
  274. model : History,
  275. /** logger used to record this.log messages, commonly set to console */
  276. //logger : console,
  277. urlRoot : ( window.galaxy_config? galaxy_config.root : '/' ) + 'api/histories',
  278. //url : function(){ return this.urlRoot; },
  279. initialize : function( models, options ){
  280. options = options || {};
  281. this.log( 'HistoryCollection.initialize', arguments );
  282. this.includeDeleted = options.includeDeleted || false;
  283. //this.on( 'all', function(){
  284. // console.info( 'event:', arguments );
  285. //});
  286. this.setUpListeners();
  287. },
  288. setUpListeners : function setUpListeners(){
  289. var collection = this;
  290. // when a history is deleted, remove it from the collection (if optionally set to do so)
  291. this.on( 'change:deleted', function( history ){
  292. this.debug( 'change:deleted', collection.includeDeleted, history.get( 'deleted' ) );
  293. if( !collection.includeDeleted && history.get( 'deleted' ) ){
  294. collection.remove( history );
  295. }
  296. });
  297. // listen for a history copy, adding it to the beginning of the collection
  298. this.on( 'copied', function( original, newData ){
  299. this.unshift( new History( newData, [] ) );
  300. });
  301. },
  302. create : function create( data, hdas, historyOptions, xhrOptions ){
  303. var collection = this,
  304. history = new History( data || {}, hdas || [], historyOptions || {} );
  305. return history.save( xhrOptions ).done( function( newData ){
  306. // new histories go in the front
  307. //TODO: (implicit ordering by update time...)
  308. collection.unshift( history );
  309. });
  310. },
  311. toString: function toString(){
  312. return 'HistoryCollection(' + this.length + ')';
  313. }
  314. });
  315. //==============================================================================
  316. return {
  317. History : History,
  318. HistoryCollection : HistoryCollection
  319. };});