PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

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

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