abe6cb020492b82545875d59c570d0a3acc51376 jcasper Wed Feb 4 14:41:49 2026 -0800 Adjusting faceted composite cart logic to support tracks that are on by default, refs #36320 diff --git src/hg/cartDump/cartDump.c src/hg/cartDump/cartDump.c index c74cc4544ea..43fde324e08 100644 --- src/hg/cartDump/cartDump.c +++ src/hg/cartDump/cartDump.c @@ -54,75 +54,135 @@ if (trackName != NULL && hashNumEntries(oldVars) > 0) { char *db = cartString(cart, "db"); struct trackDb *tdb = hTrackDbForTrack(db, trackName); if (tdb != NULL && tdbIsComposite(tdb)) { struct lm *lm = lmInit(0); cartTdbTreeCleanupOverrides(tdb,cart,oldVars,lm); lmCleanup(&lm); } } if (cgiVarExists(mName)) { char *mdid = cgiOptionalString(mName); - char mdid_de[1024], mdid_dt[1024]; - safef(mdid_de, sizeof(mdid_de), "%s.de", mdid); - safef(mdid_dt, sizeof(mdid_dt), "%s.dt", mdid); - struct slName *de_list = cgiStringList(mdid_de); - struct slName *dt_list = cgiStringList(mdid_dt); - - // This component should change. Erasing settings doesn't preserve any user selections - // (like bigWig display changes) and doesn't work at all for tracks that are on by default. - char wc_mdid[1024]; - safef(wc_mdid, sizeof(wc_mdid), "*_%s", mdid); - cartRemoveLike(cart, wc_mdid); - cartRemovePrefix(cart, mdid); - - // Check for empty-string sentinel: means no data type suffix - boolean noDataTypes = (dt_list != NULL && - dt_list->name[0] == '\0' && - dt_list->next == NULL); + char mdid_de_was[1024], mdid_de_now[1024], mdid_dt_was[1024], mdid_dt_now[1024]; + safef(mdid_de_was, sizeof(mdid_de_was), "%s.de_was", mdid); + safef(mdid_de_now, sizeof(mdid_de_now), "%s.de_now", mdid); + safef(mdid_dt_was, sizeof(mdid_dt_was), "%s.dt_was", mdid); + safef(mdid_dt_now, sizeof(mdid_dt_now), "%s.dt_now", mdid); + // Grab the lists of which de/dt elements were on before and after the user + // changed settings around + struct slName *de_was_list = cgiStringList(mdid_de_was); + struct slName *de_now_list = cgiStringList(mdid_de_now); + struct slName *dt_was_list = cgiStringList(mdid_dt_was); + struct slName *dt_now_list = cgiStringList(mdid_dt_now); + + // For faster lookup + struct hash *de_was_hash = hashFromSlNameList(de_was_list); + struct hash *de_now_hash = hashFromSlNameList(de_now_list); + struct hash *dt_was_hash = hashFromSlNameList(dt_was_list); + struct hash *dt_now_hash = hashFromSlNameList(dt_now_list); + + // Check if we sent dt_was/now variables, indicating that this composite has data types + boolean hasDataTypes = (dt_was_list != NULL || dt_now_list != NULL); char subtrackSetting[1024]; - for (struct slName *de_itr = de_list; de_itr; de_itr = de_itr->next) + + if (hasDataTypes) + { + // Cross-product mode: tracks are identified by mdid_de_dt + + // Turn ON: (de_on x dt_now), (de_now x dt_on) + // where de_on = de_now - de_was, dt_on = dt_now - dt_was + for (struct slName *de = de_now_list; de != NULL; de = de->next) { - if (noDataTypes) + boolean de_is_new = (hashLookup(de_was_hash, de->name) == NULL); + for (struct slName *dt = dt_now_list; dt != NULL; dt = dt->next) { - // No data type suffix - use simpler track names - safef(subtrackSetting, sizeof(subtrackSetting), "%s_%s_sel", mdid, de_itr->name); - cartSetString(cart, subtrackSetting, "1"); - safef(subtrackSetting, sizeof(subtrackSetting), "%s_%s", mdid, de_itr->name); + boolean dt_is_new = (hashLookup(dt_was_hash, dt->name) == NULL); + if (de_is_new || dt_is_new) + { + safef(subtrackSetting, sizeof(subtrackSetting), + "%s_%s_%s_sel", mdid, de->name, dt->name); cartSetString(cart, subtrackSetting, "1"); } + } + } + + // Turn OFF: (de_off x dt_was), (de_was x dt_off) + // where de_off = de_was - de_now, dt_off = dt_was - dt_now + for (struct slName *de = de_was_list; de != NULL; de = de->next) + { + boolean de_is_off = (hashLookup(de_now_hash, de->name) == NULL); + for (struct slName *dt = dt_was_list; dt != NULL; dt = dt->next) + { + boolean dt_is_off = (hashLookup(dt_now_hash, dt->name) == NULL); + if (de_is_off || dt_is_off) + { + safef(subtrackSetting, sizeof(subtrackSetting), + "%s_%s_%s_sel", mdid, de->name, dt->name); + cartSetString(cart, subtrackSetting, "0"); + } + } + } + + } else { - struct slName *dt_itr = NULL; - for (dt_itr = dt_list; dt_itr; dt_itr = dt_itr->next) + // Data elements only mode: tracks are identified by mdid_de + + // Turn ON: de_now - de_was + for (struct slName *de = de_now_list; de != NULL; de = de->next) { - safef(subtrackSetting, sizeof(subtrackSetting), "%s_%s_%s_sel", mdid, de_itr->name, dt_itr->name); - cartSetString(cart, subtrackSetting, "1"); - safef(subtrackSetting, sizeof(subtrackSetting), "%s_%s_%s", mdid, de_itr->name, dt_itr->name); + if (hashLookup(de_was_hash, de->name) == NULL) + { + safef(subtrackSetting, sizeof(subtrackSetting), + "%s_%s_sel", mdid, de->name); cartSetString(cart, subtrackSetting, "1"); } } + + // Turn OFF: de_was - de_now + for (struct slName *de = de_was_list; de != NULL; de = de->next) + { + if (hashLookup(de_now_hash, de->name) == NULL) + { + safef(subtrackSetting, sizeof(subtrackSetting), + "%s_%s_sel", mdid, de->name); + cartSetString(cart, subtrackSetting, "0"); + } + } + } + hashFree(&de_was_hash); + hashFree(&de_now_hash); + hashFree(&dt_was_hash); + hashFree(&dt_now_hash); + + slFreeList(&de_was_list); + slFreeList(&de_now_list); + slFreeList(&dt_was_list); + slFreeList(&dt_now_list); + cartRemove(cart, mName); - cartRemove(cart, mdid_de); - cartRemove(cart, mdid_dt); + cartRemove(cart, mdid_de_was); + cartRemove(cart, mdid_de_now); + cartRemove(cart, mdid_dt_was); + cartRemove(cart, mdid_dt_now); } return; } // To discourage hacking, call bottleneck if (issueBotWarning) { char *ip = getenv("REMOTE_ADDR"); botDelayMessage(ip, botDelayMillis); } if (asTable) { jsIncludeFile("jquery.js",NULL); // required by utils.js