bfc66e321227aee4b768698682efc18733ce3ff8
chmalee
Thu Oct 20 16:16:01 2022 -0700
Include footer at the bottom of the search page, refs #29693
diff --git src/hg/hgSearch/hgSearch.c src/hg/hgSearch/hgSearch.c
index 36ed9b4..ca82ebe 100644
--- src/hg/hgSearch/hgSearch.c
+++ src/hg/hgSearch/hgSearch.c
@@ -1,609 +1,610 @@
/* 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("");
puts("");
puts("");
puts("");
puts("\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");
+webIncludeFile("inc/gbFooter.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;
}