PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/nsoranzo/galaxy-central
JavaScript | 422 lines | 247 code | 54 blank | 121 comment | 42 complexity | 060498180b487f7e39d51be04651ed88 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. /** purge this history, _Mark_ing it as purged and removing all dataset data from the server */
  184. purge : function( options ){
  185. if( this.get( 'purged' ) ){ return jQuery.when(); }
  186. return this.save( { purged: true }, options );
  187. },
  188. /** save this history, _Mark_ing it as undeleted */
  189. undelete : function( options ){
  190. if( !this.get( 'deleted' ) ){ return jQuery.when(); }
  191. return this.save( { deleted: false }, options );
  192. },
  193. /** Make a copy of this history on the server
  194. * @param {Boolean} current if true, set the copy as the new current history (default: true)
  195. * @param {String} name name of new history (default: none - server sets to: Copy of <current name>)
  196. * @fires copied passed this history and the response JSON from the copy
  197. * @returns {xhr}
  198. */
  199. copy : function( current, name ){
  200. current = ( current !== undefined )?( current ):( true );
  201. if( !this.id ){
  202. throw new Error( 'You must set the history ID before copying it.' );
  203. }
  204. var postData = { history_id : this.id };
  205. if( current ){
  206. postData.current = true;
  207. }
  208. if( name ){
  209. postData.name = name;
  210. }
  211. //TODO:?? all datasets?
  212. var history = this,
  213. copy = jQuery.post( this.urlRoot, postData );
  214. // if current - queue to setAsCurrent before firing 'copied'
  215. if( current ){
  216. return copy.then( function( response ){
  217. var newHistory = new History( response );
  218. return newHistory.setAsCurrent()
  219. .done( function(){
  220. history.trigger( 'copied', history, response );
  221. });
  222. });
  223. }
  224. return copy.done( function( response ){
  225. history.trigger( 'copied', history, response );
  226. });
  227. },
  228. setAsCurrent : function(){
  229. var history = this,
  230. xhr = jQuery.getJSON( '/history/set_as_current?id=' + this.id );
  231. xhr.done( function(){
  232. history.trigger( 'set-as-current', history );
  233. });
  234. return xhr;
  235. },
  236. // ........................................................................ misc
  237. toString : function(){
  238. return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';
  239. }
  240. }));
  241. //------------------------------------------------------------------------------ CLASS VARS
  242. /** When the history has running hdas,
  243. * this is the amount of time between update checks from the server
  244. */
  245. History.UPDATE_DELAY = 4000;
  246. /** Get data for a history then its hdas using a sequential ajax call, return a deferred to receive both */
  247. History.getHistoryData = function getHistoryData( historyId, options ){
  248. options = options || {};
  249. var detailIdsFn = options.detailIdsFn || [];
  250. var hdcaDetailIds = options.hdcaDetailIds || [];
  251. //console.debug( 'getHistoryData:', historyId, options );
  252. var df = jQuery.Deferred(),
  253. historyJSON = null;
  254. function getHistory( id ){
  255. // get the history data
  256. if( historyId === 'current' ){
  257. return jQuery.getJSON( galaxy_config.root + 'history/current_history_json' );
  258. }
  259. return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyId );
  260. }
  261. function isEmpty( historyData ){
  262. // get the number of hdas accrd. to the history
  263. return historyData && historyData.empty;
  264. }
  265. function getContents( historyData ){
  266. // get the hda data
  267. // if no hdas accrd. to history: return empty immed.
  268. if( isEmpty( historyData ) ){ return []; }
  269. // if there are hdas accrd. to history: get those as well
  270. if( _.isFunction( detailIdsFn ) ){
  271. detailIdsFn = detailIdsFn( historyData );
  272. }
  273. if( _.isFunction( hdcaDetailIds ) ){
  274. hdcaDetailIds = hdcaDetailIds( historyData );
  275. }
  276. var data = {};
  277. if( detailIdsFn.length ) {
  278. data.dataset_details = detailIdsFn.join( ',' );
  279. }
  280. if( hdcaDetailIds.length ) {
  281. // for symmetry, not actually used by backend of consumed
  282. // by frontend.
  283. data.dataset_collection_details = hdcaDetailIds.join( ',' );
  284. }
  285. return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyData.id + '/contents', { data: data });
  286. }
  287. // getting these concurrently is 400% slower (sqlite, local, vanilla) - so:
  288. // chain the api calls - getting history first then contents
  289. var historyFn = options.historyFn || getHistory,
  290. contentsFn = options.contentsFn || getContents;
  291. // chain ajax calls: get history first, then hdas
  292. var historyXHR = historyFn( historyId );
  293. historyXHR.done( function( json ){
  294. // set outer scope var here for use below
  295. historyJSON = json;
  296. df.notify({ status: 'history data retrieved', historyJSON: historyJSON });
  297. });
  298. historyXHR.fail( function( xhr, status, message ){
  299. // call reject on the outer deferred to allow its fail callback to run
  300. df.reject( xhr, 'loading the history' );
  301. });
  302. var contentsXHR = historyXHR.then( contentsFn );
  303. contentsXHR.then( function( contentsJSON ){
  304. df.notify({ status: 'contents data retrieved', historyJSON: historyJSON, contentsJSON: contentsJSON });
  305. // we've got both: resolve the outer scope deferred
  306. df.resolve( historyJSON, contentsJSON );
  307. });
  308. contentsXHR.fail( function( xhr, status, message ){
  309. // call reject on the outer deferred to allow its fail callback to run
  310. df.reject( xhr, 'loading the contents', { history: historyJSON } );
  311. });
  312. return df;
  313. };
  314. //==============================================================================
  315. /** @class A collection of histories (per user).
  316. * (stub) currently unused.
  317. */
  318. var HistoryCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
  319. /** @lends HistoryCollection.prototype */{
  320. model : History,
  321. /** logger used to record this.log messages, commonly set to console */
  322. //logger : console,
  323. urlRoot : ( window.galaxy_config? galaxy_config.root : '/' ) + 'api/histories',
  324. //url : function(){ return this.urlRoot; },
  325. initialize : function( models, options ){
  326. options = options || {};
  327. this.log( 'HistoryCollection.initialize', arguments );
  328. this.includeDeleted = options.includeDeleted || false;
  329. //this.on( 'all', function(){
  330. // console.info( 'event:', arguments );
  331. //});
  332. this.setUpListeners();
  333. },
  334. setUpListeners : function setUpListeners(){
  335. var collection = this;
  336. // when a history is deleted, remove it from the collection (if optionally set to do so)
  337. this.on( 'change:deleted', function( history ){
  338. this.debug( 'change:deleted', collection.includeDeleted, history.get( 'deleted' ) );
  339. if( !collection.includeDeleted && history.get( 'deleted' ) ){
  340. collection.remove( history );
  341. }
  342. });
  343. // listen for a history copy, adding it to the beginning of the collection
  344. this.on( 'copied', function( original, newData ){
  345. this.unshift( new History( newData, [] ) );
  346. });
  347. },
  348. create : function create( data, hdas, historyOptions, xhrOptions ){
  349. var collection = this,
  350. xhr = jQuery.getJSON( galaxy_config.root + 'history/create_new_current' );
  351. return xhr.done( function( newData ){
  352. var history = new History( newData, [], historyOptions || {} );
  353. // new histories go in the front
  354. //TODO: (implicit ordering by update time...)
  355. collection.unshift( history );
  356. collection.trigger( 'new-current' );
  357. });
  358. //TODO: move back to using history.save (via Deferred.then w/ set_as_current)
  359. },
  360. toString: function toString(){
  361. return 'HistoryCollection(' + this.length + ')';
  362. }
  363. });
  364. //==============================================================================
  365. return {
  366. History : History,
  367. HistoryCollection : HistoryCollection
  368. };});