d121edc0bd2809f1d6de6185a497e6a288958479 braney Tue May 12 09:18:29 2026 -0700 hgConvert quickLift: skip pre-lifted tracks, append-and-merge hub file, per-track remove UI, refs #37535 In hgConvert / trackHubBuild: - Skip tracks that already came from a quickLift hub (quickLiftUrl / quickLifted setting) so they don't get re-lifted to a new destination. - Append new track stanzas to an existing per-source hub file instead of overwriting it; new stanzas get priorities after the existing max; duplicate track names are skipped. Also avoids re-emitting a parent supertrack that's already in the file. New public quickLiftHubRemoveTrack(cart, sourceDb, trackName) in trackHub.c rewrites the per-source hub file with the named stanza removed plus all descendant stanzas (parent reference cascade, transitive). hgTrackUi: adds a "Remove from QuickLift" link next to "Duplicate track" for any tdb carrying a quickLiftDb setting. The link hits hgTrackUi_op=quickLiftRemove which calls quickLiftHubRemoveTrack, hides the track in the cart, and 302s to hgTracks. The op argument cart var is qlSourceDb (renamed from quickLiftSourceDb to avoid colliding with the quickLift.* prefix used elsewhere; values cloned out of the cart hash before cartRemove so the helper doesn't see freed strings). hgTracks: adds a small "x" icon (printQuickLiftDelIcon) on tracks in a quickLift group, suppressed on the synthetic bigQuickLiftChain track. JS onQuickLiftDelIconClick fires the same hgTrackUi_op endpoint via synchronous XHR and removes every TD whose icon matches the deleted data-track, so the row goes away in both the QuickLift group and the Visible Tracks group. hubConnect cart handling fixes shaken out by the above: - hubConnectRemakeTrackHubVar's cart-var prefix is now "quickLift." (with the trailing dot) instead of "quickLift", so unrelated keys like qlSourceDb no longer get parsed as hubId/db and crash cart loading on every CGI. Also skips entries whose hubStatus lookup returned NULL. - hubConnectStatusListFromCart no longer calls removeQuickListReference when the current db isn't the lift's destination. A side trip to another assembly between two lifts to the same destination was deleting the earlier attachment's cart var; just skip attaching this load and leave the cart alone so the lift survives the round trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c index 9a055a1b35c..09ad270f6c7 100644 --- src/hg/hgTrackUi/hgTrackUi.c +++ src/hg/hgTrackUi/hgTrackUi.c @@ -1,27 +1,28 @@ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "linefile.h" #include "hash.h" #include "cheapcgi.h" #include "htmshell.h" #include "jksql.h" #include "jsHelper.h" #include "trackDb.h" #include "hgTrackUi.h" +#include "quickLift.h" #include "hdb.h" #include "hCommon.h" #include "hui.h" #include "fileUi.h" #include "ldUi.h" #include "snpUi.h" #include "snp125Ui.h" #include "snp125.h" #include "sample.h" #include "wiggle.h" #include "hgMaf.h" #include "obscure.h" #include "chainCart.h" #include "chainDb.h" #include "gvUi.h" @@ -3996,30 +3997,38 @@ /* Offer to dupe the non-containery tracks including composite and supertrack elements */ if (tdbIsDupable(tdb)) { printf("\n <a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=dupe' >Duplicate track</a>\n", hgTrackUiName(), cartSessionVarName(), cartSessionId(cart), chromosome, cgiEncode(tdb->track)); if (isDupTrack(tdb->track)) { /* Offer to undupe */ printf("\n <a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=undupe' >Remove duplicate</a>\n", hgTrackUiName(), cartSessionVarName(), cartSessionId(cart), chromosome, cgiEncode(tdb->track)); } } + /* Offer to remove tracks coming from a quickLift hub. */ + char *quickLiftSourceDb = trackDbSetting(tdb, "quickLiftDb"); + if (quickLiftSourceDb != NULL) + { + printf("\n <a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=quickLiftRemove&qlSourceDb=%s' >Remove from QuickLift</a>\n", + hgTrackUiName(), cartSessionVarName(), cartSessionId(cart), + chromosome, cgiEncode(tdb->track), cgiEncode(quickLiftSourceDb)); + } } if (ct) { puts(" "); cgiMakeButton(CT_DO_REMOVE_VAR, "Remove custom track"); cgiMakeHiddenVar(CT_SELECTED_TABLE_VAR, tdb->track); puts(" "); if (differentString(tdb->type, "chromGraph")) { char buf[256]; if (ajax) // reference to a separate form doesn't work in modal dialog, // so change window.location directly. safef(buf, sizeof(buf), "window.location='%s?hgsid=%s&%s=%s';return false;", @@ -4381,31 +4390,49 @@ puts(content); freeMem(content); freeMem(fileUrl); } void doMiddle(struct cart *theCart) /* Write body of web page. */ { boolean isFileFetch = isNotEmpty(cartOptionalString(theCart, "fileUrl")); if (isFileFetch) { handleFileFetch(theCart); // file fetch workaround for CORS issues return; } -else + +char *earlyOp = cartOptionalString(theCart, "hgTrackUi_op"); +if (earlyOp != NULL && sameString(earlyOp, "quickLiftRemove")) + { + char *opTrack = cloneString(cartOptionalString(theCart, "g")); + char *opSourceDb = cloneString(cartOptionalString(theCart, "qlSourceDb")); + char *opDb = cloneString(cartUsualString(theCart, "db", "")); + cartRemove(theCart, "hgTrackUi_op"); + cartRemove(theCart, "qlSourceDb"); + if (opTrack != NULL && opSourceDb != NULL) + { + quickLiftHubRemoveTrack(theCart, opSourceDb, opTrack); + cartSetString(theCart, opTrack, "hide"); + } + printf("Location: %s?db=%s&%s\r\n\r\n", + hgTracksName(), opDb, cartSidUrlString(theCart)); + return; + } + cartWriteHeaderAndCont(theCart, NULL, NULL); // "normal" hgTrackUi struct trackDb *tdbList = NULL; struct trackDb *tdb = NULL; char *track; struct customTrack *ct = NULL, *ctList = NULL; char *ignored; /* used to have hgBotDelayFrac(0.25) here, replaced with earlyBotCheck() * at the beginning of main() to output message here if in delay time * 2021-06-21 - Hiram */ if (issueBotWarning) { char *ip = getenv("REMOTE_ADDR");