8032b3fdf26b7de0f295588f47265207c0e41520 angie Fri Sep 15 10:40:41 2023 -0700 Include MicrobeTrace subtree links in results table. diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c index a1ffa17..5b12724 100644 --- src/hg/hgPhyloPlace/phyloPlace.c +++ src/hg/hgPhyloPlace/phyloPlace.c @@ -1312,32 +1312,32 @@ 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 *urlBase = nextstrainUrlBase(); -struct dyString *dy = dyStringCreate("%s%s?f_userOrOld=uploaded%%20sample", - urlBase, skipProtocol(jsonUrlForNextstrain)); +struct dyString *dy = dyStringCreate("%s%s%s", urlBase, skipProtocol(jsonUrlForNextstrain), + NEXTSTRAIN_URL_PARAMS); 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); } @@ -1368,30 +1368,41 @@ } 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 char *microbeTraceUrlFromTn(struct tempName *jsonTn) +/* Return a link to MicrobeTrace to view an annotated subtree. */ +{ +char *jsonUrl = urlFromTn(jsonTn); +char *urlBase = microbeTraceUrlBase(); +struct dyString *dy = dyStringCreate("%s%s", urlBase, jsonUrl); +freeMem(jsonUrl); +freeMem(urlBase); +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]); } @@ -1442,35 +1453,36 @@ { 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); + NEXTSTRAIN_URL_PARAMS, TRUE); puts("<br>"); if (subtreeSize <= MAX_MICROBETRACE_SUBTREE_SIZE) { - makeSubtreeJumpButton(subtreeDropdownName, "MicrobeTrace", microbeTraceUrlBase(), "", FALSE); + makeSubtreeJumpButton(subtreeDropdownName, "MicrobeTrace", microbeTraceUrlBase(), + MICROBETRACE_URL_PARAMS, 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; @@ -1774,53 +1786,71 @@ static void printLineageTd(char *lineage, char *alt, char *db) /* Print a table cell with lineage (& link to outbreak.info if not 'None') or alt if no lineage. */ { if (isEmpty(lineage)) printf("<td>%s</td>", alt); else { printf("<td>"); printLineageLink(lineage, db); printf("</td>"); } } static void printSubtreeTd(struct subtreeInfo *subtreeInfoList, struct tempName *jsonTns[], - char *seqName) + char *seqName, int subtreeSize) /* Print a table cell with subtree (& link if possible) if found. */ { int ix; struct subtreeInfo *ti = subtreeInfoForSample(subtreeInfoList, seqName, &ix); if (ix < 0) //#*** Probably an error. printf("<td>n/a</td>"); else { printf("<td>%d", ix+1); - if (ti && nextstrainHost()) + if (ti) + { + boolean canNextstrain = (nextstrainHost() != NULL); + boolean canMicrobeTrace = (microbeTraceHost() != NULL && + subtreeSize <= MAX_MICROBETRACE_SUBTREE_SIZE); + if (canNextstrain || canMicrobeTrace) + { + printf(" (view in"); + if (canNextstrain) { char *nextstrainUrl = nextstrainUrlFromTn(jsonTns[ix]); - printf(" (<a href='%s' target=_blank>view in Nextstrain<a>)", nextstrainUrl); + printf(" <a href='%s' target=_blank>Nextstrain</a>", nextstrainUrl); } - printf("</td>"); + if (canNextstrain && canMicrobeTrace) + printf(" or "); + if (canMicrobeTrace) + { + char *mtUrl = microbeTraceUrlFromTn(jsonTns[ix]); + printf(" <a href='%s' target=_blank>MicrobeTrace</a>", mtUrl); + } + printf(")"); + } + } + puts("</td>"); } } static void summarizeSequences(struct seqInfo *seqInfoList, boolean isFasta, struct usherResults *ur, struct tempName *jsonTns[], - char *refAcc, char *db) + char *refAcc, char *db, int subtreeSize) /* Show a table with composition & alignment stats for each sequence that passed basic QC. */ { if (seqInfoList) { puts("<table class='seqSummary'>"); boolean gotClades = FALSE, gotLineages = FALSE; lookForCladesAndLineages(ur->samplePlacements, &gotClades, &gotLineages); printSummaryHeader(isFasta, gotClades, gotLineages, refAcc, db); puts("<tbody>"); struct dyString *dy = dyStringNew(0); struct seqInfo *si; for (si = seqInfoList; si != NULL; si = si->next) { puts("<tr>"); printf("<th>%s</td>", replaceChars(si->seq->name, "|", " | ")); @@ -1968,40 +1998,40 @@ } printf("</td><td class='%s'>%d", qcClassForPlacements(pi->bestNodeCount), pi->bestNodeCount); printf("</td><td class='%s'>%d", qcClassForPScore(pi->parsimonyScore), pi->parsimonyScore); printf("</td>"); } else { if (gotClades) printf("<td>n/a</td>"); if (gotLineages) printf("<td>n/a</td>"); printf("<td>n/a</td><td>n/a</td><td>n/a</td><td>n/a</td><td>n/a</td>"); } - printSubtreeTd(ur->subtreeInfoList, jsonTns, si->seq->name); + printSubtreeTd(ur->subtreeInfoList, jsonTns, si->seq->name, subtreeSize); puts("</tr>"); } puts("</tbody></table><p></p>"); } } static void summarizeSubtrees(struct slName *sampleIds, struct usherResults *results, struct hash *sampleMetadata, struct tempName *jsonTns[], - char *db) + char *db, int subtreeSize) /* Print a summary table of pasted/uploaded identifiers and subtrees */ { boolean gotClades = FALSE, gotLineages = FALSE; lookForCladesAndLineages(results->samplePlacements, &gotClades, &gotLineages); puts("<table class='seqSummary'><tbody>"); puts("<tr><th>Sequence</th>"); if (gotClades) puts("<th>Nextstrain clade (UShER)" TOOLTIP("The <a href='https://nextstrain.org/blog/2021-01-06-updated-SARS-CoV-2-clade-naming' " "target=_blank>Nextstrain clade</a> " "assigned to the sequence by UShER according to its place in the phylogenetic tree") "</th>"); if (gotLineages) puts("<th>Pango lineage (UShER)" TOOLTIP("The <a href='https://cov-lineages.org/' " @@ -2026,31 +2056,31 @@ printf("<td>%s</td>", pi->nextClade ? pi->nextClade : "n/a"); if (gotLineages) printLineageTd(pi->pangoLineage, "n/a", db); } else { if (gotClades) printf("<td>n/a</td>"); if (gotLineages) printf("<td>n/a</td>"); } // pangolin-assigned lineage char *lineage = lineageForSample(sampleMetadata, si->name); printLineageTd(lineage, "n/a", db); // Maybe also #mutations with mouseover to show mutation path? - printSubtreeTd(results->subtreeInfoList, jsonTns, si->name); + printSubtreeTd(results->subtreeInfoList, jsonTns, si->name, subtreeSize); } puts("</tbody></table><p></p>"); } static struct singleNucChange *sncListFromSampleMutsAndImputed(struct slName *sampleMuts, struct baseVal *imputedBases, struct seqWindow *gSeqWin) /* Convert a list of "<ref><pos><alt>" names to struct singleNucChange list. * However, if <alt> is ambiguous, skip it because variantProjector doesn't like it. * Add imputed base predictions. */ { struct singleNucChange *sncList = NULL; struct slName *mut; for (mut = sampleMuts; mut != NULL; mut = mut->next) { @@ -3196,31 +3226,31 @@ "</p>\n"); puts("<p><em>Note: " "The Nextstrain subtree views, and Download files below, are temporary files and will " "expire within two days. " "Please download the Nextstrain subtree JSON files if you will want to view them " "again in the future. The JSON files can be drag-dropped onto " "<a href='https://auspice.us/' target=_blank>https://auspice.us/</a>." "</em></p>"); struct tempName *tsvTn = NULL, *sTsvTn = NULL; struct tempName *zipTn = makeSubtreeZipFile(results, jsonTns, singleSubtreeJsonTn, &startTime); struct tempName *ctTn = NULL; if (subtreesOnly) { - summarizeSubtrees(sampleIds, results, sampleMetadata, jsonTns, db); + summarizeSubtrees(sampleIds, results, sampleMetadata, jsonTns, db, subtreeSize); reportTiming(&startTime, "describe subtrees"); } else { findNearestNeighbors(results, sampleMetadata); reportTiming(&startTime, "find nearest neighbors"); // Make custom tracks for uploaded samples and subtree(s). struct phyloTree *sampleTree = NULL; ctTn = writeCustomTracks(db, vcfTn, results, sampleIds, source, fontHeight, &sampleTree, &startTime); // Make a sample summary TSV file and accumulate S gene changes struct hash *seqInfoHash = hashFromSeqInfoListAndIds(seqInfoList, sampleIds); addSampleMutsFromSeqInfo(results->samplePlacements, seqInfoHash); @@ -3229,31 +3259,31 @@ geneInfoList, gSeqWin, spikeChanges, &startTime); sTsvTn = writeSpikeChangeSummary(spikeChanges, slCount(sampleIds)); downloadsRow(results->bigTreePlusTn->forHtml, tsvTn->forHtml, sTsvTn->forHtml, zipTn->forHtml); int seqCount = slCount(seqInfoList); if (seqCount <= MAX_SEQ_DETAILS) { char *refAcc = cloneString(chrom); if (regexMatch(refAcc, "v[0-9]+$")) { char *v = strrchr(refAcc, 'v'); assert(v != NULL); *v = '.'; } - summarizeSequences(seqInfoList, isFasta, results, jsonTns, refAcc, db); + summarizeSequences(seqInfoList, isFasta, results, jsonTns, refAcc, db, subtreeSize); reportTiming(&startTime, "write summary table (including reading in lineages)"); for (ix = 0, ti = results->subtreeInfoList; ti != NULL; ti = ti->next, ix++) { int subtreeUserSampleCount = slCount(ti->subtreeUserSampleIds); printf("<h3>Subtree %d: ", ix+1); if (subtreeUserSampleCount > 1) printf("%d related samples", subtreeUserSampleCount); else if (subtreeCount > 1) printf("Unrelated sample"); printf("</h3>\n"); makeNextstrainButtonN("viewNextstrainSub", ix, subtreeUserSampleCount, subtreeSize, jsonTns); puts("<br>"); // Make a sub-subtree with only user samples for display: struct phyloTree *subtree = phyloOpenTree(ti->subtreeTn->forCgi);