905b9cb05eeaca7f2dcda42fc6abdb95a2d2da7f
max
Wed Sep 9 05:29:28 2026 -0700
no captcha for a command-line CGI run, and version the detailsScript module URL
Two small fixes to things noticed while adding the scatterPlot plot type.
A CGI run from the command line got the Cloudflare Turnstile challenge page
instead of the output the caller asked for, which makes "./hgc db=hg38 g=x" -
the quickest way to see what a CGI emits - useless without a hand-made hg.conf.
There is no browser to solve a captcha in that situation. printCaptcha() now
returns early when cgiWasSpoofed(). That flag cannot be set from an HTTP
request: cgiFromCommandLine() returns early and leaves it FALSE whenever the
web server has set REQUEST_METHOD. Checked that a plain argument-style run is
now clean, that a run which fakes the web environment with QUERY_STRING still
gets the captcha, and that an HTTP request behaves exactly as the unmodified
binary does.
The detailsScript module was loaded from a hardcoded import('../js/hgc.X.js'),
bypassing webTimeStampedLinkToResource(), so it was the one script on the page
with no ?v=<mtime>. That is the mechanism that flushes a browser's cache when
the CGI version changes and that keeps a mirror from pairing an old static file
with new CGIs, and without it a cached module could be handed newer bedDetails
JSON than it was written for. Now built through the helper, which also fixes the
already-shipped histogram type. The helper errAborts on a missing file and the
plot type comes from a hub, so a plot type with no module installed falls back to
the plain path: a silent failed import as before, rather than one bad hub setting
taking down the whole details page.
refs #35415
diff --git src/hg/hgc/bigBedClick.c src/hg/hgc/bigBedClick.c
index fdac6c12423..0eb19d930e3 100644
--- src/hg/hgc/bigBedClick.c
+++ src/hg/hgc/bigBedClick.c
@@ -1,767 +1,790 @@
/* Handle details pages for wiggle tracks. */
/* Copyright (C) 2013 The Regents of the University of California
* See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
#include "common.h"
#include "wiggle.h"
#include "cart.h"
#include "hgc.h"
#include "hubConnect.h"
#include "hCommon.h"
#include "hgColors.h"
#include "bigBed.h"
#include "hui.h"
#include "subText.h"
#include "web.h"
#include "chromAlias.h"
#include "quickLift.h"
#include "hgConfig.h"
#include "jsHelper.h"
#include "jsonParse.h"
#include "jsonWrite.h"
#include "net.h"
#include "trackHub.h"
static void bigGenePredLinks(char *track, char *item)
/* output links to genePred driven sequence dumps */
{
printf("<H3>Links to sequence:</H3>\n");
printf("<UL>\n");
puts("<LI>\n");
hgcAnchorSomewhere("htcTranslatedPredMRna", item, "translate", seqName);
printf("Translated Protein</A> from genomic DNA\n");
puts("</LI>\n");
puts("<LI>\n");
hgcAnchorSomewhere("htcGeneMrna", item, track, seqName);
printf("Predicted mRNA</A> \n");
puts("</LI>\n");
puts("<LI>\n");
hgcAnchorSomewhere("htcGeneInGenome", item, track, seqName);
printf("Genomic Sequence</A> from assembly\n");
puts("</LI>\n");
printf("</UL>\n");
}
void printMismatchString(char *a, char *b)
/* given two strings of same length, print . for every match and for mismatches, the letter of b */
{
int i = 0;
while (TRUE)
{
if (a[i]=='\0' || b[i]=='\0')
break;
if (a[i]==b[i])
printf(".");
else
printf("%c", (b[i]));
i++;
}
}
static void extFieldMismatchCounts(char *val)
/* crispr track: number of mismatches. A comma-sep string of integers */
{
printf("<tr><td>Number of potential off-targets</td>\n");
printf("<td>\n");
char *words[255];
int wordCount = chopByChar(val, ',', words, ArraySize(words));
int i;
printf("<table style='border-style: hidden'><tr>\n");
for (i=0; i<wordCount; i++)
printf("<td style='border:1px solid #CCCCCC; font-weight: normal; width:auto'><b>%d mismatches:</b><br>%s off-targets</td>", i, words[i]);
printf("</tr></table>\n");
}
static void extFieldCrisprOfftargets(char *val, struct slPair *extraFields)
/* crispr track: locations of off-targets. A |-separated string of coords, including strand and
a score
e.g. chr15;63615585-;71|chr16;8835640+;70 */
{
if (NULL == val)
{
printf("<br><table class='bedExtraTbl'>\n");
printf("<tr><td>Potential Off-targets</td>\n");
printf("<td>No Off-targets found for this guide</td></tr>\n");
printf("</table>\n");
return;
}
printf("<tr><td>Potential Off-targets</td>\n");
printf("<td>\n");
char *coords[65536];
int coordCount = chopByChar(val, '|', coords, ArraySize(coords));
int i;
struct subText *subList = NULL;
slSafeAddHead(&subList, subTextNew("ig:", "intergenic "));
slSafeAddHead(&subList, subTextNew("ex:", "exon "));
slSafeAddHead(&subList, subTextNew("in:", "intron "));
slSafeAddHead(&subList, subTextNew("|", "-"));
boolean hasDb = sqlDatabaseExists(database);
boolean hasLocus = FALSE;
struct sqlConnection *conn = NULL;
if (hasDb)
{
conn = hAllocConn(database);
hasLocus = sqlTableExists(conn, "locusName");
}
if (coordCount==0)
puts("Too many off-targets found to display or no off-targets. Please use the Crispor.org link at the top of the page to show all off-targets.\n");
else
{
printf("<table style='border-collapse:collapse; font-size:12px; table-layout:fixed'>\n");
printf("<tr>\n"
"<th style='width:26em'>Mismatched nucleotides</th>\n"
"<th style='width:9em'>CFD Score</th>\n");
if (hasLocus)
printf("<th style='width:40em'>Locus</th>\n");
printf("<th style='width:30em'>Position</th></tr>\n");
}
boolean collapsed = FALSE;
for (i=0; i<coordCount; i++)
{
if (i>10)
{
collapsed = TRUE;
printf("<tr class='crisprLinkHidden' style='display:none'>\n");
}
else
printf("<tr>\n");
// parse single coordinate string
// chr15;63615585-;71 = chrom;startPosStrand;scoreAsInt
char *parts[3];
chopByChar(coords[i], ';', parts, 3);
char* chrom = parts[0];
char* posStrand = parts[1];
char* scoreStr = parts[2];
// get score and strand
char strand = *(posStrand+strlen(posStrand)-1);
int pos = atol(posStrand);
int scoreInt = atoi(scoreStr);
float score = (float)scoreInt/1000;
// get the DNA sequence - this is slow! twoBit currently does not cache
// if the input is not sorted and this list is sorted by off-target score (CFD)
struct dnaSeq *seq = hDnaFromSeq(database, chrom, pos, pos+23, dnaUpper);
if (strand=='-')
reverseComplement(seq->dna, seq->size);
char *guideSeq = (char*)slPairFindVal(extraFields, "guideSeq");
// PAM = the last three chars of the off-target
int seqLen = strlen(seq->dna);
char *pam = seq->dna+(seqLen-3);
// print sequence + PAM
printf("<td><tt>");
printMismatchString(guideSeq, seq->dna);
printf(" %s", pam);
printf("</tt></td>\n");
// print score of off-target
printf("<td>%0.3f</td>", score);
// print name of this locus
if (hasLocus)
{
struct sqlResult *sr = hRangeQuery(conn, "locusName", chrom, pos, pos+23, NULL, 0);
char **row;
row = sqlNextRow(sr);
if (row != NULL)
{
char *desc = row[4];
char *descLong = subTextString(subList, desc);
printf("<td>%s</td>", descLong);
freeMem(descLong);
}
sqlFreeResult(&sr);
}
// print link to location
printf("<td><a href='%s&db=%s&position=%s%%3A%d-%d'>%s:%d (%c)</a></td>\n",
hgTracksPathAndSettings(), database,
chrom, pos+1, pos+23, chrom, pos+1, strand);
printf("</tr>\n");
}
if (hasDb)
hFreeConn(&conn);
printf("<tr>\n");
if (coordCount!=0)
printf("</table>\n");
if (collapsed)
{
printf("<p><a id='crisprShowAllLink' href='#'>"
"Show all %d off-targets...</a>\n", coordCount);
jsOnEventById("click", "crisprShowAllLink", "crisprShowAll(); return false;");
// inline .js is bad style but why pollute our global .js files for such a rare
// case? Maybe we should have a generic "collapsible" class, like bootstrap?
jsInline(
"function crisprShowAll() {\n"
" $('#crisprShowAllLink').hide();\n"
" $('.crisprLinkHidden').show();\n"
" return false;\n"
"}\n"
);
}
}
static void detailsTabPrintSpecial(char *name, char *val, struct slPair *extraFields)
/* some extra fields require special printing code, they all start with '_' */
{
if (sameWord(name, "_mismatchCounts"))
extFieldMismatchCounts(val);
else if (sameWord(name, "_crisprOfftargets"))
extFieldCrisprOfftargets(val, extraFields);
}
static int seekAndPrintTable(struct trackDb *tdb, char *detailsUrl, off_t offset, struct slPair *extraFields)
/* seek to 0 at url, get headers, then seek to offset, read tab-sep fields and output
* (extraFields are needed for some special field handlers). Return the number of fields
* successfully printed. */
{
int printCount = 0;
// open the URL and get the first line
char *headerLine = readOneLineMaybeBgzip(detailsUrl, 0, 0);
if (headerLine == NULL)
{
printf("Error: Could not open the URL referenced in detailsUrls, %s", detailsUrl);
return printCount;
}
boolean skipEmptyFields = trackDbSettingOn(tdb, "skipEmptyFields");
// get the headers
char *headers[1024];
int headerCount = chopTabs(headerLine, headers);
// clone the headers
int i;
for (i=0; i<headerCount; i++)
headers[i] = cloneString(headers[i]);
// read a line
char *detailsLine = readOneLineMaybeBgzip(detailsUrl, offset, 0);;
if (!detailsLine || isEmpty(detailsLine))
return printCount;
char *fields[1024];
int fieldCount = chopTabs(detailsLine, fields);
if (fieldCount!=headerCount)
{
printf("Error encountered when reading %s:<br>", detailsUrl);
printf("The header line of the tab-sep file has a different number of fields compared ");
printf("with the line pointed to by offset %lld in the bigBed file.<br>", (long long int)offset);
printf("Number of headers: %d", headerCount);
printf("Number of fields at offset: %d", fieldCount);
return printCount;
}
struct slName *tblFieldNames = NULL;
struct hash *fieldsToEmbeddedTbl = hashNew(0);
struct embeddedTbl *tblList = NULL;
getExtraTableFields(tdb, &tblFieldNames, &tblList, fieldsToEmbeddedTbl);
// print the table for all external extra fields
printf("<br><table class='bedExtraTbl'>\n");
fieldCount = min(fieldCount, headerCount);
struct embeddedTbl *userTbl = NULL;
struct dyString *tableLabelsDy = dyStringNew(0);
dyStringPrintf(tableLabelsDy, "var _jsonHgcLabels = [");
for (i=0; i<fieldCount; i++)
{
char *name = headers[i];
char *val = fields[i];
// skip this field if it's empty and "skipEmptyFields" option is set
if (skipEmptyFields && isEmpty(val))
continue;
// skip an optional '#' on the first field name
if (i == 0 && startsWith("#", name))
name = skipBeyondDelimit(name, '#');
if (startsWith("_", name) && !(startsWith("_json", name)) && !(startsWith("json", name)))
detailsTabPrintSpecial(name, val, extraFields);
else if (slNameInList(tblFieldNames, name))
{
userTbl = (struct embeddedTbl *)hashFindVal(fieldsToEmbeddedTbl, name);
userTbl->encodedTbl = val;
printEmbeddedTable(tdb, userTbl, tableLabelsDy);
}
else
{
// the field name and value come from the hub's bigBed when this is a hub track
char *encName = hubEncode(tdb, name);
printFieldLabelWithId(encName, encName);
printf("<td>%s</td></tr>\n", hubEncode(tdb, val));
}
printCount++;
}
printf("</table>\n");
dyStringPrintf(tableLabelsDy, "];\n");
jsInline(dyStringCannibalize(&tableLabelsDy));
return printCount;
}
struct slPair *parseDetailsTablUrls(struct trackDb *tdb)
/* Parse detailsUrls setting string into an slPair list of {offset column name, fileOrUrl} */
{
char *detailsUrlsStr = trackDbSetting(tdb, "detailsUrls");
if (!detailsUrlsStr)
detailsUrlsStr = trackDbSetting(tdb, "detailsTabUrls");
if (!detailsUrlsStr)
return NULL;
struct slPair *detailsUrls = slPairListFromString(detailsUrlsStr, TRUE);
if (!detailsUrls)
{
printf("Problem when parsing trackDb setting detailsUrls<br>\n");
printf("Expected: a space-separated key=val list, like 'fieldName1=URL1 fieldName2=URL2'<br>\n");
printf("But got: '%s'<br>", detailsUrlsStr);
return NULL;
}
struct slPair *pair;
for (pair = detailsUrls; pair != NULL; pair = pair->next)
pair->val = hReplaceGbdb(replaceChars(pair->val, "$db", database));
return detailsUrls;
}
static int printAllExternalExtraFields(struct trackDb *tdb, struct slPair *extraFields)
/* handle the "detailsUrls" trackDb setting:
* For each field, print a separate html table with all field names and values
* from the external tab-sep file. Return the number of fields we successfully printed */
{
int printCount = 0;
struct slPair *detailsUrls = parseDetailsTablUrls(tdb), *pair;
for (pair = detailsUrls; pair != NULL; pair = pair->next)
{
char *fieldName = pair->name;
char *detailsUrl = pair->val;
// get extra bigBed field (=the offset) and seek to it
void *p = slPairFindVal(extraFields, fieldName);
if (p==NULL)
{
printf("Error when parsing trackDb detailsUrls statement:<br>\n");
printf("Cannot find extra bigBed field with name %s\n", fieldName);
return 0;
}
char *offsetStr = (char*)p;
if (offsetStr==NULL || sameWord(offsetStr, "0"))
{
/* need to show the empty off-targets for crispr tracks */
if (startsWith("crispr", tdb->track))
extFieldCrisprOfftargets(NULL, NULL);
// empty or "0" value in bigBed means that the lookup should not be performed
continue;
}
off_t offset = atoll(offsetStr);
printCount += seekAndPrintTable(tdb, detailsUrl, offset, extraFields);
}
slPairFreeValsAndList(&detailsUrls);
return printCount;
}
static struct hash *detailsScriptGroupByPlotType(struct trackDb *tdb)
/* Parse detailsScript.<plotType>.<fieldName> trackDb settings and return a hash
* of plotType -> slPair list (fieldName -> jsonConfig). Returns NULL if no settings found.
* See also hgc.c detailsScriptFieldNames() which parses the same settings for field skipping. */
{
struct slName *settings = trackDbLocalSettingsWildMatch(tdb, DETAILS_SCRIPT_PREFIX);
if (settings == NULL)
return NULL;
struct hash *plotTypeHash = hashNew(0);
struct slName *setting;
for (setting = settings; setting != NULL; setting = setting->next)
{
// Parse "detailsScript.<plotType>.<fieldName>"
char *key = cloneString(setting->name);
char *dot1 = strchr(key, '.');
if (dot1 == NULL)
continue;
dot1++;
char *dot2 = strchr(dot1, '.');
if (dot2 == NULL)
continue;
*dot2 = '\0';
char *plotType = dot1;
if (!isSymbolString(plotType)) // plotTypes must be simple strings - no XSS injection from hub
continue;
char *fieldName = dot2 + 1;
char *jsonConfig = trackDbSetting(tdb, setting->name);
struct slPair *entry;
AllocVar(entry);
entry->name = cloneString(fieldName);
entry->val = cloneString(jsonConfig);
struct slPair *existing = hashFindVal(plotTypeHash, plotType);
slAddTail(&existing, entry);
if (hashLookup(plotTypeHash, plotType) == NULL)
hashAdd(plotTypeHash, plotType, entry);
else
hashReplace(plotTypeHash, plotType, existing);
}
slFreeList(&settings);
return plotTypeHash;
}
static void bigBedClick(char *fileName, struct trackDb *tdb,
char *item, int start, int end, int bedSize)
/* Handle click in generic bigBed track. */
{
char *chrom = cartString(cart, "c");
/* Open BigWig file and get interval list. */
struct bbiFile *bbi = bigBedFileOpenAlias(fileName, chromAliasFindAliases);
struct lm *lm = lmInit(0);
int ivStart = start, ivEnd = end;
char *itemForUrl = item;
if (start == end)
{
// item is an insertion; expand the search range from 0 bases to 2 so we catch it:
ivStart = max(0, start-1);
ivEnd++;
}
char *quickLiftFile = cloneString(trackDbSetting(tdb, "quickLiftUrl"));
struct hash *chainHash = NULL;
struct bigBedInterval *bbList = NULL;
if (quickLiftFile)
bbList = quickLiftGetIntervals(quickLiftFile, bbi, chrom, ivStart, ivEnd, &chainHash);
else
bbList = bigBedIntervalQuery(bbi, chrom, ivStart, ivEnd, 0, lm);
/* Get bedSize if it's not already defined. */
if (bedSize == 0)
bedSize = bbi->definedFieldCount;
/* A bigBed always has at least chrom, chromStart and chromEnd. A smaller count
* can only come from a bad type line, and the bedSize - 3 below would then run
* off the front of restFields[]. */
if (bedSize < 3)
errAbort("Track %s declares 'type bigBed %d', but a bigBed has at least 3 fields.",
tdb->track, bedSize);
char *scoreFilter = cartOrTdbString(cart, tdb, "scoreFilter", NULL);
int minScore = 0;
if (scoreFilter)
minScore = atoi(scoreFilter);
/* Find particular item in list - matching start, and item if possible. */
boolean found = FALSE;
boolean firstTime = TRUE;
struct bigBedInterval *bb;
for (bb = bbList; bb != NULL; bb = bb->next)
{
if (bedSize > 3)
{
char *name = cloneFirstWordByDelimiterNoSkip(bb->rest, '\t');
boolean match = (isEmpty(name) && isEmpty(item)) || sameOk(name, item);
freez(&name);
if (!match)
continue;
}
int seq1Seq2Fields = 0;
// check for seq1 and seq2 in columns 7+8 (eg, pairedTagAlign)
boolean seq1Seq2 = sameOk(trackDbSetting(tdb, BASE_COLOR_USE_SEQUENCE), "seq1Seq2");
if (seq1Seq2 && bedSize == 6)
seq1Seq2Fields = 2;
char *fields[bedSize+seq1Seq2Fields];
char startBuf[16], endBuf[16];
char *rest = cloneString(bb->rest);
char *restFields[256];
int restCount = 0;
int restBedFields = 0;
char **extraFields = NULL;
int extraFieldCount = 0;
struct slPair *extraFieldPairs = NULL;
if (isNotEmpty(rest))
{
restCount = chopTabs(rest, restFields);
restBedFields = bedSize - 3;
if (restCount > restBedFields)
{
extraFields = (restFields + restBedFields);
extraFieldCount = restCount - restBedFields;
extraFieldPairs = getExtraFields(tdb, extraFields, extraFieldCount);
}
}
int bbFieldCount = bigBedIntervalToRow(bb, chrom, startBuf, endBuf, fields,
bedSize+seq1Seq2Fields);
if (bbFieldCount != bedSize+seq1Seq2Fields)
{
errAbort("Disagreement between trackDb field count (%d) and %s fieldCount (%d)",
bedSize, fileName, bbFieldCount);
}
struct bed *bed = NULL;
if (quickLiftFile)
{
if ((bed = quickLiftIntervalsToBed(bbi, chainHash, bb)) == NULL)
continue;
}
else
{
bed = bedLoadN(fields, bedSize);
}
if ((bed == NULL) || (bedSize >= 6 && scoreFilter && bed->score < minScore))
continue;
if (!(bed->chromStart == start && bed->chromEnd == end))
continue;
found = TRUE;
if (firstTime)
{
printf("<BR>\n");
firstTime = FALSE;
}
// if there are extra fields, load them up because we may want to use them in URL:
itemForUrl = getIdInUrl(tdb, item);
printCustomUrlWithFields(tdb, bed->name, bed->name, item == itemForUrl, extraFieldPairs);
if (itemForUrl)
printIframe(tdb, itemForUrl);
bedPrintPos(bed, bedSize, tdb);
// display seq1 and seq2
if (seq1Seq2 && bedSize+seq1Seq2Fields == 8)
printf("<table><tr><th>Sequence 1</th><th>Sequence 2</th></tr>"
"<tr><td> %s </td><td> %s </td></tr></table>", fields[6], fields[7]);
else if (restCount > 0)
{
if (restCount > restBedFields)
{
int printCount = extraFieldsPrint(tdb, NULL, extraFields, extraFieldCount);
printCount += printAllExternalExtraFields(tdb, extraFieldPairs);
if (printCount == 0)
{
int i;
char label[20];
safef(label, sizeof(label), "nonBedFieldsLabel");
printf("<B>%s </B>",
trackDbSettingOrDefault(tdb, label, "Non-BED fields:"));
for (i = restBedFields; i < restCount; i++)
printf("%s%s", (i > 0 ? "\t" : ""), restFields[i]);
printf("<BR>\n");
}
}
if (sameString(tdb->type, "bigGenePred"))
bigGenePredLinks(tdb->track, item);
if (startsWith("hprcDeletions", tdb->track) || startsWith("hprcInserts", tdb->track) || startsWith("hprcArr", tdb->track))
{
// the source field, which is the first item after the itemRgb will
// have all the other chains
// TODO: make this controlled by a trackDb setting
char *oChainList[2048];
int i, numChains = chopCommas(cloneString(restFields[6]), oChainList);
char *oChain = NULL;
struct dyString *ds = dyStringNew(0);
dyStringPrintf(ds, "var chainVis = {");
for (i = 0; i < numChains; i++)
{
oChain = oChainList[i];
char *cartVar = catTwoStrings("chainHprc", oChain);
char *chainVis = cartOptionalString(cart, cartVar);
if (chainVis == NULL)
{
cartVar = catTwoStrings(cartVar, "_sel");
chainVis = cartOptionalString(cart, cartVar);
// TODO: this is not getting the vis right, because _sel is not the
// same as a visibility
}
dyStringPrintf(ds, "\"%s\": \"%s\", ", oChain, chainVis != NULL ? hStringFromTv(hTvFromString(chainVis)) : "Hide");
}
dyStringPrintf(ds, "};\n");
jsInline(dyStringCannibalize(&ds));
}
}
if (isCustomTrack(tdb->track))
{
time_t timep = bbiUpdateTime(bbi);
printBbiUpdateTime(&timep);
}
char *motifPwmTable = trackDbSetting(tdb, "motifPwmTable");
if (motifPwmTable)
{
struct dnaSeq *seq = hDnaFromSeq(database, bed->chrom, bed->chromStart, bed->chromEnd, dnaLower);
if (bed->strand[0] == '-')
reverseComplement(seq->dna, seq->size);
struct dnaMotif *motif = loadDnaMotif(bed->name, motifPwmTable);
motifHitSection(seq, motif);
}
// detailsScript.*: load JS visualization scripts and export field data as JSON
// see also hgc.c detailsScriptFieldNames() which parses the same settings to skip fields
struct hash *plotTypeHash = detailsScriptGroupByPlotType(tdb);
if (plotTypeHash)
{
// Build the bedDetails JSON object using jsonWrite
struct jsonWrite *jw = jsonWriteNew();
jsonWriteObjectStart(jw, NULL);
jsonWriteString(jw, "track", tdb->track);
jsonWriteString(jw, "chrom", chrom);
jsonWriteNumber(jw, "start", bed->chromStart);
jsonWriteNumber(jw, "end", bed->chromEnd);
// Caching turned off for this session (the hgHubConnect file-caching button).
// A module that fetches a file has to say so, because a GET the browser has
// already cached would defeat it; the same flag hgTrackUi hands its own JS.
if (isNotEmpty(cartOptionalString(cart, "udcTimeout")))
jsonWriteBoolean(jw, "udcTimeout", TRUE);
jsonWriteObjectStart(jw, "scripts");
struct hashEl *hel, *helList = hashElListHash(plotTypeHash);
for (hel = helList; hel != NULL; hel = hel->next)
{
struct slPair *fieldList = hel->val;
jsonWriteListStart(jw, hel->name);
struct slPair *fp;
for (fp = fieldList; fp != NULL; fp = fp->next)
{
jsonWriteObjectStart(jw, NULL);
jsonWriteString(jw, "field", fp->name);
// Look up field value from bigBed extra fields
char *fv = "";
if (extraFieldPairs)
{
char *found = slPairFindVal(extraFieldPairs, fp->name);
if (found)
fv = found;
}
jsonWriteString(jw, "value", fv);
// Parse trackDb JSON config and merge its keys into this object
char *jsonConfig = fp->val;
if (isNotEmpty(jsonConfig))
{
struct jsonElement *configEl = jsonParse(jsonConfig);
// jsonObjectVal hands back NULL for a JSON null, and the hash
// routines below dereference their argument, so a hub writing
// "detailsScript.<plotType>.<field> null" would crash us.
struct hash *configHash = jsonObjectVal(configEl, "detailsScript config");
if (configHash == NULL)
{
jsonWriteObjectEnd(jw);
continue;
}
struct hashEl *cel, *celList = hashElListHash(configHash);
for (cel = celList; cel != NULL; cel = cel->next)
{
// A config key ending in "Url" names a file, by the same convention
// trackSettingIsFile() uses. The JS does not fetch it directly: it
// asks hgTrackUi for it, which checks the path against the hubs on
// this cart and reads it with udc. So resolve a relative path here
// against the track's own bigDataUrl, which works whether the hub
// was loaded over http or from a local path. A path the author
// already made absolute is left alone.
struct jsonElement *cval = cel->val;
if (endsWith(cel->name, "Url") && cval != NULL
&& cval->type == jsonString && isNotEmpty(cval->val.jeString)
&& !hasProtocol(cval->val.jeString)
&& cval->val.jeString[0] != '/')
{
char *base = trackDbSetting(tdb, "bigDataUrl");
if (isNotEmpty(base))
{
char *abs = trackHubRelativeUrl(base, cval->val.jeString);
if (abs != NULL)
{
jsonWriteString(jw, cel->name, abs);
freeMem(abs);
continue;
}
}
}
jsonWriteJsonElement(jw, cel->name, cval);
}
hashElFreeList(&celList);
// exportFields names other bigBed fields whose values are exported too,
// so one setting can drive a plot that needs several fields. Only fields
// that exist in this bigBed are exported, so a hub cannot name anything
// else, and the type is checked rather than asserted because jsonListVal
// and jsonStringVal errAbort on a mismatch and this JSON is hub-authored.
struct jsonElement *efEl = hashFindVal(configHash,
DETAILS_SCRIPT_EXPORT_FIELDS);
if (efEl != NULL && efEl->type == jsonList)
{
jsonWriteObjectStart(jw, "fieldValues");
struct slRef *ref;
int efCount = 0;
for (ref = efEl->val.jeList;
ref != NULL && efCount < DETAILS_SCRIPT_MAX_EXPORT;
ref = ref->next)
{
struct jsonElement *nameEl = ref->val;
if (nameEl == NULL || nameEl->type != jsonString)
continue;
char *efName = nameEl->val.jeString;
if (isEmpty(efName) || extraFieldPairs == NULL)
continue;
char *efVal = slPairFindVal(extraFieldPairs, efName);
if (efVal == NULL)
continue;
jsonWriteString(jw, efName, efVal);
efCount++;
}
jsonWriteObjectEnd(jw);
}
}
jsonWriteObjectEnd(jw);
}
jsonWriteListEnd(jw);
}
jsonWriteObjectEnd(jw); // scripts
jsonWriteObjectEnd(jw); // root
// Emit as inline JavaScript
struct dyString *ds = dyStringNew(1024);
dyStringPrintf(ds, "var bedDetails = %s;\n", jw->dy->string);
- // Dynamically import and call each plot type's module
+ // Dynamically import and call each plot type's module. The URL carries
+ // ?v=<mtime>, as every other js file does, so that a browser cannot serve a
+ // cached module against newer bedDetails JSON and a mirror cannot pair an old
+ // module with new CGIs. webTimeStampedLinkToResource() errAborts on a missing
+ // file and plotType comes from a hub, so a plotType with no module installed
+ // falls back to the plain path: that leaves a silent failed import as before,
+ // rather than taking the whole details page down over one bad hub setting.
for (hel = helList; hel != NULL; hel = hel->next)
+ {
+ char modFile[PATH_LEN];
+ safef(modFile, sizeof modFile, "hgc.%s.js", hel->name);
+ char fallBack[PATH_LEN];
+ safef(fallBack, sizeof fallBack, "../js/%s", modFile);
+ char *modUrl = fallBack;
+ char *docRoot = hDocumentRoot();
+ if (docRoot != NULL)
+ {
+ char onDisk[PATH_LEN];
+ safef(onDisk, sizeof onDisk, "%s/js/%s", docRoot, modFile);
+ if (fileExists(onDisk))
+ modUrl = webTimeStampedLinkToResource(modFile, FALSE);
+ }
dyStringPrintf(ds, "$(document).ready(function() {\n"
- " import('../js/hgc.%s.js').then(function(mod) { mod.%s(bedDetails); });\n"
- "});\n", hel->name, hel->name);
+ " import('%s').then(function(mod) { mod.%s(bedDetails); });\n"
+ "});\n", modUrl, hel->name);
+ if (modUrl != fallBack)
+ freeMem(modUrl);
+ }
jsInline(dyStringCannibalize(&ds));
jsonWriteFree(&jw);
hashElFreeList(&helList);
hashFree(&plotTypeHash);
}
}
if (!found)
{
printf("No item %s starting at %d\n", emptyForNull(item), start);
}
lmCleanup(&lm);
bbiFileClose(&bbi);
}
void genericBigBedClick(struct sqlConnection *conn, struct trackDb *tdb,
char *item, int start, int end, int bedSize)
/* Handle click in generic bigBed track. */
{
char *fileName = bbiNameFromSettingOrTable(tdb, conn, tdb->table);
bigBedClick(fileName, tdb, item, start, end, bedSize);
}
void bigBedCustomClick(struct trackDb *tdb)
/* Display details for BigWig custom tracks. */
{
char *fileName = trackDbSetting(tdb, "bigDataUrl");
char *item = cartOptionalString(cart, "i");
int start = cartInt(cart, "o");
int end = cartInt(cart, "t");
bigBedClick(fileName, tdb, item, start, end, 0);
}