b6d356738ffdf5562cc71d22bacbfa1f0c5c65c4 angie Tue Sep 12 16:16:04 2023 -0700 Add linkout (per subtree) to MicrobeTrace (in addition to Nextstrain) if subtreeSize is at most 500. diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c index c44cc02..50d20b3 100644 --- src/hg/hgPhyloPlace/phyloPlace.c +++ src/hg/hgPhyloPlace/phyloPlace.c @@ -272,31 +272,34 @@ treeChoices->sampleNameFiles[treeChoices->count] = cloneString(dy->string); } treeChoices->count++; dyStringFree(&dy); } lineFileClose(&lf); } return treeChoices; } static char *urlFromTn(struct tempName *tn) /* Make a full URL to a trash file that our net.c code will be able to follow, for when we can't * just leave it up to the user's web browser to do the right thing with "../". */ { struct dyString *dy = dyStringCreate("%s%s", hLocalHostCgiBinUrl(), tn->forHtml); -return dyStringCannibalize(&dy); +char *url = dyStringCannibalize(&dy); +int size = strlen(url) + 1; +strSwapStrs(url, size, "cgi-bin/../", ""); +return url; } void reportTiming(int *pStartTime, char *message) /* Print out a report to stderr of how much time something took. */ { if (measureTiming) { int now = clock1000(); fprintf(stderr, "%dms to %s\n", now - *pStartTime, message); *pStartTime = now; } } static boolean lfLooksLikeFasta(struct lineFile *lf) /* Peek at file to see if it looks like FASTA, i.e. begins with a >header. */ @@ -1285,44 +1288,58 @@ if (isNotEmpty(pi->nextClade)) gotClades = TRUE; if (isNotEmpty(pi->pangoLineage)) { gotLineages = TRUE; } if (gotClades && gotLineages) break; } } *retGotClades = gotClades; *retGotLineages = gotLineages; } static char *nextstrainHost() -/* Return the nextstrain hostname from an hg.conf param, or NULL if missing. */ +/* Return the nextstrain hostname from an hg.conf param, or NULL if missing. Do not free result. */ { return cfgOption("nextstrainHost"); } +static char *nextstrainUrlBase() +/* Alloc & return the part of the nextstrain URL before the JSON filename. */ +{ +struct dyString *dy = dyStringCreate("%s/fetch/", nextstrainHost()); +return dyStringCannibalize(&dy); +} + +static char *skipProtocol(char *url) +/* Skip the protocol:// part at the beginning of url if found. Do not free result. */ +{ +char *protocol = strstr(url, "://"); +return protocol ? protocol + strlen("://") : url; +} + static char *nextstrainUrlFromTn(struct tempName *jsonTn) /* Return a link to Nextstrain to view an annotated subtree. */ { char *jsonUrlForNextstrain = urlFromTn(jsonTn); -char *protocol = strstr(jsonUrlForNextstrain, "://"); -if (protocol) - jsonUrlForNextstrain = protocol + strlen("://"); -struct dyString *dy = dyStringCreate("%s/fetch/%s?f_userOrOld=uploaded%%20sample", - nextstrainHost(), jsonUrlForNextstrain); +char *urlBase = nextstrainUrlBase(); +struct dyString *dy = dyStringCreate("%s/%s?f_userOrOld=uploaded%%20sample", + urlBase, skipProtocol(jsonUrlForNextstrain)); +freeMem(jsonUrlForNextstrain); +freeMem(urlBase); return dyStringCannibalize(&dy); } static void makeNextstrainButton(char *id, struct tempName *tn, char *label, char *mouseover) /* Make a button to view an auspice JSON file in Nextstrain. */ { char *nextstrainUrl = nextstrainUrlFromTn(tn); struct dyString *js = dyStringCreate("window.open('%s');", nextstrainUrl); cgiMakeOnClickButtonWithMsg(id, js->string, label, mouseover); dyStringFree(&js); freeMem(nextstrainUrl); } static void makeNextstrainButtonN(char *idBase, int ix, int userSampleCount, int subtreeSize, struct tempName *jsonTns[]) @@ -1338,61 +1355,155 @@ ix+1, userSampleCount, subtreeSize - userSampleCount); makeNextstrainButton(buttonId, jsonTns[ix], buttonLabel, dyMo->string); dyStringFree(&dyMo); } static void makeNsSingleTreeButton(struct tempName *tn) /* Make a button to view single subtree (with all uploaded samples) in Nextstrain. */ { makeNextstrainButton("viewNextstrainSingleSubtree", tn, "view downsampled global tree in Nextstrain", "view one subtree that includes all of your uploaded sequences plus " SINGLE_SUBTREE_SIZE" randomly selected sequences from the global phylogenetic " "tree for context"); } +char *microbeTraceHost() +/* Return the MicrobeTrace hostname from an hg.conf param, or NULL if missing. Do not free result. */ +{ +return cfgOption("microbeTraceHost"); +} + +static char *microbeTraceUrlBase() +/* Alloc & return the part of the MicrobeTrace URL before the JSON filename. */ +{ +struct dyString *dy = dyStringCreate("%s/MicrobeTrace/?url=", microbeTraceHost()); +return dyStringCannibalize(&dy); +} + +static void makeSubtreeDropdown(char *subtreeDropdownName, struct subtreeInfo *subtreeInfoList, + struct tempName **jsonTns) +/* Let user choose subtree to view */ +{ +int count = slCount(subtreeInfoList); +char *labels[count]; +char *values[count]; +struct subtreeInfo *ti; +int ix; +for (ix = 0, ti = subtreeInfoList; ti != NULL; ti = ti->next, ix++) + { + struct dyString *dy = dyStringCreate("subtree %d", ix+1); + labels[ix] = dyStringCannibalize(&dy); + values[ix] = urlFromTn(jsonTns[ix]); + } +cgiMakeDropListWithVals(subtreeDropdownName, labels, values, count, NULL); +for (ix = 0; ix < count; ix++) + { + freeMem(labels[ix]); + } +} + +static void makeSubtreeJumpButton(char *subtreeDropdownName, char *dest, char *destUrlBase, + char *destUrlParams, boolean skipProtocol) +/* Make a button with javascript to get a JSON filename from a dropdown element, format a link + * to dest, and jump to that dest when clicked. */ +{ +char *mouseover = "view selected subtree with your sequences and other sequences from the " + "full phylogenetic tree for context"; +struct dyString *js = dyStringCreate("jsonUrl = document.querySelector('select[name=\"%s\"]').value;" + "if (%d) { ix = jsonUrl.indexOf('://');" + " if (ix >= 0) { jsonUrl = jsonUrl.substr(ix+3); } }" + "window.open('%s' + jsonUrl + '%s');", + subtreeDropdownName, skipProtocol, destUrlBase, destUrlParams); +struct dyString *id = dyStringCreate("jumpTo%s", dest); +printf("<input type='button' id='%s' value='%s' title='%s' class='fullwidth'>", + id->string, dest, mouseover); +jsOnEventById("click", id->string, js->string); +dyStringFree(&js); +dyStringFree(&id); +} + static void makeButtonRow(struct tempName *singleSubtreeJsonTn, struct tempName *jsonTns[], struct subtreeInfo *subtreeInfoList, int subtreeSize, boolean isFasta, boolean offerCustomTrack) /* Russ's suggestion: row of buttons at the top to view results in GB, Nextstrain, Nextclade. */ { puts("<p>"); +puts("<table class='invisalign'><tbody><tr>"); if (offerCustomTrack) + { + puts("<td>"); cgiMakeButtonWithMsg("submit", "view in Genome Browser", "view your uploaded sequences, their phylogenetic relationship and their " "mutations along with many other datasets available in the Genome Browser"); + puts("</td>"); + } +// SingleSubtree -- only for Nextstrain, not really applicable to MicrobeTrace if (nextstrainHost()) { - printf(" "); + puts("<td>"); makeNsSingleTreeButton(singleSubtreeJsonTn); + puts("</td>"); + } + +// If both Nextstrain and MicrobeTrace are configured then make a subtree dropdown and buttons +// to view in Nextstrain or MicrobeTrace +if (nextstrainHost() && microbeTraceHost()) + { + puts("<td>View subtree</td><td>"); + char *subtreeDropdownName = "subtreeSelect"; + makeSubtreeDropdown(subtreeDropdownName, subtreeInfoList, jsonTns); + puts("</td><td>in</td><td>"); + makeSubtreeJumpButton(subtreeDropdownName, "Nextstrain", nextstrainUrlBase(), + "?f_userOrOld=uploaded%20sample", TRUE); + puts("<br>"); + if (subtreeSize <= MAX_MICROBETRACE_SUBTREE_SIZE) + { + makeSubtreeJumpButton(subtreeDropdownName, "MicrobeTrace", microbeTraceUrlBase(), "", FALSE); + } + else + { + cgiMakeOptionalButton("disabledMTButton", "MicrobeTrace", TRUE); + printf(" (%d samples is too many to view in MicrobeTrace; maximum is %d)", + subtreeSize, MAX_MICROBETRACE_SUBTREE_SIZE); + } + puts("</td>"); + } +else if (nextstrainHost()) + { + // Nextstrain only: do the old row of subtree buttons + puts("<td>"); struct subtreeInfo *ti; int ix; for (ix = 0, ti = subtreeInfoList; ti != NULL; ti = ti->next, ix++) { int userSampleCount = slCount(ti->subtreeUserSampleIds); printf(" "); makeNextstrainButtonN("viewNextstrainTopRow", ix, userSampleCount, subtreeSize, jsonTns); } + puts("</td>"); } if (0 && isFasta) { - printf(" "); + puts("<td>"); struct dyString *js = dyStringCreate("window.open('https://master.clades.nextstrain.org/" "?input-fasta=%s');", "needATn"); //#*** TODO: save FASTA to file cgiMakeOnClickButton("viewNextclade", js->string, "view sequences in Nextclade"); + puts("</td>"); } +puts("</tr></tbody></table>"); puts("</p>"); } #define TOOLTIP(text) " <div class='tooltip'>(?)<span class='tooltiptext'>" text "</span></div>" static void printSummaryHeader(boolean isFasta, boolean gotClades, boolean gotLineages, char *refName, char *db) /* Print the summary table header row with tooltips explaining columns. */ { puts("<thead><tr>"); if (isFasta) puts("<th>Fasta Sequence</th>\n" "<th>Size" TOOLTIP("Length of uploaded sequence in bases, excluding runs of N bases at " "beginning and/or end")