0201e8ea6a448b2124e117c5909ced7ff4a253c8 hiram Tue May 19 13:43:53 2026 -0700 adding login verification to the liftRequest functions refs #31811 diff --git src/hg/js/liftRequest.js src/hg/js/liftRequest.js index 9e7dcea3c79..c08b55d24ef 100644 --- src/hg/js/liftRequest.js +++ src/hg/js/liftRequest.js @@ -1,309 +1,403 @@ /* 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]; var numeric = parts[1].split('.')[0]; var groups = numeric.match(/.{1,3}/g); return prefix + "/" + groups.join("/") + "/" + identifier; } function liftOverPath(asm1, asm2) { var liftPath = ""; if (/^GC[AF]_/.test(asm1)) { liftPath = "https://hgdownload.soe.ucsc.edu/hubs/"; liftPath += gcPath(asm1); } else { liftPath = "https://hgdownload.soe.ucsc.edu/goldenPath/"; liftPath += asm1; } liftPath += "/liftOver/" + asm1 + "To"; liftPath += asm2.charAt(0).toUpperCase() + asm2.slice(1); liftPath += ".over.chain.gz"; return(liftPath); } function checkAssemblyCompatibility(asm1, asm2) { const url = "/cgi-bin/hubApi/liftOver/listExisting?fromGenome=" + encodeURIComponent(asm1) + ";" + "toGenome=" + encodeURIComponent(asm2); fetch(url) .then(response => response.json()) .then(response => { // console.log(JSON.stringify(response, null, 2)); if (response.itemsReturned >= 1) { const liftPath1 = liftOverPath(asm1, asm2); const liftPath2 = liftOverPath(asm2, asm1); const browser1 = "/cgi-bin/hgTracks?db=" + asm1; const browser2 = "/cgi-bin/hgTracks?db=" + asm2; fileExists(liftPath1, function(exists) { if (exists) { document.getElementById("genome1Link").href = browser1; document.getElementById("genome1Link").textContent = assembly1Value; document.getElementById("genome1LiftOver").href = liftPath1; document.getElementById("genome1LiftOver").textContent = asm1 + " to " + asm2; document.getElementById("liftExists").style.display = "block"; document.getElementById("emailForm").style.display = "none"; document.getElementById("commentsForm").style.display = "none"; document.getElementById("submitButton").style.display = "none"; } }); 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(); checkBothAssembliesSelected(); } function validateEmail(checkEmail) { // Require at least one dot in domain var validEmail = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/; if (!validEmail.test(checkEmail)) { alert("You have entered an invalid email address !"); return false; } return true; } +function checkLoginStatus() { + // Check user login status and update UI accordingly + const returnTo = encodeURIComponent(window.location.href); + fetch(`/cgi-bin/hubApi/liftOver/loginStatus?returnTo=${returnTo}`) + .then(response => response.json()) + .then(data => { + updateUIForLoginStatus(data); + }) + .catch(error => { + console.log('Login status check failed:', error); + // Assume not logged in on error + updateUIForLoginStatus({userName: null}); + }); +} + +function updateUIForLoginStatus(loginData) { + const emailInput = document.getElementById('emailInput'); + const submitBtn = document.getElementById('submitBtn'); + + // Create or update login banner + let loginBanner = document.getElementById('loginBanner'); + if (!loginBanner) { + loginBanner = document.createElement('div'); + loginBanner.id = 'loginBanner'; + loginBanner.style.cssText = 'background-color: #f0f8ff; padding: 10px; margin-bottom: 15px; border-radius: 5px; border: 1px solid #ddd;'; + + // Insert after the h1 title + const formContainer = document.getElementById('formContainer'); + const title = formContainer.querySelector('h1'); + title.parentNode.insertBefore(loginBanner, title.nextSibling); + } + + if (loginData.userName) { + // User is logged in + var displayName = loginData.realName || loginData.userName; + loginBanner.innerHTML = '<p>Welcome, <strong>' + displayName + '</strong> | ' + + '<a href="' + loginData.logoutUrl + '">Sign out</a></p>'; + + // Prefill and lock email field + emailInput.value = loginData.email || ''; + emailInput.readOnly = true; + emailInput.style.backgroundColor = '#f5f5f5'; + emailInput.style.cursor = 'not-allowed'; + + // Update email form description + const emailForm = document.getElementById('emailForm'); + const description = emailForm.querySelector('.description'); + if (description) { + description.textContent = 'Notifications will be sent to your registered account email address.'; + } + + // Enable submit button + submitBtn.disabled = false; + submitBtn.value = 'Submit Request'; + submitBtn.style.backgroundColor = ''; + submitBtn.style.cursor = ''; + } else { + // User is not logged in + loginBanner.innerHTML = '<p>To submit requests, please ' + + '<a href="' + loginData.loginUrl + '">sign in</a> or ' + + '<a href="' + loginData.signupUrl + '">create an account</a></p>'; + + // Disable and gray out email field + emailInput.value = ''; + emailInput.placeholder = 'Sign in required'; + emailInput.disabled = true; + emailInput.style.backgroundColor = '#f0f0f0'; + + // Update email form description + const emailForm = document.getElementById('emailForm'); + const description = emailForm.querySelector('.description'); + if (description) { + description.textContent = 'You must sign in to submit alignment requests.'; + } + + // Disable submit button + submitBtn.disabled = true; + submitBtn.value = 'Sign in Required'; + submitBtn.style.backgroundColor = '#ddd'; + submitBtn.style.cursor = 'not-allowed'; + } +} + function submitForm() { + // Check if submit button is disabled (user not logged in) + var submitBtn = document.getElementById("submitBtn"); + if (submitBtn.disabled) { + alert("Please sign in to submit alignment requests"); + return; + } + var email = document.getElementById("emailInput").value; var comments = document.getElementById("commentsInput").value; // Hide any previous error message document.getElementById("errorMessage").style.display = "none"; document.getElementById("errorHeading").textContent = "Error"; document.getElementById("errorText").textContent = ""; if (!assembly1Value) { alert("Please select Assembly 1"); return; } if (!assembly2Value) { alert("Please select Assembly 2"); return; } if (!email) { alert("Please enter an email address"); return; } if (! validateEmail(email)) { return; } // Build the API URL with query parameters var comment = comments.slice(0, 1000); // make sure that URL is not too long comment += ", from: " + assembly1Value + ", to: " + assembly2Value; var apiUrl = "/cgi-bin/hubApi/liftRequest?" + "fromGenome=" + encodeURIComponent(genome1) + ";" + "toGenome=" + encodeURIComponent(genome2) + ";" + "email=" + encodeURIComponent(email) + ";" + "comment=" + encodeURIComponent(comment); fetch(apiUrl, { method: 'GET' }) .then((response) => { return response.text().then((text) => { if (response.ok) { localStorage.setItem('liftRequestEmail', email); document.getElementById("formContainer").style.display = "none"; document.getElementById("successMessage").style.display = "block"; } else { // Try to extract error message from JSON or text 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 = ""; document.getElementById("genomeSearch2").value = ""; assembly1Value = ""; assembly2Value = ""; genome1 = ""; genome2 = ""; } function onSearchError(jqXHR, textStatus, errorThrown, term) { return [{label: 'No genomes found', value: '', genome: '', disabled: true}]; } document.addEventListener("DOMContentLoaded", () => { // Assembly 1 autocomplete let selectEle1 = document.getElementById("genomeLabel1"); let boundSelect1 = assembly1Select.bind(null, selectEle1); initSpeciesAutoCompleteDropdown('genomeSearch1', boundSelect1, "/cgi-bin/hubApi/findGenome?browser=mustExist;q=", null, null, onSearchError); let btn1 = document.getElementById("genomeSearchButton1"); btn1.addEventListener("click", () => { let val = document.getElementById("genomeSearch1").value; $("[id='genomeSearch1']").autocompleteCat("search", val); }); // Assembly 2 autocomplete let selectEle2 = document.getElementById("genomeLabel2"); let boundSelect2 = assembly2Select.bind(null, selectEle2); 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 + // restore saved email if it exists (only for anonymous users) + // Note: logged-in users get their email from the login system var savedEmail = localStorage.getItem('liftRequestEmail'); if (savedEmail) { + // Will be overridden by checkLoginStatus() if user is logged in document.getElementById("emailInput").value = savedEmail; } document.getElementById("dismissLiftExists").addEventListener("click", dismissLiftExists); document.getElementById("dismissPending").addEventListener("click", resetFormVisibility); document.getElementById("dismissError").addEventListener("click", resetFormVisibility); + + checkLoginStatus(); });