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

/client/galaxy/scripts/mvc/history/history-model.js

https://bitbucket.org/dan/galaxy-central
JavaScript | 417 lines | 243 code | 54 blank | 120 comment | 41 complexity | 45aaf27020b500318403c43175ec80ec 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. BASE_MVC.mixin( BASE_MVC.SearchableModelMixin, /** @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. // ........................................................................ search
  106. /** What model fields to search with */
  107. searchAttributes : [
  108. 'name', 'annotation', 'tags'
  109. ],
  110. /** Adding title and singular tag */
  111. searchAliases : {
  112. title : 'name',
  113. tag : 'tags'
  114. },
  115. // ........................................................................ updates
  116. /** does the contents collection indicate they're still running and need to be updated later?
  117. * delay + update if needed
  118. * @param {Function} onReadyCallback function to run when all contents are in the ready state
  119. * events: ready
  120. */
  121. checkForUpdates : function( onReadyCallback ){
  122. //this.info( 'checkForUpdates' )
  123. // get overall History state from collection, run updater if History has running/queued contents
  124. // boiling it down on the client to running/not
  125. if( this.contents.running().length ){
  126. this.setUpdateTimeout();
  127. } else {
  128. this.trigger( 'ready' );
  129. if( _.isFunction( onReadyCallback ) ){
  130. onReadyCallback.call( this );
  131. }
  132. }
  133. return this;
  134. },
  135. /** create a timeout (after UPDATE_DELAY or delay ms) to refetch the contents. Clear any prev. timeout */
  136. setUpdateTimeout : function( delay ){
  137. delay = delay || History.UPDATE_DELAY;
  138. var history = this;
  139. // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id
  140. this.clearUpdateTimeout();
  141. this.updateTimeoutId = setTimeout( function(){
  142. history.refresh();
  143. }, delay );
  144. return this.updateTimeoutId;
  145. },
  146. /** clear the timeout and the cached timeout id */
  147. clearUpdateTimeout : function(){
  148. if( this.updateTimeoutId ){
  149. clearTimeout( this.updateTimeoutId );
  150. this.updateTimeoutId = null;
  151. }
  152. },
  153. /* update the contents, getting full detailed model data for any whose id is in detailIds
  154. * set up to run this again in some interval of time
  155. * @param {String[]} detailIds list of content ids to get detailed model data for
  156. * @param {Object} options std. backbone fetch options map
  157. */
  158. refresh : function( detailIds, options ){
  159. //this.info( 'refresh:', detailIds, this.contents );
  160. detailIds = detailIds || [];
  161. options = options || {};
  162. var history = this;
  163. // add detailIds to options as CSV string
  164. options.data = options.data || {};
  165. if( detailIds.length ){
  166. options.data.details = detailIds.join( ',' );
  167. }
  168. var xhr = this.contents.fetch( options );
  169. xhr.done( function( models ){
  170. history.checkForUpdates( function(){
  171. // fetch the history inside onReadyCallback in order to recalc history size
  172. this.fetch();
  173. });
  174. });
  175. return xhr;
  176. },
  177. // ........................................................................ ajax
  178. /** save this history, _Mark_ing it as deleted (just a flag) */
  179. _delete : function( options ){
  180. if( this.get( 'deleted' ) ){ return jQuery.when(); }
  181. return this.save( { deleted: true }, options );
  182. },
  183. /** save this history, _Mark_ing it as undeleted */
  184. undelete : function( options ){
  185. if( !this.get( 'deleted' ) ){ return jQuery.when(); }
  186. return this.save( { deleted: false }, options );
  187. },
  188. /** Make a copy of this history on the server
  189. * @param {Boolean} current if true, set the copy as the new current history (default: true)
  190. * @param {String} name name of new history (default: none - server sets to: Copy of <current name>)
  191. * @fires copied passed this history and the response JSON from the copy
  192. * @returns {xhr}
  193. */
  194. copy : function( current, name ){
  195. current = ( current !== undefined )?( current ):( true );
  196. if( !this.id ){
  197. throw new Error( 'You must set the history ID before copying it.' );
  198. }
  199. var postData = { history_id : this.id };
  200. if( current ){
  201. postData.current = true;
  202. }
  203. if( name ){
  204. postData.name = name;
  205. }
  206. //TODO:?? all datasets?
  207. var history = this,
  208. copy = jQuery.post( this.urlRoot, postData );
  209. // if current - queue to setAsCurrent before firing 'copied'
  210. if( current ){
  211. return copy.then( function( response ){
  212. var newHistory = new History( response );
  213. return newHistory.setAsCurrent()
  214. .done( function(){
  215. history.trigger( 'copied', history, response );
  216. });
  217. });
  218. }
  219. return copy.done( function( response ){
  220. history.trigger( 'copied', history, response );
  221. });
  222. },
  223. setAsCurrent : function(){
  224. var history = this,
  225. xhr = jQuery.getJSON( '/history/set_as_current?id=' + this.id );
  226. xhr.done( function(){
  227. history.trigger( 'set-as-current', history );
  228. });
  229. return xhr;
  230. },
  231. // ........................................................................ misc
  232. toString : function(){
  233. return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';
  234. }
  235. }));
  236. //------------------------------------------------------------------------------ CLASS VARS
  237. /** When the history has running hdas,
  238. * this is the amount of time between update checks from the server
  239. */
  240. History.UPDATE_DELAY = 4000;
  241. /** Get data for a history then its hdas using a sequential ajax call, return a deferred to receive both */
  242. History.getHistoryData = function getHistoryData( historyId, options ){
  243. options = options || {};
  244. var detailIdsFn = options.detailIdsFn || [];
  245. var hdcaDetailIds = options.hdcaDetailIds || [];
  246. //console.debug( 'getHistoryData:', historyId, options );
  247. var df = jQuery.Deferred(),
  248. historyJSON = null;
  249. function getHistory( id ){
  250. // get the history data
  251. if( historyId === 'current' ){
  252. return jQuery.getJSON( galaxy_config.root + 'history/current_history_json' );
  253. }
  254. return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyId );
  255. }
  256. function isEmpty( historyData ){
  257. // get the number of hdas accrd. to the history
  258. return historyData && historyData.empty;
  259. }
  260. function getContents( historyData ){
  261. // get the hda data
  262. // if no hdas accrd. to history: return empty immed.
  263. if( isEmpty( historyData ) ){ return []; }
  264. // if there are hdas accrd. to history: get those as well
  265. if( _.isFunction( detailIdsFn ) ){
  266. detailIdsFn = detailIdsFn( historyData );
  267. }
  268. if( _.isFunction( hdcaDetailIds ) ){
  269. hdcaDetailIds = hdcaDetailIds( historyData );
  270. }
  271. var data = {};
  272. if( detailIdsFn.length ) {
  273. data.dataset_details = detailIdsFn.join( ',' );
  274. }
  275. if( hdcaDetailIds.length ) {
  276. // for symmetry, not actually used by backend of consumed
  277. // by frontend.
  278. data.dataset_collection_details = hdcaDetailIds.join( ',' );
  279. }
  280. return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyData.id + '/contents', { data: data });
  281. }
  282. // getting these concurrently is 400% slower (sqlite, local, vanilla) - so:
  283. // chain the api calls - getting history first then contents
  284. var historyFn = options.historyFn || getHistory,
  285. contentsFn = options.contentsFn || getContents;
  286. // chain ajax calls: get history first, then hdas
  287. var historyXHR = historyFn( historyId );
  288. historyXHR.done( function( json ){
  289. // set outer scope var here for use below
  290. historyJSON = json;
  291. df.notify({ status: 'history data retrieved', historyJSON: historyJSON });
  292. });
  293. historyXHR.fail( function( xhr, status, message ){
  294. // call reject on the outer deferred to allow its fail callback to run
  295. df.reject( xhr, 'loading the history' );
  296. });
  297. var contentsXHR = historyXHR.then( contentsFn );
  298. contentsXHR.then( function( contentsJSON ){
  299. df.notify({ status: 'contents data retrieved', historyJSON: historyJSON, contentsJSON: contentsJSON });
  300. // we've got both: resolve the outer scope deferred
  301. df.resolve( historyJSON, contentsJSON );
  302. });
  303. contentsXHR.fail( function( xhr, status, message ){
  304. // call reject on the outer deferred to allow its fail callback to run
  305. df.reject( xhr, 'loading the contents', { history: historyJSON } );
  306. });
  307. return df;
  308. };
  309. //==============================================================================
  310. /** @class A collection of histories (per user).
  311. * (stub) currently unused.
  312. */
  313. var HistoryCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
  314. /** @lends HistoryCollection.prototype */{
  315. model : History,
  316. /** logger used to record this.log messages, commonly set to console */
  317. //logger : console,
  318. urlRoot : ( window.galaxy_config? galaxy_config.root : '/' ) + 'api/histories',
  319. //url : function(){ return this.urlRoot; },
  320. initialize : function( models, options ){
  321. options = options || {};
  322. this.log( 'HistoryCollection.initialize', arguments );
  323. this.includeDeleted = options.includeDeleted || false;
  324. //this.on( 'all', function(){
  325. // console.info( 'event:', arguments );
  326. //});
  327. this.setUpListeners();
  328. },
  329. setUpListeners : function setUpListeners(){
  330. var collection = this;
  331. // when a history is deleted, remove it from the collection (if optionally set to do so)
  332. this.on( 'change:deleted', function( history ){
  333. this.debug( 'change:deleted', collection.includeDeleted, history.get( 'deleted' ) );
  334. if( !collection.includeDeleted && history.get( 'deleted' ) ){
  335. collection.remove( history );
  336. }
  337. });
  338. // listen for a history copy, adding it to the beginning of the collection
  339. this.on( 'copied', function( original, newData ){
  340. this.unshift( new History( newData, [] ) );
  341. });
  342. },
  343. create : function create( data, hdas, historyOptions, xhrOptions ){
  344. var collection = this,
  345. xhr = jQuery.getJSON( galaxy_config.root + 'history/create_new_current' );
  346. return xhr.done( function( newData ){
  347. var history = new History( newData, [], historyOptions || {} );
  348. // new histories go in the front
  349. //TODO: (implicit ordering by update time...)
  350. collection.unshift( history );
  351. collection.trigger( 'new-current' );
  352. });
  353. //TODO: move back to using history.save (via Deferred.then w/ set_as_current)
  354. },
  355. toString: function toString(){
  356. return 'HistoryCollection(' + this.length + ')';
  357. }
  358. });
  359. //==============================================================================
  360. return {
  361. History : History,
  362. HistoryCollection : HistoryCollection
  363. };});