4bb9e8caea515342ba98d3871da76cd4ec69916f chmalee Fri May 1 14:10:00 2026 -0700 Initial myVariants implementation: a form on hgTracks where users can enter item details in one of three ways: hgvs/item search, simple bed form, advanced bed form where additional non-bed fields can dynamically created. Allows changing the color of items, writing descriptions, and editing the items after creation. Show overlaps with hardcoded tracks when hgc page is open (not in the hgc dialog). Next commit has implementation of sharing these tracks with other users diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c index b1d04bdcaf6..4cca7023327 100644 --- src/hg/hgCustom/hgCustom.c +++ src/hg/hgCustom/hgCustom.c @@ -13,30 +13,33 @@ #include "hdb.h" #include "hui.h" #include "hCommon.h" #include "customTrack.h" #include "customFactory.h" #include "portable.h" #include "errCatch.h" #include "knetUdc.h" #include "udc.h" #include "net.h" #include "jsHelper.h" #include <signal.h> #include "trackHub.h" #include "botDelay.h" #include "chromAlias.h" +#include "myVariants.h" +#include "myVariantsShare.h" +#include "wikiLink.h" #include "hgConfig.h" static long loadTime = 0; static boolean issueBotWarning = FALSE; #define delayFraction 0.25 /* same as hgTracks */ void usage() /* Explain usage and exit. */ { errAbort( "hgCustom - Custom track management CGI\n" "usage:\n" " hgCustom <CGI settings>\n" ); } @@ -1064,31 +1067,52 @@ /* not the first */ dyStringAppend(dsWarn, ", "); dyStringAppend(dsWarn, ct->tdb->shortLabel); } return dyStringCannibalize(&dsWarn); } void doDeleteCustom() /* remove custom tracks from list based on cart variables */ { struct customTrack *ct; for (ct = ctList; ct != NULL; ct = ct->next) { char var[256]; safef(var, sizeof var, "%s_%s", hgCtDeletePrefix, ct->tdb->track); - if (cartUsualBoolean(cart, var, FALSE)) + if (!cartUsualBoolean(cart, var, FALSE)) + continue; + /* myVariants tracks are backed by a per-user SQL table, not the CT file, + * so just dropping them from ctList isn't enough -- the next page load + * regenerates the CT entry from the database. Delete the underlying + * data (own track) or revoke the accepted share (shared track) so the + * removal sticks. */ + char *trackName = ct->tdb->track; + if (startsWith("myVariants_shared_", trackName)) + { + char *token = trackName + strlen("myVariants_shared_"); + char shareCartVar[256]; + safef(shareCartVar, sizeof shareCartVar, + MYVAR_SHARED_CART_PREFIX "%s", token); + cartRemove(cart, shareCartVar); + } + else if (startsWith("myVariants_", trackName)) + { + char *userName = wikiLinkUserName(); + if (isNotEmpty(userName)) + myVariantsDeleteForDb(userName, database); + } slRemoveEl(&ctList, ct); } } void doRefreshCustom(char **warnMsg) /* reparse custom tracks from URLs based on cart variables */ { struct customTrack *ct; struct customTrack *replacedCts = NULL; struct customTrack *refreshCts = NULL; for (ct = ctList; ct != NULL; ct = ct->next) { char var[256]; safef(var, sizeof var, "%s_%s", hgCtRefreshPrefix, ct->tdb->track); @@ -1198,30 +1222,104 @@ struct customTrack *replacedCts = NULL; char *err = NULL, *warnMsg = NULL; char *selectedTable = NULL; struct customTrack *ct = NULL; boolean ctUpdated = FALSE; char *initialDb = NULL; long thisTime = clock1000(); cart = theCart; measureTiming = isNotEmpty(cartOptionalString(cart, "measureTiming")); initialDb = cloneString(cartUsualString(cart, "db", "")); getDbAndGenome(cart, &database, &organism, oldVars); chromAliasSetup(database); +/* Ensure myVariants CT is registered in this cart if user has items for current db */ +if (cfgOptionBooleanDefault("doMyVariants", FALSE)) + { + char *user = wikiLinkUserName(); + if (isNotEmpty(user)) + { + char *dbTable = myVariantsTableExists(user); + if (isNotEmpty(dbTable)) + { + struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH); + char query[512]; + sqlSafef(query, sizeof query, "select count(*) from %s where db='%s'", dbTable, database); + int ctCount = sqlQuickNum(conn, query); + if (ctCount > 0) + { + /* Prefer writing a concrete CT file to trash to ensure CT is recognized immediately */ + char *ctFile = myVariantsWriteCtFile(user, database, cart); + if (isNotEmpty(ctFile)) + { + char varName[256]; + safef(varName, sizeof varName, CT_FILE_VAR_PREFIX "%s", database); + cartSetString(cart, varName, ctFile); + freeMem(ctFile); + } + } + hFreeConn(&conn); + } + } + } + +/* Special endpoint: stream a custom track generated from the user's myVariants table */ +char *op = cgiOptionalString("op"); +if (cfgOptionBooleanDefault("doMyVariants", FALSE) && op && sameString(op, "myVariantsCt")) + { + char *user = wikiLinkUserName(); + if (isEmpty(user)) + errAbort("myVariantsCt: must be logged in"); + + // Build fully-qualified table name and verify existence + char *dbTable = myVariantsGetDbTable(user); + struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH); + if (!sqlTableExists(conn, dbTable)) + { + hFreeConn(&conn); + errAbort("myVariantsCt: table does not exist for current user"); + } + + // Emit CT text: a track line and BED9 rows filtered by current database + // Plain text response, no HTML + puts("Content-Type: text/plain\n"); + /* Keep track name stable so re-import replaces */ + char *userEnc = htmlEncode(user); + printf("track name=\"myVariants\" type=\"bed 9\" itemRgb=\"on\" visibility=\"pack\" shortLabel=\"My Variants\" longLabel=\"My Variants (%s)\"\n", userEnc); + freeMem(userEnc); + + struct dyString *query = dyStringNew(0); + sqlDyStringPrintf(query, + "select chrom, chromStart, chromEnd, name, score, strand, thickStart, thickEnd, itemRgb " + "from %s where db='%s'", + dbTable, database); + struct sqlResult *sr = sqlGetResult(conn, query->string); + char **row; + while ((row = sqlNextRow(sr)) != NULL) + { + /* BED9: chrom start end name score strand thickStart thickEnd itemRgb */ + printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]); + } + sqlFreeResult(&sr); + hFreeConn(&conn); + /* Done with special op */ + return; + } + customFactoryEnableExtraChecking(TRUE); knetUdcInstall(); if (udcCacheTimeout() < 300) udcSetCacheTimeout(300); if (sameString(initialDb, "0")) { /* when an organism is selected from the custom track management page, * set the database to be the default only if it has custom tracks. * Otherwise, pick an assembly for that organism that does have custom tracks. */ struct dbDb *dbDb, *dbDbs = getCustomTrackDatabases(); char *dbWithCts = NULL; for (dbDb = dbDbs; dbDb != NULL; dbDb = dbDb->next) {