e0db353cfefb9bde9cce31d6a2c2d2762dc1de4a
kate
  Wed Sep 12 17:19:14 2018 -0700
IMplement fieldName> expansion in trackDb url setting. refs #22078

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 1307b9e..42c61c1 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -885,73 +885,76 @@
 if (sameWord(urlSetting, "url"))
     url = tdb->url;
 else
     url = trackDbSetting(tdb, urlSetting);
 return url;
 }
 
 void printIframe(struct trackDb *tdb, char *itemName)
 /* print an iframe with the URL specified in trackDb (iframeUrl), can have 
  * the standard codes in it (like $$ for itemName, etc)
  */
 {
 char *url = getUrlSetting(tdb, "iframeUrl");
 if (url==NULL)
     return;
-char *eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, tdb->track, FALSE);
+char *eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, 
+                                tdb->track, FALSE, NULL);
 if (eUrl==NULL)
     return;
 
 char *iframeOptions = trackDbSettingOrDefault(tdb, "iframeOptions", "width='100%%' height='1024'");
 // Resizing requires the hgcDetails pages to include a bit of javascript.
 //
 // Explanation how this works and why the javascript is needed:
 // http://stackoverflow.com/questions/153152/resizing-an-iframe-based-on-content
 // In short:
 // - iframes have a fixed size in html, resizing can only be done in javascript
 // - the iframed page cannot call the resize() function in the hgc html directly, as they have
 //   been loaded from different webservers
 // - one way around it is that the iframed page includes a helper page on our server and 
 //   send their size to the helper page (pages can call functions of included pages)
 // - the helper page then sends the size back to hgc (pages on the same server can
 //   call each others' functions)
 //   width='%s' height='%s' src='%s' seamless scrolling='%s' frameborder='%s'
 
 printf(" \
 <script> \
 function resizeIframe(height) \
 { \
      document.getElementById('hgcIframe').height = parseInt(height)+10; \
 } \
 </script> \
  \
 <iframe id='hgcIframe' src='%s' %s></iframe> \
 <p>", eUrl, iframeOptions);
 }
 
-void printCustomUrlWithLabel(struct trackDb *tdb, char *itemName, char *itemLabel, char *urlSetting, boolean encode)
+void printCustomUrlWithLabel(struct trackDb *tdb, char *itemName, char *itemLabel, 
+                                char *urlSetting, boolean encode, struct slPair *fields)
 /* Print custom URL specified in trackDb settings. */
 {
 char urlLabelSetting[32];
 
 // replace the $$ and other wildchards with the url given in tdb 
 char *url = getUrlSetting(tdb, urlSetting);
 //char* eUrl = constructUrl(tdb, url, itemName, encode);
 if (url==NULL || isEmpty(url))
     return;
 
-char* eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, tdb->track, encode);
+char *eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, tdb->track, 
+                            encode, fields);
 if (eUrl==NULL)
     return;
 
 /* create the url label setting for trackDb from the url
    setting prefix */
 safef(urlLabelSetting, sizeof(urlLabelSetting), "%sLabel", urlSetting);
 char *linkLabel = trackDbSettingOrDefault(tdb, urlLabelSetting, "Outside Link:");
 
 // if we got no item name from hgTracks or the item name does not appear in the URL
 // there is no need to show the item name at all
 if (isEmpty(itemName) || !stringIn("$$", url))
     {
     printf("<A TARGET=_blank HREF='%s'>%s</A><BR>",eUrl, linkLabel);
     return;
     }
@@ -962,44 +965,58 @@
 
 if (sameWord(tdb->table, "npredGene"))
     {
     printf("%s (%s)</A><BR>\n", itemName, "NCBI MapView");
     }
 else
     {
     char *label = itemName;
     if (isNotEmpty(itemLabel) && sameString(itemName, itemLabel))
         label = itemLabel;
     printf("%s</A><BR>\n", label);
     }
 //freeMem(&eUrl); small memory leak
 }
 
-void printCustomUrl(struct trackDb *tdb, char *itemName, boolean encode)
-/* Wrapper to call printCustomUrlWithLabel using the url setting in trackDb */
+void printCustomUrlWithFields(struct trackDb *tdb, char *itemName, char *itemLabel, boolean encode,                                                      struct slPair *fields) 
+/* Wrapper to call printCustomUrlWithLabel with additional fields to substitute */
 {
 char urlSetting[10];
 safef(urlSetting, sizeof(urlSetting), "url");
 
-printCustomUrlWithLabel(tdb, itemName, itemName, urlSetting, encode);
+printCustomUrlWithLabel(tdb, itemName, itemName, urlSetting, encode, fields);
+}
+
+void printCustomUrl(struct trackDb *tdb, char *itemName, boolean encode)
+/* Wrapper to call printCustomUrlWithLabel using the url setting in trackDb */
+{
+printCustomUrlWithFields(tdb, itemName, itemName, encode, NULL);
+}
+
+void printOtherCustomUrlWithFields(struct trackDb *tdb, char *itemName, char *urlSetting, 
+                                        boolean encode, struct slPair *fields)
+/* Wrapper to call printCustomUrlWithLabel to use another url setting other than url in trackDb e.g. url2, this allows the use of multiple urls for a track
+ to be set in trackDb. */
+{
+printCustomUrlWithLabel(tdb, itemName, itemName, urlSetting, encode, fields);
 }
 
 void printOtherCustomUrl(struct trackDb *tdb, char *itemName, char* urlSetting, boolean encode)
 /* Wrapper to call printCustomUrlWithLabel to use another url setting other than url in trackDb e.g. url2, this allows the use of multiple urls for a track
  to be set in trackDb. */
 {
-printCustomUrlWithLabel(tdb, itemName, itemName, urlSetting, encode);
+printCustomUrlWithLabel(tdb, itemName, itemName, urlSetting, encode, NULL);
 }
 
 void genericSampleClick(struct sqlConnection *conn, struct trackDb *tdb,
                         char *item, int start, int smpSize)
 /* Handle click in generic sample (wiggle) track. */
 {
 char table[64];
 boolean hasBin;
 struct sample *smp;
 char query[512];
 struct sqlResult *sr;
 char **row;
 boolean firstTime = TRUE;
 
 hFindSplitTable(database, seqName, tdb->table, table, &hasBin);
@@ -1432,31 +1449,31 @@
         }
 
     // a | character can optionally be used to separate the ID used for $$ from the name shown in the link (like in Wikimedia markup)
     char *idForUrl = itemName;
     boolean encode = TRUE;
     if (strstr(itemName, "|"))
         {
         char *parts[2];
 	chopString(itemName, "|", parts, ArraySize(parts));
         idForUrl = parts[0];
         itemName = parts[1];
         encode = FALSE; // assume the link is already encoded
         }
 
     char *idUrl = replaceInUrl(url, idForUrl, cart, database, seqName, winStart, 
-                    winEnd, tdb->track, encode);
+                    winEnd, tdb->track, encode, NULL);
     printf("<a href=\"%s\" target=\"_blank\">%s</a>", idUrl, itemName);
     } 
 printf("</td></tr>\n");
 freeMem(slIds);
 //freeMem(idNames);
 }
 
 int extraFieldsStart(struct trackDb *tdb, int fieldCount, struct asObject *as)
 /* return the index of the first extra field */
 {
 int start = 0;
 char *type = cloneString(tdb->type);
 char *word = nextWord(&type);
 if (word && (sameWord(word,"bed") || sameWord(word,"bigBed") || sameWord(word,"bigGenePred") || sameWord(word,"bigPsl")  || sameWord(word,"bigBarChart")))
     {
@@ -1488,30 +1505,56 @@
     {
     struct slPair *slp;
     AllocVar(slp);
     char *fieldName = col->name;
     char *fieldVal = fields[count];
     slp->name = fieldName;
     slp->val = fieldVal;
     slAddHead(&extraFields, slp);
     count++;
     //printf("name %s, val %s, idx %d<br>", fieldName, fieldVal, count);
     }
 slReverse(extraFields);
 return extraFields;
 }
 
+struct slPair *getFields(struct trackDb *tdb, char **row)
+/* return field names and their values as a list of slPairs.  */
+// TODO: refactor with getExtraFields
+{
+struct asObject *as = asForDb(tdb, database);
+if (as == NULL)
+    return NULL;
+int fieldCount = slCount(as->columnList);
+struct slPair *fields = NULL;
+struct asColumn *col = as->columnList;
+int count = 0;
+for (count = 0; col != NULL && count < fieldCount; col = col->next)
+    {
+    struct slPair *field;
+    AllocVar(field);
+    char *fieldName = col->name;
+    char *fieldVal = row[count];
+    field->name = fieldName;
+    field->val = fieldVal;
+    slAddHead(&fields, field);
+    count++;
+    }
+slReverse(fields);
+return fields;
+}
+
 int extraFieldsPrint(struct trackDb *tdb,struct sqlResult *sr,char **fields,int fieldCount)
 // Any extra bed or bigBed fields (defined in as and occurring after N in bed N + types.
 // sr may be null for bigBeds.
 // Returns number of extra fields actually printed.
 {
 struct asObject *as = asForDb(tdb, database);
 if (as == NULL)
     return 0;
 
 // We are trying to print extra fields so we need to figure out how many fields to skip
 int start = extraFieldsStart(tdb, fieldCount, as);
 
 struct asColumn *col = as->columnList;
 char *urlsStr = trackDbSettingClosestToHomeOrDefault(tdb, "urls", NULL);
 struct hash* fieldToUrl = hashFromString(urlsStr);
@@ -4064,42 +4107,44 @@
     type = words[0];
     if (sameString(type, "maf") || sameString(type, "wigMaf") || sameString(type, "bigMaf") || sameString(type, "netAlign")
         || sameString(type, "encodePeak"))
         headerItem = NULL;
     else if ((  sameString(type, "narrowPeak")
              || sameString(type, "broadPeak")
              || sameString(type, "gappedPeak") )
          &&  headerItem
          &&  sameString(headerItem, ".") )
         headerItem = NULL;
     }
 
 /* Print header. */
 genericHeader(tdb, headerItem);
 
+if (differentString(type, "bigInteract") && differentString(type, "interact"))
+    {
+    // skip generic URL code as these may have multiple items returned for a click
     itemForUrl = getIdInUrl(tdb, item);
     if (itemForUrl != NULL && trackDbSetting(tdb, "url"))
         {
         printCustomUrl(tdb, itemForUrl, item == itemForUrl);
         printIframe(tdb, itemForUrl);
         }
-
+    }
 if (plus != NULL)
     {
     fputs(plus, stdout);
     }
-
 if (container != NULL)
     {
     genericContainerClick(conn, container, tdb, item, itemForUrl);
     }
 else if (wordCount > 0)
     {
     type = words[0];
     if (sameString(type, "bed"))
 	{
 	int num = 0;
 	if (wordCount > 1)
 	    num = atoi(words[1]);
 	if (num < 3) num = 3;
         genericBedClick(conn, tdb, item, start, num);
 	}
@@ -4118,32 +4163,31 @@
 	}
     else if (sameString(type, "sample"))
 	{
 	int num = 9;
         genericSampleClick(conn, tdb, item, start, num);
 	}
     else if (sameString(type, "genePred"))
         {
 	char *pepTable = NULL, *mrnaTable = NULL;
 	if ((wordCount > 1) && !sameString(words[1], "."))
 	    pepTable = words[1];
 	if ((wordCount > 2) && !sameString(words[2], "."))
 	    mrnaTable = words[2];
 	genericGenePredClick(conn, tdb, item, start, pepTable, mrnaTable);
 	}
-    else if ( sameString(type, "bigPsl"))
-        {
+    else if ( sameString(type, "bigPsl")) {
 	genericBigPslClick(conn, tdb, item, start, end);
 	}
     else if (sameString(type, "psl"))
         {
 	char *subType = ".";
 	if (wordCount > 1)
 	    subType = words[1];
 	genericPslClick(conn, tdb, item, start, subType);
 	}
     else if (sameString(type, "netAlign"))
         {
 	if (wordCount < 3)
 	    errAbort("Missing field in netAlign track type field");
 	genericNetClick(conn, tdb, item, start, words[1], words[2]);
 	}
@@ -11957,31 +12001,31 @@
 	    {
 	    if (!dbToLabel)
 	        errAbort("can not find trackDb dbPrefixLabels to correspond with dbPrefixUrls\n");
 	    char *databasePrefix = cloneString(database);
 	    while (strlen(databasePrefix) && isdigit(lastChar(databasePrefix)))
 	        trimLastChar(databasePrefix);
 	    struct hashEl *hel = hashLookup(dbToUrl, databasePrefix);
 	    if (hel)
 		{
 		struct hashEl *label = hashLookup(dbToLabel, databasePrefix);
 		if (!label)
 		    errAbort("missing trackDb dbPrefixLabels for database prefix: '%s'\n", databasePrefix);
 		char *url = (char *)hel->val;
 		char *labelStr = (char *)label->val;
 		char *idUrl = replaceInUrl(url, nrl->externalId, cart, database,
-		    nrl->externalId, winStart, winEnd, tdb->track, TRUE);
+		    nrl->externalId, winStart, winEnd, tdb->track, TRUE, NULL);
 		printf("<b>%s:</b> ", labelStr);
 		printf("<a href='%s' target='_blank'>%s</a><br>\n",
 		    idUrl, nrl->externalId);
                 }
 	    }
 	}
     }
 
 if (differentWord(nrl->locusLinkId, ""))
     {
     printf("<b>Entrez Gene:</b> ");
     printf("<a href='https://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids=%s' TARGET=_blank>",
            nrl->locusLinkId);
     printf("%s</a><br>\n", nrl->locusLinkId);
     }
@@ -24980,31 +25024,31 @@
     bed = bedLoadN(row+hasBin, 12);
     int lastBlk = bed->blockCount - 1;
     int endForUrl = (bed->chromStart + bed->chromStarts[lastBlk] +
 		     bed->blockSizes[lastBlk]);
     char *endFudge = trackDbSetting(tdb, "endFudge");
     if (endFudge && !strstr(bed->name, "OEA"))
 	endForUrl += atoi(endFudge);
     char sampleName[16];
     safecpy(sampleName, sizeof(sampleName),
 	    tdb->table + strlen(KIDD_EICHLER_DISC_PREFIX));
     touppers(sampleName);
     char itemPlus[2048];
     safef(itemPlus, sizeof(itemPlus),
 	  "%s&o=%d&t=%d&g=%s_discordant&%s_discordant=full",
 	  cgiEncode(item), start, endForUrl, sampleName, sampleName);
-    printCustomUrlWithLabel(tdb, itemPlus, item, "url", FALSE);
+    printCustomUrlWithLabel(tdb, itemPlus, item, "url", FALSE, NULL);
     printKiddEichlerNcbiLinks(tdb, bed->name);
     printf("<B>Score:</B> %d<BR>\n", bed->score);
     printPosOnChrom(bed->chrom, bed->chromStart, bed->chromEnd, bed->strand,
 		    TRUE, bed->name);
     }
 sqlFreeResult(&sr);
 printTrackHtml(tdb);
 }
 
 void doBedDetail(struct trackDb *tdb, struct customTrack *ct, char *itemName)
 /* generate the detail page for a custom track of bedDetail type */
 {
 char *table;
 struct bedDetail *r = NULL;
 struct sqlConnection *conn;