748b06ac95ff2a3957be3845bd3594984e3cc3cf
chmalee
  Wed Aug 17 19:21:48 2022 -0700
Rename test cgi to official name. Always search everything, only show
categories that have matches in the result list. Add /search endpoint to
hubApi, add code to search help docs in hgSuggest but don't call it yet
in autoComplete.js. Minor fixups so search result links work correctly.

Fixing up old programs that call hgPositionsFind

diff --git src/hg/hgSearch/hgSearch.c src/hg/hgSearch/hgSearch.c
new file mode 100644
index 0000000..36ed9b4
--- /dev/null
+++ src/hg/hgSearch/hgSearch.c
@@ -0,0 +1,609 @@
+/* hgSearch - User interface to explore hgFind search results */
+#include "common.h"
+#include "linefile.h"
+#include "hash.h"
+#include "options.h"
+#include "jksql.h"
+#include "htmshell.h"
+#include "web.h"
+#include "cheapcgi.h"
+#include "cart.h"
+#include "hui.h"
+#include "udc.h"
+#include "knetUdc.h"
+#include "hgConfig.h"
+#include "jsHelper.h"
+#include "errCatch.h"
+#include "hgFind.h"
+#include "cartJson.h"
+#include "trackHub.h"
+#include "hubConnect.h"
+#include "jsonWrite.h"
+#include "hgFind.h"
+#include "trix.h"
+#include "genbank.h"
+#include "cartTrackDb.h"
+
+// name of the searchBar form variable from the HTML
+#define SEARCH_TERM_VAR "search"
+#define SEARCH_LIMIT 100
+
+/* Standard CGI Global Variables */
+struct cart *cart;             /* CGI and other variables */
+struct hash *oldVars = NULL;
+boolean measureTiming = FALSE;
+
+/* These globals are for creating the category tree interface */
+struct trackDb *hgFindTdbList = NULL;
+struct grp *hgFindGrpList = NULL;
+
+struct dbDbHelper
+/* A struct dbDb with an extra field for whether the assembly is the default */
+{
+struct dbDbHelper *next;
+struct dbDb *dbDb;
+boolean isDefault;
+};
+
+/* Helper functions for cart handlers */
+static struct dbDbHelper *getDbDbWithDefault()
+/* Grab rows from dbDb along with whether assembly is the default or not */
+{
+struct dbDbHelper *ret = NULL;
+struct sqlConnection *conn = hConnectCentral();
+struct sqlResult *sr;
+char **row;
+struct dbDb *db;
+struct hash *hash = sqlHashOfDatabases();
+
+char query[1024];
+sqlSafef(query, sizeof query,  "select dbDb.*,defaultDb.name from %s "
+        "join %s on %s.genome=%s.genome where active=1 order by orderKey,dbDb.name desc",
+        dbDbTable(), defaultDbTable(), dbDbTable(), defaultDbTable());
+sr = sqlGetResult(conn, query);
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    struct dbDbHelper *this;
+    db = dbDbLoad(row);
+    boolean isGenarkHub = sameOk(db->nibPath, "genark");
+    if (isGenarkHub || hashLookup(hash, db->name))
+        {
+        AllocVar(this);
+        this->dbDb = db;
+        this->isDefault = sameString(row[14],row[0]);
+        slAddTail(&ret, this);
+        }
+    else
+        dbDbFree(&db);
+    }
+sqlFreeResult(&sr);
+hashFree(&hash);
+hDisconnectCentral(&conn);
+slReverse(&ret);
+return ret;
+}
+
+static struct trackDb *addSupers(struct trackDb *trackList)
+/* Insert supertracks into the hierarchy. */
+{
+struct trackDb *newList = NULL;
+struct trackDb *tdb, *nextTdb;
+struct hash *superHash = newHash(5);
+
+for(tdb = trackList; tdb;  tdb = nextTdb)
+    {
+    nextTdb = tdb->next;
+    if (tdb->parent)
+        {
+        // part of a super track
+        if (hashLookup(superHash, tdb->parent->track) == NULL)
+            {
+            hashStore(superHash, tdb->parent->track);
+
+            slAddHead(&newList, tdb->parent);
+            }
+        slAddTail(&tdb->parent->subtracks, tdb);
+        }
+    else
+        slAddHead(&newList, tdb);
+    }
+slReverse(&newList);
+return newList;
+}
+
+static void hashTdbNames(struct trackDb *tdb, struct hash *trackHash)
+/* Store the track names for lookup, except for knownGene which gets its own special
+ * category in the UI */
+{
+struct trackDb *tmp;
+for (tmp = tdb; tmp != NULL; tmp = tmp->next)
+    {
+    if (tmp->subtracks)
+        hashTdbNames(tmp->subtracks, trackHash);
+    if (!sameString(tmp->table, tmp->track))
+        hashAdd(trackHash, tmp->track, tmp);
+    hashAdd(trackHash, tmp->table, tmp);
+    }
+}
+
+void hashTracksAndGroups(struct cart *cart, char *db)
+/* get the list of tracks available for this assembly, along with their group names
+ * and visibility-ness. Note that this implicitly makes connected hubs show up
+ * in the trackList struct, which means we get item search for connected
+ * hubs for free */
+{
+if (hgFindTdbList != NULL && hgFindGrpList != NULL)
+    return;
+
+if (!hgFindTrackHash)
+    hgFindTrackHash = hashNew(0);
+if (!hgFindGroupHash)
+    hgFindGroupHash = hashNew(0);
+cartTrackDbInit(cart, &hgFindTdbList, &hgFindGrpList, FALSE);
+if (!hgFindTdbList)
+    errAbort("Error getting tracks for %s", db);
+if (!hgFindGrpList)
+    errAbort("Error getting groups for %s", db);
+struct trackDb *superList = addSupers(hgFindTdbList);
+hgFindTdbList = superList;
+hashTdbNames(superList, hgFindTrackHash);
+struct grp *g;
+for (g = hgFindGrpList; g != NULL; g = g->next)
+    if (!sameString(g->name, "allTracks") && !sameString(g->name, "allTables"))
+        hashStore(hgFindGroupHash, g->name);
+}
+
+
+void doQuery(struct jsonWrite *jw, char *db, struct searchCategory *categories, char *searchTerms, boolean measureTiming)
+/* Fire off a query. If the query results in a single position and we came from another CGI,
+ * redirect right back to that CGI (except if we came from hgGateway redirect to hgTracks),
+ * otherwise return the position list as JSON */
+{
+struct hgPositions *hgp = NULL;
+hgp = hgPositionsFind(db, searchTerms, "", "hgSearch", cart, FALSE, measureTiming, categories);
+if (hgp)
+    {
+    // if we got an hgvs match to chromInfo (example: chrX:g.31500000_31600000del),
+    // or just a plain position range was searched, we have to create the json
+    // manually, cause the tdb lookups in hgPositionsJson() won't work
+    if (hgp->singlePos && hgp->posCount == 1 && hgp->tableList != NULL &&
+            sameString(hgp->tableList->name, "chromInfo"))
+        {
+        struct hgPosTable *table = hgp->tableList;
+        jsonWriteListStart(jw, "positionMatches");
+        jsonWriteObjectStart(jw, NULL);
+        jsonWriteString(jw, "name", "chromInfo");
+        jsonWriteString(jw, "description", table->description);
+        if (table->searchTime >= 0)
+            jsonWriteNumber(jw, "searchTime", table->searchTime);
+        jsonWriteListStart(jw, "matches");
+
+        jsonWriteObjectStart(jw, NULL);
+        char position[512];
+        safef(position, sizeof(position), "%s:%d-%d", hgp->singlePos->chrom, hgp->singlePos->chromStart, hgp->singlePos->chromEnd);
+        jsonWriteString(jw, "position", position);
+        jsonWriteString(jw, "posName", hgp->query);
+        jsonWriteObjectEnd(jw);
+
+        jsonWriteListEnd(jw); // end matches
+        jsonWriteObjectEnd(jw); // end one table
+        jsonWriteListEnd(jw); // end positionMatches
+        }
+    else
+        hgPositionsJson(jw, db, hgp, cart);
+    }
+}
+
+static void addCategoryFieldsToHash(struct hash *elementHash, struct searchCategory *category)
+{
+hashAdd(elementHash, "name", newJsonString(category->name));
+hashAdd(elementHash, "id", newJsonString(category->id));
+hashAdd(elementHash, "visibility", newJsonNumber((long)category->visibility));
+hashAdd(elementHash, "group", newJsonString(category->groupName));
+hashAdd(elementHash, "label", newJsonString(category->label));
+hashAdd(elementHash, "description", newJsonString(category->description));
+hashAdd(elementHash, "parents", newJsonString(slNameListToString(category->parents, ',')));
+hashAdd(elementHash, "priority", newJsonDouble(category->priority));
+}
+
+static boolean nonTrackCateg(struct searchCategory *categ)
+{
+if (sameString("publicHubs", categ->id) ||
+        sameString("helpDocs", categ->id) ||
+        startsWith("trackDb", categ->id) ||
+        sameString("knownGene", categ->id))
+    return TRUE;
+return FALSE;
+}
+
+static struct jsonElement *jsonElementFromVisibleCategs(struct searchCategory *categs)
+{
+struct searchCategory *categ = NULL;
+struct jsonElement *ret = newJsonList(NULL);
+for (categ = categs; categ != NULL; categ = categ->next)
+    {
+    if (categ->visibility > 0)
+        jsonListAdd(ret, newJsonString(categ->id));
+    }
+return ret;
+}
+
+struct searchCategory *getVisibleCategories(struct searchCategory *categories)
+/* Return a list of only the visible categories. Use CloneVar so our original list
+ * stays alive */
+{
+struct searchCategory *ret = NULL, *categ = NULL;
+for (categ = categories; categ != NULL; categ = categ->next)
+    {
+    if (categ->visibility > 0)
+        {
+        struct searchCategory *temp = (struct searchCategory *)CloneVar(categ);
+        slAddHead(&ret, temp);
+        }
+    }
+return ret;
+}
+
+struct jsonElement *makeTrackGroupsJson(char *db)
+/* Turn the available track groups for a database into JSON. Needed for
+ * sorting the categories */
+{
+hashTracksAndGroups(cart, db);
+struct jsonElement *retObj = newJsonObject(hashNew(0));
+struct grp *grp;
+for (grp = hgFindGrpList; grp != NULL; grp = grp->next)
+    {
+    if (!sameString(grp->name, "allTracks") && !sameString(grp->name, "allTables"))
+        {
+        struct jsonElement *grpObj = newJsonObject(hashNew(0));
+        jsonObjectAdd(grpObj, "name", newJsonString(grp->name));
+        jsonObjectAdd(grpObj, "label", newJsonString(grp->label));
+        jsonObjectAdd(grpObj, "priority", newJsonDouble(grp->priority));
+        jsonObjectAdd(retObj, grp->name, grpObj);
+        }
+    }
+return retObj;
+}
+
+static struct jsonElement *jsonElementFromSearchCategory(struct searchCategory *categories, char *db)
+/* Turn struct searchCategory into jsonElement */
+{
+struct jsonElement *trackList = newJsonList(NULL);
+struct jsonElement *nonTrackList = newJsonList(NULL);
+struct searchCategory *categ = NULL;
+for (categ = categories; categ != NULL; categ = categ->next)
+    {
+    struct hash *eleHash = hashNew(0);
+    struct jsonElement *categJson = newJsonObject(eleHash);
+    addCategoryFieldsToHash(eleHash, categ);
+
+    // now add to one of our final lists
+    if (nonTrackCateg(categ))
+        {
+        jsonListAdd(nonTrackList, categJson);
+        }
+    else
+        {
+        if (categ->id)
+            jsonListAdd(trackList, categJson);
+        }
+    }
+
+// we need to enclose the track list categories in a parent category
+// for the javascript to function:
+struct jsonElement *trackContainerEle = newJsonObject(hashNew(0));
+jsonObjectAdd(trackContainerEle, "id", newJsonString("trackData"));
+char name[2048];
+safef(name, sizeof(name), "%s Track Data", db);
+jsonObjectAdd(trackContainerEle, "name", newJsonString(name));
+jsonObjectAdd(trackContainerEle, "tracks", trackList);
+jsonObjectAdd(trackContainerEle, "label", newJsonString(name));
+jsonObjectAdd(trackContainerEle, "priority", newJsonDouble(2.0));
+jsonObjectAdd(trackContainerEle, "description", newJsonString("Search for track data items"));
+jsonListAdd(nonTrackList, trackContainerEle);
+return nonTrackList;
+}
+
+struct searchCategory *makeCategsFromJson(struct jsonElement *searchCategs, char *db)
+/* User has selected some categories, parse the JSON into a struct searchCategory */
+{
+if (searchCategs == NULL)
+    return getAllCategories(cart, db, hgFindGroupHash);
+struct searchCategory *ret = NULL;
+struct slRef *jsonVal = NULL;
+for (jsonVal= searchCategs->val.jeList; jsonVal != NULL; jsonVal = jsonVal->next)
+    {
+    struct jsonElement *val = jsonVal->val;
+    if (val->type != jsonString)
+        errAbort("makeCategsFromJson: non-string argument value for 'categs'");
+    char *categName = val->val.jeString;
+    struct searchCategory *category = NULL;
+    if (!sameString(categName, "allTracks"))
+        category = makeCategory(cart, categName, NULL, db, hgFindGroupHash);
+    else
+        {
+        struct hashEl *hel, *helList = hashElListHash(hgFindTrackHash);
+        for (hel = helList; hel != NULL; hel = hel->next)
+            {
+            struct trackDb *tdb = hel->val;
+            if (!sameString(tdb->track, "knownGene") && !sameString(tdb->table, "knownGene"))
+                slAddHead(&category, makeCategory(cart, tdb->track, NULL, db, hgFindGroupHash));
+            }
+        // the hgFindTrackHash can contain both a composite track (where makeCategory would make categories
+        // for each of the subtracks, and a subtrack, where makeCategory just makes a single
+        // category, which means our final list can contain duplicate categories, so do a
+        // uniqify here so we only have one category for each category
+        slUniqify(&category, cmpCategories, searchCategoryFree);
+        }
+    if (category != NULL)
+        {
+        if (ret)
+            slCat(&ret, category);
+        else
+            ret = category;
+        }
+    }
+return ret;
+}
+
+void jsonElementSaveCategoriesToCart(char *db, struct jsonElement *jel)
+/* Write the currently selected categories to the cart so on back button navigation
+ * we only search the selected categories. jel must be a jeList of strings */
+{
+char cartSetting[512];
+safef(cartSetting, sizeof(cartSetting), "hgSearch_categs_%s", db);
+struct dyString *cartVal = dyStringNew(0);
+struct slRef *categ;
+for (categ = jel->val.jeList; categ != NULL; categ = categ->next)
+    {
+    struct jsonElement *val = categ->val;
+    if (val->type != jsonString)
+        errAbort("saveCategoriesToCart: category is not a string");
+    else
+        dyStringPrintf(cartVal, "%s,", val->val.jeString);
+    }
+cartSetString(cart, cartSetting, dyStringCannibalize(&cartVal));
+}
+
+static void writeDefaultForDb(struct jsonWrite *jw, char *database)
+/* Put up the default view when entering this page for the first time, which basically
+ * means return the list of searchable stuff. */
+{
+struct searchCategory *defaultCategories = getAllCategories(cart, database, hgFindGroupHash);
+struct jsonElement *categsJsonElement = jsonElementFromSearchCategory(defaultCategories, database);
+struct jsonElement *selectedCategsJsonList = jsonElementFromVisibleCategs(defaultCategories);
+jsonElementSaveCategoriesToCart(database,selectedCategsJsonList);
+dyStringPrintf(jw->dy, ", \"db\": \"%s\"", database);
+dyStringPrintf(jw->dy, ", \"categs\": ");
+jsonDyStringPrint(jw->dy, categsJsonElement, NULL, -1);
+dyStringPrintf(jw->dy, ", \"trackGroups\": ");
+jsonDyStringPrint(jw->dy, makeTrackGroupsJson(database), NULL, -1);
+}
+
+void printMainPageIncludes()
+{
+webIncludeResourceFile("gb.css");
+webIncludeResourceFile("gbStatic.css");
+webIncludeResourceFile("spectrum.min.css");
+webIncludeResourceFile("hgGtexTrackSettings.css");
+puts("<link rel='stylesheet' href='https://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css'>");
+puts("<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css' />");
+puts("<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js'></script>");
+puts("<script src=\"//code.jquery.com/ui/1.10.3/jquery-ui.min.js\"></script>");
+puts("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js\"></script>\n");
+jsIncludeFile("utils.js", NULL);
+jsIncludeFile("ajax.js", NULL);
+jsIncludeFile("lodash.3.10.0.compat.min.js", NULL);
+jsIncludeFile("cart.js", NULL);
+jsIncludeFile("hgSearch.js", NULL);
+
+// Write the skeleton HTML, which will get filled out by the javascript
+webIncludeFile("inc/hgSearch.html");
+}
+
+/* End handler helper functions */
+
+/* Handlers for returning JSON to client */
+static void getSearchResults(struct cartJson *cj, struct hash *paramHash)
+/* User has entered a term to search, search this term against the selected categories */
+{
+char *db = cartJsonRequiredParam(paramHash, "db", cj->jw, "getSearchResults");
+cartSetString(cj->cart, "db", db);
+initGenbankTableNames(db);
+hashTracksAndGroups(cj->cart, db);
+char *searchTerms = cartJsonRequiredParam(paramHash, SEARCH_TERM_VAR, cj->jw, "getSearchResults");
+measureTiming = cartUsualBoolean(cj->cart, "measureTiming", FALSE);
+struct jsonElement *searchCategs = hashFindVal(paramHash, "categs");
+struct searchCategory *searchCategoryList = makeCategsFromJson(searchCategs, db);
+doQuery(cj->jw, db, searchCategoryList, searchTerms, measureTiming);
+fprintf(stderr, "performed query on %s\n", searchTerms);
+}
+
+static void getUiState(struct cartJson *cj, struct hash *paramHash)
+/* We haven't seen this database before, return list of all searchable stuff */
+{
+char *db = cartJsonRequiredParam(paramHash, "db", cj->jw, "getUiState");
+cartSetString(cj->cart, "db", db);
+initGenbankTableNames(db);
+hashTracksAndGroups(cj->cart, db);
+writeDefaultForDb(cj->jw, db);
+}
+
+static struct jsonElement *getGenomes()
+/* Return a string that the javascript can use to put up a species and db select. */
+{
+struct jsonElement *genomesObj = newJsonObject(hashNew(0));
+struct dbDbHelper *localDbs = getDbDbWithDefault();
+struct dbDbHelper *temp;
+for (temp = localDbs; temp != NULL; temp = temp->next)
+    {
+    // fill out the dbDb fields into a struct
+    struct jsonElement *genomeObj = newJsonObject(hashNew(0));
+    jsonObjectAdd(genomeObj, "organism", newJsonString(temp->dbDb->organism));
+    jsonObjectAdd(genomeObj, "name", newJsonString(temp->dbDb->name));
+    jsonObjectAdd(genomeObj, "genome", newJsonString(temp->dbDb->genome));
+    jsonObjectAdd(genomeObj, "description", newJsonString(temp->dbDb->description));
+    jsonObjectAdd(genomeObj, "orderKey", newJsonNumber(temp->dbDb->orderKey));
+    jsonObjectAdd(genomeObj, "isDefault", newJsonBoolean(temp->isDefault));
+
+    // finally add the dbDb object to the hash on species, either create a new
+    // list if this is the first time seeing this species or append to the end
+    // of the list to keep things in the right order
+    struct jsonElement *genomeList = NULL;
+    if ( (genomeList = jsonFindNamedField(genomesObj, temp->dbDb->genome, temp->dbDb->genome)) != NULL)
+        jsonListAdd(genomeList, genomeObj);
+    else
+        {
+        genomeList = newJsonList(NULL);
+        jsonListAdd(genomeList, genomeObj);
+        jsonObjectAdd(genomesObj, temp->dbDb->genome, genomeList);
+        }
+    }
+
+struct dbDb *trackHubDbs = trackHubGetDbDbs(NULL);
+struct dbDb *dbDb;
+for (dbDb = trackHubDbs; dbDb != NULL; dbDb = dbDb->next)
+    {
+    // fill out the dbDb fields into a struct
+    struct jsonElement *genomeObj = newJsonObject(hashNew(0));
+    jsonObjectAdd(genomeObj, "organism", newJsonString(dbDb->organism));
+    jsonObjectAdd(genomeObj, "name", newJsonString(dbDb->name));
+    jsonObjectAdd(genomeObj, "genome", newJsonString(dbDb->genome));
+    jsonObjectAdd(genomeObj, "description", newJsonString(dbDb->description));
+
+    // finally add the dbDb object to the hash on species, either create a new
+    // list if this is the first time seeing this species or append to the end
+    // of the list to keep things in the right order
+    struct jsonElement *genomeList = NULL;
+    if ( (genomeList = jsonFindNamedField(genomesObj, dbDb->genome, dbDb->genome)) != NULL)
+        jsonListAdd(genomeList, genomeObj);
+    else
+        {
+        genomeList = newJsonList(NULL);
+        jsonListAdd(genomeList, genomeObj);
+        jsonObjectAdd(genomesObj, dbDb->genome, genomeList);
+        }
+    }
+return genomesObj;
+}
+
+/* End Handlers */
+
+/* Start do commands, functions that dispatch */
+void doSaveCategoriesToCart(struct cartJson *cj, struct hash *paramHash)
+/* Save any selected categories to the cart so if the user clicks the back button
+ * from hgTracks/hgTrackUi/etc we know what they were last looking at */
+{
+char *db = cartUsualString(cj->cart, "db", hDefaultDb());
+struct jsonElement *jel = hashFindVal(paramHash, "categs");
+if (jel == NULL)
+    {
+    jsonWriteStringf(cj->jw, "error",
+		     "saveCategoriesToCart: required param 'categs' is missing");
+    return;
+    }
+jsonElementSaveCategoriesToCart(db,jel);
+}
+
+void doCartJson()
+/* Register functions that return JSON to client */
+{
+struct cartJson *cj = cartJsonNew(cart);
+cartJsonRegisterHandler(cj, "getSearchResults", getSearchResults);
+cartJsonRegisterHandler(cj, "getUiState", getUiState);
+cartJsonRegisterHandler(cj, "saveCategoriesToCart", doSaveCategoriesToCart);
+cartJsonExecute(cj);
+}
+
+void doMainPage()
+/* Print the basic HTML page and include any necessary Javascript. AJAX calls
+ * will fill out the page later */
+{
+char *database = NULL;
+char *genome = NULL;
+getDbAndGenome(cart, &database, &genome, oldVars);
+webStartGbNoBanner(cart, database, "Search Disambiguation");
+printMainPageIncludes();
+jsInlineF("var hgsid='%s';\n", cartSessionId(cart));
+struct jsonElement *cartJson = newJsonObject(hashNew(0));
+jsonObjectAdd(cartJson, "db", newJsonString(database));
+jsonObjectAdd(cartJson, "genomes", getGenomes());
+struct dyString *cartJsonDy = dyStringNew(0);
+jsonDyStringPrint(cartJsonDy, cartJson, "cartJson", -1);
+jsInlineF("%s;\n", dyStringCannibalize(&cartJsonDy));
+// Call our init function to fill out the page
+jsInline("hgSearch.init();\n");
+webEndGb();
+}
+
+void doSearchOnly()
+/* Send back search results along with whatever we need to make the UI */
+{
+char *db = NULL;
+char *genome = NULL;
+getDbAndGenome(cart, &db, &genome, oldVars);
+char *userSearch = cartCgiUsualString(cart, "search", NULL);
+if (userSearch == NULL || isEmpty(userSearch))
+    {
+    doMainPage();
+    return;
+    }
+initGenbankTableNames(db);
+hashTracksAndGroups(cart, db);
+struct searchCategory *allCategories = getAllCategories(cart, db, hgFindGroupHash);
+struct jsonElement *categsJsonElement = jsonElementFromSearchCategory(allCategories, db);
+
+struct cartJson *cj = cartJsonNew(cart);
+dyStringPrintf(cj->jw->dy, "\"db\": '%s'", db);
+dyStringPrintf(cj->jw->dy, ", \"categs\": ");
+jsonDyStringPrint(cj->jw->dy, categsJsonElement, NULL,-1);
+dyStringPrintf(cj->jw->dy, ", \"trackGroups\": ");
+jsonDyStringPrint(cj->jw->dy, makeTrackGroupsJson(db), NULL, -1);
+dyStringPrintf(cj->jw->dy, ", \"genomes\": ");
+jsonDyStringPrint(cj->jw->dy, getGenomes(), NULL, -1);
+dyStringPrintf(cj->jw->dy, ", ");
+
+measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
+doQuery(cj->jw, db, allCategories, userSearch, measureTiming);
+// Now we need to actually spit out the page + json
+webStartGbNoBanner(cart, db, "Search Disambiguation");
+printMainPageIncludes();
+jsInlineF("var hgsid='%s';\n", cartSessionId(cart));
+jsInline("var cartJson = {");
+jsInline(cj->jw->dy->string);
+jsInline("};\n");
+jsInline("hgSearch.init();\n");
+webEndGb();
+}
+/* End do commands */
+
+void doMiddle(struct cart *theCart)
+/* Set up globals and make web page */
+{
+cart = theCart;
+
+if (cgiOptionalString(CARTJSON_COMMAND))
+    doCartJson();
+else if (cgiOptionalString("search"))
+    // we got here from:
+    //   1. changing the URL arguments
+    //   2. a back button reload
+    // regardless, we can just put up the whole page with search results already
+    // included in the returned json
+    doSearchOnly();
+else
+    doMainPage();
+}
+
+
+/* Null terminated list of CGI Variables we don't want to save
+ * permanently. */
+char *excludeVars[] = {"Submit", "submit", NULL,};
+
+int main(int argc, char *argv[])
+/* Process command line. */
+{
+cgiSpoof(&argc, argv);
+cartEmptyShellNoContent(doMiddle, hUserCookie(), excludeVars, oldVars);
+return 0;
+}