a6e1074250a8fdd5e952ab3ad7bd91705aa56222 angie Thu Sep 30 09:55:40 2021 -0700 UI changes suggested by Mary Goldman: reorder elements, simplify, bigger fonts for headers. diff --git src/hg/hgPhyloPlace/hgPhyloPlace.c src/hg/hgPhyloPlace/hgPhyloPlace.c index d46a4a0..2dc6bba 100644 --- src/hg/hgPhyloPlace/hgPhyloPlace.c +++ src/hg/hgPhyloPlace/hgPhyloPlace.c @@ -1,523 +1,504 @@ /* hgPhyloPlace - Upload SARS-CoV-2 sequence for placement in phylo tree. */ /* Copyright (C) 2020 The Regents of the University of California */ #include "common.h" #include "botDelay.h" #include "cart.h" #include "cgiApoptosis.h" #include "cheapcgi.h" #include "hCommon.h" #include "hash.h" #include "hui.h" #include "jsHelper.h" #include "knetUdc.h" #include "linefile.h" #include "net.h" #include "options.h" #include "phyloPlace.h" #include "portable.h" #include "trackLayout.h" #include "udc.h" #include "web.h" /* Global Variables */ struct cart *cart = NULL; // CGI and other variables struct hash *oldVars = NULL; // Old contents of cart before it was updated by CGI boolean measureTiming = FALSE; // Print out how long things take char *leftLabelWidthForLongNames = "55";// Leave plenty of room for tree and long virus strain names /* for botDelay call, 10 second for warning, 20 second for immediate exit */ #define delayFraction 0.25 static boolean issueBotWarning = FALSE; static long enteredMainTime = 0; #define seqFileVar "sarsCoV2File" #define pastedIdVar "namesOrIds" #define remoteFileVar "remoteFile" static struct lineFile *lineFileFromFileInput(struct cart *cart, char *fileVar) /* Return a lineFile on data from an uploaded file with cart variable name fileVar. * If the file is binary, attempt to decompress it. Return NULL if no data are found * or if there is a problem decompressing binary data. If retFileName is not NULL */ { struct lineFile *lf = NULL; // Depending on whether the file is plain text or binary, different cart variables are present. char *filePlainContents = cartOptionalString(cart, fileVar); char cartVar[2048]; safef(cartVar, sizeof cartVar, "%s__binary", fileVar); char *fileBinaryCoords = cartOptionalString(cart, cartVar); // Also get the file name for error reporting. safef(cartVar, sizeof cartVar, "%s__filename", fileVar); char *fileName = cartOptionalString(cart, cartVar); if (fileName == NULL) fileName = ""; if (isNotEmpty(filePlainContents)) { lf = lineFileOnString(fileName, TRUE, cloneString(trimSpaces(filePlainContents))); } else if (isNotEmpty(fileBinaryCoords)) { fprintf(stderr, "%s=%s fileBinaryCoords=%s\n", cartVar, fileName, fileBinaryCoords); char *binInfo = cloneString(fileBinaryCoords); char *words[2]; char *mem; unsigned long size; chopByWhite(binInfo, words, ArraySize(words)); mem = (char *)sqlUnsignedLong(words[0]); size = sqlUnsignedLong(words[1]); lf = lineFileDecompressMem(TRUE, mem, size); } return lf; } static void newPageStartStuff() { // Copied these from hgGtexTrackSettings.c which says "// NOTE: This will likely go to web.c". puts(""); puts(""); //#*** TODO: move this out to a CSS (hardcoding for now because we're doing a standalone push //#*** independent of the release cycle). puts("\n" ); // Container for bootstrap grid layout puts( "
\n"); } static void newPageEndStuff() { puts( "
"); jsIncludeFile("utils.js", NULL); webIncludeFile("inc/gbFooter.html"); webEndJWest(); } #define CHECK_FILE_OR_PASTE_INPUT_JS(fileVarName, pasteVarName) \ "{ var $fileInput = $('input[name="fileVarName"]');" \ " var $pasteInput = $('textarea[name="pasteVarName"]');" \ " if ($fileInput && $fileInput[0] && $fileInput[0].files && !$fileInput[0].files.length &&" \ " $pasteInput && !$pasteInput.val()) {" \ " alert('Please either choose a file or paste in sequence names/IDs first, ' +" \ " 'and then click the upload button.');" \ " return false; " \ " } else if ($fileInput && $fileInput[0] && $fileInput[0].files && " \ " !!$fileInput[0].files.length &&" \ " $pasteInput && !!$pasteInput.val()) {" \ " alert('Sorry, unable to process both a file and pasted-in sequence names/IDs at the ' +" \ " 'same time. Please clear one or the other and then click the upload button.');" \ " return false; " \ " } else { loadingImage.run(); return true; } }" static void inputForm() /* Ask the user for FASTA or VCF. */ { printf("
\n\n", "hgPhyloPlace"); cartSaveSession(cart); char *db = "wuhCor1"; cgiMakeHiddenVar("db", db); +puts("
"); puts("
"); +puts("
" + "Place your SARS-CoV-2 sequences in a global phylogenetic tree
"); +printf("

Select your FASTA, VCF or list of sequence names/IDs: "); +printf("", + seqFileVar, seqFileVar); +printf("

or paste in sequence names/IDs:
\n"); +cgiMakeTextArea(pastedIdVar, "", 10, 70); +struct treeChoices *treeChoices = loadTreeChoices(db); +if (treeChoices) + { + puts("

"); + printf("Phylogenetic tree version: "); + char *phyloPlaceTree = cartOptionalString(cart, "phyloPlaceTree"); + cgiMakeDropListWithVals("phyloPlaceTree", treeChoices->descriptions, treeChoices->protobufFiles, + treeChoices->count, phyloPlaceTree); + } +puts("

"); +printf("Number of samples per subtree showing sample placement: "); +int subtreeSize = cartUsualInt(cart, "subtreeSize", 50); +cgiMakeIntVarWithLimits("subtreeSize", subtreeSize, + "Number of samples in subtree showing neighborhood of placement", + 5, 10, 5000); +puts("

"); +cgiMakeOnClickSubmitButton(CHECK_FILE_OR_PASTE_INPUT_JS(seqFileVar, pastedIdVar), + "submit", "Upload"); +puts("  "); +cgiMakeOnClickSubmitButton("{ loadingImage.run(); return true; }", + "exampleButton", "Upload Example File"); +puts("  "); +puts("More example files"); +puts("

"); +// Add a loading image to reassure people that we're working on it when they upload a big file +printf("
\n"); +printf("
\n"); +jsInline("$(document).ready(function() {\n" + " loadingImage.init($('#loadingImg'), $('#loadingMsg'), " + "'

Uploading and processing your sequences " + "may take some time. Please leave this window open while we work on your sequences.

');" + "});\n"); + +puts("
"); +puts("
"); puts("
"); +puts("
"); +puts("

More information

"); puts("

Upload your SARS-CoV-2 sequence (FASTA or VCF file) to find the most similar\n" "complete, high-coverage samples from \n" "GISAID\n" "or from public sequence databases (" "NCBI Virus / GenBank,\n" "COG-UK and the\n" "China National Center for Bioinformation), " "and your sequence's placement in the phylogenetic tree generated by the\n" "sarscov2phylo\n" "pipeline.\n" "Placement is performed by\n" "" "Ultrafast Sample placement on Existing tRee (UShER) " "(" "Turakhia et al.). UShER also generates local subtrees to show samples " "in the context of the most closely related sequences. The subtrees can be visualized " "as Genome Browser custom tracks and/or using " "Nextstrain's interactive display " "which supports " "drag-and-drop of local metadata that remains on your computer.

\n"); -puts("
"); -puts("
"); -puts("
"); -puts("
\n" - "
" - "\n" - "

" - "The " - "CDC COVID-19 Genomic Epidemiology Toolkit now includes a training module for UShER!\n" - "Module 3.3 includes a video, " - "slides (PDF), and links to more resources.\n" - "

" - ); +puts("

\n" + "GISAID data displayed in the Genome Browser are subject to GISAID's\n" + "" + "Terms and Conditions.\n" + "SARS-CoV-2 genome sequences and metadata are available for download from\n" + "GISAID EpiCoV™.\n" + "

"); +puts("

\n" + "COVID-19 Pandemic Resources at UCSC

\n"); puts("
"); puts("
"); puts("
"); puts("
"); -puts("

Note: " - "Please do not upload any files that contain " +puts("

Privacy and sharing

"); +puts("

Please do not upload " "Protected Health Information (PHI) " - "to UCSC.\n" + "target=_blank>Protected Health Information (PHI).

\n" "If even virus sequence files must remain local on your computer, then you can try " "ShUShER " "which runs entirely in your web browser so that no files leave your computer." "

\n" "

We do not store your information " "(aside from the information necessary to display results)\n" "and will not share it with others unless you choose to share your Genome Browser view.

\n" "

In order to enable rapid progress in SARS-CoV-2 research and genomic contact tracing,\n" "please share your SARS-CoV-2 sequences by submitting them to an " "INSDC member institution\n" "(NCBI,\n" "EMBL-EBI\n" "or DDBJ)\n" "and GISAID.\n" "

\n"); puts("
"); puts("
"); -puts("
"); -printf("

Select your FASTA, VCF or list of sequence names/IDs: "); -printf("", - seqFileVar, seqFileVar); -printf("

or paste in sequence names/IDs:
\n"); -cgiMakeTextArea(pastedIdVar, "", 10, 70); -struct treeChoices *treeChoices = loadTreeChoices(db); -if (treeChoices) - { - puts("

"); - printf("Phylogenetic tree version: "); - char *phyloPlaceTree = cartOptionalString(cart, "phyloPlaceTree"); - cgiMakeDropListWithVals("phyloPlaceTree", treeChoices->descriptions, treeChoices->protobufFiles, - treeChoices->count, phyloPlaceTree); - } -puts("

"); -printf("Number of samples per subtree showing sample placement: "); -int subtreeSize = cartUsualInt(cart, "subtreeSize", 50); -cgiMakeIntVarWithLimits("subtreeSize", subtreeSize, - "Number of samples in subtree showing neighborhood of placement", - 5, 10, 5000); -puts("

"); -cgiMakeOnClickSubmitButton(CHECK_FILE_OR_PASTE_INPUT_JS(seqFileVar, pastedIdVar), - "submit", "upload"); -puts("

"); -// Add a loading image to reassure people that we're working on it when they upload a big file -printf("
\n"); -printf("
\n"); -jsInline("$(document).ready(function() {\n" - " loadingImage.init($('#loadingImg'), $('#loadingMsg'), " - "'

Uploading and processing your sequences " - "may take some time. Please leave this window open while we work on your sequences.

');" - "});\n"); - -puts("
"); -puts("
"); -} - -static void exampleForm() -/* Let the user try Russ's example. */ -{ -printf("
\n\n", - "hgPhyloPlace"); -cartSaveSession(cart); -cgiMakeHiddenVar("db", "wuhCor1"); -puts("
"); -puts("If you don't have a local file, you can try an " - "example: "); -cgiMakeButton("submit", "try example"); -puts("
"); -puts("
"); -} - -static void linkToLandingPage() -/* David asked for a link back to our covid19 landing page. */ -{ -puts("
"); puts("
"); -puts("

"); -puts("

\n" - "COVID-19 Pandemic Resources at UCSC

\n"); -puts("
"); -puts("
"); -} - -static void gisaidFooter() -/* GISAID wants this on all pages that have anything to do with GISAID samples. */ -{ puts("
"); -puts("
"); -puts("

"); -puts("

\n" - "GISAID data displayed in the Genome Browser are subject to GISAID's\n" - "" - "Terms and Conditions.\n" - "SARS-CoV-2 genome sequences and metadata are available for download from\n" - "GISAID EpiCoV™.\n" - "

"); +puts("

Tutorial

"); +puts("\n" + "

Slides for tutorial

\n" + "

" + "More tutorials from CDC COVID-19 Genomic Epidemiology Toolkit

\n" + "

" + ); puts("
"); puts("
"); +puts(""); } static void mainPage(char *db) { // Start web page with new-style header webStartGbNoBanner(cart, db, "UShER: Upload"); jsIncludeFile("jquery.js", NULL); jsIncludeFile("ajax.js", NULL); newPageStartStuff(); puts("
" - "
\n" + "
\n" "
UShER: Ultrafast Sample placement on Existing tRee
\n" "
\n" "
\n" "
\n" "
\n"); if (hgPhyloPlaceEnabled()) { inputForm(); - exampleForm(); - linkToLandingPage(); - gisaidFooter(); } else { puts("
"); puts(" Sorry, this server is not configured to perform phylogenetic placement."); puts("
"); } puts("
\n"); newPageEndStuff(); } static void resultsPage(char *db, struct lineFile *lf) /* QC the user's uploaded sequence(s) or VCF; if input looks valid then run usher * and display results. */ { webStartGbNoBanner(cart, db, "UShER: Results"); jsIncludeFile("jquery.js", NULL); jsIncludeFile("ajax.js", NULL); newPageStartStuff(); if (issueBotWarning) { char *ip = getenv("REMOTE_ADDR"); botDelayMessage(ip, botDelayMillis); } // Allow 10 minutes for big sets of sequences lazarusLives(15 * 60); puts("
" - "
\n" + "
\n" "
UShER: Ultrafast Sample placement on Existing tRee
\n" "
\n" "
\n" "
\n" "
\n"); // Form submits subtree custom tracks to hgTracks printf("
\n\n", hgTracksName(), cartUsualString(cart, "formMethod", "POST")); cartSaveSession(cart); puts("
"); fflush(stdout); if (lf != NULL) { // Use trackLayout to get hgTracks parameters relevant to displaying trees: struct trackLayout tl; trackLayoutInit(&tl, cart); // Do our best to place the user's samples, make custom tracks if successful: char *phyloPlaceTree = cartOptionalString(cart, "phyloPlaceTree"); int subtreeSize = cartUsualInt(cart, "subtreeSize", 50); boolean success = FALSE; char *ctFile = phyloPlaceSamples(lf, db, phyloPlaceTree, measureTiming, subtreeSize, tl.fontHeight, &success); if (ctFile) { cgiMakeHiddenVar(CT_CUSTOM_TEXT_VAR, ctFile); if (tl.leftLabelWidthChars < 0 || tl.leftLabelWidthChars == leftLabelWidthDefaultChars) cgiMakeHiddenVar(leftLabelWidthVar, leftLabelWidthForLongNames); cgiMakeButton("submit", "view in Genome Browser"); puts("
"); puts("
"); } else if (! success) { puts("

"); puts("
"); puts(""); // Let the user upload something else and try again: inputForm(); } } else { warn("Unable to read your uploaded data - please choose a file and try again, or click the " ""try example" button."); // Let the user try again: puts("
"); puts(""); inputForm(); - exampleForm(); } puts("
\n"); -linkToLandingPage(); -gisaidFooter(); newPageEndStuff(); } static void doMiddle(struct cart *theCart) /* Set up globals and make web page */ { cart = theCart; char *db = NULL, *genome = NULL, *clade = NULL; getDbGenomeClade(cart, &db, &genome, &clade, oldVars); int timeout = cartUsualInt(cart, "udcTimeout", 300); if (udcCacheTimeout() < timeout) udcSetCacheTimeout(timeout); knetUdcInstall(); measureTiming = cartUsualBoolean(cart, "measureTiming", measureTiming); char *submitLabel = cgiOptionalString("submit"); -if (submitLabel && sameString(submitLabel, "try example")) +char *newExampleButton = cgiOptionalString("exampleButton"); +if ((submitLabel && sameString(submitLabel, "try example")) || + (newExampleButton && sameString(newExampleButton, "Upload Example File"))) { char *exampleFile = phyloPlaceDbSettingPath(db, "exampleFile"); struct lineFile *lf = lineFileOpen(exampleFile, TRUE); resultsPage(db, lf); } else if (cgiOptionalString(remoteFileVar)) { char *url = cgiString(remoteFileVar); struct lineFile *lf = netLineFileOpen(url); resultsPage(db, lf); } else if (isNotEmpty(trimSpaces(cgiOptionalString(pastedIdVar)))) { char *pastedIds = cgiString(pastedIdVar); struct lineFile *lf = lineFileOnString("pasted names/IDs", TRUE, pastedIds); resultsPage(db, lf); } else if (cgiOptionalString(seqFileVar) || cgiOptionalString(seqFileVar "__filename")) { struct lineFile *lf = lineFileFromFileInput(cart, seqFileVar); resultsPage(db, lf); } else mainPage(db); } #define LD_LIBRARY_PATH "LD_LIBRARY_PATH" static void addLdLibraryPath() /* usher requires a tbb lib that is not in the yum package tbb-devel, so for now * I'm adding the .so files to hgPhyloPlaceData. Set environment variable LD_LIBRARY_PATH * to pick them up from there. */ { char *oldValue = getenv(LD_LIBRARY_PATH); struct dyString *dy = dyStringNew(0); if (startsWith("/", PHYLOPLACE_DATA_DIR)) dyStringAppend(dy, PHYLOPLACE_DATA_DIR); else { char cwd[4096]; getcwd(cwd, sizeof cwd); dyStringPrintf(dy, "%s/%s", cwd, PHYLOPLACE_DATA_DIR); } if (isNotEmpty(oldValue)) dyStringPrintf(dy, ":%s", oldValue); setenv(LD_LIBRARY_PATH, dyStringCannibalize(&dy), TRUE); } int main(int argc, char *argv[]) /* Process command line. */ { /* Null terminated list of CGI Variables we don't want to save to cart */ char *excludeVars[] = {"submit", "Submit", seqFileVar, seqFileVar "__binary", seqFileVar "__filename", pastedIdVar, NULL}; enteredMainTime = clock1000(); issueBotWarning = earlyBotCheck(enteredMainTime, "hgPhyloPlace", delayFraction, 0, 0, "html"); cgiSpoof(&argc, argv); oldVars = hashNew(10); addLdLibraryPath(); cartEmptyShellNoContent(doMiddle, hUserCookie(), excludeVars, oldVars); cgiExitTime("hgPhyloPlace", enteredMainTime); return 0; }