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

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

https://bitbucket.org/afgane/galaxy-central
JavaScript | 384 lines | 206 code | 48 blank | 130 comment | 22 complexity | fe89083980b994eeda9301a31a3c8995 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. define([
  2. "mvc/dataset/states",
  3. "mvc/base-mvc",
  4. "utils/localization"
  5. ], function( STATES, BASE_MVC, _l ){
  6. //==============================================================================
  7. var searchableMixin = BASE_MVC.SearchableModelMixin;
  8. /** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).
  9. * No knowledge of what type (HDA/LDDA/DCDA) should be needed here.
  10. * The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.
  11. */
  12. var DatasetAssociation = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
  13. BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{
  14. /** default attributes for a model */
  15. defaults : {
  16. state : STATES.NEW,
  17. deleted : false,
  18. purged : false,
  19. // unreliable attribute
  20. name : '(unnamed dataset)',
  21. //TODO: update to false when this is correctly passed from the API (when we have a security model for this)
  22. accessible : true,
  23. // sniffed datatype (sam, tabular, bed, etc.)
  24. data_type : '',
  25. file_ext : '',
  26. // size in bytes
  27. file_size : 0,
  28. // array of associated file types (eg. [ 'bam_index', ... ])
  29. meta_files : [],
  30. misc_blurb : '',
  31. misc_info : '',
  32. tags : []
  33. // do NOT default on annotation, as this default is valid and will be passed on 'save'
  34. // which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)
  35. //annotation : ''
  36. },
  37. /** instance vars and listeners */
  38. initialize : function( attributes, options ){
  39. this.debug( this + '(Dataset).initialize', attributes, options );
  40. //!! this state is not in trans.app.model.Dataset.states - set it here -
  41. if( !this.get( 'accessible' ) ){
  42. this.set( 'state', STATES.NOT_VIEWABLE );
  43. }
  44. /** Datasets rely/use some web controllers - have the model generate those URLs on startup */
  45. this.urls = this._generateUrls();
  46. this._setUpListeners();
  47. },
  48. /** returns misc. web urls for rendering things like re-run, display, etc. */
  49. _generateUrls : function(){
  50. //TODO: would be nice if the API did this
  51. var id = this.get( 'id' );
  52. if( !id ){ return {}; }
  53. var urls = {
  54. 'purge' : 'datasets/' + id + '/purge_async',
  55. 'display' : 'datasets/' + id + '/display/?preview=True',
  56. 'edit' : 'datasets/' + id + '/edit',
  57. 'download' : 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),
  58. 'report_error' : 'dataset/errors?id=' + id,
  59. 'rerun' : 'tool_runner/rerun?id=' + id,
  60. 'show_params' : 'datasets/' + id + '/show_params',
  61. 'visualization' : 'visualization',
  62. 'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='
  63. };
  64. //TODO: global
  65. var root = ( window.galaxy_config && galaxy_config.root )?( galaxy_config.root ):( '/' );
  66. _.each( urls, function( value, key ){
  67. urls[ key ] = root + value;
  68. });
  69. this.urls = urls;
  70. return urls;
  71. },
  72. /** set up any event listeners
  73. * event: state:ready fired when this DA moves into/is already in a ready state
  74. */
  75. _setUpListeners : function(){
  76. // if the state has changed and the new state is a ready state, fire an event
  77. this.on( 'change:state', function( currModel, newState ){
  78. this.log( this + ' has changed state:', currModel, newState );
  79. if( this.inReadyState() ){
  80. this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );
  81. }
  82. });
  83. // the download url (currently) relies on having a correct file extension
  84. this.on( 'change:id change:file_ext', function( currModel ){
  85. this._generateUrls();
  86. });
  87. },
  88. // ........................................................................ common queries
  89. /** override to add urls */
  90. toJSON : function(){
  91. var json = Backbone.Model.prototype.toJSON.call( this );
  92. //console.warn( 'returning json?' );
  93. //return json;
  94. return _.extend( json, {
  95. urls : this.urls
  96. });
  97. },
  98. /** Is this dataset deleted or purged? */
  99. isDeletedOrPurged : function(){
  100. return ( this.get( 'deleted' ) || this.get( 'purged' ) );
  101. },
  102. /** Is this dataset in a 'ready' state; where 'Ready' states are states where no
  103. * processing (for the ds) is left to do on the server.
  104. */
  105. inReadyState : function(){
  106. var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );
  107. return ( this.isDeletedOrPurged() || ready );
  108. },
  109. /** Does this model already contain detailed data (as opposed to just summary level data)? */
  110. hasDetails : function(){
  111. //?? this may not be reliable
  112. return _.has( this.attributes, 'genome_build' );
  113. },
  114. /** Convenience function to match dataset.has_data. */
  115. hasData : function(){
  116. return ( this.get( 'file_size' ) > 0 );
  117. },
  118. // ........................................................................ ajax
  119. fetch : function( options ){
  120. var dataset = this;
  121. return Backbone.Model.prototype.fetch.call( this, options )
  122. .always( function(){
  123. dataset._generateUrls();
  124. });
  125. },
  126. //NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly
  127. /** save this dataset, _Mark_ing it as deleted (just a flag) */
  128. 'delete' : function( options ){
  129. if( this.get( 'deleted' ) ){ return jQuery.when(); }
  130. return this.save( { deleted: true }, options );
  131. },
  132. /** save this dataset, _Mark_ing it as undeleted */
  133. undelete : function( options ){
  134. if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }
  135. return this.save( { deleted: false }, options );
  136. },
  137. /** remove the file behind this dataset from the filesystem (if permitted) */
  138. purge : function _purge( options ){
  139. //TODO: use, override model.destroy, HDA.delete({ purge: true })
  140. if( this.get( 'purged' ) ){ return jQuery.when(); }
  141. options = options || {};
  142. //var hda = this,
  143. // //xhr = jQuery.ajax( this.url() + '?' + jQuery.param({ purge: true }), _.extend({
  144. // xhr = jQuery.ajax( this.url(), _.extend({
  145. // type : 'DELETE',
  146. // data : {
  147. // purge : true
  148. // }
  149. // }, options ));
  150. //
  151. //xhr.done( function( response ){
  152. // hda.debug( 'response', response );
  153. // //hda.set({ deleted: true, purged: true });
  154. // hda.set( response );
  155. //});
  156. //return xhr;
  157. options.url = this.urls.purge;
  158. //TODO: ideally this would be a DELETE call to the api
  159. // using purge async for now
  160. var hda = this,
  161. xhr = jQuery.ajax( options );
  162. xhr.done( function( message, status, responseObj ){
  163. hda.set({ deleted: true, purged: true });
  164. });
  165. xhr.fail( function( xhr, status, message ){
  166. // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'
  167. // unbury and re-add to xhr
  168. var error = _l( "Unable to purge dataset" );
  169. var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '
  170. + 'is not allowed in this Galaxy instance' );
  171. if( xhr.responseJSON && xhr.responseJSON.error ){
  172. error = xhr.responseJSON.error;
  173. } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){
  174. error = messageBuriedInUnfortunatelyFormattedError;
  175. }
  176. xhr.responseText = error;
  177. hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );
  178. });
  179. return xhr;
  180. },
  181. // ........................................................................ searching
  182. // see base-mvc, SearchableModelMixin
  183. /** what attributes of an HDA will be used in a text search */
  184. searchAttributes : [
  185. 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'
  186. ],
  187. /** our attr keys don't often match the labels we display to the user - so, when using
  188. * attribute specifiers ('name="bler"') in a term, allow passing in aliases for the
  189. * following attr keys.
  190. */
  191. searchAliases : {
  192. title : 'name',
  193. format : 'file_ext',
  194. database : 'genome_build',
  195. blurb : 'misc_blurb',
  196. description : 'misc_blurb',
  197. info : 'misc_info',
  198. tag : 'tags'
  199. },
  200. // ........................................................................ misc
  201. /** String representation */
  202. toString : function(){
  203. var nameAndId = this.get( 'id' ) || '';
  204. if( this.get( 'name' ) ){
  205. nameAndId = '"' + this.get( 'name' ) + '",' + nameAndId;
  206. }
  207. return 'Dataset(' + nameAndId + ')';
  208. }
  209. }));
  210. //==============================================================================
  211. /** @class Backbone collection for dataset associations.
  212. */
  213. var DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
  214. /** @lends HistoryContents.prototype */{
  215. model : DatasetAssociation,
  216. /** logger used to record this.log messages, commonly set to console */
  217. //logger : console,
  218. /** root api url */
  219. urlRoot : (( window.galaxy_config && galaxy_config.root )?( galaxy_config.root ):( '/' ))
  220. + 'api/datasets',
  221. // ........................................................................ common queries
  222. /** Get the ids of every item in this collection
  223. * @returns array of encoded ids
  224. */
  225. ids : function(){
  226. return this.map( function( item ){ return item.get('id'); });
  227. },
  228. /** Get contents that are not ready
  229. * @returns array of content models
  230. */
  231. notReady : function(){
  232. return this.filter( function( content ){
  233. return !content.inReadyState();
  234. });
  235. },
  236. // /** Get the id of every model in this collection not in a 'ready' state (running).
  237. // * @returns an array of model ids
  238. // */
  239. // running : function(){
  240. // var idList = [];
  241. // this.each( function( item ){
  242. // var isRunning = !item.inReadyState();
  243. // if( isRunning ){
  244. ////TODO: is this still correct since type_id
  245. // idList.push( item.get( 'id' ) );
  246. // }
  247. // });
  248. // return idList;
  249. // },
  250. /** return true if any datasets don't have details */
  251. haveDetails : function(){
  252. return this.all( function( dataset ){ return dataset.hasDetails(); });
  253. },
  254. // ........................................................................ ajax
  255. ///** fetch detailed model data for all datasets in this collection */
  256. //fetchAllDetails : function( options ){
  257. // options = options || {};
  258. // var detailsFlag = { details: 'all' };
  259. // options.data = ( options.data )?( _.extend( options.data, detailsFlag ) ):( detailsFlag );
  260. // return this.fetch( options );
  261. //},
  262. /** using a queue, perform ajaxFn on each of the models in this collection */
  263. ajaxQueue : function( ajaxFn, options ){
  264. var deferred = jQuery.Deferred(),
  265. startingLength = this.length,
  266. responses = [];
  267. if( !startingLength ){
  268. deferred.resolve([]);
  269. return deferred;
  270. }
  271. // use reverse order (stylistic choice)
  272. var ajaxFns = this.chain().reverse().map( function( dataset, i ){
  273. return function(){
  274. var xhr = ajaxFn.call( dataset, options );
  275. // if successful, notify using the deferred to allow tracking progress
  276. xhr.done( function( response ){
  277. deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });
  278. });
  279. // (regardless of previous error or success) if not last ajax call, shift and call the next
  280. // if last fn, resolve deferred
  281. xhr.always( function( response ){
  282. responses.push( response );
  283. if( ajaxFns.length ){
  284. ajaxFns.shift()();
  285. } else {
  286. deferred.resolve( responses );
  287. }
  288. });
  289. };
  290. }).value();
  291. // start the queue
  292. ajaxFns.shift()();
  293. return deferred;
  294. },
  295. // ........................................................................ sorting/filtering
  296. /** return a new collection of datasets whose attributes contain the substring matchesWhat */
  297. matches : function( matchesWhat ){
  298. return this.filter( function( dataset ){
  299. return dataset.matches( matchesWhat );
  300. });
  301. },
  302. // ........................................................................ misc
  303. /** override to get a correct/smarter merge when incoming data is partial */
  304. set : function( models, options ){
  305. // arrrrrrrrrrrrrrrrrg...
  306. // (e.g. stupid backbone)
  307. // w/o this partial models from the server will fill in missing data with model defaults
  308. // and overwrite existing data on the client
  309. // see Backbone.Collection.set and _prepareModel
  310. var collection = this;
  311. models = _.map( models, function( model ){
  312. if( !collection.get( model.id ) ){ return model; }
  313. // merge the models _BEFORE_ calling the superclass version
  314. var merged = existing.toJSON();
  315. _.extend( merged, model );
  316. return merged;
  317. });
  318. // now call superclass when the data is filled
  319. Backbone.Collection.prototype.set.call( this, models, options );
  320. },
  321. ///** Convert this ad-hoc collection of hdas to a formal collection tracked
  322. // by the server.
  323. //**/
  324. //promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){
  325. //},
  326. /** String representation. */
  327. toString : function(){
  328. return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));
  329. }
  330. });
  331. //==============================================================================
  332. return {
  333. DatasetAssociation : DatasetAssociation,
  334. DatasetAssociationCollection : DatasetAssociationCollection
  335. };
  336. });