f3009e2f9d911795bd1d13a0f95bbbee46309f83 galt Sun Nov 14 18:54:39 2021 -0800 Adding warn-and-continue option to hgCustom. Motivation is to support https warnings but will be useful more widely. refs #28458 diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c index 250b19a..5a93aee 100644 --- src/hg/hgCustom/hgCustom.c +++ src/hg/hgCustom/hgCustom.c @@ -72,31 +72,31 @@ #define hgCtDoDelete hgCtDo "delete" #define hgCtDoDeleteSet hgCtDo "delete_set" #define hgCtDoDeleteClr hgCtDo "delete_clr" #define hgCtDoRefresh hgCtDo "refresh" #define hgCtDoRefreshSet hgCtDo "refresh_set" #define hgCtDoRefreshClr hgCtDo "refresh_clr" #define hgCtDoGenomeBrowser hgCtDo "gb" #define hgCtDoTableBrowser hgCtDo "tb" #ifdef PROGRESS_METER #define hgCtDoProgress hgCtDo "progress" #endif /* Global variables */ struct cart *cart; struct hash *oldVars = NULL; -char *excludeVars[] = {"Submit", "submit", "SubmitFile", NULL}; +char *excludeVars[] = {"Submit", "submit", "SubmitFile", "ContinueWithWarn", NULL}; char *database = NULL; char *organism = NULL; struct customTrack *ctList = NULL; void makeClearButton(char *field) /* UI button that clears a text field */ { char id[256]; safef(id, sizeof id, "%s_clear", field); char javascript[1024]; safef(javascript, sizeof javascript, "document.mainForm.%s.value = '';", field); cgiMakeOnClickButton(id, javascript, " Clear "); } @@ -136,31 +136,31 @@ " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#TRACK'>track</A>\n" " and" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#BROWSER'>browser</A>\n" " line attributes as described in the \n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html'>User's Guide</A>.<br>\n" " Examples are\n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#EXAMPLE1'>here</A>.\n" " If you do not have web-accessible data storage available, please see the\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hgTrackHubHelp.html#Hosting'>Hosting</A> section of the Track Hub Help documentation.\n<br><br>" " Please note a much more efficient way to load data is to use\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hgTrackHubHelp.html'>Track Hubs</A>, which are loaded\n" " from the <A HREF='hgHubConnect'>Track Hubs Portal</A> found in the menu under My Data.\n" ); } -void addCustomForm(struct customTrack *ct, char *err) +void addCustomForm(struct customTrack *ct, char *err, boolean warnOnly) /* display UI for adding custom tracks by URL or pasting data */ { char *dataUrl = NULL; char buf[1024]; boolean gotClade = FALSE; boolean isUpdateForm = FALSE; if (ct) { isUpdateForm = TRUE; dataUrl = ctDataUrl(ct); } else /* add form needs clade for assembly menu */ gotClade = hGotClade(); @@ -211,60 +211,66 @@ puts("</TD></TR></TABLE>\n"); } /* intro text */ puts("<P>"); if (isUpdateForm) puts("Update your custom track configuration, data, and/or documentation."); else puts("Display your own data as custom annotation tracks in the browser."); addIntro(); puts("<P>"); /* row for error message */ if (isNotEmpty(err)) { - printf("<P><B> <span style='color:RED; font-style:italic;'>" - "Error</span> %s</B><P>", err); + char *fullErrString = replaceChars(err, "\n", "<br>\n"); + printf("<P><B> <span style='color:%s; font-style:italic;'>" + "%s</span><P>%s</B><P>", warnOnly ? "ORANGE" : "RED", warnOnly ? "Warning" : "Error", fullErrString); + freeMem(fullErrString); /* send two lines of the message to the apache error log also: */ - char *tmpString = cloneString(err); - char *lineBreak = strchr(tmpString, '\n'); - if (lineBreak) /* first line break becomes a blank */ - *lineBreak = ' '; - lineBreak = strchr(tmpString, '\n'); - if (lineBreak) /* second one becomes end of string */ - *lineBreak = (char) 0; + char *tmpString = replaceChars(err, "\n", " "); fprintf(stderr, "hgCustom load error: %s\n", tmpString); + freeMem(tmpString); } cgiSimpleTableStart(); /* first rows for update form are for track and browser line entry */ if (isUpdateForm) { /* row for instructions */ cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); if (dataUrl) puts("Configuration:"); else { puts("Edit configuration:"); } cgiTableFieldEnd(); cgiTableField(" "); puts("<TD ALIGN='RIGHT'>"); + if (warnOnly) + { + cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);"); + printf(" "); + jsInline( + "$('textarea').change(function() {\n" + " $('#ContinueWithWarn').hide();\n" + "});\n"); + } cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); cgiTableField(" "); cgiTableRowEnd(); /* row for text entry box */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (dataUrl) { /* can't update via pasting if loaded from URL */ cgiMakeTextAreaDisableable(hgCtConfigLines, cartUsualString(cart, hgCtConfigLines, customTrackUserConfig(ct)), CONFIG_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); @@ -307,30 +313,39 @@ else cgiTableField("Paste URLs or data:"); if (isUpdateForm && dataUrl) cgiTableField(" "); else { puts("<TD ALIGN='RIGHT'>"); puts("Or upload: "); cgiMakeFileEntry(hgCtDataFile); cgiTableFieldEnd(); } if (!isUpdateForm) { cgiSimpleTableFieldStart(); + if (warnOnly) + { + cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);"); + printf(" "); + jsInline( + "$('textarea').change(function() {\n" + " $('#ContinueWithWarn').hide();\n" + "});\n"); + } cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); } cgiTableRowEnd(); /* next row - text entry box for data, and clear button */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (dataUrl) { /* can't update via pasting if loaded from URL */ safef(buf, sizeof buf, "Replace data at URL: %s", ctDataUrl(ct)); cgiMakeTextAreaDisableable(hgCtDataText, buf, TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); @@ -938,52 +953,52 @@ #endif static void webBotWarning() /* display the overuse warning message in the javaScript warning text box * html output has already started at this point, just need to add the * warning handler and setup the warning javaScript box, warnings after * this will go to that text box */ { pushWarnHandler(webVaWarn); htmlWarnBoxSetup(stdout); char *ip = getenv("REMOTE_ADDR"); botDelayMessage(ip, botDelayMillis); } -void doAddCustom(char *err) +void doAddCustom(char *err, boolean warnOnly) /* display form for adding custom tracks. * Include error message, if any */ { cartWebStart(cart, database, "Add Custom Tracks"); -addCustomForm(NULL, err); +addCustomForm(NULL, err, warnOnly); helpCustom(); if (issueBotWarning) webBotWarning(); cartWebEnd(cart); } -void doUpdateCustom(struct customTrack *ct, char *err) +void doUpdateCustom(struct customTrack *ct, char *err, boolean warnOnly) /* display form for adding custom tracks. * Include error message, if any */ { char *longLabel = htmlEncode(ct->tdb->longLabel); cartWebStart(cart, database, "Update Custom Track: %s [%s]", longLabel, database); freeMem(longLabel); cartSetString(cart, hgCtDocText, ct->tdb->html); -addCustomForm(ct, err); +addCustomForm(ct, err, warnOnly); helpCustom(); cartWebEnd(cart); } static void doManageCustom(char *warnMsg) /* display form for deleting & updating custom tracks. * Include warning message, if any */ { cartWebStart(cart, database, "Manage Custom Tracks"); jsIncludeFile("jquery.js", NULL); manageCustomForm(warnMsg); webNewSection("Managing Custom Tracks"); webIncludeHelpFile("customTrackManage", FALSE); if (issueBotWarning) webBotWarning(); @@ -1183,66 +1198,68 @@ break; if (sameString(organism, dbDb->organism)) { if (!dbWithCts) dbWithCts = cloneString(dbDb->name); } } if (dbWithCts) // set the database for the selected organism to an assembly that { // has custom tracks database = dbWithCts; cartSetString(cart, "db", database); } } if (cartVarExists(cart, hgCtDoAdd)) - doAddCustom(NULL); + doAddCustom(NULL, FALSE); #ifdef PROGRESS_METER else if (cartVarExists(cart, hgCtDoProgress)) { doProgress(NULL); } #endif else if (cartVarExists(cart, hgCtTable)) { /* update track */ /* need to clone the hgCtTable value, as the ParseCart will remove the variable */ selectedTable = cloneString(cartString(cart, hgCtTable)); if (isNotEmpty(selectedTable)) { ctList = customTracksParseCart(database, cart, NULL, NULL); ct = ctFromList(ctList, selectedTable); } if (ct) - doUpdateCustom(ct, NULL); + doUpdateCustom(ct, NULL, FALSE); else - doAddCustom(NULL); + doAddCustom(NULL, FALSE); } else { /* get new and existing custom tracks from cart and decide what to do */ // setup a timer to periodically print out something to stdout to make sure apache or the web browser doesn't time us out (see redmine #3002). // e.g. see http://stackoverflow.com/questions/5547166/how-to-avoid-cgi-timeout struct sigaction *act; AllocVar(act); act->sa_handler = timer; act->sa_flags = SA_RESTART; sigaction(SIGALRM, act, NULL); alarm(TIMER_INTERVAL); + boolean warnOnly = FALSE; + char *customText = fixNewData(cart); /* save input so we can display if there's an error */ char *savedCustomText = saveLines(cloneString(customText), SAVED_LINE_COUNT); char *trackConfig = cartOptionalString(cart, hgCtConfigLines); char *savedConfig = cloneString(trackConfig); struct dyString *dsWarn = dyStringNew(0); char *fileName = cartOptionalString(cart, hgCtDataFileName); boolean hasData = (isNotEmpty(customText) || isNotEmpty(fileName)); if (cartVarExists(cart, hgCtUpdatedTrack) && hasData) { /* from 'update' screen */ /* prepend config to data for parser */ struct dyString *dsTrack = dyStringNew(0); @@ -1264,41 +1281,44 @@ if (startsWith(trackConfig, customText)) trackConfig = ""; else customText = "Duplicate track configuration found - remove track and browser lines from Configuration box or from Data"; } dyStringPrintf(dsTrack, "%s\n%s\n", trackConfig, customText); customText = dyStringCannibalize(&dsTrack); cartSetString(cart, hgCtDataText, customText); if (isNotEmpty(fileContents)) { /* already handled file */ cartRemove(cart, hgCtDataFile); cartRemove(cart, hgCtDataFileName); } } + warnOnly = FALSE; struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName, - &replacedCts, NULL, &err); + &replacedCts, NULL, &err, &warnOnly); errCatchEnd(catch); if (catch->gotError) { addWarning(dsWarn, err); } if (isNotEmpty(catch->message->string)) + { addWarning(dsWarn, catch->message->string); + } errCatchFree(&catch); /* exclude special setting used by table browser to indicate * db assembly for error-handling purposes only */ char *db = NULL; if (trackConfig && (db = stringIn("db=", trackConfig)) != NULL) { db += 3; char *nextTok = skipToSpaces(db); if (!nextTok) nextTok = strchr(db, 0); db = cloneStringZ(db,nextTok-db); if (!sameString(db,database)) err = "Invalid configuration found - remove db= or return it to its original value. "; } @@ -1317,84 +1337,86 @@ { customTrackUpdateFromConfig(ct, database, trackConfig, &browserLines); ctUpdated = TRUE; } errCatchEnd(catch); if (isNotEmpty(catch->message->string)) err = catTwoStrings(emptyForNull(err), catch->message->string); errCatchFree(&catch); } } } addWarning(dsWarn, replacedTracksMsg(replacedCts)); doBrowserLines(browserLines, &warnMsg); addWarning(dsWarn, warnMsg); - if (err) + if (err && !(warnOnly && cartVarExists(cart, "ContinueWithWarn"))) { char *selectedTable = NULL; cartSetString(cart, hgCtDataText, savedCustomText); cartSetString(cart, hgCtConfigLines, savedConfig); if ((selectedTable= cartOptionalString(cart, hgCtUpdatedTable)) != NULL) { ct = ctFromList(ctList, selectedTable); - doUpdateCustom(ct, err); + doUpdateCustom(ct, err, warnOnly); } else - doAddCustom(err); + doAddCustom(err, warnOnly); cartRemovePrefix(cart, hgCt); return; } if (cartVarExists(cart, hgCtDoDelete)) { doDeleteCustom(); ctUpdated = TRUE; } if (cartVarExists(cart, hgCtDoRefresh)) { doRefreshCustom(&warnMsg); addWarning(dsWarn, warnMsg); ctUpdated = TRUE; } if (ctUpdated || ctConfigUpdate(ctFileName)) { customTracksSaveCart(database, cart, ctList); /* refresh ctList again to pickup remote resource error state */ + warnOnly=FALSE; struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName, - &replacedCts, NULL, &err); + &replacedCts, NULL, &err, &warnOnly); errCatchEnd(catch); if (catch->gotError) { addWarning(dsWarn, err); addWarning(dsWarn, catch->message->string); } errCatchFree(&catch); - } warnMsg = dyStringCannibalize(&dsWarn); if (measureTiming) { long lastTime = clock1000(); loadTime = lastTime - thisTime; } + + if (ctList || cartVarExists(cart, hgCtDoDelete)) doManageCustom(warnMsg); else - doAddCustom(warnMsg); + doAddCustom(warnMsg, warnOnly); } cartRemovePrefix(cart, hgCt); cartRemove(cart, CT_CUSTOM_TEXT_VAR); } int main(int argc, char *argv[]) /* Process command line. */ { long enteredMainTime = clock1000(); issueBotWarning = earlyBotCheck(enteredMainTime, "hgCustom", delayFraction, 0, 0, "html"); htmlPushEarlyHandlers(); oldVars = hashNew(10); cgiSpoof(&argc, argv); cartEmptyShell(doMiddle, hUserCookie(), excludeVars, oldVars);