ab2163a1bcc671f938eee484695ad77df98b4d79
chmalee
  Fri Feb 13 11:15:47 2026 -0800
Add special item search descriptions to MANE and HGNC tracks, refs #34483

diff --git src/hg/lib/bigBedFind.c src/hg/lib/bigBedFind.c
index d0a354d8167..5edf68b624e 100644
--- src/hg/lib/bigBedFind.c
+++ src/hg/lib/bigBedFind.c
@@ -1,105 +1,149 @@
 #include "common.h"
 #include "bPlusTree.h"
 #include "bbiFile.h"
 #include "bigBed.h"
 #include "hgFind.h"
 #include "trix.h"
 #include "trackHub.h"
 #include "hubConnect.h"
 #include "hdb.h"
 #include "errCatch.h"
 #include "bigBedLabel.h"
 #include "bigBedFind.h"
+#include "genbank.h"
 
 static struct hgPos *bigBedIntervalListToHgPositions(struct cart *cart, struct trackDb *tdb,
                         struct bbiFile *bbi, char *term, struct bigBedInterval *intervalList,
-                        char *description, struct hgFindSpec *hfs)
+                        char *description, struct hgFindSpec *hfs, char *db)
 /* Given an open bigBed file, and an interval list, return a pointer to a list of hgPos structures. */
 {
 struct hgPos *posList = NULL;
 char chromName[bbi->chromBpt->keySize+1];
 int lastChromId = -1;
 struct bigBedInterval *interval;
 struct slInt *labelColumns = NULL;
 
+struct asObject *as = NULL;
+int ncbiIdIx = -1, geneNameIx = -1;
+struct sqlConnection *conn = NULL;
+if (sameString(hfs->searchName, "mane") || sameString(hfs->searchName, "hgnc"))
+    {
+    // TODO: right now we are only doing this for MANE and HGNC, but if we are gonna add
+    // special descriptions to more tracks in the future then we should have some generic
+    // way of attaching a description to an hfs, whether that hfs is a mysql search or a
+    // bigBed search
+    as = bigBedAsOrDefault(bbi);
+    if (sameString(hfs->searchName, "mane"))
+        {
+        conn = hAllocConn(db);
+        ncbiIdIx = asColumnFindIx(as->columnList, "ncbiId");
+        }
+    else if (sameString(hfs->searchName, "hgnc"))
+        geneNameIx = asColumnFindIx(as->columnList, "geneName");
+    }
+
 bigBedLabelCalculateFields(cart, tdb, bbi,  &labelColumns );
 for (interval = intervalList; interval != NULL; interval = interval->next)
     {
     struct hgPos *hgPos;
     AllocVar(hgPos);
     slAddHead(&posList, hgPos);
 
     bbiCachedChromLookup(bbi, interval->chromId, lastChromId, chromName, sizeof(chromName));
     lastChromId = interval->chromId;
 
     hgPos->chrom = cloneString(chromName);
     hgPos->chromStart = interval->start;
     hgPos->chromEnd = interval->end;
     hgPos->name = bigBedMakeLabel(tdb, labelColumns, interval, chromName);
     hgPos->browserName = cloneString(term);
     hgPos->description = cloneString(description);
     if (hfs)
         {
         char *paddingStr = hgFindSpecSetting(hfs, "padding");
         int padding = isEmpty(paddingStr) ? 0 : atoi(paddingStr);
         if (padding > 0)
             {
             // highlight the item bases only, to distinguish from padding
             hgPos->highlight = addHighlight(cartString(cart, "db"),
                                             hgPos->chrom, hgPos->chromStart, hgPos->chromEnd);
             hgPos->chromStart -= padding;
             hgPos->chromEnd   += padding;
             if (hgPos->chromStart < 0)
                 hgPos->chromStart = 0;
             }
+        // special code here for per hfs bigBed searches
+        if (sameString(hfs->searchName, "mane") || sameString(hfs->searchName, "hgnc"))
+            {
+            char startBuf[256], endBuf[256], *row[bbi->fieldCount];
+            bigBedIntervalToRow(interval, chromName, startBuf, endBuf, row, bbi->fieldCount);
+            if (sameString(hfs->searchName, "mane"))
+                {
+                // here the description comes from hgFixed.refLink.product, linked via mane.bb.ncbiId
+                if (ncbiIdIx > 0)
+                    {
+                    struct dyString *query = sqlDyStringCreate("select product from %s where mrnaAcc=substring_index('%s', '.', 1)", refLinkTable, row[ncbiIdIx]);
+                    hgPos->description = sqlQuickString(conn, dyStringCannibalize(&query));
+                    }
+                }
+            else
+                {
+                // the description is the geneName field of the bigBed
+                if (geneNameIx > 0)
+                    hgPos->description = cloneString(row[geneNameIx]);
+                }
+            }
         }
     }
 
+if (conn)
+    hFreeConn(&conn);
+
 return posList;
 }
 
 static struct hgPos *getPosFromBigBed(struct cart *cart, struct trackDb *tdb, struct bbiFile *bbi,
-                        char *indexField, char *term, char *description, struct hgFindSpec *hfs)
+                        char *indexField, char *term, char *description, struct hgFindSpec *hfs, char *db)
 /* Given a bigBed file with a search index, check for term. */
 {
 struct errCatch *errCatch = errCatchNew();
 struct hgPos *posList = NULL;
 if (errCatchStart(errCatch))
     {
     int fieldIx;
     struct bptFile *bpt = bigBedOpenExtraIndex(bbi, indexField, &fieldIx);
     struct lm *lm = lmInit(0);
     struct bigBedInterval *intervalList;
     intervalList = bigBedNameQuery(bbi, bpt, fieldIx, term, lm);
 
-    posList = bigBedIntervalListToHgPositions(cart, tdb,  bbi, term, intervalList, description, hfs);
+    posList = bigBedIntervalListToHgPositions(cart, tdb,  bbi, term, intervalList, description, hfs, db);
     bptFileDetach(&bpt);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError) 
     {
     // we fail silently if there is a problem e.g. bad index name
     return NULL;
     }
 
 return posList;
 }
 
 static struct hgPos *doTrixSearch(struct cart *cart, struct trackDb *tdb, char *trixFile,
                         struct slName *indices, struct bbiFile *bbi, char *term, char *description,
-                        struct hgFindSpec *hfs)
+                        struct hgFindSpec *hfs, char *db)
 /* search a trix file in the "searchTrix" field of a bigBed trackDb */
 {
 struct trix *trix = trixOpen(trixFile);
 int trixWordCount = 0;
 char *tmp = cloneString(term);
 char *val = nextWord(&tmp);
 char *trixWords[128];
 
 while (val != NULL)
     {
     trixWords[trixWordCount] = strLower(val);
     trixWordCount++;
     if (trixWordCount == sizeof(trixWords)/sizeof(char*))
 	errAbort("exhausted space for trixWords");
 
@@ -117,31 +161,31 @@
 boolean doSnippets = FALSE;
 if (context && sameString(context, "on"))
     {
     doSnippets = TRUE;
     initSnippetIndex(trix);
     }
 struct hgPos *posList = NULL;
 for ( ; tsList != NULL; tsList = tsList->next)
     {
     struct slName *oneIndex = indices;
     if (doSnippets)
         addSnippetForResult(tsList, trix);
     for (; oneIndex; oneIndex = oneIndex->next)
 	{
 	struct hgPos *posList2 = getPosFromBigBed(cart, tdb, bbi, oneIndex->name,
-                                                  tsList->itemId, tsList->snippet, hfs);
+                                                  tsList->itemId, tsList->snippet, hfs, db);
 
 	posList = slCat(posList, posList2);
 	}
     }
 
 return posList;
 }
 
 int posListCompare(const void *va, const void *vb)
 /* Compare to sort based on name and then position. */
 {
 const struct hgPos *a = *((struct hgPos **)va);
 const struct hgPos *b = *((struct hgPos **)vb);
 int diff = strcmp(a->name, b->name);
 if (diff == 0)
@@ -222,43 +266,43 @@
     {
     bigBedFileClose(&bbi);
     return FALSE;
     }
 
 struct slName *indexList = slNameListFromString(indexField, ',');
 struct hgPos *posList1 = NULL, *posList2 = NULL;
 char *trixFile = trackDbSetting(tdb, "searchTrix");
 // if there is a trix file, use it to search for the term
 if (trixFile != NULL)
     {
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         {
         posList1 = doTrixSearch(cart, tdb, hReplaceGbdb(trixFile), indexList, bbi, term,
-                                NULL, hfs);
+                                NULL, hfs, db);
         }
     errCatchEnd(errCatch);
     if (errCatch->gotError)
         warn("trix search failure for %s: %s", tdb->table, dyStringContents(errCatch->message));
     errCatchFree(&errCatch);
     }
 
 // now search for the raw id's
 struct slName *oneIndex=indexList;
 for (; oneIndex; oneIndex = oneIndex->next)
     {
-    posList2 = getPosFromBigBed(cart, tdb, bbi, oneIndex->name, term, NULL, hfs);
+    posList2 = getPosFromBigBed(cart, tdb, bbi, oneIndex->name, term, NULL, hfs, db);
     posList1 = slCat(posList1, posList2);
     }
 // the trix search and the id search may have found the same item so uniqify:
 slUniqify(&posList1, posListCompare, hgPosFree);
 
 if (posList1 != NULL)
     {
     struct hgPosTable *table;
 
     found = TRUE;
     AllocVar(table);
     slAddHead(&hgp->tableList, table);
     table->description = cloneString(description ? description : tdb->longLabel);
     table->name = cloneString(tdb->table);
     table->searchTime = -1;