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