47ea57080b515e5dad5f658c58feb8944a7e7d61
chmalee
  Thu Jan 29 15:30:26 2026 -0800
Replace clade/assembly dropdowns with a search bar on most CGIs. Add a recents list to hgGateway and to the species bar and to the 'Genomes' dropdown menu. Track recently selected species in localStorage. Add toGenome and fromGenome arguemnts to hubApi/liftOver in order to find appropriate liftover assemblies, refs #36232

diff --git src/hg/hubApi/liftOver.c src/hg/hubApi/liftOver.c
index 7f0b6a0429a..1630e9945cc 100644
--- src/hg/hubApi/liftOver.c
+++ src/hg/hubApi/liftOver.c
@@ -1,25 +1,26 @@
 /* liftOver functions */
 
 #include "dataApi.h"
 #include "hgFind.h"
 #include "cartTrackDb.h"
 #include "cartJson.h"
 #include "genark.h"
 #include "asmAlias.h"
 #include "assemblyList.h"
 #include "liftOver.h"
+#include "liftOverChain.h"
 #include "net.h"
 #include "mailViaPipe.h"
 
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 static long chainTotalBlockSize(struct chain *chain)
 /* Return sum of sizes of all blocks in chain */
 {
 struct cBlock *block;
 long total = 0;
 for (block = chain->blockList; block != NULL; block = block->next)
     total += block->tEnd - block->tStart;
 return total;
 }
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 
@@ -57,81 +58,86 @@
         100.0 * blockSize / origSize,
         100.0 * (chain->tEnd - chain->tStart) / origSize);
     jsonWriteListStart(jw, NULL);
     jsonWriteString(jw, NULL, toDb);
     jsonWriteString(jw, NULL, position);
     jsonWriteString(jw, NULL, coverage);
     jsonWriteListEnd(jw);
     }
 jsonWriteListEnd(jw);
 apiFinishOutput(0, NULL, jw);
 }
 
 static void listExisting()
 /* output the fromDb,toDb from liftOverChain.hgcentral SQL table */
 {
-long long itemCount = 0;
 char *filter = cgiOptionalString(argFilter);
 char *fromDb = cgiOptionalString(argFromGenome);
 char *toDb = cgiOptionalString(argToGenome);
 
 struct sqlConnection *conn = hConnectCentral();
 char *tableName = cloneString(liftOverChainTable());
 struct dyString *query = newDyString(0);
 sqlDyStringPrintf(query, "SELECT count(*) FROM %s", tableName);
 long long totalRows = sqlQuickLongLong(conn, dyStringContents(query));
 dyStringClear(query);
 
 if (isNotEmpty(fromDb) || isNotEmpty(toDb))
     {
-    sqlDyStringPrintf(query, "SELECT fromDb,toDb FROM %s WHERE ", tableName);
+    sqlDyStringPrintf(query, "SELECT * FROM %s WHERE ", tableName);
     if (isNotEmpty(fromDb))
         sqlDyStringPrintf(query, "LOWER(fromDb) = LOWER('%s') %s ", fromDb, isNotEmpty(toDb) ? "AND" : "");
     if (isNotEmpty(toDb))
         sqlDyStringPrintf(query, "LOWER(toDb) = LOWER('%s') ", toDb);
-    sqlDyStringPrintf(query, "LIMIT %d;", maxItemsOutput);
     }
 else if (isNotEmpty(filter))
     {
-    sqlDyStringPrintf(query, "SELECT fromDb,toDb FROM %s WHERE LOWER(fromDb) = LOWER('%s') OR LOWER(toDb) = LOWER('%s') LIMIT %d;", tableName, filter, filter, maxItemsOutput);
+    sqlDyStringPrintf(query, "SELECT * FROM %s WHERE LOWER(fromDb) = LOWER('%s') OR LOWER(toDb) = LOWER('%s')", tableName, filter, filter);
     }
 else
     {
-    sqlDyStringPrintf(query, "SELECT fromDb,toDb FROM %s LIMIT %d", tableName, maxItemsOutput);
+    sqlDyStringPrintf(query, "SELECT * FROM %s", tableName);
     }
+sqlDyStringPrintf(query, " LIMIT %d;", maxItemsOutput);
 
 char *dataTime = sqlTableUpdate(conn, tableName);
 time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
 replaceChar(dataTime, ' ', 'T');	/* ISO 8601 */
 struct jsonWrite *jw = apiStartOutput();
 jsonWriteString(jw, "dataTime", dataTime);
 jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
 
 jsonWriteListStart(jw, "existingLiftOvers");
-struct sqlResult *sr = sqlGetResult(conn, dyStringCannibalize(&query));
-char **row;
-while ((row = sqlNextRow(sr)) != NULL)
+struct liftOverChain *chain, *chainList = liftOverChainLoadByQuery(conn, dyStringCannibalize(&query));
+for (chain = chainList; chain != NULL; chain = chain->next)
     {
-    ++itemCount;
-    jsonWriteListStart(jw, NULL);
-    jsonWriteString(jw, NULL, row[0]);
-    jsonWriteString(jw, NULL, row[1]);
-    jsonWriteListEnd(jw);
+    jsonWriteObjectStart(jw, NULL);
+    jsonWriteString(jw, "fromDb", chain->fromDb);
+    jsonWriteString(jw, "toDb", chain->toDb);
+    jsonWriteString(jw, "path", chain->path);
+    jsonWriteDouble(jw, "minMatch", chain->minMatch);
+    jsonWriteNumber(jw, "minChainT", chain->minChainT);
+    jsonWriteNumber(jw, "minSizeQ", chain->minSizeQ);
+    jsonWriteString(jw, "multiple", chain->multiple);
+    jsonWriteDouble(jw, "minBlocks", chain->minBlocks);
+    jsonWriteString(jw, "fudgeThick", chain->fudgeThick);
+    jsonWriteObjectEnd(jw);
     }
 jsonWriteListEnd(jw);
 jsonWriteNumber(jw, "totalLiftOvers", totalRows);
-jsonWriteNumber(jw, "itemsReturned", itemCount);
+jsonWriteNumber(jw, "itemsReturned", slCount(chainList));
+liftOverChainFreeList(&chainList);
 
 apiFinishOutput(0, NULL, jw);
 hDisconnectCentral(&conn);
 }
 
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 static char *skipWord(char *fw)
 /* skips over current word to start of next.
  * Error for this not to exist. */
 {
 char *s;
 s = skipToSpaces(fw);
 if (s == NULL)
     errAbort("Expecting two words in .ra file line %s\n", fw);
 s = skipLeadingSpaces(s);