1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2008-2016 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 /**
 21  * @namespace The Experiment static class allows you to create hidden run groups and other experiment-related functionality.
 22  *            <p>Additional Documentation:
 23  *              <ul>
 24  *                  <li><a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>LabKey File-Based Assays</a></li>
 25  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
 26  *              </ul>
 27  *           </p>
 28  */
 29 LABKEY.Experiment = new function()
 30 {
 31     function getSuccessCallbackWrapper(createExpFn, fn, scope)
 32     {
 33         return function(response, options)
 34         {
 35             //ensure response is JSON before trying to decode
 36             var json = null;
 37             var experiment = null;
 38             if (response && response.getResponseHeader && response.getResponseHeader('Content-Type')
 39                     && response.getResponseHeader('Content-Type').indexOf('application/json') >= 0)
 40             {
 41                 json = LABKEY.Utils.decode(response.responseText);
 42                 experiment = createExpFn(json);
 43             }
 44 
 45             if(fn)
 46                 fn.call(scope || this, experiment, response);
 47         };
 48     }
 49 
 50     function _saveBatches(config, createExps)
 51     {
 52         LABKEY.Ajax.request({
 53             url: LABKEY.ActionURL.buildURL("assay", "saveAssayBatch", LABKEY.ActionURL.getContainer()),
 54             method: 'POST',
 55             jsonData: {
 56                 assayId: config.assayId,
 57                 assayName: config.assayName,
 58                 providerName: config.providerName,
 59                 batches: config.batches
 60             },
 61             success: getSuccessCallbackWrapper(createExps, config.success, config.scope),
 62             failure: LABKEY.Utils.getCallbackWrapper(config.failure, config.scope, true),
 63             scope: config.scope,
 64             headers: {
 65                 'Content-Type' : 'application/json'
 66             }
 67         });
 68     }
 69 
 70     // normalize the different config object passed in for saveBatch and saveBatches into one config
 71     // appropriate for _saveBatches call above
 72     function getSaveBatchesConfig(config)
 73     {
 74         var wrapConfig = {};
 75 
 76         if (config.batches)
 77         {
 78             wrapConfig.batches = config.batches;
 79         }
 80         else
 81         {
 82             wrapConfig.batches=[];
 83             wrapConfig.batches.push(config.batch);
 84         }
 85         wrapConfig.assayId = config.assayId;
 86         wrapConfig.assayName = config.assayName;
 87         wrapConfig.providerName = config.providerName;
 88         wrapConfig.scope = config.scope;
 89         wrapConfig.success = LABKEY.Utils.getOnSuccess(config);
 90         wrapConfig.failure = LABKEY.Utils.getOnFailure(config);
 91         return wrapConfig;
 92     }
 93 
 94     /** @scope LABKEY.Experiment */
 95     return {
 96 
 97         /**
 98          * Create or recycle an existing run group. Run groups are the basis for some operations, like comparing
 99          * MS2 runs to one another.
100          * @param config A configuration object with the following properties:
101          * @param {function} config.success A reference to a function to call with the API results. This
102          * function will be passed the following parameters:
103          * <ul>
104          * <li><b>runGroup:</b> a {@link LABKEY.Exp.RunGroup} object containing properties about the run group</li>
105          * <li><b>response:</b> The XMLHttpResponse object</li>
106          * </ul>
107          * @param {Integer[]} [config.runIds] An array of integer ids for the runs to be members of the group. Either
108          * runIds or selectionKey must be specified.
109          * @param {string} [config.selectionKey] The DataRegion's selectionKey to be used to resolve the runs to be
110          * members of the group. Either runIds or selectionKey must be specified.
111          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
112          * function will be passed the following parameters:
113          * <ul>
114          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
115          * <li><b>response:</b> The XMLHttpResponse object</li>
116          * </ul>
117          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
118          * the current container path will be used.
119          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
120          * @static
121          */
122         createHiddenRunGroup : function (config)
123         {
124             function createExp(json)
125             {
126                 return new LABKEY.Exp.RunGroup(json);
127             }
128 
129             var jsonData = {};
130             if (config.runIds && config.selectionKey)
131             {
132                 throw "Only one of runIds or selectionKey config parameter is allowed for a single call.";
133             }
134             else if (config.runIds)
135             {
136                 jsonData.runIds = config.runIds;
137             }
138             else if (config.selectionKey)
139             {
140                 jsonData.selectionKey = config.selectionKey;
141             }
142             else
143             {
144                 throw "Either the runIds or the selectionKey config parameter is required.";
145             }
146             LABKEY.Ajax.request(
147             {
148                 url : LABKEY.ActionURL.buildURL("experiment", "createHiddenRunGroup", config.containerPath),
149                 method : 'POST',
150                 jsonData : jsonData,
151                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
152                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
153                 headers :
154                 {
155                     'Content-Type' : 'application/json'
156                 }
157             });
158         },
159 
160         /**
161          * Loads a batch from the server.
162          * @param config An object that contains the following configuration parameters
163          * @param {Number} config.assayId The assay protocol id.
164          * @param {Number} config.batchId The batch id.
165          * @param {function} config.success The function to call when the function finishes successfully.
166          * This function will be called with a the parameters:
167          * <ul>
168          * <li><b>batch</b> A new {@link LABKEY.Exp.RunGroup} object.
169          * <li><b>response</b> The original response
170          * </ul>
171          * @param {function} [config.failure] The function to call if this function encounters an error.
172          * This function will be called with the following parameters:
173          * <ul>
174          * <li><b>response</b> The original response
175          * </ul>
176          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
177          * @see The <a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>Module Assay</a> documentation for more information.
178          * @static
179          */
180         loadBatch : function (config)
181         {
182             function createExp(json)
183             {
184                 return new LABKEY.Exp.RunGroup(json.batch);
185             }
186 
187             LABKEY.Ajax.request({
188                 url: LABKEY.ActionURL.buildURL("assay", "getAssayBatch", LABKEY.ActionURL.getContainer()),
189                 method: 'POST',
190                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
191                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
192                 scope: config.scope,
193                 jsonData : {
194                     assayId: config.assayId,
195                     assayName: config.assayName,
196                     providerName: config.providerName,
197                     batchId: config.batchId
198                 },
199                 headers : {
200                     'Content-Type' : 'application/json'
201                 }
202             });
203         },
204 
205         /**
206          * Loads batches from the server.
207          * @param config An object that contains the following configuration parameters
208          * @param {Number} config.assayId The assay protocol id.
209          * @param {Number} config.batchIds The list of batch ids.
210          * @param {function} config.success The function to call when the function finishes successfully.
211          * This function will be called with a the parameters:
212          * <ul>
213          * <li><b>batches</b> The list of {@link LABKEY.Exp.RunGroup} objects.
214          * <li><b>response</b> The original response
215          * </ul>
216          * @param {function} [config.failure] The function to call if this function encounters an error.
217          * This function will be called with the following parameters:
218          * <ul>
219          * <li><b>response</b> The original response
220          * </ul>
221          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
222          * @see The <a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>Module Assay</a> documentation for more information.
223          * @static
224          */
225         loadBatches : function (config)
226         {
227             function createExp(json)
228             {
229                 var batches = [];
230                 if (json.batches) {
231                     for (var i = 0; i < json.batches.length; i++) {
232                         batches.push(new LABKEY.Exp.RunGroup(json.batches[i]));
233                     }
234                 }
235                 return batches;
236             }
237 
238             LABKEY.Ajax.request({
239                 url: LABKEY.ActionURL.buildURL("assay", "getAssayBatches", LABKEY.ActionURL.getContainer()),
240                 method: 'POST',
241                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
242                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
243                 scope: config.scope,
244                 jsonData : {
245                     assayId: config.assayId,
246                     assayName: config.assayName,
247                     providerName: config.providerName,
248                     batchIds: config.batchIds
249                 },
250                 headers : {
251                     'Content-Type' : 'application/json'
252                 }
253             });
254         },
255 
256         /**
257          * Saves a modified batch.
258          * Runs within the batch may refer to existing data and material objects, either inputs or outputs, by ID or LSID.
259          * Runs may also define new data and materials objects by not specifying an ID or LSID in their properties.
260          * @param config An object that contains the following configuration parameters
261          * @param {Number} config.assayId The assay protocol id.
262          * @param {LABKEY.Exp.RunGroup} config.batch The modified batch object.
263          * @param {function} config.success The function to call when the function finishes successfully.
264          * This function will be called with the following parameters:
265          * <ul>
266          * <li><b>batch</b> A new {@link LABKEY.Exp.RunGroup} object.  Some values (such as IDs and LSIDs) will be filled in by the server.
267          * <li><b>response</b> The original response
268          * </ul>
269          * @param {function} [config.failure] The function to call if this function encounters an error.
270          * This function will be called with the following parameters:
271          * <ul>
272          * <li><b>response</b> The original response
273          * </ul>
274          * @see The <a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>Module Assay</a> documentation for more information.
275          * @static
276          */
277         saveBatch : function (config)
278         {
279             _saveBatches(getSaveBatchesConfig(config), function(json) {
280                 if (json.batches) {
281                     return new LABKEY.Exp.RunGroup(json.batches[0])
282                 }
283              });
284         },
285 
286         /**
287          * Saves an array of modified batches.
288          * Runs within the batches may refer to existing data and material objects, either inputs or outputs, by ID or LSID.
289          * Runs may also define new data and materials objects by not specifying an ID or LSID in their properties.
290          * @param config An object that contains the following configuration parameters
291          * @param {Number} config.assayId The assay protocol id.
292          * @param {LABKEY.Exp.RunGroup[]} config.batches The modified batch objects.
293          * @param {function} config.success The function to call when the function finishes successfully.
294          * This function will be called with the following parameters:
295          * <ul>
296          * <li><b>batches</b> An array of new {@link LABKEY.Exp.RunGroup} objects.  Some values (such as IDs and LSIDs) will be filled in by the server.
297          * <li><b>response</b> The original response
298          * </ul>
299          * @param {function} [config.failure] The function to call if this function encounters an error.
300          * This function will be called with the following parameters:
301          * <ul>
302          * <li><b>response</b> The original response
303          * </ul>
304          * @see The <a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>Module Assay</a> documentation for more information.
305          * @static
306          */
307         saveBatches : function (config)
308         {
309             _saveBatches(getSaveBatchesConfig(config), function(json){
310                 var batches = [];
311                 if (json.batches) {
312                     for (var i = 0; i < json.batches.length; i++) {
313                         batches.push(new LABKEY.Exp.RunGroup(json.batches[i]));
314                     }
315                 }
316                 return batches;
317             });
318         },
319 
320         /**
321          * Saves materials.
322          * @deprecated Use LABKEY.Query.insertRows({schemaName: 'Samples', queryName: '<sample set name>', ...});
323          *
324          * @param config An object that contains the following configuration parameters
325          * @param config.name name of the sample set
326          * @param config.materials An array of LABKEY.Exp.Material objects to be saved.
327          * @param {function} config.success The function to call when the function finishes successfully.
328          * This function will be called with the following parameters:
329          * <ul>
330          * <li><b>batch</b> A new {@link LABKEY.Exp.RunGroup} object.  Some values will be filled in by the server.
331          * <li><b>response</b> The original response
332          * </ul>
333          * @param {function} [config.failure] The function to call if this function encounters an error.
334          * This function will be called with the following parameters:
335          * <ul>
336          * <li><b>response</b> The original response
337          * </ul>
338          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
339          * @static
340          */
341         saveMaterials : function (config)
342         {
343             LABKEY.Query.insertRows({
344                 schemaName: 'Samples',
345                 queryName: config.name,
346                 rows: config.materials,
347                 success: LABKEY.Utils.getOnSuccess(config),
348                 failure: LABKEY.Utils.getOnFailure(config),
349                 scope: config.scope
350             });
351         },
352 
353         /**
354          * Get parent/child relationships of an ExpData or ExpMaterial.
355          * @param config
356          * @param config.rowId The row id of the seed ExpData or ExpMaterial.  Either rowId or lsid is required.
357          * @param config.lsid The LSID of the seed ExpData or ExpMaterial.  Either rowId or lsid is required.
358          * @param {Number} [config.depth] An optional depth argument.  Defaults to include all.
359          * @param {Boolean} [config.parents] Include parents in the lineage response.  Defaults to true.
360          * @param {Boolean} [config.children] Include children in the lineage response.  Defaults to true.
361          * @param {String} [config.expType] Optional experiment type to filter response -- either "Data", "Material", or "ExperimentRun".  Defaults to include all.
362          * @param {String} [config.cpasType] Optional LSID of a SampleSet or DataClass to filter the response.  Defaults to include all.
363          * @static
364          */
365         lineage : function (config)
366         {
367             var params = {};
368             if (config.rowId)
369                 params.rowId = config.rowId;
370             else if (config.lsid)
371                 params.lsid = config.lsid;
372 
373             if (config.hasOwnProperty('parents'))
374                 params.parents = config.parents;
375             if (config.hasOwnProperty('children'))
376                 params.children = config.children;
377             if (config.hasOwnProperty('depth'))
378                 params.depth = config.depth;
379 
380             if (config.expType)
381                 params.expType = config.expType;
382             if (config.cpasType)
383                 params.cpasType = config.cpasType;
384 
385             LABKEY.Ajax.request({
386                 method: 'GET',
387                 url: LABKEY.ActionURL.buildURL("experiment", "lineage.api"),
388                 params: params,
389                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
390                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
391                 scope: config.scope
392             });
393         }
394     };
395 
396 };
397 
398 if (typeof LABKEY.Exp == "undefined")
399     LABKEY.Exp = {};
400 
401 /**
402  * This constructor isn't called directly, but is used by derived classes.
403  * @class The experiment object base class describes basic
404  * characteristics of a protocol or an experimental run.  Many experiment classes (such as {@link LABKEY.Exp.Run},
405  * {@link LABKEY.Exp.Data} and {@link LABKEY.Exp.Material}) are subclasses
406  * of ExpObject, so they provide the fields defined by this object (e.g., name, lsid, etc).
407  * In a Java representation of these same classes, ExpObject is an abstract class.
408  *            <p>Additional Documentation:
409  *              <ul>
410  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
411  *              </ul>
412  *           </p>
413  * @memberOf LABKEY.Exp
414  *
415  * @param {Object} [config] Configuration object.
416  *
417  * @param {String} config.lsid The LSID of the ExpObject.
418  * @param {String} config.name The name of the ExpObject.
419  * @param {number} config.id The id of the ExpObject.
420  * @param {number} config.rowId The id of the ExpObject (alias of id property)
421  * @param {String} config.comment User editable comment.
422  * @param {Date} config.created When the ExpObject was created.
423  * @param {String} config.createdBy The person who created the ExpObject.
424  * @param {Date} config.modified When the ExpObject was last modified.
425  * @param {String} config.modifiedBy The person who last modified the ExpObject.
426  * @param {Object} config.properties Map of property descriptor names to values. Most types, such as strings and
427  * numbers, are just stored as simple properties. Properties of type FileLink will be returned by the server in the
428  * same format as {@link LABKEY.Exp.Data} objects (missing many properties such as id and createdBy if they exist on disk but
429  * have no row with metadata in the database). FileLink values are accepted from the client in the same way, or a simple value of the
430  * following three types:  the data's RowId, the data's LSID, or the full path on the server's file system.
431  */
432 LABKEY.Exp.ExpObject = function (config) {
433     config = config || {};
434     this.lsid = config.lsid;
435     this.name = config.name;
436     this.id = config.id || config.rowId;
437     this.rowId = this.id;
438     this.comment = config.comment;
439     this.created = config.created;
440     this.createdBy = config.createdBy;
441     this.modified = config.modified;
442     this.modifiedBy = config.modifiedBy;
443     this.properties = config.properties || {};
444 };
445 
446 /**
447  * Constructs a new experiment run object.
448  * @class The Exp.Run class describes an experiment run.  An experiment run is an application of an experimental
449  * protocol to concrete inputs, producing concrete outputs. In object-oriented terminology, a protocol would be a class
450  * while a run would be an instance.
451  *            <p>Additional Documentation:
452  *              <ul>
453  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
454  *              </ul>
455  *           </p>
456  * @extends LABKEY.Exp.ExpObject
457  * @memberOf LABKEY.Exp
458  *
459  * @param {Object} [config] The configuration object.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
460  * @param {Object[]} config.dataInputs Array of {@link LABKEY.Exp.Data} objects that are the inputs to this run. Datas typically represent a file on the server's file system.
461  * @param {Object[]} config.dataOutputs Array of {@link LABKEY.Exp.Data} objects that are the outputs from this run. Datas typically represent a file on the server's file system.
462  * @param {Object[]} config.dataRows Array of Objects where each Object corresponds to a row in the results domain.
463  * @param {Object[]} config.materialInputs Array of {@link LABKEY.Exp.Material} objects that are material/sample inputs to the run.
464  * @param {Object[]} config.materialOutputs Array of {@link LABKEY.Exp.Material} objects that are material/sample outputs from the run.
465  *
466  * @see LABKEY.Exp.Data#getContent
467  *
468  * @example
469  * var result = // ... result of uploading a new assay results file
470  * var data = new LABKEY.Exp.Data(result);
471  *
472  * var run = new LABKEY.Exp.Run();
473  * run.name = data.name;
474  * run.properties = { "MyRunProperty" : 3 };
475  * run.dataInputs = [ data ];
476  *
477  * data.getContent({
478  *   format: 'jsonTSV',
479  *   success: function (content, format) {
480  *     data.content = content;
481  *     var sheet = content.sheets[0];
482  *     var filedata = sheet.data;
483  *
484  *     // transform the file content into the dataRows array used by the run
485  *     run.dataRows = [];
486  *     for (var i = 1; i < filedata.length; i++) {
487  *       var row = filedata[i];
488  *       run.dataRows.push({
489  *         "SampleId": row[0],
490  *         "DataValue": row[1],
491  *         // ... other columns
492  *       });
493  *     }
494  *
495  *     var batch = // ... the LABKEY.Exp.RunGroup object
496  *     batch.runs.push(run);
497  *   },
498  *   failure: function (error, format) {
499  *     alert("error: " + error);
500  *   }
501  * });
502  */
503 LABKEY.Exp.Run = function (config) {
504     LABKEY.Exp.ExpObject.call(this, config);
505     config = config || {};
506 
507     this.experiments = config.experiments || [];
508     this.protocol = config.protocol;
509     this.filePathRoot = config.filePathRoot;
510 
511     this.dataInputs = [];
512     if (config.dataInputs) {
513         for (var i = 0; i < config.dataInputs.length; i++) {
514             this.dataInputs.push(new LABKEY.Exp.Data(config.dataInputs[i]));
515         }
516     }
517 
518     this.dataOutputs = config.dataOutputs || [];
519     this.dataRows = config.dataRows || [];
520     this.materialInputs = config.materialInputs || [];
521     this.materialOutputs = config.materialOutputs || [];
522     this.objectProperties = config.objectProperties || {};
523 };
524 LABKEY.Exp.Run.prototype = new LABKEY.Exp.ExpObject;
525 LABKEY.Exp.Run.prototype.constructor = LABKEY.Exp.Run;
526 
527 /**
528  * Deletes the run from the database.
529  * @param config An object that contains the following configuration parameters
530  * @param {Function} config.success A reference to a function to call with the API results. This
531  * function will be passed the following parameters:
532  * <ul>
533  * <li><b>data:</b> a simple object with one property called 'success' which will be set to true.</li>
534  * <li><b>response:</b> The XMLHttpResponse object</li>
535  * </ul>
536  * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
537  * function will be passed the following parameters:
538  * <ul>
539  * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
540  * <li><b>response:</b> The XMLHttpResponse object</li>
541  * </ul>
542  */
543 LABKEY.Exp.Run.prototype.deleteRun = function(config)
544 {
545     LABKEY.Ajax.request(
546     {
547         url : LABKEY.ActionURL.buildURL("experiment", "deleteRun"),
548         method : 'POST',
549         params : { runId : this.id },
550         success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), this, false),
551         failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), this, true)
552     });
553 };
554 
555 
556 
557 /**
558  * The Protocol constructor is private.
559  * @class Experiment protocol.
560  * @extends LABKEY.Exp.ExpObject
561  * @memberOf LABKEY.Exp
562  *
563  * @param {Object} [config] private constructor argument.  Inherits config properties of {@link LABKEY.Exp.ExpObject}.
564  *
565  * @ignore hide from JsDoc for now
566  */
567 LABKEY.Exp.Protocol = function (config) {
568     LABKEY.Exp.ExpObject.call(this, config);
569     config = config || {};
570 
571     this.instrument = config.instrument;
572     this.software = config.software;
573     this.contact = config.contact;
574     this.childProtocols = config.childProtocols || [];
575     this.steps = config.steps || [];
576     this.applicationType = config.applicationType;
577     this.description = config.description;
578     this.runs = [];
579     if (config.runs) {
580         for (var i = 0; i < config.runs.length; i++) {
581             this.runs.push(new LABKEY.Exp.Run(config.runs[i]));
582         }
583     }
584 };
585 LABKEY.Exp.Protocol.prototype = new LABKEY.Exp.ExpObject;
586 LABKEY.Exp.Protocol.prototype.constructor = LABKEY.Exp.Protocol;
587 
588 /**
589  * The RunGroup constructor is private.  To retrieve a batch RunGroup
590  * from the server, see {@link LABKEY.Experiment.loadBatch}.
591  * @class An experiment run group contains an array of
592  * {@link LABKEY.Exp.Run}s.  If all runs have the same assay protocol, the run group
593  * is considered a batch.  To add runs to a batch, insert new {@link LABKEY.Exp.Run}
594  * instances into to the 'runs' Array and save the batch.
595  * <p>
596  * Use {@link LABKEY.Experiment.loadBatch} and {@link LABKEY.Experiment.saveBatch} to
597  * load and save a RunGroup.
598  * </p>
599  *            <p>Additional Documentation:
600  *              <ul>
601  *                  <li><a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>LabKey File-Based Assays</a></li>
602  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
603  *              </ul>
604  *           </p>
605  * @extends LABKEY.Exp.ExpObject
606  * @memberOf LABKEY.Exp
607  *
608  * @param {Object} [config] Private configuration object. Inherits config properties of {@link LABKEY.Exp.ExpObject}.
609  * @param {LABKEY.Exp.Run[]} config.runs Array of {@link LABKEY.Exp.Run}s in this run group.
610  * @param {Boolean} config.hidden Determines whether the RunGroup is hidden.
611  */
612 LABKEY.Exp.RunGroup = function (config) {
613     LABKEY.Exp.ExpObject.call(this, config);
614     config = config || {};
615 
616     this.batchProtocolId = config.batchProtocolId || 0;
617     this.runs = [];
618     if (config.runs) {
619         for (var i = 0; i < config.runs.length; i++) {
620             this.runs.push(new LABKEY.Exp.Run(config.runs[i]));
621         }
622     }
623     //this.protocols = config.protocols || [];
624     //this.batchProtocol = config.batchProtocol;
625     this.hidden = config.hidden;
626 };
627 LABKEY.Exp.RunGroup.prototype = new LABKEY.Exp.ExpObject;
628 LABKEY.Exp.RunGroup.prototype.constructor = LABKEY.Exp.RunGroup;
629 
630 /**
631  * The ProtocolApplication constructor is private.
632  * @class Experiment ProtocolApplication.
633  * @extends LABKEY.Exp.ExpObject
634  * @memberOf LABKEY.Exp
635  *
636  * @param {Object} [config] Private configuration object. Inherits config properties of {@link LABKEY.Exp.ExpObject}.
637  *
638  * @ignore hide from JsDoc for now
639  */
640 LABKEY.Exp.ProtocolApplication = function (config) {
641     LABKEY.Exp.ExpObject.call(this, config);
642     config = config || {};
643 
644 };
645 LABKEY.Exp.ProtocolApplication.prototype = new LABKEY.Exp.ExpObject;
646 LABKEY.Exp.ProtocolApplication.prototype.constructor = LABKEY.Exp.ProtocolApplication;
647 
648 /**
649  * @class The SampleSet class describes a collection of experimental samples, which are
650  * also known as materials (see {@link LABKEY.Exp.Material}). This class defines the set of fields that
651  * you you wish to attach to all samples in the group. These fields supply characteristics of the sample
652  * (e.g., its volume, number of cells, color, etc.).
653  *            <p>Additional Documentation:
654  *              <ul>
655  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
656  *              </ul>
657  *           </p>
658  * @extends LABKEY.Exp.ExpObject
659  * @memberOf LABKEY.Exp
660  *
661  * @param {Object} [config] Describes the SampleSet's properties.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
662  * @param {Object[]} config.samples Array of {@link LABKEY.Exp.Material} config objects.
663  * @param {String} config.description Description of the SampleSet
664  */
665 
666 LABKEY.Exp.SampleSet = function (config) {
667     LABKEY.Exp.ExpObject.call(this, config);
668     config = config || {};
669     this.samples = config.samples;
670     this.description = config.description;
671 };
672 LABKEY.Exp.SampleSet.prototype = new LABKEY.Exp.ExpObject;
673 LABKEY.Exp.SampleSet.prototype.constructor = LABKEY.Exp.SampleSet;
674 
675 /**
676  * Get a domain design for the SampleSet.
677  *
678  * @param {Function} config.success Required. Function called if the
679  *	"getDomain" function executes successfully. Will be called with the argument {@link LABKEY.Domain.DomainDesign},
680  *    which describes the fields of a domain.
681  * @param {Function} [config.failure] Function called if execution of the "getDomain" function fails.
682  * @param {String} [config.containerPath] The container path in which the requested Domain is defined.
683  *       If not supplied, the current container path will be used.
684  *
685  * @ignore hide from JsDoc for now
686  *
687  * @example
688  * <script type="text/javascript">
689  *   Ext.onReady(function() {
690  *     var ss = new LABKEY.Exp.SampleSet({name: 'MySampleSet'});
691  *     ss.getDomain({
692  *       success : function (domain) {
693  *         console.log(domain);
694  *       }
695  *     });
696  *   }
697  * </script>
698  */
699 LABKEY.Exp.SampleSet.prototype.getDomain = function (config)
700 {
701     LABKEY.Domain.get(LABKEY.Utils.getOnSuccess(config), LABKEY.Utils.getOnFailure(config), "Samples", this.name, config.containerPath);
702 };
703 
704 /**
705  * Create a new Sample Set definition.
706  * @param {Function} config.success Required callback function.
707  * @param {Function} [config.failure] Failure callback function.
708  * @param {LABKEY.Domain.DomainDesign} config.domainDesign The domain design to save.
709  * @param {Object} [config.options] Set of extra options used when creating the SampleSet:
710  * <ul>
711  *   <li>idCols: Optional. Array of indexes into the domain design fields.  If the domain design contains a 'Name' field, no idCols are allowed.  Either a 'Name' field must be present or at least one idCol must be supplied..
712  *   <li>parentCol: Optional. Index of the parent id column.
713  * </ul>
714  * @param {String} [config.containerPath] The container path in which to create the domain.
715  * @static
716  *
717  * @ignore hide from JsDoc for now
718  *
719  * @example
720  * var domainDesign = {
721  *   name: "BoyHowdy",
722  *   description: "A client api created sample set",
723  *   fields: [{
724  *     name: 'TestName',
725  *     label: 'The First Field',
726  *     rangeURI: 'http://www.w3.org/2001/XMLSchema#string'
727  *   },{
728  *     name: 'Num',
729  *     rangeURI: 'http://www.w3.org/2001/XMLSchema#int'
730  *   },{
731  *     name: 'Parent',
732  *     rangeURI: 'http://www.w3.org/2001/XMLSchema#string'
733  *   }]
734  * };
735  *
736  * LABKEY.Exp.SampleSet.create({
737  *   success: function () { alert("success!"); },
738  *   failure: function () { alert("failure!"); },
739  *   domainDesign: domainDesign,
740  *   options: { idCols: [0, 1], parentCol: 2 }
741  * });
742  */
743 LABKEY.Exp.SampleSet.create = function (config)
744 {
745     LABKEY.Domain.create(LABKEY.Utils.getOnSuccess(config), LABKEY.Utils.getOnFailure(config), "SampleSet", config.domainDesign, config.options, config.containerPath);
746 };
747 
748 /**
749  * DataClass represents a set of ExpData objects that share a set of properties.
750  *
751  * @class DataClass describes a collection of Data objects.
752  * This class defines the set of fields that you you wish to attach to all datas in the group.
753  * Within the DataClass, each Data has a unique name.
754  *
755  * @extends LABKEY.Exp.ExpObject
756  * @memberOf LABKEY.Exp
757  *
758  * @param config
759  * @param {String} config.description Description of the DataClass.
760  * @param {String} [config.nameExpression] Optional name expression used to generate unique names for ExpData inserted into the DataClass.
761  * @param {Object} [config.sampleSet] The optional SampleSet the DataClass is associated with.  With the following properties:
762  * @param {Integer} [config.sampleSet.id] The row id of the SampleSet.
763  * @param {String} [config.sampleSet.name] The name of the SampleSet.
764  * @constructor
765  */
766 LABKEY.Exp.DataClass = function (config)
767 {
768     "use strict";
769 
770     LABKEY.Exp.ExpObject.call(this, config);
771     config = config || {};
772     this.data = config.data;
773     this.description = config.description;
774     this.sampleSet = config.sampleSet;
775 };
776 LABKEY.Exp.DataClass.prototype = new LABKEY.Exp.ExpObject;
777 LABKEY.Exp.DataClass.prototype.constructor = LABKEY.Exp.DataClass;
778 
779 LABKEY.Exp.DataClass.prototype.getDomain = function (config)
780 {
781     "use strict";
782     LABKEY.Domain.get({
783         success: LABKEY.Utils.getOnSuccess(config),
784         failure: LABKEY.Utils.getOnFailure(config),
785         schemaName: "exp.data",
786         queryName: this.name,
787         containerPath: config.containerPath
788     });
789 };
790 
791 LABKEY.Exp.DataClass.create = function (config)
792 {
793     "use strict";
794     LABKEY.Domain.create({
795         success: LABKEY.Utils.getOnSuccess(config),
796         failure: LABKEY.Utils.getOnFailure(config),
797         type: "dataclass",
798         domainDesign: config.domainDesign,
799         options: config.options,
800         containerPath: config.containerPath
801     });
802 };
803 
804 /**
805  * The ChildObject constructor is private.
806  * @class Experiment Child
807  * @extends LABKEY.Exp.ExpObject
808  * @memberOf LABKEY.Exp
809  *
810  * @param {Object} [config] Private configuration object. Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
811  *
812  * @ignore hide from JsDoc for now
813  */
814 LABKEY.Exp.ChildObject = function (config) {
815     LABKEY.Exp.ExpObject.call(this, config);
816     config = config || {};
817     // property holder
818 };
819 LABKEY.Exp.ChildObject.prototype = new LABKEY.Exp.ExpObject;
820 LABKEY.Exp.ChildObject.prototype.constructor = LABKEY.Exp.ChildObject;
821 
822 /**
823  * The ProtocolOutput constructor is private.
824  * @class Experiment Protocol Output.  Base class for {@link LABKEY.Exp.Data}
825  * and {@link LABKEY.Exp.Material}.
826  * @extends LABKEY.Exp.ExpObject
827  * @memberOf LABKEY.Exp
828  * @param {Object} [config] Private configuration object. Inherits config properties of {@link LABKEY.Exp.ExpObject}.
829  * @ignore hide from JsDoc for now
830  */
831 LABKEY.Exp.ProtocolOutput = function (config) {
832     LABKEY.Exp.ExpObject.call(this, config);
833     config = config || {};
834 
835     this.sourceProtocol = config.sourceProtocol;
836     this.run = config.run;
837     this.targetApplications = config.targetApplications;
838     this.sourceApplications = config.sourceApplications;
839     this.sucessorRuns = config.sucessorRuns;
840     this.cpasType = config.cpasType;
841 };
842 LABKEY.Exp.ProtocolOutput.prototype = new LABKEY.Exp.ExpObject;
843 LABKEY.Exp.ProtocolOutput.prototype.constructor = LABKEY.Exp.ProtocolOutput;
844 
845 /**
846  * Constructs a new experiment material object.
847  * @class The Exp.Material class describes an experiment material.  "Material" is a synonym for both
848  * "sample" and "specimen."  Thus, for example, the input to an assay could be called a material.
849  * The fields of this class are inherited from the {@link LABKEY.Exp.ExpObject} object and
850  * the private LABKEY.Exp.ProtocolOutput object.
851  *            <p>Additional Documentation:
852  *              <ul>
853  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
854  *              </ul>
855  *           </p>
856  * @extends LABKEY.Exp.ProtocolOutput
857  * @extends LABKEY.Exp.ExpObject
858  * @memberOf LABKEY.Exp
859  *
860  * @param {Object} [config] Configuration object.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
861  * @param {Object} [config.sampleSet] The SampleSet the material belongs to.  With the following properties:
862  * @param {Integer} [config.sampleSet.id] The row id of the SampleSet.
863  * @param {String} [config.sampleSet.name] The name of the SampleSet.
864  */
865 LABKEY.Exp.Material = function (config) {
866     LABKEY.Exp.ProtocolOutput.call(this, config);
867     config = config || {};
868 
869     this.sampleSet = config.sampleSet;
870 };
871 LABKEY.Exp.Material.prototype = new LABKEY.Exp.ProtocolOutput;
872 LABKEY.Exp.Material.prototype.constructor = LABKEY.Exp.Material;
873 
874 /**
875  * The Data constructor is private.
876  * To create a LABKEY.Exp.Data object, upload a file using to the "assayFileUpload" action of
877  * the "assay" controller.
878  *
879  * @class The Experiment Data class describes the data input or output of a {@link LABKEY.Exp.Run}.  This typically
880  * corresponds to an assay results file uploaded to the LabKey server.
881  * <p>
882  * To create a LABKEY.Exp.Data object, upload a file using to the "assayFileUpload" action of
883  * the "assay" controller.
884  * </p>
885  *            <p>Additional Documentation:
886  *              <ul>
887  *                  <li><a href='https://www.labkey.org/wiki/home/Documentation/page.view?name=moduleassay'>LabKey File-Based Assays</a></li>
888  *                  <li><a href="https://www.labkey.org/wiki/home/Documentation/page.view?name=experiment">LabKey Experiment</a></li>
889  *              </ul>
890  *           </p>
891  *
892  * @extends LABKEY.Exp.ProtocolOutput
893  * @extends LABKEY.Exp.ExpObject
894  * @memberOf LABKEY.Exp
895  *
896  * @param {Object} [config] Private configuration object.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
897  * @param {String} config.dataFileURL The local file url of the uploaded file.
898  * @param {Object} [config.dataClass] The DataClass the data belongs to.  With the following properties:
899  * @param {Integer} [config.dataClass.id] The row id of the DataClass.
900  * @param {String} [config.dataClass.name] The name of the DataClass.
901  *
902  * @example
903  * // To perform a file upload over HTTP:
904  * <form id="upload-run-form" enctype="multipart/form-data" method="POST">
905  *   <div id="upload-run-button"></div>
906  * </form>
907  * <script type="text/javascript">
908  *    LABKEY.Utils.requiresScript("FileUploadField.js");
909  *    // Optional - specify a protocolId so that the Exp.Data object is assigned the related LSID namespace.
910  *    var url = LABKEY.ActionURL.buildURL("assay", "assayFileUpload", LABKEY.ActionURL.getContainer(), { protocolId: 50 });
911  *    Ext.onReady(function() {
912  *       var form = new Ext.form.BasicForm(
913  *       Ext.get("upload-run-form"), {
914  *          fileUpload: true,
915  *          frame: false,
916  *          url: url,
917  *          listeners: {
918  *             actioncomplete : function (form, action) {
919  *                alert('Upload successful!');
920  *                var data = new LABKEY.Exp.Data(action.result);
921  *
922  *                // now add the data as a dataInput to a LABKEY.Exp.Run
923  *                var run = new LABKEY.Exp.Run();
924  *                run.name = data.name;
925  *                run.dataInputs = [ data ];
926  *
927  *                // add the new run to a LABKEY.Exp.Batch object and
928  *                // fetch the parsed file contents from the data object
929  *                // using the LABKEY.Exp.Data#getContent() method.
930  *             },
931  *             actionfailed: function (form, action) {
932  *                alert('Upload failed!');
933  *             }
934  *          }
935  *       });
936  *
937  *       var uploadField = new Ext.form.FileUploadField({
938  *          id: "upload-run-field",
939  *          renderTo: "upload-run-button",
940  *          buttonText: "Upload Data...",
941  *          buttonOnly: true,
942  *          buttonCfg: { cls: "labkey-button" },
943  *          listeners: {
944  *             "fileselected": function (fb, v) {
945  *                form.submit();
946  *             }
947  *          }
948  *       });
949  *    });
950  * </script>
951  *
952  * // Or, to upload the contents of a JavaScript string as a file:
953  * <script type="text/javascript">
954  * Ext.onReady(function() {
955  *    LABKEY.Ajax.request({
956  *      url: LABKEY.ActionURL.buildURL("assay", "assayFileUpload"),
957  *      params: { fileName: 'test.txt', fileContent: 'Some text!' },
958  *      success: function(response, options) {
959  *         var data = new LABKEY.Exp.Data(Ext.util.JSON.decode(response.responseText));
960  *
961  *         // now add the data as a dataInput to a LABKEY.Exp.Run
962  *         var run = new LABKEY.Exp.Run();
963  *         run.name = data.name;
964  *         run.dataInputs = [ data ];
965  *
966  *         // add the new run to a LABKEY.Exp.Batch object here
967  *      }
968  *    });
969  *  });
970  *
971  * </script>
972  */
973 LABKEY.Exp.Data = function (config) {
974     LABKEY.Exp.ProtocolOutput.call(this, config);
975     config = config || {};
976 
977     this.dataType = config.dataType;
978     this.dataFileURL = config.dataFileURL;
979     this.dataClass = config.dataClass;
980     if (config.pipelinePath)
981         this.pipelinePath = config.pipelinePath;
982     if (config.role)
983         this.role = config.role;
984 };
985 LABKEY.Exp.Data.prototype = new LABKEY.Exp.ProtocolOutput;
986 LABKEY.Exp.Data.prototype.constructor = LABKEY.Exp.Data;
987 
988 /**
989  * Retrieves the contents of the data object from the server.
990  * @param config An object that contains the following configuration parameters
991  * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
992  * @param {function} config.success The function to call when the function finishes successfully.
993  * This function will be called with the parameters:
994  * <ul>
995  * <li><b>content</b> The type of the content varies based on the format requested.
996  * <li><b>format</b> The format used in the request
997  * <li><b>response</b> The original response
998  * </ul>
999  * @param {function} [config.failure] The function to call if this function encounters an error.
1000  * This function will be called with the following parameters:
1001  * <ul>
1002  * <li><b>errorInfo:</b> An object with a property called "exception," which contains the error message.</li>
1003  * <li><b>format</b> The format used in the request
1004  * <li><b>response</b> The original response
1005  * </ul>
1006  * @param {String} [config.format] How to format the content. Defaults to plaintext, supported for text/* MIME types,
1007  * including .html, .xml, .tsv, .txt, and .csv. Use 'jsonTSV' to get a JSON version of the .xls, .tsv, .or .csv
1008  * files, the structure of which matches the argument to convertToExcel in {@link LABKEY.Utils}.
1009  * <ul>
1010  * <li><b>fileName:</b> the name of the file</li>
1011  * <li><b>sheets:</b> an array of the sheets in the file. Text file types will have a single sheet named 'flat'.
1012  * <ul><li><b>name:</b> the name of the sheet</li>
1013  *     <li><b>values:</b> two-dimensional array of all the cells in the worksheet. First array index is row, second is column</li>
1014  * </ul>
1015  * </ul>
1016  * <br/>Use 'jsonTSVExtended' to get include metadata in the 2D array of cells.
1017  * Text file types will not supply additional metadata but populate the 'value' attribute in the map.
1018  * Excel files will include:
1019  * <ul>
1020  * <li><b>value:</b> the string, boolean, date, or number in the cell</li>
1021  * <li><b>timeOnly:</b> whether the date part should be ignored for dates</li>
1022  * <li><b>formatString:</b> the Java format string to be used to render the value for dates and numbers</li>
1023  * <li><b>formattedValue:</b> the formatted string for that value for all value types</li>
1024  * <li><b>error:</b> true if this cell has an error</li>
1025  * <li><b>formula:</b> if the cell's value is specified by a formula, the text of the formula</li>
1026  * </ul>
1027  * <br/>Use 'jsonTSVIgnoreTypes' to always return string values for all cells, regardless of type.
1028  * <br/>
1029  * An example of the results for a request for 'jsonTsv' format:
1030  * <pre>
1031  * {
1032 "sheets": [
1033     {
1034         "name": "Sheet1",
1035         "data": [
1036             [
1037                 "StringColumn",
1038                 "DateColumn"
1039             ],
1040             [
1041                 "Hello",
1042                 "16 May 2009 17:00:00"
1043             ],
1044             [
1045                 "world",
1046                 "12/21/2008 08:45AM"
1047             ]
1048         ]
1049     },
1050     {
1051         "name": "Sheet2",
1052         "data": [
1053             ["NumberColumn"],
1054             [55.44],
1055             [100.34],
1056             [-1]
1057         ]
1058     },
1059     {
1060         "name": "Sheet3",
1061         "data": []
1062     }
1063 ],
1064 "fileName": "SimpleExcelFile.xls"
1065 }</pre>
1066  <br/>
1067  An example of the same file in the 'jsonTSVExtended' format:
1068  <pre>
1069  * {
1070 "sheets": [
1071     {
1072         "name": "Sheet1",
1073         "data": [
1074             [
1075                 {
1076                     "value": "StringColumn",
1077                     "formattedValue": "StringColumn"
1078                 },
1079                 {
1080                     "value": "DateColumn",
1081                     "formattedValue": "DateColumn"
1082                 }
1083             ],
1084             [
1085                 {
1086                     "value": "Hello",
1087                     "formattedValue": "Hello"
1088                 },
1089                 {
1090                     "formatString": "MMMM d, yyyy",
1091                     "value": "16 May 2009 17:00:00",
1092                     "timeOnly": false,
1093                     "formattedValue": "May 17, 2009"
1094                 }
1095             ],
1096             [
1097                 {
1098                     "value": "world",
1099                     "formattedValue": "world"
1100                 },
1101                  {
1102                      "formatString": "M/d/yy h:mm a",
1103                      "value": "21 Dec 2008 19:31:00",
1104                      "timeOnly": false,
1105                      "formattedValue": "12/21/08 7:31 PM"
1106                  }
1107             ]
1108         ]
1109     },
1110     {
1111         "name": "Sheet2",
1112         "data": [
1113             [{
1114                 "value": "NumberColumn",
1115                 "formattedValue": "NumberColumn"
1116             }],
1117             [{
1118                 "formatString": "$#,##0.00",
1119                 "value": 55.44,
1120                 "formattedValue": "$55.44"
1121             }],
1122             [{
1123                 "value": 100.34,
1124                 "formattedValue": "100.34"
1125             }],
1126             [{
1127                 "value": -1,
1128                 "formattedValue": "-1"
1129             }]
1130         ]
1131     },
1132     {
1133         "name": "Sheet3",
1134         "data": []
1135     }
1136 ],
1137 "fileName": "SimpleExcelFile.xls"
1138 }
1139  </pre>
1140  *
1141  */
1142 LABKEY.Exp.Data.prototype.getContent = function(config)
1143 {
1144     if(!LABKEY.Utils.getOnSuccess(config))
1145     {
1146         alert("You must specify a callback function in config.success when calling LABKEY.Exp.Data.getContent()!");
1147         return;
1148     }
1149 
1150     function getSuccessCallbackWrapper(fn, format, scope)
1151     {
1152         return function(response)
1153         {
1154             //ensure response is JSON before trying to decode
1155             var content = null;
1156             if(response && response.getResponseHeader && response.getResponseHeader('Content-Type')
1157                     && response.getResponseHeader('Content-Type').indexOf('application/json') >= 0)
1158             {
1159                 content = LABKEY.Utils.decode(response.responseText);
1160             }
1161             else
1162             {
1163                 content = response.responseText;
1164             }
1165 
1166             if(fn)
1167                 fn.call(scope || this, content, format, response);
1168         };
1169     }
1170 
1171     LABKEY.Ajax.request(
1172     {
1173         url : LABKEY.ActionURL.buildURL("experiment", "showFile"),
1174         method : 'GET',
1175         params : { rowId : this.id, format: config.format },
1176         success: getSuccessCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.format, config.scope),
1177         failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
1178     });
1179 
1180 };
1181 
1182 
1183