2521d696f5073ce8cee3f59f423f161fdb77d550
max
  Fri Sep 25 02:48:37 2026 -0700
VCF tracks: the bigBed trackDb filters (filter.*, filterByRange, filterLimits, filterValues, filterType, filterText, filterLabel) now work on INFO fields, plus ID and QUAL. The field list and types come from the VCF header; the bigBed filter code is reused, bigBed behavior unchanged, refs #37617

diff --git src/hg/lib/vcfUi.c src/hg/lib/vcfUi.c
index 7ba16bd1580..cc773906eb1 100644
--- src/hg/lib/vcfUi.c
+++ src/hg/lib/vcfUi.c
@@ -2,30 +2,31 @@
  * between more than one CGI. */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "cheapcgi.h"
 #include "errCatch.h"
 #include "hCommon.h"
 #include "hui.h"
 #include "jsHelper.h"
 #include "vcf.h"
 #include "vcfUi.h"
 #include "knetUdc.h"
 #include "udc.h"
+#include "asParse.h"
 
 INLINE char *nameOrDefault(char *thisName, char *defaultVal)
 /* If thisName is not a placeholder value, return it; otherwise return default. */
 {
 if (isNotEmpty(thisName) && !sameString(thisName, "."))
     return thisName;
 return defaultVal;
 }
 
 #define VCF_HAPLOSORT_DEFAULT_DESC "middle variant in viewing window"
 
 static void vcfCfgHaplotypeCenterHiddens(char *track, char *ctrName, char *ctrChrom, int ctrPos)
 /* Make hidden form inputs and button for setting the center variant for haplotype
  * clustering/sorting in hgTracks. */
 {
@@ -183,31 +184,31 @@
               "if (this.value == '"VCF_HAP_METHOD_CENTER_WEIGHTED"') {"
               "  $('#leafShapeContainer').show();"
               "  $('#sampleColorContainer').hide();"
               "} else if (this.value == '"VCF_HAP_METHOD_TREE_FILE"') {"
               "  $('#sampleColorContainer').show();"
               "  $('#leafShapeContainer').hide();"
               "} else {"
               "  $('#sampleColorContainer').hide();"
               "  $('#leafShapeContainer').hide();"
               "}});\n",
               varName);
     }
 }
 
 //TODO: share this code w/hgTracks, hgc in hg/lib/vcfFile.c
-static struct vcfFile *vcfHopefullyOpenHeader(struct cart *cart, struct trackDb *tdb)
+static struct vcfFile *vcfHopefullyOpenHeaderUncached(struct cart *cart, struct trackDb *tdb)
 /* Defend against network errors and return the vcfFile object with header data, or NULL. */
 {
 knetUdcInstall();
 if (udcCacheTimeout() < 300)
     udcSetCacheTimeout(300);
 char *fileOrUrl = trackDbSetting(tdb, "bigDataUrl");
 if (isEmpty(fileOrUrl))
     {
     char *db = cartString(cart, "db");
     char *table = tdb->table;
     char *dbTableName = trackDbSetting(tdb, "dbTableName");
     struct sqlConnection *conn;
     if (isCustomTrack(tdb->track) && isNotEmpty(dbTableName))
         {
         conn =  hAllocConn(CUSTOM_TRASH);
@@ -233,30 +234,93 @@
     if (startsWithWord("vcfTabix", tdb->type))
 	vcff = vcfTabixFileMayOpen(fileOrUrl, NULL, 0, 0, vcfMaxErr, -1);
     else
 	vcff = vcfFileMayOpen(fileOrUrl, NULL, 0, 0, vcfMaxErr, -1, FALSE);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     if (isNotEmpty(errCatch->message->string))
 	warn("unable to open %s: %s", fileOrUrl, errCatch->message->string);
     }
 errCatchFree(&errCatch);
 return vcff;
 }
 
+static struct vcfFile *vcfHopefullyOpenHeader(struct cart *cart, struct trackDb *tdb)
+/* Return the vcfFile object with header data, or NULL, opening the file only once per track
+ * in this CGI run: the config UI and the generic trackDb filter code both need the header. */
+{
+static struct hash *vcffCache = NULL;
+if (vcffCache == NULL)
+    vcffCache = hashNew(0);
+struct hashEl *hel = hashLookup(vcffCache, tdb->track);
+if (hel != NULL)
+    return hel->val;
+struct vcfFile *vcff = vcfHopefullyOpenHeaderUncached(cart, tdb);
+hashAdd(vcffCache, tdb->track, vcff);
+return vcff;
+}
+
+static struct asColumn *vcfFilterAsColumn(char *name, char *comment, char *asType)
+/* Make one pseudo-autoSql column for vcfInfoAsObj. */
+{
+struct asColumn *col;
+AllocVar(col);
+col->name = cloneString(name);
+col->comment = cloneString(isNotEmpty(comment) ? comment : name);
+col->lowType = asTypeFindLow(asType);
+return col;
+}
+
+struct asObject *vcfInfoAsObj(struct cart *cart, struct trackDb *tdb)
+/* Return a pseudo-autoSql object that describes the fields of a VCF track that can be used by the
+ * generic trackDb filters (filter.*, filterText.*, filterValues.* etc): one column per INFO
+ * definition in the VCF header, named by the INFO key, with the header's Description as comment,
+ * followed by the fixed columns ID and QUAL.  Returns NULL if the file cannot be opened. */
+{
+struct vcfFile *vcff = vcfHopefullyOpenHeader(cart, tdb);
+if (vcff == NULL)
+    return NULL;
+struct asObject *asObj;
+AllocVar(asObj);
+asObj->name = cloneString("vcfInfo");
+asObj->comment = cloneString("VCF INFO fields");
+struct hash *seen = hashNew(0);
+struct vcfInfoDef *def;
+for (def = vcff->infoDefs;  def != NULL;  def = def->next)
+    {
+    char *asType = "string";
+    if (def->type == vcfInfoInteger || def->type == vcfInfoFlag)
+        asType = "int";
+    else if (def->type == vcfInfoFloat)
+        asType = "double";
+    else if (def->type == vcfInfoCharacter)
+        asType = "char";
+    slAddHead(&asObj->columnList, vcfFilterAsColumn(def->key, def->description, asType));
+    hashStore(seen, def->key);
+    }
+// If an INFO key has the same name as a fixed column, the INFO field wins, also in hgTracks
+if (!hashLookup(seen, "ID"))
+    slAddHead(&asObj->columnList, vcfFilterAsColumn("ID", "Variant ID", "string"));
+if (!hashLookup(seen, "QUAL"))
+    slAddHead(&asObj->columnList, vcfFilterAsColumn("QUAL", "Quality score (QUAL)", "double"));
+slReverse(&asObj->columnList);
+hashFree(&seen);
+return asObj;
+}
+
 static void vcfCfgHapClusterEnable(struct cart *cart, struct trackDb *tdb, char *name,
 				   boolean parentLevel)
 /* Let the user enable/disable haplotype sorting display. */
 {
 boolean hapClustEnabled = cartOrTdbBoolean(cart, tdb, VCF_HAP_ENABLED_VAR, TRUE);
 char cartVar[1024];
 safef(cartVar, sizeof(cartVar), "%s." VCF_HAP_ENABLED_VAR, name);
 cgiMakeCheckBox(cartVar, hapClustEnabled);
 printf("<B>Enable %s sorting display</B><BR>\n", vcfHaplotypeOrSample(cart));
 }
 
 static void vcfCfgHapClusterColor(struct cart *cart, struct trackDb *tdb, char *name,
 				   boolean parentLevel)
 /* Let the user choose how to color the sorted haplotypes. */
 {
@@ -723,30 +787,32 @@
         {
         vcfCfgPhasedTrioUi(cart, tdb, vcff, name, parentLevel);
         }
     if (differentString(tdb->track,"evsEsp6500") && (doVcfFilterUi || doVcfQualUi))
         {
         puts("<H3>Filters</H3>");
         if (doVcfQualUi)
             vcfCfgMinQual(cart, tdb, vcff, name, parentLevel);
         if (doVcfFilterUi)
             vcfCfgFilterColumn(cart, tdb, vcff, name, parentLevel);
         }
     if (doVcfMafUi)
         vcfCfgMinAlleleFreq(cart, tdb, vcff, name, parentLevel);
     if (doVcfMinAcUi)
         vcfCfgMinAc(cart, tdb, vcff, name, parentLevel);
+    // The generic trackDb filters (filter.<INFO key>, filterValues.<INFO key>, ...), same UI as bigBed
+    trackDbFiltersCfgUi(cartString(cart, "db"), cart, tdb, name, FALSE);
     }
 else
     {
     printf("Sorry, couldn't access VCF file.<BR>\n");
     }
 
 puts("</TD>");
 if (boxed && fileExists(hHelpFile("hgVcfTrackHelp")))
     printf("<TD style='text-align:right'><A HREF=\"../goldenPath/help/hgVcfTrackHelp.html\" "
            "TARGET=_BLANK>VCF configuration help</A></TD>");
 
 printf("</TR></TABLE>");
 wigOption(cart, name, title, tdb);
 
 if (!boxed && fileExists(hHelpFile("hgVcfTrackHelp")))