e07b87b45f9293c3f45337303886450c390b2585
hiram
  Tue May 12 13:10:56 2026 -0700
protect against duplicated pending entries in the ottoRequest table refs #31811

diff --git src/hg/js/liftRequest.js src/hg/js/liftRequest.js
index 3154c58c304..9e7dcea3c79 100644
--- src/hg/js/liftRequest.js
+++ src/hg/js/liftRequest.js
@@ -1,21 +1,52 @@
 /* jshint esnext: true */
 var assembly1Value = "";
 var assembly2Value = "";
 var genome1 = "";
 var genome2 = "";
 
+// matches the ottoRequest README.txt and ottoRequestView.cgi STATUS_NAMES
+var STATUS_LABELS = {
+    0: "received by API",
+    1: "acknowledged, email sent",
+    2: "galaxy job started",
+    3: "galaxy done, download started",
+    4: "downloaded, track files made",
+    5: "symlinks ready, awaiting push",
+    6: "push complete",
+    7: "ERROR",
+    8: "COMPLETE (final email sent)"
+};
+
+function pendingMessageFor(status, requestTime) {
+    var label = STATUS_LABELS[status] || ("status " + status);
+    if (status === 7) {
+        return "A previous request for these assemblies (submitted on " +
+            requestTime + ") encountered an error. " +
+            "Please contact genome-www@soe.ucsc.edu for help.";
+    }
+    if (status === 8) {
+        return "A request for these assemblies (submitted on " +
+            requestTime + ") has already completed. " +
+            "If the chain files are not yet available, please contact" +
+            " genome-www@soe.ucsc.edu.";
+    }
+    return "A request for these assemblies was submitted on " +
+        requestTime + " and is currently in progress (" + label + ")." +
+        " You will receive an email when it completes.";
+}
+
 /* check if a file exists at the specified URL */
 function fileExists(url, callback) {
   fetch(url, { method: 'HEAD' })
     .then(response => {
       callback(response.ok);
     })
     .catch(() => {
       callback(false);
     });
 }
 
 // Convert GCA_029289425.2 into the path: GCA/028/289/425/GCA_029289425.2
 function gcPath(identifier) {
     var parts = identifier.split('_');
     var prefix = parts[0];
@@ -70,60 +101,72 @@
 
           fileExists(liftPath2, function(exists) {
             if (exists) {
               document.getElementById("genome2Link").href = browser2;
               document.getElementById("genome2Link").textContent = assembly2Value;
 
               document.getElementById("genome2LiftOver").href = liftPath2;
               document.getElementById("genome2LiftOver").textContent = asm2 + " to " + asm1;
 
               document.getElementById("liftExists").style.display = "block";
               document.getElementById("emailForm").style.display = "none";
               document.getElementById("commentsForm").style.display = "none";
               document.getElementById("submitButton").style.display = "none";
             }
           });
+        } else if (response.pending) {
+          showPending(response.pendingStatus, response.pendingRequestTime);
         }
       })
       .catch(error => {
         console.error("Error fetching liftOver list:", error);
       });
 }	// end of function checkAssemblyCompatibility(asm1, asm2)
 
 function checkBothAssembliesSelected() {
     if (genome1 && genome2) { // Both assemblies are now selected
         checkAssemblyCompatibility(genome1, genome2);
     }
 }
 
 function resetFormVisibility() {
     document.getElementById("liftExists").style.display = "none";
+    document.getElementById("pendingRequest").style.display = "none";
     document.getElementById("errorMessage").style.display = "none";
     document.getElementById("emailForm").style.display = "block";
     document.getElementById("commentsForm").style.display = "block";
     document.getElementById("submitButton").style.display = "block";
 }
 
 function showError(heading, errorMsg) {
     document.getElementById("errorHeading").textContent = heading;
     document.getElementById("errorText").textContent = errorMsg;
     document.getElementById("errorMessage").style.display = "block";
     document.getElementById("emailForm").style.display = "none";
     document.getElementById("commentsForm").style.display = "none";
     document.getElementById("submitButton").style.display = "none";
 }
 
+function showPending(status, requestTime) {
+    document.getElementById("pendingMessage").textContent =
+        pendingMessageFor(status, requestTime);
+    document.getElementById("pendingRequest").style.display = "block";
+    document.getElementById("emailForm").style.display = "none";
+    document.getElementById("commentsForm").style.display = "none";
+    document.getElementById("submitButton").style.display = "none";
+}
+
 function assembly1Select(selectEle, item) {
     selectEle.innerHTML = item.label;
     assembly1Value = item.value || item.label;
     genome1 = item.genome;
 //  console.log("asm1:", JSON.stringify(item, null, 2));
     resetFormVisibility();
     checkBothAssembliesSelected();
 }
 
 function assembly2Select(selectEle, item) {
     selectEle.innerHTML = item.label;
     assembly2Value = item.value || item.label;
     genome2 = item.genome;
 //  console.log("asm2:", JSON.stringify(item, null, 2));
     resetFormVisibility();
@@ -187,30 +230,35 @@
             var errorMsg = "Error submitting request";
             var heading = "Error";
             try {
               var parsed = JSON.parse(text);
               if (parsed.error) {
                 errorMsg = parsed.error;
               }
             } catch(e) {
               errorMsg = text || response.statusText || errorMsg;
             }
             if (response.status === 429) {
               heading = "Daily limit reached";
               errorMsg = "Thank you for your interest. " + errorMsg +
                   " If you have an urgent need, please contact us at" +
                   " genome-www@soe.ucsc.edu.";
+            } else if (response.status === 409) {
+              heading = "Already submitted";
+              errorMsg = errorMsg +
+                  " If you have not seen the completion email, please" +
+                  " contact us at genome-www@soe.ucsc.edu.";
             }
             showError(heading, errorMsg);
           }
         });
       })
       .catch((error) => {
         // Network or other fetch errors
         var errorMsg = error.message || "Unknown error occurred";
         showError("Error", errorMsg);
       });
 }	// end of function submitForm()
 
 function dismissLiftExists() {
     resetFormVisibility();
     document.getElementById("genomeSearch1").value = "";
@@ -244,17 +292,18 @@
     initSpeciesAutoCompleteDropdown('genomeSearch2', boundSelect2,
         "/cgi-bin/hubApi/findGenome?browser=mustExist;q=", null, null, onSearchError);
 
     let btn2 = document.getElementById("genomeSearchButton2");
     btn2.addEventListener("click", () => {
         let val = document.getElementById("genomeSearch2").value;
         $("[id='genomeSearch2']").autocompleteCat("search", val);
     });
     // restore saved email if it exists
     var savedEmail = localStorage.getItem('liftRequestEmail');
     if (savedEmail) {
         document.getElementById("emailInput").value = savedEmail;
     }
 
     document.getElementById("dismissLiftExists").addEventListener("click", dismissLiftExists);
+    document.getElementById("dismissPending").addEventListener("click", resetFormVisibility);
     document.getElementById("dismissError").addEventListener("click", resetFormVisibility);
 });