eef3ac987538affd38eb38ccddf6ce6721f4c6f4
jcasper
  Sun Nov 23 22:03:29 2025 -0800
Removing bigCompositeUpdate CGI (folded into cartDump), and some kent-ifying of
the remaining code.  refs #36320

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index dcba862c95d..fea1821b26e 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -2003,36 +2003,36 @@
 char options[7][256];
 int thisHeightPer;
 float thisMinYRange, thisMaxYRange;
 char *interpolate, *fill;
 
 char **row;
 int rowOffset;
 struct sample *sample;
 struct sqlResult *sr;
 
 char option[64];
 struct sqlConnection *conn = hAllocConn(database);
 
 char newRow = 0;
 
-snprintf( &options[0][0], 256, "%s.heightPer", tdb->track );
-snprintf( &options[1][0], 256, "%s.linear.interp", tdb->track );
-snprintf( &options[3][0], 256, "%s.fill", tdb->track );
-snprintf( &options[4][0], 256, "%s.min.cutoff", tdb->track );
-snprintf( &options[5][0], 256, "%s.max.cutoff", tdb->track );
-snprintf( &options[6][0], 256, "%s.interp.gap", tdb->track );
+safef(&options[0][0], 256, "%s.heightPer", tdb->track );
+safef(&options[1][0], 256, "%s.linear.interp", tdb->track );
+safef(&options[3][0], 256, "%s.fill", tdb->track );
+safef(&options[4][0], 256, "%s.min.cutoff", tdb->track );
+safef(&options[5][0], 256, "%s.max.cutoff", tdb->track );
+safef(&options[6][0], 256, "%s.interp.gap", tdb->track );
 
 thisHeightPer = atoi(cartUsualString(cart, &options[0][0], "50"));
 interpolate = cartUsualString(cart, &options[1][0], "Linear Interpolation");
 fill = cartUsualString(cart, &options[3][0], "1");
 thisMinYRange = atof(cartUsualString(cart, &options[4][0], "0.0"));
 thisMaxYRange = atof(cartUsualString(cart, &options[5][0], "1000.0"));
 
 printf("<p><b>Interpolation: </b> ");
 wiggleDropDown(&options[1][0], interpolate );
 printf(" ");
 
 printf("<br><br>");
 printf(" <b>Fill Blocks</b>: ");
 cgiMakeRadioButton(&options[3][0], "1", sameString(fill, "1"));
 printf(" on ");
@@ -2042,31 +2042,31 @@
 
 printf("<p><b>Track Height</b>:&nbsp;&nbsp;");
 cgiMakeIntVar(&options[0][0], thisHeightPer, 5 );
 printf("&nbsp;pixels");
 
 printf("<p><b>Vertical Range</b>:&nbsp;&nbsp;\nmin:");
 cgiMakeDoubleVar(&options[4][0], thisMinYRange, 6 );
 printf("&nbsp;&nbsp;&nbsp;&nbsp;max:");
 cgiMakeDoubleVar(&options[5][0], thisMaxYRange, 6 );
 
 printf("<p><b>Toggle Species on/off</b><br>" );
 sr = hRangeQuery(conn, tdb->table, chromosome, 0, 1877426, NULL, &rowOffset);
 while ((row = sqlNextRow(sr)) != NULL)
     {
     sample = sampleLoad(row + rowOffset);
-    snprintf( option, sizeof(option), "zooSpecies.%s", sample->name );
+    safef(option, sizeof(option), "zooSpecies.%s", sample->name );
     if( cartUsualBoolean(cart, option, TRUE ) )
 	cgiMakeCheckBox(option, TRUE );
     else
 	cgiMakeCheckBox(option, FALSE );
     printf("%s&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", sample->name );
 
     newRow++;
     if( newRow % 5 == 0 ) printf("<br>");
 
     sampleFree(&sample);
     }
 
 }
 
 void chainColorUi(struct trackDb *tdb)
@@ -2273,36 +2273,36 @@
 {
 char *enz = cartUsualString(cart, cutterVar, cutterDefault);
 puts("<P><B>Filter display by enzymes (separate with commas):</B><BR>");
 cgiMakeTextVar(cutterVar, enz, 100);
 }
 
 void genericWiggleUi(struct trackDb *tdb, int optionNum )
 /* put up UI for any standard wiggle track (a.k.a. sample track)*/
 {
 
 char options[7][256];
 int thisHeightPer, thisLineGap;
 float thisMinYRange, thisMaxYRange;
 char *interpolate, *fill;
 
-snprintf( &options[0][0], 256, "%s.heightPer", tdb->track );
-snprintf( &options[1][0], 256, "%s.linear.interp", tdb->track );
-snprintf( &options[3][0], 256, "%s.fill", tdb->track );
-snprintf( &options[4][0], 256, "%s.min.cutoff", tdb->track );
-snprintf( &options[5][0], 256, "%s.max.cutoff", tdb->track );
-snprintf( &options[6][0], 256, "%s.interp.gap", tdb->track );
+safef(&options[0][0], 256, "%s.heightPer", tdb->track );
+safef(&options[1][0], 256, "%s.linear.interp", tdb->track );
+safef(&options[3][0], 256, "%s.fill", tdb->track );
+safef(&options[4][0], 256, "%s.min.cutoff", tdb->track );
+safef(&options[5][0], 256, "%s.max.cutoff", tdb->track );
+safef(&options[6][0], 256, "%s.interp.gap", tdb->track );
 
 thisHeightPer = atoi(cartUsualString(cart, &options[0][0], "50"));
 interpolate = cartUsualString(cart, &options[1][0], "Linear Interpolation");
 fill = cartUsualString(cart, &options[3][0], "1");
 thisMinYRange = atof(cartUsualString(cart, &options[4][0], "0.0"));
 thisMaxYRange = atof(cartUsualString(cart, &options[5][0], "1000.0"));
 thisLineGap = atoi(cartUsualString(cart, &options[6][0], "200"));
 
 printf("<p><b>Interpolation: </b> ");
 wiggleDropDown(&options[1][0], interpolate );
 printf(" ");
 
 printf("<br><br>");
 printf(" <b>Fill Blocks</b>: ");
 cgiMakeRadioButton(&options[3][0], "1", sameString(fill, "1"));
@@ -2340,36 +2340,36 @@
 wigCfgUi(cart,tdb,tdb->track,
          "<span style='text-decoration:underline;'>Graph Plotting options:</span>",FALSE);
 printf("<p><b><span style='text-decoration:underline;'>"
        "View/Hide individual cell lines:</span></b>");
 }
 
 void humMusUi(struct trackDb *tdb, int optionNum )
 /* put up UI for human/mouse conservation sample tracks (humMusL and musHumL)*/
 {
 
 char options[7][256];
 int thisHeightPer, thisLineGap;
 float thisMinYRange, thisMaxYRange;
 char *interpolate, *fill;
 
-snprintf( &options[0][0], 256, "%s.heightPer", tdb->track );
-snprintf( &options[1][0], 256, "%s.linear.interp", tdb->track );
-snprintf( &options[3][0], 256, "%s.fill", tdb->track );
-snprintf( &options[4][0], 256, "%s.min.cutoff", tdb->track );
-snprintf( &options[5][0], 256, "%s.max.cutoff", tdb->track );
-snprintf( &options[6][0], 256, "%s.interp.gap", tdb->track );
+safef(&options[0][0], 256, "%s.heightPer", tdb->track );
+safef(&options[1][0], 256, "%s.linear.interp", tdb->track );
+safef(&options[3][0], 256, "%s.fill", tdb->track );
+safef(&options[4][0], 256, "%s.min.cutoff", tdb->track );
+safef(&options[5][0], 256, "%s.max.cutoff", tdb->track );
+safef(&options[6][0], 256, "%s.interp.gap", tdb->track );
 
 thisHeightPer = atoi(cartUsualString(cart, &options[0][0], "50"));
 interpolate = cartUsualString(cart, &options[1][0], "Linear Interpolation");
 fill = cartUsualString(cart, &options[3][0], "1");
 thisMinYRange = atof(cartUsualString(cart, &options[4][0], "0.0"));
 thisMaxYRange = atof(cartUsualString(cart, &options[5][0], "8.0"));
 thisLineGap = atoi(cartUsualString(cart, &options[6][0], "200"));
 
 printf("<p><b>Interpolation: </b> ");
 wiggleDropDown(&options[1][0], interpolate );
 printf(" ");
 
 printf("<br><br>");
 printf(" <b>Fill Blocks</b>: ");
 cgiMakeRadioButton(&options[3][0], "1", sameString(fill, "1"));
@@ -2833,207 +2833,218 @@
 char **ancestors;
 AllocArray(ancestors, count);
 count = 0;
 for(sp=speciesList; sp; sp = sp->next)
     {
     ancestors[count] = sp->name;
     count++;
     }
 char *coalescent = cartOptionalString(cart, codeVarName);
 printf("<B>Set Coalescent Ancestor to:</B>");
 cgiMakeDropListFull(codeVarName, ancestors, ancestors,
     count, coalescent, NULL, NULL);
 }
 #endif
 
-static char **
-parseDataTypes(struct trackDb *tdb, int *out_count) {
+static char **parseDataTypes(struct trackDb *tdb, int *out_count)
+{
 // ADS: almost certainly a function already exists to do this??
 /* Get entries 'nDataTypes' and 'dataTypes' from the 'settings'
  * field for the given 'trackDb' entry. 'nDataTypes' is a count, and
  * 'dataTypes' is a space separated list of words, each indicating a
  * data type. This function returns the array of data types. The
  * caller must free the returned array and each member. If
  * nDataTypes does not match the number of words parsed from
  * 'dataTypes' it is an error. Return value is NULL on error.
  */
-  const char *n_datatypes_str =
-    (const char *)hashMustFindVal(tdb->settingsHash, "nDataTypes");
+const char *n_datatypes_str = (const char *)hashMustFindVal(tdb->settingsHash, "nDataTypes");
 const int n_datatypes = atoi(n_datatypes_str);
-  if (n_datatypes <= 0) return NULL;
+if (n_datatypes <= 0)
+    return NULL;
 
-  const char *datatypes_str =
-    (const char *)hashMustFindVal(tdb->settingsHash, "dataTypes");
-  if (!datatypes_str) return NULL;
+const char *datatypes_str = (const char *)hashMustFindVal(tdb->settingsHash, "dataTypes");
+if (!datatypes_str)
+    return NULL;
 
 // returned, must be freed
 char **datatypes = calloc(n_datatypes, sizeof(char *));
-  if (!datatypes) return NULL;
+if (!datatypes)
+    return NULL;
 
 const char *name_start = datatypes_str;
 int observed_count = 0;
 
-  for (int i = 0; i < n_datatypes; ++i) {
+for (int i = 0; i < n_datatypes; ++i)
+    {
     const char *name_end = strchr(name_start, ' ');
-    if (!name_end) name_end = strchr(name_start, '\0');
+    if (!name_end)
+        name_end = strchr(name_start, '\0');
 
     const int name_len = name_end - name_start;
-    if (name_len <= 0) break;  // skip empty segments
+    if (name_len <= 0)
+        break;  // skip empty segments
 
     datatypes[i] = calloc(name_len + 1, sizeof(char));
-    if (!datatypes[i]) {  // cleanup on failure
+    if (!datatypes[i])
+        {  // cleanup on failure
         for (int j = 0; j < i; ++j) free(datatypes[j]);
         free(datatypes);
         return NULL;
         }
     memcpy(datatypes[i], name_start, name_len);
     datatypes[i][name_len] = '\0';
 
     ++observed_count;
 
-    if (*name_end == '\0') break;
+    if (*name_end == '\0')
+        break;
     name_start = name_end + 1;
     }
 
-  if (n_datatypes != observed_count) {  // cleanup on failure
-    for (int i = 0; i < n_datatypes; ++i) free(datatypes[i]);
+if (n_datatypes != observed_count)
+    {  // cleanup on failure
+    for (int i = 0; i < n_datatypes; ++i)
+        free(datatypes[i]);
     free(datatypes);
     return NULL;
     }
 
-  if (out_count) *out_count = observed_count;
+if (out_count)
+    *out_count = observed_count;
 return datatypes;
 }
 
 unsigned int cartDbParseId(char *, char **);  // ADS: avoid extra include
 
 #define COMMA_IF(x) (((x)++) ? "," : "")  // ADS: pattern for JSON comma
 
-static void
-bigCompositeCfgUi(struct trackDb *tdb) {
-  /* ADS: How bigComposite differs from other track types
-   * - track name is bigComposite (!)
+static void facetedCompositeUi(struct trackDb *tdb)
+{
+/* ADS: How facetedComposite differs from other track types
+ * - compositeTrack track setting is "faceted"
  * - Required fields in the 'settings' longblob for the trackDb entry:
  * - 'metaDataUrl': a non-blocked URL (can be server-local) with
-   *   metadata to generate the table.
+ *   metadata to generate the table.  This might change to an existing metadata
+ *   setting in the future.
  * - 'nDataTypes': positive integer counting the data types for each sample.
  * - 'dataTypes': the names of the data types, ordered and space separated.
  *
  * This function will embed sessionDb.settings/cart data in the
  * generated HTML. Instead of embedding all relevant tracks, it
  * parses the tracks named like:
  *
-   *    bigCompositeName_dataElementName_dataTypeName_sel=1
+ *    facetedCompositeName_dataElementName_dataTypeName_sel=1
  *
  * And only sends unique dataElement and dataTypes, which might be
-   * much smaller. The bigComposite assumes that selection of
+ * 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;
 
 // 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/bigComposite.js'></script>\n";
-  // const char metaDataUrlFmt[] = "\"metadata_url\": \"%s\"";
+    "<script type='text/javascript' src='/js/facetedComposite.js'></script>\n";
 
 /* ADS: maybe cart should be used below, but I don't know how from here */
 // parse the hgsid as id and sessionKey
-  char *hgsid = cgiString("hgsid");
+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 =
-    (const char *)hashMustFindVal(tdb->settingsHash, "metaDataUrl");
-  const char *primaryKey =
-    (const char *)hashMustFindVal(tdb->settingsHash, "primaryKey");
+const char *metaDataUrl = trackDbSetting(tdb, "metaDataUrl");
+//    (const char *)hashMustFindVal(tdb->settingsHash, "metaDataUrl");
+const char *primaryKey = trackDbSetting(tdb, "primaryKey");
+//    (const char *)hashMustFindVal(tdb->settingsHash, "primaryKey");
+
 int nDataTypes = 0;
 char **dataTypes = parseDataTypes(tdb, &nDataTypes);
 if (!dataTypes)
-    errAbort("Failed to parse data types from bigComposite settings for: %s",
-             tdb->track);
+    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");
+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;
 
-  char queryFmt[] =
-    "SELECT contents FROM sessionDb WHERE id='%d' AND sessionKey='%s';";
+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 embedded JSON data --- */
 printf(openJSON);
 printf(openDataTypesJSON);
 // find selected data types
 int not_first = 0;
 int anySelDataType = -1;  // non-neg val will be used as index and flag
-  for (int i = 0; i < nDataTypes; ++i) {
+for (int i = 0; i < nDataTypes; ++i)
+    {
     char toMatch[token_size];
-    snprintf(toMatch, token_size, "_%s_sel", dataTypes[i]);
+    safef(toMatch, token_size, "_%s_sel", dataTypes[i]);
     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;
     printf("%s\"%s\": %d", COMMA_IF(not_first), dataTypes[i], dataTypeSel ? 1 : 0);
     anySelDataType = dataTypeSel ? i : anySelDataType;
     }
 printf(closeDataTypesJSON);
 printf(",");  // add separator
 // find selected data sets
 printf(openDataElementsJSON);
 not_first = 0;
-  if (anySelDataType >= 0) {
+if (anySelDataType >= 0)
+    {
     char suffix[token_size];
-    snprintf(suffix, token_size, "_%s_sel", dataTypes[anySelDataType]);
+    safef(suffix, token_size, "_%s_sel", dataTypes[anySelDataType]);
     for (struct cgiVar *le = varList->list; le; le = le->next)
-      if (startsWith(metaDataId, le->name) && endsWith(le->name, suffix)) {
-        const char *nameStart = strchr(le->name, '_');
-        if (nameStart) {
+        if (startsWith(metaDataId, le->name) && endsWith(le->name, suffix))
+            {
+            const char *nameStart = strchr(le->name+strlen(metaDataId), '_');
+            if (nameStart)
+                {
                 ++nameStart;  // move past '_'
                 const char *nameEnd = strchr(nameStart, '_');
-          if (nameEnd && nameEnd > nameStart) {
+                if (nameEnd && nameEnd > nameStart)
+                    {
                     const int nameLen = nameEnd - nameStart;
                     printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart);
                     }
                 }
             }
     }
 printf(closeDataElementsJSON);
 printf(",\"mdid\": \"%s\"", tdb->track);  // metadata id is track name
 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);
@@ -3058,34 +3069,32 @@
 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");
 boolean isGencode3 = trackDbSettingOn(tdb, "isGencode3");
 // UI precedence:
 // 1) supers to get them out of the way: they have no controls
 // 2) special cases based upon track name (developer please avoid)
 // 3) cfgTypeFromTdb()/cfgByCfgType() <== prefered method
 // 4) special cases falling through the cracks but based upon type
 if (tdbIsSuperTrack(tdb))
     superTrackUi(tdb, tdbList);
-else if (sameString(tdb->type, "bigComposite"))
-    // ADS: switching on 'type' here, instead of track name as for all
-    // other cases (except hic and ld2)
-    bigCompositeCfgUi(tdb);
+else if (tdbIsComposite(tdb) && sameOk(trackDbLocalSetting(tdb, "compositeTrack"), "faceted"))
+    facetedCompositeUi(tdb);
 else if (sameString(track, "stsMap"))
     stsMapUi(tdb);
 else if (sameString(track, "affyTxnPhase2"))
     affyTxnPhase2Ui(tdb);
 else if (sameString(track, "cgapSage"))
     cgapSageUi(tdb);
 else if (sameString(track, "stsMapMouseNew"))
     stsMapMouseUi(tdb);
 else if (sameString(track, "stsMapRat"))
     stsMapRatUi(tdb);
 else if (sameString(track, "snpMap"))
     snpMapUi(tdb);
 else if (sameString(track, "snp"))
     snpUi(tdb);
 else if (snpVersion(track) >= 125)
@@ -3244,34 +3253,34 @@
     else if (startsWith("sample", tdb->type))
         genericWiggleUi(tdb,7);
     else if (startsWithWord("array",tdb->type)) // not quite the same as "expRatio" (custom tracks)
         expRatioCtUi(tdb);
     else if (startsWithWord("factorSource",tdb->type))
         factorSourceUi(db,tdb);
     else if (startsWithWord("bigBed",tdb->type))
         labelCfgUi(db, cart, tdb, tdb->track);
     }
 
 if (!ajax) // ajax asks for a simple cfg dialog for right-click popup or hgTrackUi subtrack cfg
     {
     // Composites *might* have had their top level controls just printed,
     // but almost certainly have additional controls
     boolean isLogo = (trackDbSetting(tdb, "logo") != NULL);
-    // ADS: added 'bigComposite' below to avoid generating the
-    // hCompositeUi controls for bigComposite. Better solution would
-    // be define bigComposite to avoid using 'compositeTrack on'
-    if (!sameString(tdb->type, "bigComposite") && tdbIsComposite(tdb) && !isLogo) // for the moment generalizing this to include other containers...
+    // It'd be nice to handle faceted composites as a separate container type, but practically so much
+    // of the display features we want are identical to composites - it's easier to special case the UI.
+    if (tdbIsComposite(tdb) && !isLogo) // for the moment generalizing this to include other containers...
+        if (!sameOk(trackDbLocalSetting(tdb, "compositeTrack"), "faceted")) // but not faceted containers ...
             hCompositeUi(db, cart, tdb, NULL, NULL, MAIN_FORM);
 
     // Additional special case navigation links may be added
     extraUiLinks(db, tdb, cart);
     }
 }
 
 #ifdef UNUSED
 static void findSuperChildrenAndSettings(struct trackDb *tdbList, struct trackDb *super)
 /* Find the tracks that have super as a parent and stuff references to them on
  * super's children list. Also do some visibility and parentName futzing. */
 {
 struct trackDb *tdb;
 for (tdb = tdbList; tdb != NULL; tdb = tdb->next)
     {