Merge pull request #5191 from solgenomics/topic/quality_control
[sgn.git] / mason / tools / sequence_metadata / upload_sequence_metadata_workflow.mas
blob527c46641b2371bca2ce380df0873819f6308af6
2 <%args>
3 </%args>
5 <& /util/import_javascript.mas, classes => [ 'jquery', 'jqueryui' ] &>
7 <div class="modal fade" id="upload_sequence_metadata_workflow_modal" name="upload_high_dim_phenotypes_spreadsheet_dialog" tabindex="-1" role="dialog" aria-labelledby="addHighDimDataDialog">
8   <div class="modal-dialog modal-xl" role="document">
9     <div class="modal-content">
10       <div class="modal-header">
11         <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
12         <h4 class="modal-title" id="addHighDimDataDialog">Upload Sequence Metadata</h4>
13       </div>
14       <div class="modal-body">
15         <div class="container-fluid">
16             
17           <&| /util/workflow.mas, id=> "upload_sequence_metadata_workflow" &>
18             
19             <!-- Step 1: Intro -->
20             <&| /util/workflow.mas:step, title=> "Intro" &>
21               <& /page/page_title.mas, title=>"Upload Sequence Metadata" &>
22               <div class="well">
23                   <p>This workflow will guide you through the process of uploading sequence metadata to the database using a 
24                   <a href="https://m.ensembl.org/info/website/upload/gff3.html">gff3-formatted</a> file.</p>
25                   <p>The following gff3 columns are used by the sequence metadata uploader:</p>
26                   <ul>
27                     <li>
28                       <strong>#1 / seqid:</strong> The name of the database feature (ie chromosome) the metadata is associated with<br />
29                       <em>The names in this column must match existing features in the database.</em>
30                     </li>
31                     <li><strong>#4 / start:</strong> The metadata's start position</li>
32                     <li><strong>#5 / end:</strong> The metadata's end position</li>
33                     <li><strong>#6 / score:</strong> The primary score attribute of the metadata</li>
34                     <li>
35                       <strong>#9 / attributes:</strong> Secondary key/value attributes to be saved with the score<br />
36                       <em>The attribute key cannot be either <strong>score</strong>, <strong>start</strong>, or <strong>end</strong>.</em>
37                     </li>
38                   </ul>
39               </div>
41               <div class="center">
42                 <button class="btn btn-primary" id="sequence_metadata_intro_step">Go to Next Step</button>
43               </div>
44             </&>
45             <!-- End Intro -->
48             <!-- Step 2: Data Type -->
49             <&| /util/workflow.mas:step, title=> "Data Type" &>
50               <& /page/page_title.mas, title=>"Select the type of sequence metadata" &>
51               <br /><br />
52               <form class="form-horizontal">
53                 <div class="form-group">
54                   <label class="col-sm-3 control-label">Data Type:&nbsp;</label>
55                   <div class="col-sm-9">
56                     <select class="form-control" id="sequence_metadata_upload_type_select" disabled>
57                       <option value="">Loading...</option>
58                     </select>
59                     <br />
60                     <div class="well">
61                       <p><span class="glyphicon glyphicon-info-sign"></span>&nbsp;<span id="sequence_metadata_upload_type_definition"></span></p>
62                     </div>
63                   </div>
64                 </div>
65               </form>
66               <p><span class="glyphicon glyphicon-question-sign"></span>&nbsp;If your data is not one of the above data types, please <a href="/contact/form">contact us</a> about adding a new data type.</p>
67               
68               <br /><br />
70               <div class="center">
71                 <button class="btn btn-primary" id="sequence_metadata_upload_type_step">Go to Next Step</button>
72               </div>
73             </&>
74             <!-- End Data Type -->
77             <!-- Step 3: Protocol Info -->
78             <&| /util/workflow.mas:step, title=> "Protocol Info" &>
79               <& /page/page_title.mas, title=>"Select a protocol for how your data was generated or create a new protocol" &>
81               <ul class="nav nav-tabs">
82                 <li class="active"><a data-toggle="tab" href="#sequence_metadata_upload_protocol_select">Select Existing Protocol</a></li>
83                 <li><a data-toggle="tab" href="#sequence_metadata_upload_protocol_div">Create New Protocol</a></li>
84               </ul>
86               <div class="tab-content">
88                 <br />
90                 <!-- Will be filled with existing protocol table -->
91                 <div id="sequence_metadata_upload_protocol_select" class="tab-pane fade in active"></div>
92               
93                 <!-- Form for creating a new protocol -->
94                 <div id="sequence_metadata_upload_protocol_div" class="tab-pane fade">
95                   <form class="form-horizontal" id="sequence_metadata_upload_protocol_form" name="sequence_metadata_upload_file_form">
97                     <h3>Protocol Information</h3>
98                     
99                     <!-- Protocol Name -->
100                     <div class="form-group">
101                       <label class="col-sm-3 control-label">Protocol Name: </label>
102                       <div class="col-sm-9">
103                         <input class="form-control" id="sequence_metadata_upload_protocol_name" type="text">
104                       </div>
105                     </div>
107                     <!-- Protocol Description -->
108                     <div class="form-group">
109                       <label class="col-sm-3 control-label">Protocol Description: </label>
110                       <div class="col-sm-9">
111                         <textarea class="form-control" id="sequence_metadata_upload_protocol_description" rows="5" placeholder="Include a description of the methods used to generate the data"></textarea>
112                       </div>
113                     </div>
115                     <!-- Sequence Metadata Type -->
116                     <div class="form-group">
117                       <label class="col-sm-3 control-label">Sequence Metadata Type: </label>
118                       <div class="col-sm-9">
119                         <input class="form-control" id="sequence_metadata_upload_protocol_sequence_metadata_type" type="text" disabled>
120                       </div>
121                     </div>
122                     
123                     <!-- Reference Genome -->
124                     <div class="form-group">
125                       <label class="col-sm-3 control-label">Reference Genome: </label>
126                       <div class="col-sm-9">
127                         <select class="form-control" id="sequence_metadata_upload_protocol_reference_genome" disabled>
128                           <option value="">Loading...</option>
129                         </select>
130                       </div>
131                     </div>
133                     <br /><br />
135                     <!-- Attribute Descriptions -->
136                     <h3>Attribute Descriptions</h3>
138                     <div class="form-group">
139                       <label class="col-sm-3 control-label">Score Description: </label>
140                       <div class="col-sm-9">
141                         <input class="form-control" id="sequence_metadata_upload_score_description" type="text" placeholder="Include a description of the values in the 'score' column">
142                       </div>
143                     </div>
145                     <br />
146                     <p><strong>Add a description for each of the key/value pairs included in the attributes column.</strong></p>
147                     <p><em>Specifying the attribute here in the protocol will allow the attribute to be used as a filter in a query.  Make sure the attribute key exactly matches the key used in the gff file.</em></p>
149                     <div class="form-group">
150                       <label class="col-sm-3 control-label">Attribute Key: </label>
151                       <div class="col-sm-9">
152                         <input class="form-control" id="sequence_metadata_upload_new_attribute_key" type="text" placeholder="Enter attribute key">
153                       </div>
154                     </div>
156                     <div class="form-group">
157                       <label class="col-sm-3 control-label">Attribute Description: </label>
158                       <div class="col-sm-9">
159                         <input class="form-control" id="sequence_metadata_upload_new_attribute_description" type="text" placeholder="Enter a description of this attribute">
160                       </div>
161                     </div>
163                     <div class="form-group center">
164                       <button class="btn btn-info" id="sequence_metadata_upload_new_attribute_submit">Save Attribute</button>
165                     </div>
167                     <div>
168                       <p><strong>Saved Attributes:</strong></p>
169                       <table id="sequence_metadata_upload_new_attributes_table" class="table table-striped table-hover">
170                         <tr>
171                           <th>Key</th>
172                           <th>Description</th>
173                           <th></th>
174                         </tr>
175                       </table>
176                     </div>
178                     <br /><br />
180                     <!-- External Link Templates -->
181                     <h3>External Links</h3>
183                     <p><strong>Add templates for generating external links displayed with the sequence metadata results.</strong></p>
184                     <p>External links are defined using templates that can include the feature/chrom name, start and/or end position, and/or any attribute value 
185                     as a variable in the URL.  The variable will be replaced with the value for each sequence metadata result and displayed in the query results table.  The variable in the URL takes the form of: <code>{{variable}}</code> where <code>variable</code> can be: <code>feature</code>, <code>start</code>,
186                     <code>end</code> or the key of any attribute defined by the sequence metadata protocol.</p>
187                     <p>Example: <code>https://wheat.pw.usda.gov/jb/?data=/ggds/whe-iwgsc2018&loc={{Locus}}&highlight={{feature}}:{{start}}..{{end}}</code></p>
189                     <div class="form-group">
190                       <label class="col-sm-3 control-label">Link Title: </label>
191                       <div class="col-sm-9">
192                         <input class="form-control" id="sequence_metadata_upload_new_link_title" type="text" placeholder="Enter external link title">
193                       </div>
194                     </div>
196                     <div class="form-group">
197                       <label class="col-sm-3 control-label">Link URL Template: </label>
198                       <div class="col-sm-9">
199                         <input class="form-control" id="sequence_metadata_upload_new_link_url" type="text" placeholder="Enter external link URL template">
200                       </div>
201                     </div>
203                     <div class="form-group center">
204                       <button class="btn btn-info" id="sequence_metadata_upload_new_link_submit">Save Link</button>
205                     </div>
207                     <div>
208                       <p><strong>Saved Links:</strong></p>
209                       <table id="sequence_metadata_upload_new_links_table" class="table table-striped table-hover">
210                         <tr>
211                           <th>Title</th>
212                           <th>URL</th>
213                           <th></th>
214                         </tr>
215                       </table>
216                     </div>
218                   </form>
219                 </div>
220                 <!-- End new protocol form -->
222               </div>
224               <br /><br />
226               <div class="center">
227                 <button class="btn btn-primary" id="sequence_metadata_upload_protocol_step">Go to Next Step</button>
228               </div>
230               <br /><br />
232             </&>
233             <!-- End Protocol Info -->
236             <!-- Step 4: Upload -->
237             <&| /util/workflow.mas:step, title=> "Upload" &>
238               <& /page/page_title.mas, title=>"Upload your gff3 file" &>
239               <br /><br />
241               <p>Select your gff3 file to upload.  Then, click the <strong>Verify</strong> button to upload your file and check its format and contents.  If 
242               successfully verified, click the <strong>Store</strong> button to store the data in the database.</p>
244               <br /><br />
246               <form id="sequence_metadata_upload_file_form" class="form-horizontal" enctype="multipart/form-data">
247                 <div class="form-group">
248                   <label class="col-sm-3 control-label">Select GFF3 File: </label>
249                   <div class="col-sm-9" >
250                       <input type="file" id="sequence_metadata_upload_file_input" name="sequence_metadata_upload_file_input" encoding="multipart/form-data" />
251                   </div>
252                 </div>
253               </form>
255               <br /><br />
257               <div class="center">
258                 <button id="sequence_metadata_upload_verify" class="btn btn-info">Verify</button>
259                 <button id="sequence_metadata_upload_store" class="btn btn-primary" disabled>Store</button>
260               </div>
262               <br /><br />
264               <div id="sequence_metdata_upload_messages">
265                 <ul id="sequence_metadata_upload_messages_list" class='list-group'></ul>
266               </div>
267             </&>
268             <!-- End Upload -->
270           </&>
272         </div>
273       </div>
274       <div class="modal-footer">
275         <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
276       </div>
277     </div>
278   </div>
279 </div>
281 <style>
282   div.center {
283     text-align: center
284   }
285 </style>
287 <script>
289 var sequence_metadata_data_types = [];                // List of sequence metadata type properties (pulled from the database)
290 var sequence_metadata_upload_type_id = undefined;     // Selected sequence metadata type id
291 var new_attribute_count = 0;                          // Count of (max) number of attribute keys/descriptions added
292 var new_link_count = 0;                               // Count of (max) number of external links added
293 var use_existing_protocol = undefined;                // Flag set to true if user has selected an existing protocol
294 var new_protocol_name = undefined;                    // Name of new protocol
295 var new_protocol_description = undefined;             // Description of new protocol
296 var processed_filepath = undefined;                   // Server filepath to processed file generated and returned by verification step
298 jQuery(document).ready(function() {
300     // Start SMD Upload Workflow
301     jQuery('#upload_smd').click( function() {
302         jQuery('#upload_sequence_metadata_workflow_modal').modal("show");
303     });
305     // Complete the Intro Step
306     jQuery('#sequence_metadata_intro_step').click(complete_intro_step);
308     // Complete the Data Type Step
309     jQuery('#sequence_metadata_upload_type_step').click(complete_data_type_step);
311     // Complete Protocol Step
312     jQuery('#sequence_metadata_upload_protocol_step').click(complete_protocol_step);
314     // Save a new attribute description
315     jQuery('#sequence_metadata_upload_new_attribute_submit').click(save_attribute_description);
317     // Save a new external link definition
318     jQuery('#sequence_metadata_upload_new_link_submit').click(save_link_definition);
320     // Upload and Verify File
321     jQuery('#sequence_metadata_upload_verify').click(upload_and_verify); 
323     // Store Data
324     jQuery('#sequence_metadata_upload_store').click(store);
330 // WORKFLOW STEP COMPLETION
334  * Complete the Intro Step
335  * - Get the available data types from the DB
336  * - Set the data types select box in the Data Type step
337  * - Complete Intro Step and move to Data Type step
338  */
339 function complete_intro_step() {
340   
341   // Get and set the data types
342   jQuery.ajax({
343     type: 'GET',
344     dataType: 'json',
345     url: '/ajax/sequence_metadata/types',
346     success: function(data) {
347       if ( data && data.types ) {
348         sequence_metadata_data_types = data.types;
349         let html = "";
350         for ( let i = 0; i < data.types.length; i++ ) {
351           html += "<option value='" + data.types[i].type_id + "'>" + data.types[i].type_name + "</option>";
352         }
353         jQuery('#sequence_metadata_upload_type_select').html(html);
354         jQuery('#sequence_metadata_upload_type_select').prop('disabled', false);
355         jQuery('#sequence_metadata_upload_type_select').on('change', function() {
356           setDataTypeDefinition(this.value);
357         });
358         setDataTypeDefinition(data.types[0].type_id);
359       }
360       else {
361         alert("ERROR: Could not load data types!");
362       }
363     },
364     error: function() {
365       alert("ERROR: Could not load data types!");
366     }
367   });
369   // Proceeed to Next Step
370   Workflow.complete('#sequence_metadata_intro_step');
371   Workflow.focus('#upload_sequence_metadata_workflow', 1);
376  * Complete the Data Type Step
377  * - Set the selected sequence metadata type id
378  * - Get the existing protocols for matching data type from DB
379  * - Set existing protocol table in Protocol step
380  * - Get the available reference genomes from the DB
381  * - Set the reference genomes select box in the Protocol step
382  * - Complete Data Type step and move to Protocol step
383  */
384 function complete_data_type_step() {
385   sequence_metadata_upload_type_id = jQuery('#sequence_metadata_upload_type_select').val();
387   // Set the existing protocol list
388   get_select_box('sequence_metadata_protocols', 'sequence_metadata_upload_protocol_select', {'checkbox_name': 'sequence_metadata_upload_protocol_id', 'sequence_metadata_data_type_id': sequence_metadata_upload_type_id});
389   
390   // Set sequence metadata type value
391   for ( let i = 0; i < sequence_metadata_data_types.length; i++ ) {
392     if ( parseInt(sequence_metadata_data_types[i].type_id) === parseInt(sequence_metadata_upload_type_id) ) {
393       jQuery('#sequence_metadata_upload_protocol_sequence_metadata_type:text').val(sequence_metadata_data_types[i].type_name);
394     }
395   }
397   // Set the reference genome list
398   jQuery.ajax({
399     type: 'GET',
400     dataType: 'json',
401     url: '/ajax/markers/genotyped/reference_genomes',
402     success: function(data) {
403       if ( data && data.reference_genomes ) {
404         let options = "";
405         for ( let i = 0; i < data.reference_genomes.length; i++ ) {
406           let rg_name = data.reference_genomes[i].reference_genome_name;
407           let sp_name = data.reference_genomes[i].species_name;
408           let rg_label = rg_name + " (" + sp_name + ")";
409           options += "<option value='" + rg_name + "' data-species='" + sp_name + "'>" + rg_label + "</option>";
410         }
411         jQuery('#sequence_metadata_upload_protocol_reference_genome').html(options);
412         jQuery('#sequence_metadata_upload_protocol_reference_genome').prop('disabled', false);
413       }
414       else {
415         alert("ERROR: Could not load reference genomes!");
416       }
417     },
418     error: function() {
419       alert("ERROR: Could not load reference genomes");
420     }
421   });
423   Workflow.complete("#sequence_metadata_upload_type_step");
424   Workflow.focus('#upload_sequence_metadata_workflow', 2);
428  * Complete the Protocol Step
429  * - Determine if user has selected an existing protocol / created a new protocol
430  * - Check for required protocol params
431  * - Complete the Protocol Step and move to Upload step
432  */
433 function complete_protocol_step() {
434   use_existing_protocol = jQuery('#sequence_metadata_upload_protocol_select').is(':visible');
435   if ( use_existing_protocol ) {
436     if ( jQuery('input[name="sequence_metadata_upload_protocol_id"]:checked').length !== 1 ) {
437       alert('Please select one existing protocol!');
438       return false;
439     }
440   }
441   else {
442     new_protocol_name = jQuery('#sequence_metadata_upload_protocol_name').val();
443     new_protocol_description = jQuery('#sequence_metadata_upload_protocol_description').val();
444     if ( !new_protocol_name || new_protocol_name === "" ) {
445       alert('Please enter a name for the new protocol');
446       return false;
447     }
448     if ( !new_protocol_description || new_protocol_description === "" ) {
449       alert('Please enter a description for the new protocol');
450       return false;
451     }
452   }
454   jQuery('#sequence_metadata_upload_file_input').val(undefined);
455   jQuery('#sequence_metadata_upload_verify').attr('disabled', false);
456   jQuery('#sequence_metadata_upload_store').attr('disabled', true);
457   jQuery('#sequence_metadata_upload_messages_list').empty();
459   Workflow.complete("#sequence_metadata_upload_protocol_step");
460   Workflow.focus('#upload_sequence_metadata_workflow', 3);
464 // VERIFY AND STORE
468  * Upload and verify the gff file
469  * - Make POST request to verify endpoint (upload file, run verification script)
470  * - Handle response and display verification messages
471  * - Enable store button if verification passes
472  */
473 function upload_and_verify() {
474   let file = jQuery('#sequence_metadata_upload_file_input').val();
475   if ( !file || file === '' ) {
476     alert("Please select your gff file");
477     return false;
478   }
480   // Setup working modal
481   jQuery('#working_msg').html("Uploading GFF File");
482   jQuery('#working_modal_progress').css('width', 0).attr('aria-valuenow', 0);
483   jQuery('#working_modal_progress_div').css('display', 'block');
484   jQuery('#working_modal').modal("show");
485   jQuery('#sequence_metadata_upload_messages_list').empty();
486   
487   
488   // Build the FormData to pass as POST arguments
489   let formData = new FormData();
490   formData.append('file', jQuery('#sequence_metadata_upload_file_input').prop('files')[0]);
491   formData.append('use_existing_protocol', use_existing_protocol);
492   if ( use_existing_protocol ) {
493     formData.append('existing_protocol_id', jQuery('input[name="sequence_metadata_upload_protocol_id"]:checked').val());
494   }
495   else {
496     formData.append('species', jQuery('#sequence_metadata_upload_protocol_reference_genome option:selected').attr('data-species'));
497     formData.append('new_protocol_attribute_count', new_attribute_count);
498     for ( let i = 0; i <= new_attribute_count; i++ ) {
499       formData.append('new_protocol_attribute_key_' + i, jQuery('#sequence_metadata_upload_attribute_key_' + i).val());
500     }
501   }
503   // Make the POST request
504   jQuery.ajax({
505     url: '/ajax/sequence_metadata/file_upload_verify',
506     type: 'POST',
507     data: formData,
508     cache: false,
509     contentType: false,
510     processData: false,
511     xhr: function () {
512       var myXhr = jQuery.ajaxSettings.xhr();
513       if (myXhr.upload) {
514         myXhr.upload.addEventListener('progress', function (e) {
515           if (e.lengthComputable) {
516             let prog = (e.loaded / e.total)*100;
517             jQuery('#working_modal_progress').css('width', prog + "%").attr('aria-valuenow', prog);
518             if ( prog >= 100 ) {
519               jQuery('#working_msg').html("Verifying GFF File");
520             }
521           }
522         }, false);
523       }
524       return myXhr;
525     },
526     success: function(response) {
528       // Clear working modal
529       jQuery('#working_msg').html("");
530       jQuery('#working_modal_progress').css('width', 0).attr('aria-valuenow', 0);
531       jQuery('#working_modal_progress_div').css('display', 'none');
532       jQuery('#working_modal').modal("hide");
534       console.log("VERIFICATION RESPONSE");
535       console.log(response);
537       // Add Response Messages
538       if ( response && response.error ) {
539         add_message("error", response.error);
540       }
541       else if ( response && response.results ) {
542         if ( response.results.processed === 1 ) {
543           add_message("success", "File successfully uploaded and processed");
544         }
545         else {
546           add_message("error", "ERROR: File not successfully uploaded and processed!");
547         }
548         
549         if ( response.results.verified === 1 ) {
550           add_message("success", "File successfully verified");
551         }
552         else {
553           add_message("error", "ERROR: File not successfully verfied!");
554         }
556         if ( response.results.missing_features && response.results.missing_features.length > 0 ) {
557           let msg = "ERROR: The following features do not exist in the database.  Contact us to have them added or change the feature names to match existing features.";
558           msg += "<ul>";
559           for ( let i = 0; i < response.results.missing_features.length; i++ ) {
560             msg += "<li>" + response.results.missing_features[i] + "</li>";
561           }
562           msg += "</ul>";
563           add_message("error", msg);
564         }
566         if ( response.results.missing_attributes && response.results.missing_attributes.length > 0 ) {
567           let msg = "WARNING: The following attributes are defined in the protocol but are <em>not present in the gff file</em>.";
568           msg += "<ul>";
569           for ( let i = 0; i < response.results.missing_attributes.length; i++ ) {
570             msg += "<li>" + response.results.missing_attributes[i] + "</li>";
571           }
572           msg += "</ul>";
573           msg += "You can either <a href='javascript:Workflow.focus(\"#upload_sequence_metadata_workflow\", 2);'>remove the attributes from the protocol info</a> or continue to store the file with the attributes defined in the protocol but missing from the file."; 
574           add_message("warning", msg);
575         }
577         if ( response.results.undefined_attributes && response.results.undefined_attributes.length > 0 ) {
578           let msg = "WARNING: The following attributes are present in the gff file but are <em>not defined in the protocol</em>.";
579           msg += "<ul>";
580           for ( let i = 0; i < response.results.undefined_attributes.length; i++ ) {
581             msg += "<li>" + response.results.undefined_attributes[i] + "</li>";
582           }
583           msg += "</ul>";
584           msg += "You can either <a href='javascript:Workflow.focus(\"#upload_sequence_metadata_workflow\", 2);'>add the attributes to the protocol info</a> or continue to store the file with the attributes not defined in the protocol."; 
585           add_message("warning", msg);
586         }
587       }
589       // Enable the Store button if processed and verified
590       if ( response && response.results && response.results.processed && response.results.processed === 1 && response.results.verified && response.results.verified === 1 && response.results.processed_filepath ) {
591         jQuery('#sequence_metadata_upload_verify').attr('disabled', true);
592         jQuery('#sequence_metadata_upload_store').attr('disabled', false);
593         add_message("info", "Verification Complete.  Next, click the <strong>Store</strong> button to save the data to the database.");
594         processed_filepath = response.results.processed_filepath;
595       }
597     },
598     error: function() {
599       jQuery('#working_msg').html("");
600       jQuery('#working_modal_progress').css('width', 0).attr('aria-valuenow', 0);
601       jQuery('#working_modal_progress_div').css('display', 'none');
602       jQuery('#working_modal').modal("hide");
604       alert("ERROR: Could not upload and verify gff file due to server error!");
605     }
606   });
610  * Store the Data
611  */
612 function store() {
614   // Setup working modal
615   jQuery('#working_msg').html("Storing Sequence Metadata");
616   jQuery('#working_modal').modal("show");
617   jQuery('#sequence_metadata_upload_messages_list').empty();
619   // Setup FormData to pass as POST arguments
620   let formData = new FormData();
621   formData.append('processed_filepath', processed_filepath);
622   formData.append('use_existing_protocol', use_existing_protocol);
623   if ( use_existing_protocol ) {
624     formData.append('existing_protocol_id', jQuery('input[name="sequence_metadata_upload_protocol_id"]:checked').val());
625     formData.append('existing_protocol_sequence_metadata_type', jQuery('#sequence_metadata_upload_type_select option:selected').val());
626   }
627   else {
628     formData.append('new_protocol_name', new_protocol_name);
629     formData.append('new_protocol_description', new_protocol_description);
630     formData.append('new_protocol_sequence_metadata_type', jQuery('#sequence_metadata_upload_type_select option:selected').val());
631     formData.append('new_protocol_reference_genome', jQuery('#sequence_metadata_upload_protocol_reference_genome option:selected').val());
632     formData.append('new_protocol_species', jQuery('#sequence_metadata_upload_protocol_reference_genome option:selected').attr('data-species'));
633     formData.append('new_protocol_score_description', jQuery('#sequence_metadata_upload_score_description').val());
634     formData.append('new_protocol_attribute_count', new_attribute_count);
635     for ( let i = 1; i <= new_attribute_count; i++ ) {
636       formData.append('new_protocol_attribute_key_' + i, jQuery('#sequence_metadata_upload_attribute_key_' + i).val());
637       formData.append('new_protocol_attribute_description_' + i, jQuery('#sequence_metadata_upload_attribute_description_' + i).val());
638     }
639     formData.append('new_protocol_link_count', new_link_count);
640     for ( let i = 1; i <= new_link_count; i++ ) {
641       formData.append('new_protocol_link_title_' + i, jQuery('#sequence_metadata_upload_link_title_' + i).val());
642       formData.append('new_protocol_link_url_' + i, jQuery('#sequence_metadata_upload_link_url_' + i).val());
643     }
644   }
646   // Make the POST request
647   jQuery.ajax({
648     url: '/ajax/sequence_metadata/store',
649     type: 'POST',
650     data: formData,
651     cache: false,
652     contentType: false,
653     processData: false,
654     success: function(response) {
656       // Clear working modal
657       jQuery('#working_msg').html("");
658       jQuery('#working_modal').modal("hide");
660       console.log("STORE RESPONSE");
661       console.log(response);
663       // Add Response Messages
664       if ( response && response.error ) {
665         add_message("error", response.error);
666       }
667       else if ( response && response.results ) {
668         if ( response.results.stored === 1 ) {
669           add_message("success", "File successfully stored");
670           if ( response.results.chunks ) {
671             add_message("info", response.results.chunks + " chunks written to the database.");
672           }
673         }
674         else {
675           add_message("error", "ERROR: File not stored!");
676         }
677       }
679       // Disable the verify and store buttons
680       if ( response && response.results && response.results.stored && response.results.stored === 1 ) {
681         jQuery('#sequence_metadata_upload_verify').attr('disabled', true);
682         jQuery('#sequence_metadata_upload_store').attr('disabled', true);
683       }
685     },
686     error: function() {
687       jQuery('#working_msg').html("");
688       jQuery('#working_modal').modal("hide");
689       alert("ERROR: Could not store sequence metadata due to server error!");
690     }
691   });
697 // HELPER FUNCTIONS
701  * Callback listener for data type selection change
702  * - Update the data type definition
703  */
704 function setDataTypeDefinition(id) {
705   for ( let i = 0; i < sequence_metadata_data_types.length; i++ ) {
706     if ( parseInt(sequence_metadata_data_types[i].type_id) === parseInt(id) ) {
707       jQuery('#sequence_metadata_upload_type_definition').html(sequence_metadata_data_types[i].type_definition);
708     }
709   }
713  * Save attribute key and description
714  * - Get user inputs
715  * - Add key and description to table (along with hidden inputs used by the form)
716  */
717 function save_attribute_description() {
718   new_attribute_count++;
720   let key = jQuery('#sequence_metadata_upload_new_attribute_key').val();
721   let description = jQuery('#sequence_metadata_upload_new_attribute_description').val();
722   if ( !key || key === "" ) {
723     alert("An attribute key is required!");
724     return false;
725   }
727   let html = "<tr id='sequence_metadata_upload_new_attributes_table_row_" + new_attribute_count + "'>";
728   html += "<td>" + key + "<input type='hidden' name='sequence_metadata_upload_attribute_key_" + new_attribute_count + "' id='sequence_metadata_upload_attribute_key_" + new_attribute_count + "' value='" + key + "'></td>";
729   html += "<td>" + description + "<input type='hidden' name='sequence_metadata_upload_attribute_description_" + new_attribute_count + "' id='sequence_metadata_upload_attribute_description_" + new_attribute_count + "' value='" + description + "'</td>";
730   html += "<td style='text-align: right'><button id='sequence_metadata_upload_new_attributes_table_remove_" + new_attribute_count + "' data-row='" + new_attribute_count + "' class='btn btn-danger btn-xs'><span class='glyphicon glyphicon-remove'></span></button></td>";
731   html += "</tr>";
733   jQuery('#sequence_metadata_upload_new_attribute_key').val("");
734   jQuery('#sequence_metadata_upload_new_attribute_description').val("");
735   jQuery('#sequence_metadata_upload_new_attributes_table').append(html);
736   jQuery('#sequence_metadata_upload_new_attributes_table_remove_' + new_attribute_count).click(function() {
737     let row = jQuery(this).attr('data-row');
738     jQuery('#sequence_metadata_upload_new_attributes_table_row_' + row).remove();
739   });
741   return false;
745  * Save external link title and url definition
746  * - Get user inputs
747  * - Add title and url to table (along with hidden inputs used by the form)
748  */
749 function save_link_definition() {
750   new_link_count++;
752   let title = jQuery('#sequence_metadata_upload_new_link_title').val();
753   let url = jQuery('#sequence_metadata_upload_new_link_url').val();
754   if ( !title || title === "" ) {
755     alert("A link title is required!");
756     return false;
757   }
758   if ( !url || url === "" ) {
759     alert("A link url is required!");
760     return false;
761   }
763   let html = "<tr id='sequence_metadata_upload_new_links_table_row_" + new_link_count + "'>";
764   html += "<td>" + title + "<input type='hidden' name='sequence_metadata_upload_link_title" + new_link_count + "' id='sequence_metadata_upload_link_title_" + new_link_count + "' value='" + title + "'></td>";
765   html += "<td>" + url + "<input type='hidden' name='sequence_metadata_upload_link_url_" + new_link_count + "' id='sequence_metadata_upload_link_url_" + new_link_count + "' value='" + url + "'</td>";
766   html += "<td style='text-align: right'><button id='sequence_metadata_upload_new_links_table_remove_" + new_link_count + "' data-row='" + new_link_count + "' class='btn btn-danger btn-xs'><span class='glyphicon glyphicon-remove'></span></button></td>";
767   html += "</tr>";
769   jQuery('#sequence_metadata_upload_new_link_title').val("");
770   jQuery('#sequence_metadata_upload_new_link_url').val("");
771   jQuery('#sequence_metadata_upload_new_links_table').append(html);
772   jQuery('#sequence_metadata_upload_new_links_table_remove_' + new_link_count).click(function() {
773     let row = jQuery(this).attr('data-row');
774     jQuery('#sequence_metadata_upload_new_links_table_row_' + row).remove();
775   });
777   return false;
781  * Add Message to Upload step
782  * @param {string} type Message Type (success, error)
783  * @param {string} message Message Text
784  */
785 function add_message(type, message) {
786   let html = "";
787   let class_name = "danger";
788   let icon_name = "remove";
789   
790   if ( type === "success" ) {
791     class_name = "success";
792     icon_name = "ok";
793   }
794   else if ( type === "warning" ) {
795     class_name = "warning";
796     icon_name = "asterisk";
797   }
798   else if ( type === "info" ) {
799     class_name = "info";
800     icon_name = "info-sign";
801   }
803   html += "<li class='list-group-item list-group-item-" + class_name + "'>";
804   html += "<span class='badge'><span class='glyphicon glyphicon-" + icon_name + "'></span></span>";
805   html += message;
806   html += "</li>";
808   jQuery('#sequence_metadata_upload_messages_list').append(html);
810 </script>