2a8b200a7ce182a27d6f090fe0729e089d3cf9b0
angie
  Mon Nov 4 11:31:48 2024 -0800
Store Auspice JSON files gzip-compressed (.json --> .json.gz).  Nextstrain display requires an Apache config change to serve .json with 'Content-Encoding: gzip' header instead of .json.gz.

diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c
index 1db8526..a104db7 100644
--- src/hg/hgPhyloPlace/phyloPlace.c
+++ src/hg/hgPhyloPlace/phyloPlace.c
@@ -1458,30 +1458,35 @@
 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);
+// Nextstrain doesn't accept .json.gz files -- only .json, optionally compressed in HTTPS with
+// Content-Encoding: gzip in the headers.  Apache can be config'd to serve that up from .json.gz
+// files on disk, see https://github.com/nextstrain/nextstrain.org/issues/1058
+if (endsWith(jsonUrlForNextstrain, ".json.gz"))
+    chopSuffix(jsonUrlForNextstrain);
 char *urlBase = nextstrainUrlBase();
 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);
@@ -1555,42 +1560,44 @@
 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]);
     }
 return cloneString(subtreeDropdownName);
 }
 
 static void makeSubtreeJumpButton(char *subtreeDropdownName, char *dest, char *destUrlBase,
-                                  char *destUrlParams, boolean skipProtocol)
+                                  char *destUrlParams, boolean skipProtocol, boolean skipGz)
 /* 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. */
 {
 static int serial = 0;
 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); } }"
+                                     "if (%d) { ix = jsonUrl.indexOf('.gz');"
+                                     "          if (ix >= 0) { jsonUrl = jsonUrl.substr(0, ix); } }"
                                      "window.open('%s' + jsonUrl + '%s');",
-                                     subtreeDropdownName, skipProtocol, destUrlBase, destUrlParams);
+                                     subtreeDropdownName, skipProtocol, skipGz, destUrlBase, destUrlParams);
 struct dyString *id = dyStringCreate("jumpTo%s_%d", dest, serial++);
 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>");
@@ -1606,36 +1613,36 @@
 if (nextstrainHost())
     {
     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 = makeSubtreeDropdown(subtreeInfoList, jsonTns);
     puts("</td><td>in</td><td>");
     makeSubtreeJumpButton(subtreeDropdownName, "Nextstrain", nextstrainUrlBase(),
-                          NEXTSTRAIN_URL_PARAMS, TRUE);
+                          NEXTSTRAIN_URL_PARAMS, TRUE, TRUE);
     puts("<br>");
     if (subtreeSize <= MAX_MICROBETRACE_SUBTREE_SIZE)
         {
         makeSubtreeJumpButton(subtreeDropdownName, "MicrobeTrace", microbeTraceUrlBase(),
-                              MICROBETRACE_URL_PARAMS, FALSE);
+                              MICROBETRACE_URL_PARAMS, FALSE, TRUE);
         }
     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() && slCount(subtreeInfoList) <= MAX_SUBTREE_BUTTONS)
     {
     // Nextstrain only: do the old row of subtree buttons
     puts("<td>");
     struct subtreeInfo *ti;
     int ix;
@@ -3458,45 +3465,45 @@
     slSort(&results->subtreeInfoList, subTreeInfoUserSampleCmp);
     // Make Nextstrain/auspice JSON file for each subtree.
     char *bigGenePredFile = phyloPlaceRefSettingPath(org, refName, "bigGenePredFile");
     struct geneInfo *geneInfoList = getGeneInfoList(bigGenePredFile, refGenome);
     struct seqWindow *gSeqWin = memSeqWindowNew(refGenome->name, refGenome->dna);
     boolean subtreePersist = cartUsualBoolean(cart, "subtreePersist", FALSE);
     struct hash *sampleUrls = hashNew(0);
     struct tempName *jsonTns[subtreeCount];
     struct subtreeInfo *ti;
     int ix;
     for (ix = 0, ti = results->subtreeInfoList;  ti != NULL;  ti = ti->next, ix++)
         {
         AllocVar(jsonTns[ix]);
         char subtreeName[512];
         safef(subtreeName, sizeof(subtreeName), "subtreeAuspice%d", ix+1);
-        trashDirFile(jsonTns[ix], "hgPhyloPlace", subtreeName, ".json");
+        trashDirFile(jsonTns[ix], "hgPhyloPlace", subtreeName, ".json.gz");
         treeToAuspiceJson(ti, org, refName, geneInfoList, gSeqWin, sampleMetadata, NULL,
                           results->samplePlacements, jsonTns[ix]->forCgi, source);
         if (subtreePersist)
             saveTrashFile(jsonTns[ix]);
         // Add a link for every sample to this subtree, so the single-subtree JSON can
         // link to subtree JSONs
         char *subtreeUrl = nextstrainUrlFromTn(jsonTns[ix]);
         struct slName *sample;
         for (sample = ti->subtreeUserSampleIds;  sample != NULL;  sample = sample->next)
             hashAdd(sampleUrls, sample->name, subtreeUrl);
         }
     struct tempName *singleSubtreeJsonTn;
     AllocVar(singleSubtreeJsonTn);
-    trashDirFile(singleSubtreeJsonTn, "hgPhyloPlace", "singleSubtreeAuspice", ".json");
+    trashDirFile(singleSubtreeJsonTn, "hgPhyloPlace", "singleSubtreeAuspice", ".json.gz");
     treeToAuspiceJson(results->singleSubtreeInfo, org, refName, geneInfoList, gSeqWin, sampleMetadata,
                       sampleUrls, results->samplePlacements, singleSubtreeJsonTn->forCgi, source);
     if (subtreePersist)
         saveTrashFile(singleSubtreeJsonTn);
     reportTiming(&startTime, "make Auspice JSON");
     char *dbSetting = phyloPlaceRefSetting(org, refName, "db");
     if (dbSetting)
         db = connectIfHub(cart, dbSetting);
     boolean canDoCustomTracks = (!subtreesOnly &&
                                  (sameString(db, refName) || isNotEmpty(dbSetting)));
     if (canDoCustomTracks)
         // Form submits subtree custom tracks to hgTracks
         printf("<form action='%s' name='resultsForm_%s' method=%s>\n\n",
                hgTracksName(), db, cartUsualString(cart, "formMethod", "POST"));