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