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);