7033f67074594c3d9cc3487f079456928fdf3260 angie Tue Apr 12 13:46:14 2016 -0700 Major change to hgGateway: the contents are replaced by a new page designed by a graphic artist. It has icons for selecting popular species, an autocomplete input for typing in species or common names, as well as a phylogenetic tree display that shows the relationships of the species that we host. It has a menu for selecting the assembly of the selected species' genome and the usual assembly description. refs #15277 diff --git src/hg/hgGateway/hgGateway.c src/hg/hgGateway/hgGateway.c index 330bce8..fc5a359 100644 --- src/hg/hgGateway/hgGateway.c +++ src/hg/hgGateway/hgGateway.c @@ -1,310 +1,741 @@ -/* hgGateway - Human Genome Browser Gateway. */ - -/* Copyright (C) 2014 The Regents of the University of California - * See README in this or parent directory for licensing information. */ +/* hgGateway: make it easy to select a species and assembly + * + * Copyright (C) 2016 The Regents of the University of California + * + * This CGI has three modes of operation: + * - HTML output for main page (default); most HTML is in hgGateway.html + * - cart-based JSON responses to ajax requests from javascript (using hg/lib/cartJson.c) + * (if CGI param CARTJSON_COMMAND exists) + * - no cart; fast JSON responses to species-search autocomplete requests + * (if CGI param SEARCH_TERM exists) + */ #include "common.h" -#include "linefile.h" -#include "hash.h" -#include "cheapcgi.h" -#include "htmshell.h" -#include "obscure.h" -#include "web.h" #include "cart.h" -#include "hdb.h" -#include "dbDb.h" -#include "hgFind.h" +#include "cartJson.h" +#include "cheapcgi.h" +#include "errCatch.h" +#include "googleAnalytics.h" #include "hCommon.h" -#include "hui.h" -#include "customTrack.h" -#include "hubConnect.h" #include "hgConfig.h" +#include "hdb.h" +#include "htmshell.h" +#include "hubConnect.h" +#include "hui.h" #include "jsHelper.h" -#include "hPrint.h" +#include "jsonParse.h" +#include "obscure.h" // for readInGulp +#include "regexHelper.h" #include "suggest.h" -#include "search.h" -#include "geoMirror.h" #include "trackHub.h" +#include "trix.h" +#include "web.h" + +/* Global Variables */ +struct cart *cart = NULL; /* CGI and other variables */ +struct hash *oldVars = NULL; /* Old contents of cart before it was updated by CGI */ + +#define SEARCH_TERM "hggw_term" + +static char *maybeGetDescriptionText(char *db) +/* Slurp the description.html file for db into a string (if possible, don't die if + * we can't read it) and return it. */ +{ +struct errCatch *errCatch = errCatchNew(); +char *descText = NULL; +if (errCatchStart(errCatch)) + { + char *htmlPath = hHtmlPath(db); + descText = udcFileReadAll(htmlPath, NULL, 0, NULL); + } +errCatchEnd(errCatch); +// Just ignore errors for now. +return descText; +} + +static int trackHubCountAssemblies(struct trackHub *hub) +/* Return the number of hub's genomes that are hub assemblies, i.e. that have a twoBitPath. */ +{ +int count = 0; +struct trackHubGenome *genome; +for (genome = hub->genomeList; genome != NULL; genome = genome->next) + { + if (isNotEmpty(genome->twoBitPath)) + count++; + } +return count; +} + +static char *trackHubDefaultAssembly(struct trackHub *hub) +/* If hub->defaultDb is an assembly genome, return it; otherwise return the first assembly. + * Don't free result. */ +{ +char *defaultDb = hub->defaultDb; +char *firstAssemblyDb = NULL; +struct trackHubGenome *genome; +for (genome = hub->genomeList; genome != NULL; genome = genome->next) + { + if (isNotEmpty(genome->twoBitPath)) + { + if (sameString(defaultDb, genome->name)) + return defaultDb; + else if (firstAssemblyDb == NULL) + firstAssemblyDb = genome->name; + } + } +return firstAssemblyDb; +} -struct cart *cart = NULL; -struct hash *oldVars = NULL; -char *clade = NULL; -char *organism = NULL; -char *db = NULL; +static void listAssemblyHubs(struct jsonWrite *jw) +/* Write out JSON describing assembly hubs (not track-only hubs) connected in the cart. */ +{ +jsonWriteListStart(jw, "hubs"); +struct hubConnectStatus *status, *statusList = hubConnectStatusListFromCartAll(cart); +for (status = statusList; status != NULL; status = status->next) + { + struct trackHub *hub = status->trackHub; + if (hub == NULL) + continue; + int assemblyCount = trackHubCountAssemblies(hub); + if (assemblyCount > 0) + { + jsonWriteObjectStart(jw, NULL); + jsonWriteString(jw, "name", hub->name); + jsonWriteString(jw, "shortLabel", hub->shortLabel); + jsonWriteString(jw, "longLabel", hub->longLabel); + jsonWriteString(jw, "defaultDb", trackHubDefaultAssembly(hub)); + jsonWriteString(jw, "hubUrl", status->hubUrl); + jsonWriteNumber(jw, "assemblyCount", assemblyCount); + jsonWriteString(jw, "errorMessage", status->errorMessage); + // We might be able to do better than this for taxId, for example if defaultDb is local + // or if hub genomes ever specify taxId... + jsonWriteNumber(jw, "taxId", 0); + jsonWriteObjectEnd(jw); + } + } +jsonWriteListEnd(jw); +} -// TODO REMOVE AFTER AUTOUPGRADE COMPLETE: (added 2014-03-09) -extern struct dyString *dyUpgradeError; -void hgGateway() -/* hgGateway - Human Genome Browser Gateway. */ +static void writeFindPositionInfo(struct jsonWrite *jw, char *db, int taxId, char *hubUrl, + char *position) +/* Write JSON for the info needed to populate the 'Find Position' section. */ +{ +char *genome = hGenome(db); +if (isEmpty(genome)) { -char *defaultPosition = hDefaultPos(db); -char *position = cloneString(cartUsualString(cart, "position", defaultPosition)); -boolean gotClade = hGotClade(); -char *survey = cfgOptionEnv("HGDB_SURVEY", "survey"); -char *surveyLabel = cfgOptionEnv("HGDB_SURVEY_LABEL", "surveyLabel"); -boolean supportsSuggest = FALSE; + jsonWriteStringf(jw, "error", "No genome for db '%s'", db); + } +else + { + jsonWriteString(jw, "db", db); + jsonWriteNumber(jw, "taxId", taxId); + jsonWriteString(jw, "genome", genome); + struct slPair *dbOptions = NULL; + char genomeLabel[PATH_LEN*4]; + if (isNotEmpty(hubUrl)) + { + struct trackHub *hub = hubConnectGetHub(hubUrl); + if (hub == NULL) + { + jsonWriteStringf(jw, "error", "Can't connect to hub at '%s'", hubUrl); + return; + } + struct dbDb *dbDbList = trackHubGetDbDbs(hub->name); + dbOptions = trackHubDbDbToValueLabel(dbDbList); + safecpy(genomeLabel, sizeof(genomeLabel), hub->shortLabel); + jsonWriteString(jw, "hubUrl", hubUrl); + } + else + { + dbOptions = hGetDbOptionsForGenome(genome); + safecpy(genomeLabel, sizeof(genomeLabel), genome); + } + jsonWriteValueLabelList(jw, "dbOptions", dbOptions); + jsonWriteString(jw, "genomeLabel", genomeLabel); + jsonWriteString(jw, "position", position); + char *suggestTrack = NULL; if (! trackHubDatabase(db)) - supportsSuggest = assemblySupportsGeneSuggest(db); + suggestTrack = assemblyGeneSuggestTrack(db); + jsonWriteString(jw, "suggestTrack", suggestTrack); + char *description = maybeGetDescriptionText(db); + //#*** TODO: move jsonStringEscape inside jsonWriteString + char *encoded = jsonStringEscape(description); + jsonWriteString(jw, "description", encoded); + listAssemblyHubs(jw); + } +} -/* JavaScript to copy input data on the change genome button to a hidden form -This was done in order to be able to flexibly arrange the UI HTML -*/ -char *onChangeDB = "onchange=\"document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();\""; -char *onChangeOrg = "onchange=\"document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();\""; -char *onChangeClade = "onchange=\"document.orgForm.clade.value = document.mainForm.clade.options[document.mainForm.clade.selectedIndex].value; document.orgForm.org.value = 0; document.orgForm.db.value = 0; document.orgForm.submit();\""; - -/* - If we are changing databases via explicit cgi request, - then remove custom track data which will - be irrelevant in this new database . - If databases were changed then use the new default position too. -*/ +static void setTaxId(struct cartJson *cj, struct hash *paramHash) +/* Set db and genome according to taxId (and/or db) and return the info we'll need + * to fill in the findPosition section. */ +{ +char *taxIdStr = cartJsonRequiredParam(paramHash, "taxId", cj->jw, "setTaxId"); +char *db = cartJsonOptionalParam(paramHash, "db"); +int taxId = atoi(taxIdStr); +if (isEmpty(db)) + db = hDbForTaxon(taxId); +if (isEmpty(db)) + jsonWriteStringf(cj->jw, "error", "No db for taxId '%s'", taxIdStr); +else + { + writeFindPositionInfo(cj->jw, db, taxId, NULL, hDefaultPos(db)); + cartSetString(cart, "db", db); + cartSetString(cart, "position", hDefaultPos(db)); + } +} + +static void setHubDb(struct cartJson *cj, struct hash *paramHash) +/* Set db and genome according to hubUrl (and/or db and hub) and return the info we'll need + * to fill in the findPosition section. */ +{ +char *hubUrl = cartJsonRequiredParam(paramHash, "hubUrl", cj->jw, "setHubDb"); +char *taxIdStr = cartJsonOptionalParam(paramHash, "taxId"); +int taxId = taxIdStr ? atoi(taxIdStr) : -1; +// cart's db was already set by magic handling of hub CGI variables sent along +// with this command. +char *db = cartString(cart, "db"); +if (isEmpty(db)) + jsonWriteStringf(cj->jw, "error", "No db for hubUrl '%s'", hubUrl); +else + writeFindPositionInfo(cj->jw, db, taxId, hubUrl, hDefaultPos(db)); +} + +static void setDb(struct cartJson *cj, struct hash *paramHash) +/* Set taxId and genome according to db and return the info we'll need to fill in + * the findPosition section. */ +{ +char *db = cartJsonRequiredParam(paramHash, "db", cj->jw, "setDb"); +char *hubUrl = cartJsonOptionalParam(paramHash, "hubUrl"); +int taxId = hTaxId(db); +writeFindPositionInfo(cj->jw, db, taxId, hubUrl, hDefaultPos(db)); +cartSetString(cart, "db", db); +cartSetString(cart, "position", hDefaultPos(db)); +} + +static void getUiState(struct cartJson *cj, struct hash *paramHash) +/* Write out JSON for hgGateway.js's uiState object using current cart settings. */ +{ +char *db = cartUsualString(cj->cart, "db", hDefaultDb()); +char *position = cartUsualString(cart, "position", hDefaultPos(db)); +char *hubUrl = NULL; +if (trackHubDatabase(db)) + { + struct trackHub *hub = hubConnectGetHubForDb(db); + hubUrl = hub->url; + } +writeFindPositionInfo(cj->jw, db, hTaxId(db), hubUrl, position); +// If cart already has a pix setting, pass that along; otherwise the JS will +// set pix according to web browser window width. +int pix = cartUsualInt(cj->cart, "pix", 0); +if (pix) + jsonWriteNumber(cj->jw, "pix", pix); +} + +static void doCartJson() +/* Perform UI commands to update the cart and/or retrieve cart vars & metadata. */ +{ +struct cartJson *cj = cartJsonNew(cart); +cartJsonRegisterHandler(cj, "setTaxId", setTaxId); +cartJsonRegisterHandler(cj, "setDb", setDb); +cartJsonRegisterHandler(cj, "setHubDb", setHubDb); +cartJsonRegisterHandler(cj, "getUiState", getUiState); +cartJsonExecute(cj); +} + +static void doMainPage() +/* Send HTML with javascript to bootstrap the user interface. */ +{ +// Start web page with new banner +char *db = NULL, *genome = NULL, *clade = NULL; +getDbGenomeClade(cart, &db, &genome, &clade, oldVars); +webStartJWest(cart, db, "Genome Browser Gateway"); + +// Edit the HTML in hgGateway.html (see makefile): +puts( +#include "hgGateway.html.h" +); -if (sameString(position, "genome") || sameString(position, "hgBatch")) - position = defaultPosition; +// Set global JS variable hgsid +// We can't just use "var hgsid = " or the other scripts won't see it -- it has to be +// "window.hgsid = ". +puts(""); + +puts(""); +puts(""); +puts(""); +puts(""); -jsIncludeFile("jquery.js", NULL); webIncludeResourceFile("jquery-ui.css"); jsIncludeFile("jquery-ui.js", NULL); -jsIncludeFile("ajax.js", NULL); -jsIncludeFile("autocomplete.js", NULL); -jsIncludeFile("hgGateway.js", NULL); -jsIncludeFile("utils.js", NULL); jsIncludeFile("jquery.watermarkinput.js", NULL); +jsIncludeFile("utils.js",NULL); -puts("
" -"WARNING: This is our preview site. It is a weekly mirror of our internal development server for public access. " -"Data and tools here are under construction, have not been quality reviewed, and are subject to change " -"at any time. We provide this site for early access, with the warning that it is less available " -"and stable than our public site. For high-quality reviewed annotations on our production server, visit " -" http://genome.ucsc.edu." -"
WARNING: This is our development and test site. It usually works, but it is filled with tracks in various " -"stages of construction, and others of little interest to people outside of our local group. " -"It is usually slow because we are building databases on it. The documentation is poor. " - "More data than usual is flat out wrong. Maybe you want to go to " - "genome.ucsc.edu instead."); + safecpy(value, sizeof(value), dbDb->genome); + char *bolded = boldTerm(dbDb->genome, term, match->offset, match->type); + safecpy(label, sizeof(label), bolded); + freeMem(bolded); + } +else if (match->type == ddmtDb) + { + safef(value, sizeof(value), "%s (%s %s)", + dbDb->name, dbDb->genome, dbDb->description); + char *bolded = boldTerm(dbDb->name, term, match->offset, match->type); + safef(label, sizeof(label), "%s (%s %s)", + bolded, dbDb->genome, dbDb->description); + freeMem(bolded); + } +else if (match->type == ddmtDescription) + { + safef(value, sizeof(value), "%s (%s %s)", + dbDb->name, dbDb->genome, dbDb->description); + char *bolded = boldTerm(dbDb->description, term, match->offset, match->type); + safef(label, sizeof(label), "%s (%s %s)", + dbDb->name, dbDb->genome, bolded); + freeMem(bolded); + } +else + errAbort("writeDbDbMatch: unrecognized dbDbMatchType value %d (db %s, term %s)", + match->type, dbDb->name, term); +jsonWriteString(jw, "label", label); +jsonWriteString(jw, "value", value); +jsonWriteNumber(jw, "taxId", dbDb->taxId); +if (isNotEmpty(category)) + jsonWriteString(jw, "category", category); +jsonWriteObjectEnd(jw); } -if (hIsGsidServer()) +int wordMatchOffset(char *term, char *target) +/* If some word of target starts with term (case insensitive), return the offset of + * that word in target; otherwise return -1. */ +{ +if (startsWith(term, target)) + return 0; +int targetLen = strlen(target); +char targetClone[targetLen+1]; +safecpy(targetClone, sizeof(targetClone), target); +char *p = targetClone; +while (nextWord(&p) && p != NULL) { - webNewSection("%s", "Sequence View\n"); - printf("%s","Sequence View is a customized version of the UCSC Genome Browser, which is " - "specifically tailored to provide functions needed for the GSID HIV Data Browser.\n"); + // skip punctuation like parentheses + while (*p != '\0' && ! isalnum(*p)) + p++; + if (startsWith(term, p)) + return p - targetClone; + } +return -1; } -hgPositionsHelpHtml(organism, db); +static void addIfFirstMatch(struct dbDb *dbDb, enum dbDbMatchType type, int offset, char *target, + char *term, struct hash *matchHash, struct dbDbMatch **pMatchList) +/* If target doesn't already have a match in matchHash, compute matchLength and isWord, + * and then add the new match to pMatchList and add target to matchHash. */ +{ +if (! hashLookup(matchHash, target)) + { + char *termInTarget = (offset >= 0) ? target+offset : target; + int matchLength = countSame(term, termInTarget); + // is the match complete up to a word boundary in termInTarget? + boolean isWord = (matchLength == strlen(term) && + (termInTarget[matchLength] == '\0' || isspace(termInTarget[matchLength]))); + boolean isComplete = sameString(term, target); + struct dbDbMatch *match = dbDbMatchNew(dbDb, type, offset, isWord, isComplete); + slAddHead(pMatchList, match); + hashStore(matchHash, target); + } +} -puts("