PageRenderTime 48ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/nsoranzo/galaxy-central
JavaScript | 359 lines | 241 code | 33 blank | 85 comment | 33 complexity | 19ce722fed5126149c0a0aadb995f625 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. define([
  2. "mvc/history/history-content-model",
  3. "mvc/history/hda-model",
  4. "mvc/history/hdca-model",
  5. "mvc/base-mvc",
  6. "utils/localization"
  7. ], function( HISTORY_CONTENT, HDA_MODEL, HDCA_MODEL, BASE_MVC, _l ){
  8. //==============================================================================
  9. /** @class Backbone collection for history content.
  10. * NOTE: history content seems like a dataset collection, but differs in that it is mixed:
  11. * each element can be either an HDA (dataset) or a DatasetCollection and co-exist on
  12. * the same level.
  13. * Dataset collections on the other hand are not mixed and (so far) can only contain either
  14. * HDAs or child dataset collections on one level.
  15. * This is why this does not inherit from any of the DatasetCollections (currently).
  16. */
  17. var HistoryContents = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
  18. /** @lends HistoryContents.prototype */{
  19. //TODO:?? may want to inherit from some MixedModelCollection
  20. //TODO:?? also consider inheriting from a 'DatasetList'
  21. //TODO: can we decorate the mixed models using the model fn below (instead of having them build their own type_id)?
  22. /** logger used to record this.log messages, commonly set to console */
  23. //logger : console,
  24. /** since history content is a mix, override model fn into a factory, creating based on history_content_type */
  25. model : function( attrs, options ) {
  26. //TODO: can we move the type_id stuff here?
  27. //attrs.type_id = typeIdStr( attrs );
  28. if( attrs.history_content_type === "dataset" ) {
  29. return new HDA_MODEL.HistoryDatasetAssociation( attrs, options );
  30. } else if( attrs.history_content_type === "dataset_collection" ) {
  31. switch( attrs.collection_type ){
  32. case 'list':
  33. return new HDCA_MODEL.HistoryListDatasetCollection( attrs, options );
  34. case 'paired':
  35. return new HDCA_MODEL.HistoryPairDatasetCollection( attrs, options );
  36. case 'list:paired':
  37. return new HDCA_MODEL.HistoryListPairedDatasetCollection( attrs, options );
  38. }
  39. throw new TypeError( 'Unknown collection_type: ' + attrs.collection_type );
  40. }
  41. // TODO: Handle unknown history_content_type...
  42. throw new TypeError( 'Unknown history_content_type: ' + attrs.history_content_type );
  43. },
  44. /** Set up.
  45. * @see Backbone.Collection#initialize
  46. */
  47. initialize : function( models, options ){
  48. options = options || {};
  49. //TODO: could probably use the contents.history_id instead
  50. this.historyId = options.historyId;
  51. //this._setUpListeners();
  52. this.on( 'all', function(){
  53. this.debug( this + '.event:', arguments );
  54. });
  55. },
  56. /** root api url */
  57. urlRoot : galaxy_config.root + 'api/histories',
  58. /** complete api url */
  59. url : function(){
  60. return this.urlRoot + '/' + this.historyId + '/contents';
  61. },
  62. // ........................................................................ common queries
  63. /** Get the ids of every item in this collection
  64. * @returns array of encoded ids
  65. */
  66. ids : function(){
  67. //TODO: is this still useful since type_id
  68. return this.map( function( item ){ return item.get('id'); });
  69. },
  70. /** Get contents that are not ready
  71. * @returns array of content models
  72. */
  73. notReady : function(){
  74. return this.filter( function( content ){
  75. return !content.inReadyState();
  76. });
  77. },
  78. /** Get the id of every model in this collection not in a 'ready' state (running).
  79. * @returns an array of model ids
  80. * @see HistoryDatasetAssociation#inReadyState
  81. */
  82. running : function(){
  83. var idList = [];
  84. this.each( function( item ){
  85. var isRunning = !item.inReadyState();
  86. if( isRunning ){
  87. //TODO: is this still correct since type_id
  88. idList.push( item.get( 'id' ) );
  89. }
  90. });
  91. return idList;
  92. },
  93. /** Get the model with the given hid
  94. * @param {Int} hid the hid to search for
  95. * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found
  96. */
  97. getByHid : function( hid ){
  98. return _.first( this.filter( function( content ){ return content.get( 'hid' ) === hid; }) );
  99. },
  100. //TODO:?? this may belong in the containing view
  101. /** Get every 'shown' model in this collection based on show_deleted/hidden
  102. * @param {Boolean} show_deleted are we showing deleted content?
  103. * @param {Boolean} show_hidden are we showing hidden content?
  104. * @returns array of content models
  105. * @see HistoryDatasetAssociation#isVisible
  106. */
  107. getVisible : function( show_deleted, show_hidden, filters ){
  108. filters = filters || [];
  109. //this.debug( 'filters:', filters );
  110. // always filter by show deleted/hidden first
  111. this.debug( 'checking isVisible' );
  112. var filteredHdas = new HistoryContents( this.filter( function( item ){
  113. return item.isVisible( show_deleted, show_hidden );
  114. }));
  115. _.each( filters, function( filter_fn ){
  116. if( !_.isFunction( filter_fn ) ){ return; }
  117. filteredHdas = new HistoryContents( filteredHdas.filter( filter_fn ) );
  118. });
  119. return filteredHdas;
  120. },
  121. /** return true if any contents don't have details */
  122. haveDetails : function(){
  123. return this.all( function( content ){ return content.hasDetails(); });
  124. },
  125. // ........................................................................ ajax
  126. /** fetch detailed model data for all contents in this collection */
  127. fetchAllDetails : function( options ){
  128. options = options || {};
  129. var detailsFlag = { details: 'all' };
  130. options.data = ( options.data )?( _.extend( options.data, detailsFlag ) ):( detailsFlag );
  131. return this.fetch( options );
  132. },
  133. /** using a queue, perform ajaxFn on each of the models in this collection */
  134. ajaxQueue : function( ajaxFn, options ){
  135. var deferred = jQuery.Deferred(),
  136. startingLength = this.length,
  137. responses = [];
  138. if( !startingLength ){
  139. deferred.resolve([]);
  140. return deferred;
  141. }
  142. // use reverse order (stylistic choice)
  143. var ajaxFns = this.chain().reverse().map( function( content, i ){
  144. return function(){
  145. var xhr = ajaxFn.call( content, options );
  146. // if successful, notify using the deferred to allow tracking progress
  147. xhr.done( function( response ){
  148. deferred.notify({ curr: i, total: startingLength, response: response, model: content });
  149. });
  150. // (regardless of previous error or success) if not last ajax call, shift and call the next
  151. // if last fn, resolve deferred
  152. xhr.always( function( response ){
  153. responses.push( response );
  154. if( ajaxFns.length ){
  155. ajaxFns.shift()();
  156. } else {
  157. deferred.resolve( responses );
  158. }
  159. });
  160. };
  161. }).value();
  162. // start the queue
  163. ajaxFns.shift()();
  164. return deferred;
  165. },
  166. isCopyable : function( contentsJSON ){
  167. var copyableModelClasses = [
  168. 'HistoryDatasetAssociation',
  169. 'HistoryDatasetCollectionAssociation'
  170. ];
  171. return ( ( _.isObject( contentsJSON ) && contentsJSON.id )
  172. && ( _.contains( copyableModelClasses, contentsJSON.model_class ) ) );
  173. },
  174. /** copy an existing, accessible hda into this collection */
  175. copy : function( json ){
  176. var id, type, contentType;
  177. if( _.isString( json ) ){
  178. id = json;
  179. contentType = 'hda';
  180. type = 'dataset';
  181. } else {
  182. id = json.id;
  183. contentType = ({
  184. 'HistoryDatasetAssociation' : 'hda',
  185. 'LibraryDatasetDatasetAssociation' : 'ldda',
  186. 'HistoryDatasetCollectionAssociation' : 'hdca'
  187. })[ json.model_class ] || 'hda';
  188. type = ( contentType === 'hdca'? 'dataset_collection' : 'dataset' );
  189. }
  190. var collection = this,
  191. xhr = jQuery.post( this.url(), {
  192. content : id,
  193. source : contentType,
  194. type : type
  195. })
  196. .done( function( json ){
  197. collection.add([ json ]);
  198. })
  199. .fail( function( error, status, message ){
  200. collection.trigger( 'error', collection, xhr, {},
  201. 'Error copying contents', { type: type, id: id, source: contentType });
  202. });
  203. return xhr;
  204. },
  205. // ........................................................................ sorting/filtering
  206. /** return a new collection of contents whose attributes contain the substring matchesWhat */
  207. matches : function( matchesWhat ){
  208. return this.filter( function( content ){
  209. return content.matches( matchesWhat );
  210. });
  211. },
  212. // ........................................................................ misc
  213. /** override to get a correct/smarter merge when incoming data is partial */
  214. set : function( models, options ){
  215. this.debug( 'set:', models );
  216. // arrrrrrrrrrrrrrrrrg...
  217. // (e.g. stupid backbone)
  218. // w/o this partial models from the server will fill in missing data with model defaults
  219. // and overwrite existing data on the client
  220. // see Backbone.Collection.set and _prepareModel
  221. var collection = this;
  222. models = _.map( models, function( model ){
  223. var attrs = model.attributes || model,
  224. typeId = HISTORY_CONTENT.typeIdStr( attrs.history_content_type, attrs.id ),
  225. existing = collection.get( typeId );
  226. if( !existing ){ return model; }
  227. // merge the models _BEFORE_ calling the superclass version
  228. var merged = _.clone( existing.attributes );
  229. _.extend( merged, model );
  230. return merged;
  231. });
  232. // now call superclass when the data is filled
  233. Backbone.Collection.prototype.set.call( this, models, options );
  234. },
  235. /** Convert this ad-hoc collection of hdas to a formal collection tracked
  236. by the server.
  237. **/
  238. promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){
  239. //TODO: seems like this would be better in mvc/collections
  240. options = options || {};
  241. options.url = this.url();
  242. options.type = "POST";
  243. var full_collection_type = collection_type;
  244. var element_identifiers = [],
  245. name = null;
  246. // This mechanism is rough - no error handling, allows invalid selections, no way
  247. // for user to pick/override element identifiers. This is only really meant
  248. if( collection_type === "list" ) {
  249. this.chain().each( function( hda ) {
  250. // TODO: Handle duplicate names.
  251. var name = hda.attributes.name;
  252. var id = hda.get('id');
  253. var content_type = hda.attributes.history_content_type;
  254. if( content_type === "dataset" ) {
  255. if( full_collection_type !== "list" ) {
  256. this.log( "Invalid collection type" );
  257. }
  258. element_identifiers.push( { name: name, src: "hda", id: id } );
  259. } else {
  260. if( full_collection_type === "list" ) {
  261. full_collection_type = "list:" + hda.attributes.collection_type;
  262. } else {
  263. if( full_collection_type !== "list:" + hda.attributes.collection_type ) {
  264. this.log( "Invalid collection type" );
  265. }
  266. }
  267. element_identifiers.push( { name: name, src: "hdca", id: id } );
  268. }
  269. });
  270. name = "New Dataset List";
  271. } else if( collection_type === "paired" ) {
  272. var ids = this.ids();
  273. if( ids.length !== 2 ){
  274. // TODO: Do something...
  275. }
  276. element_identifiers.push( { name: "forward", src: "hda", id: ids[ 0 ] } );
  277. element_identifiers.push( { name: "reverse", src: "hda", id: ids[ 1 ] } );
  278. name = "New Dataset Pair";
  279. }
  280. options.data = {
  281. type: "dataset_collection",
  282. name: name,
  283. collection_type: full_collection_type,
  284. element_identifiers: JSON.stringify( element_identifiers )
  285. };
  286. var xhr = jQuery.ajax( options );
  287. xhr.done( function( message, status, responseObj ){
  288. history.refresh( );
  289. });
  290. xhr.fail( function( xhr, status, message ){
  291. if( xhr.responseJSON && xhr.responseJSON.error ){
  292. error = xhr.responseJSON.error;
  293. } else {
  294. error = xhr.responseJSON;
  295. }
  296. xhr.responseText = error;
  297. // Do something?
  298. });
  299. return xhr;
  300. },
  301. /** In this override, copy the historyId to the clone */
  302. clone : function(){
  303. var clone = Backbone.Collection.prototype.clone.call( this );
  304. clone.historyId = this.historyId;
  305. return clone;
  306. },
  307. /** debugging */
  308. print : function(){
  309. var contents = this;
  310. contents.each( function( c ){
  311. contents.debug( c );
  312. if( c.elements ){
  313. contents.debug( '\t elements:', c.elements );
  314. }
  315. });
  316. },
  317. /** String representation. */
  318. toString : function(){
  319. return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));
  320. }
  321. });
  322. //==============================================================================
  323. return {
  324. HistoryContents : HistoryContents
  325. };
  326. });