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("&nbsp;");
+    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("&nbsp;");
         makeNextstrainButtonN("viewNextstrainTopRow", ix, userSampleCount, subtreeSize, jsonTns);
         }
+    puts("</td>");
     }
 if (0 && isFasta)
     {
-    printf("&nbsp;");
+    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")