54c1befd1ac95aab8c80c7e2e400a90d01849237 angie Fri Jan 9 16:41:47 2015 -0800 New CGI hgAi (Annotation Integrator) that uses new ReactJS/ImmutableJS framework.hgAi.c has three modes of operation: - HTML output for simple main page with a
container to be filled in by javascript - JSON responses to ajax requests from javascript (using hg/lib/cartJson.c) - text output for queries on track data hgAi.jsx is the React/JSX UI view code, compiled to bundle/reactHgAi.js. hgAiModel.js is a subclass of js/model/lib/ImModel.js that gets initial state from the server and then responds to user actions. diff --git src/hg/hgAi/hgAi.c src/hg/hgAi/hgAi.c new file mode 100644 index 0000000..7a7693d --- /dev/null +++ src/hg/hgAi/hgAi.c @@ -0,0 +1,336 @@ +/* hgAi - General annotation integrator interface. */ +#include "common.h" +#include "cart.h" +#include "cartJson.h" +#include "cartTrackDb.h" +#include "cheapcgi.h" +#include "hAnno.h" +#include "hCommon.h" +#include "hdb.h" +#include "hgColors.h" +#include "hui.h" +#include "jsonParse.h" +#include "textOut.h" +#include "trackHub.h" +#include "web.h" +#include "annoFormatTab.h" +#include "annoGratorQuery.h" + +/* Global Variables */ +struct cart *cart = NULL; /* CGI and other variables */ + +#define QUERY_SPEC "hgai_querySpec" +#define DO_QUERY "hgai_doQuery" + +//#*** duplicated from hgVai... put in some anno*.h? +#define NO_MAXROWS 0 + +static void writeDbMetadata(struct cartJson *cj, struct hash *paramHash) +/* Send all the info that we'll need for working with a specific assembly db. */ +{ +cartJsonGetGroupsTracksTables(cj, paramHash); +//#*** TODO: move jsonStringEscape inside jsonWriteString +char *encoded = jsonStringEscape(cartOptionalString(cart, QUERY_SPEC)); +jsonWriteString(cj->jw, QUERY_SPEC, encoded); +} + +static void changeDb(struct cartJson *cj, struct hash *paramHash) +/* The user has changed db; send groups, tracks, tables etc. for the new db. */ +{ +cartJsonChangeDb(cj, paramHash); +writeDbMetadata(cj, paramHash); +} + +static void changeOrg(struct cartJson *cj, struct hash *paramHash) +/* The user has changed org/genome; send groups, tracks, tables etc. for the new default db. */ +{ +cartJsonChangeOrg(cj, paramHash); +writeDbMetadata(cj, paramHash); +} + +static void changeClade(struct cartJson *cj, struct hash *paramHash) +/* The user has changed clade; send groups, tracks, tables etc. for the new default db. */ +{ +cartJsonChangeClade(cj, paramHash); +writeDbMetadata(cj, paramHash); +} + +static void makeTrackLabel(struct trackDb *tdb, char *table, char *label, size_t labelSize) +/* Write tdb->shortLabel into label if table is the same as tdb->track; otherwise, write shortLabel + * followed by table name in parens. */ +{ +if (sameString(table, tdb->track)) + safecpy(label, labelSize, tdb->shortLabel); +else + safef(label, labelSize, "%s (%s)", tdb->shortLabel, table); +} + +static void getFields(struct cartJson *cj, struct hash *paramHash) +/* Print out the fields of the tables in comma-sep tables param. */ +{ +char *tableStr = cartJsonRequiredParam(paramHash, "tables", cj->jw, "getFields"); +if (! tableStr) + return; + +char *db = cartString(cart, "db"); +struct slName *table, *tables = slNameListFromComma(tableStr); +jsonWriteObjectStart(cj->jw, "tableFields"); +struct trackDb *fullTrackList = NULL; +struct grp *fullGroupList = NULL; +cartTrackDbInit(cj->cart, &fullTrackList, &fullGroupList, /* useAccessControl= */TRUE); +for (table = tables; table != NULL; table = table->next) + { + struct trackDb *tdb = tdbForTrack(NULL, table->name, &fullTrackList); + if (tdb) + { + struct asObject *asObj = hAnnoGetAutoSqlForTdb(db, hDefaultChrom(db), tdb); + if (asObj) + { + jsonWriteObjectStart(cj->jw, table->name); + char label[strlen(tdb->shortLabel) + strlen(table->name) + PATH_LEN]; + makeTrackLabel(tdb, table->name, label, sizeof(label)); + jsonWriteString(cj->jw, "label", label); + jsonWriteListStart(cj->jw, "fields"); + struct asColumn *col; + for (col = asObj->columnList; col != NULL; col = col->next) + jsonWriteString(cj->jw, NULL, col->name); + jsonWriteListEnd(cj->jw); + jsonWriteObjectEnd(cj->jw); + } + } + } +jsonWriteObjectEnd(cj->jw); +slFreeList(&tables); +} + +void doCartJson() +/* Perform UI commands to update the cart and/or retrieve cart vars & metadata. */ +{ +struct cartJson *cj = cartJsonNew(cart); +cartJsonRegisterHandler(cj, "changeDb", changeDb); +cartJsonRegisterHandler(cj, "changeOrg", changeOrg); +cartJsonRegisterHandler(cj, "changeClade", changeClade); +cartJsonRegisterHandler(cj, "getFields", getFields); +cartJsonExecute(cj); +} + +static struct pipeline *configTextOut(struct jsonElement *queryObj, int *pSavedStdout) +// Set up a textOut pipeline according to output file options in queryObj. +{ +char *fileName = ""; +char *compressType = textOutCompressNone; +struct jsonElement *outFileOptions = jsonFindNamedField(queryObj, QUERY_SPEC, "outFileOptions"); +if (outFileOptions) + { + boolean doFile = jsonOptionalBooleanField(outFileOptions, "doFile", FALSE); + if (doFile) + { + fileName = jsonOptionalStringField(outFileOptions, "fileName", "hgAiResults"); + boolean doGzip = jsonOptionalBooleanField(outFileOptions, "doGzip", FALSE); + if (doGzip) + compressType = textOutCompressGzip; + } + } +return textOutInit(fileName, compressType, pSavedStdout); +} + +static struct annoFormatter *makeTabFormatter(struct jsonElement *queryObj) +// Create and configure an annoFormatter subclass as specified by queryObj. +{ +struct annoFormatter *tabOut = annoFormatTabNew("stdout"); +// Look for fields that have been deselected by the user +struct jsonElement *outFileOptions = jsonFindNamedField(queryObj, QUERY_SPEC, "outFileOptions"); +if (outFileOptions) + { + struct jsonElement *tableFieldsObj = jsonFindNamedField(outFileOptions, "outFileOptions", + "tableFields"); + if (tableFieldsObj) + { + struct hash *tableFields = jsonObjectVal(tableFieldsObj, "tableFields"); + // Iterate over names which are tables which had better end up being annoStreamer names... + //#*** Hmmm, annoStreamer uses complete file path for big and we don't have that info + //#*** here. Better find a way to pass in names to streamers for consistency! + struct hashEl *hel; + struct hashCookie cookie = hashFirst(tableFields); + while ((hel = hashNext(&cookie)) != NULL) + { + char *sourceName = hel->name; + struct jsonElement *tableObj = hel->val; + struct hash *fieldVals = jsonObjectVal(tableObj, sourceName); + // Now iterate over field/column names to see which ones are explicitly deselected: + struct hashEl *innerHel; + struct hashCookie innerCookie = hashFirst(fieldVals); + while ((innerHel = hashNext(&innerCookie)) != NULL) + { + char *colName = innerHel->name; + struct jsonElement *enabledEl = innerHel->val; + boolean enabled = jsonBooleanVal(enabledEl, colName); + if (! enabled) + annoFormatTabSetColumnVis(tabOut, sourceName, colName, enabled); + } + } + } + } +return tabOut; +} + + +void doQuery() +/* Execute a query that has been built up by the UI. */ +{ +// Make sure we have either genome-wide search or a valid position +char *db = cartString(cart, "db"); +char *chrom = NULL; +uint start = 0, end = 0; +char *regionType = cartUsualString(cart, "hgai_range", "position"); +if (sameString(regionType, "position")) + { + char *position = cartUsualString(cart, "position", hDefaultPos(db)); + if (! parsePosition(position, &chrom, &start, &end)) + errAbort("doQuery: Expected position to be chrom:start-end but got '%s'", position); + } +struct annoAssembly *assembly = hAnnoGetAssembly(db); +// Decode and parse CGI-encoded querySpec. +char *querySpec = cartString(cart, QUERY_SPEC); +int len = strlen(querySpec); +char querySpecDecoded[len+1]; +cgiDecodeFull(querySpec, querySpecDecoded, len); +struct jsonElement *queryObj = jsonParse(querySpecDecoded); +// Set up output. +int savedStdout = -1; +struct pipeline *textOutPipe = configTextOut(queryObj, &savedStdout); +webStartText(); +// Build annoGrator query. +struct slRef *dataSources = jsonListVal(jsonFindNamedField(queryObj, "queryObj", "dataSources"), + "dataSources"); +struct grp *fullGroupList = NULL; +struct trackDb *fullTrackList = NULL; +cartTrackDbInit(cart, &fullTrackList, &fullGroupList, TRUE); +struct annoStreamer *primary = NULL; +struct annoGrator *gratorList = NULL; +struct slRef *dsRef; +int i; +for (i = 0, dsRef = dataSources; dsRef != NULL; i++, dsRef = dsRef->next) + { + struct jsonElement *dsObj = dsRef->val; + char *table = jsonStringField(dsObj, "table"); + char *track = jsonStringField(dsObj, "track"); + struct trackDb *tdb = tdbForTrack(db, table, &fullTrackList); + if (!tdb) + tdb = tdbForTrack(db, track, &fullTrackList); + if (!tdb) + errAbort("doQuery: no tdb for track %s, table %s", track, table); + if (i == 0) + { + primary = hAnnoStreamerFromTrackDb(assembly, table, tdb, chrom, NO_MAXROWS); + annoStreamerSetName(primary, table); + } + else + { + struct annoGrator *grator = hAnnoGratorFromTrackDb(assembly, table, tdb, chrom, + NO_MAXROWS, NULL, agoNoConstraint); + if (grator) + { + annoStreamerSetName((struct annoStreamer *)grator, table); + slAddHead(&gratorList, grator); + } + else + errAbort("doQuery: no grator for track %s, table %s", track, table); + } + } +slReverse(&gratorList); + +// Make an annoFormatter to print output. +// For now, tab-separated output is it. +struct annoFormatter *formatter = makeTabFormatter(queryObj); + +// Set up and execute query. +struct annoGratorQuery *query = annoGratorQueryNew(assembly, primary, gratorList, formatter); +if (chrom != NULL) + annoGratorQuerySetRegion(query, chrom, start, end); +annoGratorQueryExecute(query); +annoGratorQueryFree(&query); + +textOutClose(&textOutPipe, &savedStdout); +} + + +void doMainPage() +/* Send HTML with javascript to bootstrap the user interface. */ +{ +char *db = cartUsualString(cart, "db", hDefaultDb()); +webStartWrapperDetailedNoArgs(cart, trackHubSkipHubName(db), + "", "Annotation Integrator", + TRUE, FALSE, TRUE, TRUE); + +// Ideally these would go in the +puts(""); +puts(""); + +puts("
Loading...
"); + +// Set a global JS variable hgsid. +// Plain old "var ..." doesn't work (other scripts can't see it), it has to belong to window. +printf("\n", cartSessionVarName(), cartSessionId(cart)); + +// We need a package manager and require-handling system... bower and browserify? +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); +puts(""); + +// Invisible form for submitting a query +printf("\n
\n", + hgAiName(), cartUsualString(cart, "formMethod", "GET")); +cartSaveSession(cart); +cgiMakeHiddenVar(QUERY_SPEC, cartUsualString(cart, QUERY_SPEC, "")); +cgiMakeHiddenVar(DO_QUERY, "go"); +puts("
"); + +// Invisible form for jumping to another CGI +printf("\n
\n", cartUsualString(cart, "formMethod", "GET")); +cartSaveSession(cart); +puts("
"); + +webEnd(); +} + +void doMiddle(struct cart *theCart) +/* Depending on invocation, either perform a query and print out results, + * serve up JSON for the UI, or display the main page. */ +{ +cart = theCart; +if (cgiOptionalString(CARTJSON_COMMAND)) + doCartJson(); +else if (cgiOptionalString(DO_QUERY)) + doQuery(); +else + doMainPage(); +} + +int main(int argc, char *argv[]) +/* Process CGI / command line. */ +{ +/* Null terminated list of CGI Variables we don't want to save + * permanently. */ +char *excludeVars[] = {DO_QUERY, CARTJSON_COMMAND, NULL,}; +struct hash *oldVars = NULL; +cgiSpoof(&argc, argv); +setUdcCacheDir(); +cartEmptyShellNoContent(doMiddle, hUserCookie(), excludeVars, oldVars); +return 0; +}