8ddabffbe58063f2094af1d0fdb45721afcec97c angie Wed Mar 4 13:21:47 2015 -0800 Moved the smarts about changing clade/org/db out of cartJson.c & hgAi.c and into CladeOrgDbMixin.js & hgAiModel.js. Now the CGI sends the full tree of clades, genomes and dbs, and the model instantaneously updates when the user changes clade, org or db, and tells the CGI exactly what info it needs instead of relying on the CGI to send a special mix. refs #14579 diff --git src/hg/hgAi/hgAi.c src/hg/hgAi/hgAi.c index 5b0bfd1..c3a4533 100644 --- src/hg/hgAi/hgAi.c +++ src/hg/hgAi/hgAi.c @@ -1,361 +1,330 @@ /* hgAi - bootstrapper / back end for the Annotation Integrator user interface * This CGI has three modes of operation: * - HTML output for minimal main page with a
container to be filled in by javascript * (default, in the absence of special CGI params) * - JSON responses to ajax requests from javascript (using hg/lib/cartJson.c) * (if CGI param CARTJSON_COMMAND exists) * - text output for annoGrator queries on track data * (if CGI param DO_QUERY exists) * The UI view top level is in ../js/react/hgAi/hgAi.jsx * The UI model top level is in ../js/model/hgAi/hgAiModel.js */ #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. */ -{ -struct hash *gtdbParamHash = hashNew(0); -hashAdd(gtdbParamHash, "fields", newJsonString("track,table,shortLabel,parent,subtracks")); -cartJsonGetGroupedTrackDb(cj, gtdbParamHash); -//#*** 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) { char *tableName = table->name; if (startsWith("all_", tableName)) tableName += strlen("all_"); struct trackDb *tdb = tdbForTrack(NULL, tableName, &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); } } else warn("No tdb for %s", table->name); } jsonWriteObjectEnd(cj->jw); slFreeList(&tables); } void doCartJson() /* Perform UI commands to update the cart and/or retrieve cart vars & metadata. */ { +// When cart is brand new, we need to set db in the cart because several cartJson functions +// require it to be there. +if (! cartOptionalString(cart, "db")) + cartSetString(cart, "db", hDefaultDb()); 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; struct slRef *trackPath = jsonListVal(jsonMustFindNamedField(dsObj, "dataSource", "trackPath"), "trackPath"); // The first item in trackPath is group. The second is track (or composite): struct jsonElement *trackEl = (struct jsonElement *)(trackPath->next->val); // and the last item in trackPath is track or leaf subtrack. struct slRef *leafRef = slLastEl(trackPath); struct jsonElement *leafEl = (struct jsonElement *)(leafRef->val); char *leafTrack = jsonStringVal(leafEl, "leaf"); char *track = jsonStringVal(trackEl, "track"); struct trackDb *tdb = tdbForTrack(db, leafTrack, &fullTrackList); if (!tdb) tdb = tdbForTrack(db, track, &fullTrackList); if (!tdb) errAbort("doQuery: no tdb for track %s, leaf %s", track, leafTrack); char *table = tdb->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; }