ac48943c159a91ae0ba861823d9962e7e92efa1b jcasper Wed Jan 28 01:06:25 2026 -0800 First pass at having the facetedComposite UI correctly recognize subtracks that are on by default, refs #36320 diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c index a16aa2f2f22..d6dcd0f4c02 100644 --- src/hg/hgTrackUi/hgTrackUi.c +++ src/hg/hgTrackUi/hgTrackUi.c @@ -2947,164 +2947,207 @@ * This function will embed sessionDb.settings/cart data in the * generated HTML. Instead of embedding all relevant tracks, it * parses the tracks named like: * * facetedCompositeName_dataElementName_dataTypeName_sel=1 * * And only sends unique dataElement and dataTypes, which might be * much smaller. The faceted composite assumes that selection of * dataTypes applies to all dataElements, but within the cart, these * are separate tracks, and each must be present to be drawn. But * the JS doesn't need the product {dataType} x {dataElement}, just * the union {datType} U {dataElement}. These are put in two arrays * in a JSON section of the HTML. */ -const int token_size = 64; -const int query_buff_size = 256; +const int token_size = 1024; // html elements for the controls page (from singleCellMerged) const char pageStyle[] = "<style>body.cgi { background: #F0F0F0; }" "table.hgInside { background: #FFFFFF; }</style>"; const char placeholderDiv[] = "<div id='metadata-placeholder'></div>\n"; const char openJSON[] = "<script id=\"app-data\" type=\"application/json\">{"; const char closeJSON[] = "}</script>\n"; const char openDataTypesJSON[] = "\"dataTypes\":{"; const char closeDataTypesJSON[] = "}"; // closing a dict const char openDataElementsJSON[] = "\"dataElements\":["; const char closeDataElementsJSON[] = "]"; // closing an array const char metadataTableScriptElement[] = "<script type='text/javascript' src='/js/facetedComposite.js'></script>\n"; -// parse the hgsid as id and sessionKey -char *hgsid = cartSessionId(cart); -char *sessionKey = NULL; -const int id = cartDbParseId(hgsid, &sessionKey); -if (!sessionKey) - errAbort("Failed to parse session key from: %s", hgsid); - // --- Get data from 'settings' field in 'trackDb' entry --- // required const char *metaDataUrl = trackDbSetting(tdb, "metaDataUrl"); const char *primaryKey = trackDbSetting(tdb, "primaryKey"); struct slName *dataTypes = parseDataTypes(tdb); boolean hasDataTypes = (dataTypes != NULL); -//if (!dataTypes) -// errAbort("Failed to parse data types from faceted composite settings for: %s", tdb->track); // optional const char *colorSettingsUrl = (const char *)hashFindVal(tdb->settingsHash, "colorSettingsUrl"); const char *maxCheckboxes = (const char *)hashFindVal(tdb->settingsHash, "maxCheckboxes"); // --- done parsing values from trackDb.settings --- const char *metaDataId = tdb->track; const int metaDataIdLen = strlen(metaDataId); -char queryFmt[] = "SELECT contents FROM sessionDb WHERE id='%d' AND sessionKey='%s';"; -char query[query_buff_size]; -sqlSafef(query, query_buff_size, queryFmt, id, sessionKey); - -struct sqlConnection *conn = hConnectCentral(); -const char *contents = sqlQuickString(conn, query); -struct cgiParsedVars *varList = cgiParsedVarsNew((char *)contents); - printf(pageStyle); // css printf(placeholderDiv); // placholder +// start by figuring out what's on by default +struct hash *defaultOn = hashNew(0); +for (struct trackDb *st = tdb->subtracks; st != NULL; st = st->next) + { + char *setting = NULL; + char *words[2]; + boolean enabled = TRUE; + if ((setting = trackDbLocalSetting(st, "parent")) != NULL) + { + char *clone = NULL; + if (chopLine(clone = cloneString(setting), words) >= 2) + if (sameString(words[1], "off")) + enabled = FALSE; + freeMem(clone); + } + if (enabled) + { + char val[1024]; + safef(val, sizeof(val), "%s_sel", st->track); + hashAdd(defaultOn, val, NULL); + } + } + /* --- START embedded JSON data --- */ printf(openJSON); printf(openDataTypesJSON); // find selected data types int not_first = 0; struct slName *anySelDataType = NULL; // non-null val will be used as flag if (hasDataTypes) { for (struct slName *thisType = dataTypes; thisType != NULL; thisType = thisType->next) { char toMatch[token_size]; - safef(toMatch, token_size, "_%s_sel", thisType->name); - boolean dataTypeSel = FALSE; - for (struct cgiVar *le = varList->list; !dataTypeSel && le; le = le->next) - if (startsWith(metaDataId, le->name) && endsWith(le->name, toMatch)) - dataTypeSel = TRUE; + safef(toMatch, token_size, "%s_*_%s_sel", metaDataId, thisType->name); + boolean dataTypeSel = cartVarExistsLike(cart, toMatch) || hashItemExistsLike(defaultOn, toMatch); printf("%s\"%s\": %d", COMMA_IF(not_first), thisType->name, dataTypeSel ? 1 : 0); anySelDataType = dataTypeSel ? thisType : anySelDataType; } } // else: dataTypes dict is empty - JS will detect this printf(closeDataTypesJSON); printf(","); // add separator // find selected data sets printf(openDataElementsJSON); not_first = 0; if (hasDataTypes) { if (anySelDataType != NULL) { - char suffix[token_size]; - safef(suffix, token_size, "_%s_sel", anySelDataType->name); - for (struct cgiVar *le = varList->list; le; le = le->next) - if (startsWith(metaDataId, le->name) && endsWith(le->name, suffix)) + char toMatch[token_size]; + safef(toMatch, token_size, "%s_*_%s_sel", metaDataId, anySelDataType->name); + struct slPair *mdidVars = cartVarsLike(cart, toMatch); + for (struct slPair *le = mdidVars; le != NULL; le = le->next) + { + if (cartBoolean(cart, le->name)) { const char *nameStart = le->name + metaDataIdLen + 1; const char *nameEnd = strchr(nameStart, '_'); if (nameEnd && nameEnd > nameStart) { const int nameLen = nameEnd - nameStart; printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart); } } } + slPairFreeList(&mdidVars); + // Now add data elements on by default that haven't been modified + struct hashEl *el, *elList = hashElListHash(defaultOn); + slSort(&elList, hashElCmp); + for (el = elList; el != NULL; el = el->next) + { + if (!cartVarExistsLike(cart, el->name)) + { + const char *nameStart = el->name + metaDataIdLen + 1; + const char *nameEnd = strchr(nameStart, '_'); + if (nameEnd && nameEnd > nameStart) + { + const int nameLen = nameEnd - nameStart; + printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart); + } + } + } + hashElFreeList(&elList); + } } else { // No data types - look for {mdid}_{de}_sel pattern (no dataType component) - char suffix[] = "_sel"; - for (struct cgiVar *le = varList->list; le; le = le->next) + char toMatch[token_size]; + safef(toMatch, token_size, "%s_*_sel", metaDataId); + struct slPair *mdidVars = cartVarsLike(cart, toMatch); + for (struct slPair *le = mdidVars; le != NULL; le = le->next) { - if (startsWith(metaDataId, le->name) && endsWith(le->name, suffix)) + if (cartBoolean(cart, le->name)) { // Extract data element name: between mdid_ and _sel const char *nameStart = le->name + metaDataIdLen + 1; const char *nameEnd = strstr(nameStart, "_sel"); if (nameEnd && nameEnd > nameStart) { const int nameLen = nameEnd - nameStart; printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart); } } } + slPairFreeList(&mdidVars); + + // Now add data elements on by default that haven't been modified + struct hashEl *el, *elList = hashElListHash(defaultOn); + slSort(&elList, hashElCmp); + for (el = elList; el != NULL; el = el->next) + { + if (!cartVarExistsLike(cart, el->name)) + { + const char *nameStart = el->name + metaDataIdLen + 1; + const char *nameEnd = strstr(nameStart, "_sel"); + if (nameEnd && nameEnd > nameStart) + { + const int nameLen = nameEnd - nameStart; + printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart); + } + } + } + hashElFreeList(&elList); } printf(closeDataElementsJSON); printf(",\"mdid\": \"%s\"", metaDataId); printf(",\"primaryKey\": \"%s\"", primaryKey); // must exist if (maxCheckboxes) // only if present in trackDb.settings entry printf(",\"maxCheckboxes\": \"%s\"", maxCheckboxes); if (colorSettingsUrl) // only if present in trackDb.settings entry printf(",\"colorSettingsUrl\": \"%s\"", colorSettingsUrl); printf(",\"metadataUrl\": \"%s\"", metaDataUrl); printf(closeJSON); /* --- END embedded JSON data --- */ printf(metadataTableScriptElement); // cleanup slFreeList(&dataTypes); -cgiParsedVarsFreeList(&varList); -hDisconnectCentral(&conn); +hashFree(&defaultOn); } void specificUi(struct trackDb *tdb, struct trackDb *tdbList, struct customTrack *ct, boolean ajax) /* Draw track specific parts of UI. */ { char *track = tdb->track; char *db = database; char *liftDb = cloneString(trackDbSetting(tdb, "quickLiftDb")); if (liftDb != NULL) db = liftDb; // Ideally check cfgTypeFromTdb()/cfgByCfgType() first, but with all these special cases already in // place, lets be cautious at this time. // NOTE: Developer, please try to use cfgTypeFromTdb()/cfgByCfgType(). boolean boxed = trackDbSettingClosestToHomeOn(tdb, "boxedCfg");