0bae2d58f02a994a23de767dfb2ac8e414253a88 galt Mon May 19 22:05:32 2025 -0700 Merging in simplifyBlatAlwaysCreateCT. Uses ajax and hg.conf setting. fixes #32751. diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c index 220789493a5..3433e8ef2ec 100644 --- src/hg/hgBlat/hgBlat.c +++ src/hg/hgBlat/hgBlat.c @@ -31,30 +31,33 @@ #include "portable.h" #include "dystring.h" #include "chromInfo.h" #include "net.h" #include "fuzzyFind.h" #include "chromAlias.h" struct cart *cart; /* The user's ui state. */ struct hash *oldVars = NULL; boolean orgChange = FALSE; boolean dbChange = FALSE; boolean allGenomes = FALSE; boolean allResults = FALSE; static long enteredMainTime = 0; + +boolean autoBigPsl = FALSE; // DEFAULT VALUE change to TRUE in future + /* for earlyBotCheck() function at the beginning of main() */ #define delayFraction 0.5 /* standard penalty is 1.0 for most CGIs */ /* this one is 0.5 */ static boolean issueBotWarning = FALSE; struct gfResult /* Detailed gfServer results, this is a span of several nearby tiles, minimum 2 for dna. */ { struct gfResult *next; /* have to multiply translated coordinates by 3 */ int qStart; /* Query Start Coordinate */ int qEnd; /* Query End Coordinate */ char *chrom; /* Target Chrom Name */ int tStart; /* Target Start Coordinate */ int tEnd; /* Target End Coordinate */ @@ -489,47 +492,56 @@ } return same; } boolean allDigits(char *s) /* Return TRUE if s is all digits */ { char c; while ((c = *s++) != 0) if (!isdigit(c)) return FALSE; return TRUE; } - void printLuckyRedirect(char *browserUrl, struct psl *psl, char *database, char *pslName, char *faName, char *uiState, char *unhideTrack) /* Print out a very short page that redirects us. */ { char url[1024]; -safef(url, sizeof(url), "%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s%s", - browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, - pslName, faName, uiState, unhideTrack); /* htmlStart("Redirecting"); */ /* Odd it appears that we've already printed the Content-Typ:text/html line but I can't figure out where... */ +if (autoBigPsl) + { + // skip ss variable + safef(url, sizeof(url), "%s?position=%s:%d-%d&db=%s&%s%s", + browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + uiState, unhideTrack); + jsInlineF("luckyLocation = '%s';\n", url); + } +else + { + safef(url, sizeof(url), "%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s%s", + browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + pslName, faName, uiState, unhideTrack); htmStart(stdout, "Redirecting"); jsInlineF("location.replace('%s');\n", url); printf("<noscript>No javascript support:<br>Click <a href='%s'>here</a> for browser.</noscript>\n", url); htmlEnd(); - + } } /* forward declaration to reduce churn */ static void getCustomName(char *database, struct cart *cart, struct psl *psl, char **pName, char **pDescription); void showAliPlaces(char *pslName, char *faName, char *customText, char *database, enum gfType qType, enum gfType tType, char *organism, boolean feelingLucky) /* Show all the places that align. */ { boolean useBigPsl = cfgOptionBooleanDefault("useBlatBigPsl", TRUE); struct lineFile *lf = pslFileOpen(pslName); struct psl *pslList = NULL, *psl; char *browserUrl = hgTracksName(); @@ -558,31 +570,38 @@ if (psl->match >= minMatchShown) slAddHead(&pslList, psl); } lineFileClose(&lf); if (pslList == NULL && !jsonOut) { printf("<table><tr><td><hr>Sorry, no matches found"); if (!allResults) printf(" (with a score of at least %d)", minMatchShown); printf("<hr><td></tr></table>\n"); return; } pslSortListByVar(&pslList, sort); -if(feelingLucky) +if (feelingLucky && autoBigPsl) + { // ignore and pass-through to hyperlink. + pslOut = FALSE; + jsonOut = FALSE; + } + + +if(feelingLucky && ! autoBigPsl) // IDEA test pslOut and jsonOut { /* If we found something jump browser to there. */ if(slCount(pslList) > 0) printLuckyRedirect(browserUrl, pslList, database, pslName, faName, uiState, unhideTrack); /* Otherwise call ourselves again not feeling lucky to print empty results. */ else { cartWebStart(cart, trackHubSkipHubName(database), "%s (%s) BLAT Results", trackHubSkipHubName(organism), trackHubSkipHubName(database)); showAliPlaces(pslName, faName, customText, database, qType, tType, organism, FALSE); cartWebEnd(); } } else if (pslOut) { @@ -600,31 +619,225 @@ printf("</PRE></TT>"); } else if (jsonOut) { webStartText(); pslWriteAllJson(pslList, stdout, database, TRUE); exit(0); } else // hyperlink { printf("<H2>BLAT Search Results</H2>"); char* posStr = cartOptionalString(cart, "position"); if (posStr != NULL) printf("<P>Go back to <A HREF=\"%s\">%s</A> on the Genome Browser.</P>\n", browserUrl, posStr); - if (useBigPsl) + if (autoBigPsl) + { + char *trackName = NULL; + char *trackDescription = NULL; + getCustomName(database, cart, pslList, &trackName, &trackDescription); + + psl = pslList; + char item[1024]; + safef(item, sizeof item, "%s %s %s", pslName,faName,psl->qName); + + struct dyString *url = dyStringNew(256); + dyStringPrintf(url, "http%s://%s", sameString(getenv("HTTPS"), "on") ? "s" : "", getenv("HTTP_HOST")); + dyStringPrintf(url, "%s", hgcUrl+2); + dyStringPrintf(url, "?o=%d", psl->tStart); + dyStringPrintf(url, "&t=%d", psl->tEnd); + dyStringPrintf(url, "&g=%s", "buildBigPsl"); + dyStringPrintf(url, "&i=%s", cgiEncode(item)); + dyStringPrintf(url, "&c=%s", cgiEncode(psl->tName)); + dyStringPrintf(url, "&l=%d", psl->tStart); + dyStringPrintf(url, "&r=%d", psl->tEnd); + dyStringPrintf(url, "&%s=%s", cartSessionVarName(), cartSessionId(cart)); + dyStringPrintf(url, "&Submit=%s", cgiEncode("Create a stable custom track with these results")); + if (pslIsProtein(psl)) + dyStringPrintf(url, "&isProt=on"); + + // We will add them to the url later at runtime in js and ajax. + //dyStringPrintf(url, "&trackName=%s", cgiEncode(trackName)); + //dyStringPrintf(url, "&trackDescription=%s", cgiEncode(trackDescription)); + + //warn("GALT DEBUG hgsid = [%s]", cartSessionId(cart)); + + //warn("GALT DEBUG url = [%s]", url->string); + + jsInlineF( + "var luckyLocation = '';\n" + ); + if(feelingLucky) + { + /* If we found something jump browser to there. */ + if(slCount(pslList) > 0) + printLuckyRedirect(browserUrl, pslList, database, pslName, faName, uiState, unhideTrack); + } + + // RENAME BLAT CT FORM + // new re-submit code with new trackname and decription + if (!feelingLucky) // TODO GALT may revisit this condition + { + printf("<div id=renameFormItem style='display: none'>\n"); + printf("<FORM ACTION=>\n"); + printf("<INPUT TYPE=SUBMIT NAME=Submit id='showRenameForm' VALUE=\"Rename Custom Track\">\n"); + printf("</FORM>\n"); + printf("</div>\n"); + + printf("<div id=renameForm style='display: none'>\n"); + char *hgcUrl = hgcName(); + printf( "<DIV STYLE=\"display:block;\"><FORM ACTION=\"%s\" METHOD=\"%s\" NAME=\"customTrackForm\">\n", hgcUrl,cartUsualString(cart, "formMethod", "POST")); + + printf("<TABLE><TR><TD>Custom track name: "); + cgiMakeTextVar( "trackName", "FAKETRACKNAME", 30); // TODO GALT track name or shortLabel + printf("</TD></TR>"); + + printf("<TR><TD> Custom track description: "); + cgiMakeTextVar( "trackDescription", "FAKETRACKDESCRIPTION",50); // track description or longLabel + printf("</TD></TR>"); + + // remove the current blat ct so we can create a new one. + cgiMakeHiddenVar(CT_DO_REMOVE_VAR, "Remove Custom track"); + cgiMakeHiddenVar(CT_SELECTED_TABLE_VAR, "FAKETRACKNAME"); + + printf("<TR><TD><INPUT TYPE=SUBMIT NAME=Submit id=submitTrackNameDescr VALUE=\"Submit\">\n"); + puts("</TD></TR>"); + printf("</TABLE></FORM></DIV>"); + + printf("</div>\n"); + } + + if (!feelingLucky) // TODO GALT may revisit this condition + { + // REMOVE CT BUTTON FORM. + printf("<div id=deleteCtForm style='display: none'>\n"); + printf("<FORM ACTION=\"%s?hgsid=%s&db=%s\" NAME=\"MAIN_FORM\" METHOD=%s>\n\n", + hgTracksName(), cartSessionId(cart), database, cartUsualString(cart, "formMethod", "POST")); + cartSaveSession(cart); + cgiMakeButton(CT_DO_REMOVE_VAR, "Delete Custom Track"); + cgiMakeHiddenVar(CT_SELECTED_TABLE_VAR, "FAKETRACKNAME"); // TODO update js to populate this with the current ct ct_blat variable. + printf("</FORM>\n"); + printf("</div>\n"); + } + + + + //if (feelingLucky) + //warn("GALT DEBUG feelingLucky and autoBigPsl"); // REMOVE + + + jsInlineF( + + "var ct_blat = '';\n" + "\n" + "function buildBigPslCtSuccess (content, status)\n" + "{ // Finishes the succesful creation of blat ct bigPsl. Called by ajax return.\n" + " // saves the ct name so it can be used later for rename or delete.\n" + "//window.alert('status='+status); // debug remove\n" + "//window.alert('content='+content); // debug remove\n" + "\n" + "var matchWord = '&table=';\n" + "var ct_blatPos = content.indexOf(matchWord) + matchWord.length;\n" + "\n" + "if (ct_blatPos >= 0)\n" + " {\n" + " var ct_blatPosEnd = content.indexOf('\"', ct_blatPos);\n" + " ct_blat = content.slice(ct_blatPos, ct_blatPosEnd);\n" + " //window.alert('DEBUG GALT TEST ajax success hgc buildBigPsl called. ct_blat='+ct_blat);\n" + " if (luckyLocation == '')\n" + " {\n" + " $('input[name=\""CT_SELECTED_TABLE_VAR"\"]')[0].value = ct_blat;\n" + " $('input[name=\""CT_SELECTED_TABLE_VAR"\"]')[1].value = ct_blat;\n" + " }\n" + " }\n" + "}\n" + "\n" + "function buildBigPslCt (url, trackName, trackDescription)\n" + "{ // call hgc to buildBigPsl from blat result.\n" + "\n" + "var cgiVars = 'trackName='+encodeURIComponent(trackName)+'&trackDescription='+encodeURIComponent(trackDescription);\n" + "if (!ct_blat !== '')\n" + " {\n" + " cgiVars += '&"CT_DO_REMOVE_VAR"='+encodeURIComponent('Remove Custom Track');\n" + " cgiVars += '&"CT_SELECTED_TABLE_VAR"='+encodeURIComponent(ct_blat);\n" + " }\n" + "\n" + "$.ajax({\n" + " type: 'GET',\n" + " url: url,\n" + " data: cgiVars,\n" + " dataType: 'html',\n" + " trueSuccess: buildBigPslCtSuccess,\n" + " success: catchErrorOrDispatch,\n" + " error: errorHandler,\n" + " cache: false,\n" + " async: false\n" + " });\n" + "}\n" + "\n" + "var url='%s';\n" + "var trackName='%s';\n" + "var trackDescription='%s';\n" + "$(document).ready(function() {\n" + "//window.alert('DEBUG GALT TEST');\n" + "//window.alert('url='+url+' trackName='+trackName+' trackDescription='+trackDescription);\n" + "\n" + "buildBigPslCt(url, trackName, trackDescription);\n" + "//window.alert('result ct_blat = '+ ct_blat);\n" + "if (luckyLocation !== '')\n" + " {\n" + " //window.alert('luckyLocation = ' + luckyLocation);\n" + " location.replace(luckyLocation);\n" + " }\n" + "else\n" + " {\n" + " $('#renameFormItem')[0].style.display = 'block';\n" + " $('#deleteCtForm')[0].style.display = 'block';\n" + " }\n" + "});\n", url->string, trackName, trackDescription); + + + // RENAME CT JS CODE + if (!feelingLucky) + jsInline("$('#showRenameForm').click(function(){\n" + " $('#renameForm')[0].style.display = 'block';\n" + " $('#renameFormItem')[0].style.display = 'none';\n" + " $('#showRenameForm')[0].style.display = 'none';\n" + " $('input[name=\"trackName\"]')[0].value = trackName;\n" + " $('input[name=\"trackDescription\"]')[0].value = trackDescription;\n" + "return false;\n" + "});\n"); + + // RENAME CT JS CODE + if (!feelingLucky) + jsInline("$('#submitTrackNameDescr').click(function(){\n" + "//window.alert('DEBUG GALT TEST');\n" + " $('#renameForm')[0].style.display = 'none';\n" + " $('#renameFormItem')[0].style.display = 'block';\n" + " $('#showRenameForm')[0].style.display = 'block';\n" + " trackName = $('input[name=\"trackName\"]')[0].value;\n" + " trackDescription = $('input[name=\"trackDescription\"]')[0].value;\n" + "buildBigPslCt(url, trackName, trackDescription);\n" + "return false;\n" + "});\n"); + + dyStringFree(&url); + + + } + else if (useBigPsl) { char *trackName = NULL; char *trackDescription = NULL; getCustomName(database, cart, pslList, &trackName, &trackDescription); psl = pslList; printf( "<DIV STYLE=\"display:block;\"><FORM ACTION=\"%s\" METHOD=\"%s\" NAME=\"customTrackForm\">\n", hgcUrl,cartUsualString(cart, "formMethod", "POST")); printf("<INPUT TYPE=\"hidden\" name=\"o\" value=\"%d\" />\n",psl->tStart); printf("<INPUT TYPE=\"hidden\" name=\"t\" value=\"%d\" />\n",psl->tEnd); printf("<INPUT TYPE=\"hidden\" name=\"g\" value=\"%s\" />\n","buildBigPsl"); printf("<INPUT TYPE=\"hidden\" name=\"i\" value=\"%s %s %s\" />\n",pslName,faName,psl->qName); printf("<INPUT TYPE=\"hidden\" name=\"c\" value=\"%s\" />\n",psl->tName); printf("<INPUT TYPE=\"hidden\" name=\"l\" value=\"%d\" />\n",psl->tStart); printf("<INPUT TYPE=\"hidden\" name=\"r\" value=\"%d\" />\n",psl->tEnd); printf("<INPUT TYPE=\"hidden\" name=\"%s\" value=\"%s\" />\n", cartSessionVarName(), cartSessionId(cart)); if (pslIsProtein(psl)) @@ -674,48 +887,62 @@ printf(" STRAND START END SPAN\n"); printf("----------------------------------------------------------------------------------------------------------"); repeatCharOut(stdout, '-', maxQChromNameSize - 5); repeatCharOut(stdout, '-', maxTChromNameSize - 5); printf("\n"); for (psl = pslList; psl != NULL; psl = psl->next) { char *browserHelp = "Open a Genome Browser showing this match"; char *helpText = "Open a Genome Browser with the BLAT results, but in a new internet browser tab"; // XX putting SVG into C code like this is ugly. define somewhere? maybe have globals for these? char *icon = "<svg style='height:10px; padding-left:2px' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d='M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z'/></svg>"; + if (customText) { printf("<A TITLE='%s' HREF=\"%s?position=%s:%d-%d&db=%s&hgt.customText=%s&%s%s\">browser</A> ", browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, customText, uiState, unhideTrack); printf("<A TITLE='%s' TARGET=_BLANK HREF=\"%s?position=%s:%d-%d&db=%s&hgt.customText=%s&%s\">new tab%s</A> ", helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, customText, unhideTrack, icon); } + else + { + if (autoBigPsl) + { + // skip ss variable + printf("<A TITLE='%s' HREF=\"%s?position=%s:%d-%d&db=%s&%s%s\">browser</A> ", + browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + uiState, unhideTrack); + printf("<A TITLE='%s' TARGET=_BLANK HREF=\"%s?position=%s:%d-%d&db=%s&%s\">new tab%s</A> ", + helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, + unhideTrack, icon); + } else { printf("<A TITLE='%s' HREF=\"%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s%s\">browser</A> ", browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, pslName, faName, uiState, unhideTrack); printf("<A TITLE='%s' TARGET=_BLANK HREF=\"%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s\">new tab%s</A> ", helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, pslName, faName, unhideTrack, icon); } + } printf("<A title='Show query sequence, genome hit and sequence alignment' " "HREF=\"%s?o=%d&g=htcUserAli&i=%s+%s+%s&c=%s&l=%d&r=%d&db=%s&%s\">", hgcUrl, psl->tStart, pslName, cgiEncode(faName), psl->qName, psl->tName, psl->tStart, psl->tEnd, database, uiState); printf("details</A> "); printf("%s",psl->qName); spaceOut(stdout, maxQChromNameSize - strlen(psl->qName)); printf(" %5d %5d %5d %5d %5.1f%% ", pslScore(psl), psl->qStart+1, psl->qEnd, psl->qSize, 100.0 - pslCalcMilliBad(psl, TRUE) * 0.1); char *displayChromName = chromAliasGetDisplayChrom(database, cart, psl->tName); printf("%s",displayChromName); spaceOut(stdout, maxTChromNameSize - strlen(displayChromName)); printf(" %-2s %9d %9d %6d", @@ -1476,45 +1703,47 @@ char *genome, *db; char *type = cgiString("type"); char *seqLetters = cloneString(userSeq); struct serverTable *serve; struct gfConnection *conn = NULL; int oneSize, totalSize = 0, seqCount = 0; boolean isTx = FALSE; boolean isTxTx = FALSE; boolean txTxBoth = FALSE; struct gfOutput *gvo; boolean qIsProt = FALSE; enum gfType qType, tType; struct hash *tFileCache = gfFileCacheNew(); // allGenomes ignores I'm Feeling Lucky for simplicity boolean feelingLucky = cgiBoolean("Lucky") && !allGenomes; + char *xType = NULL; if (allGenomes) { db = database; genome = organism; } else getDbAndGenome(cart, &db, &genome, oldVars); char *output = cgiOptionalString("output"); boolean isJson= sameWordOk(output, "json"); boolean isPslRaw= sameWordOk(output, "pslRaw"); -if (!feelingLucky && !allGenomes && !isJson && !isPslRaw) + +if ((!feelingLucky && !allGenomes && !isJson && !isPslRaw) || (autoBigPsl && feelingLucky)) cartWebStart(cart, db, "%s (%s) BLAT Results", trackHubSkipHubName(organism), trackHubSkipHubName(db)); seqList = faSeqListFromMemTextRaw(seqLetters); /* Load user sequence and figure out if it is DNA or protein. */ if (sameWord(type, "DNA")) { isTx = FALSE; xType = "dna"; } else if (sameWord(type, "translated RNA")) { isTx = TRUE; isTxTx = TRUE; xType = "rnax"; @@ -1790,31 +2019,31 @@ { gfAlignStrand(conn, serve->nibDir, seq, TRUE, minMatchShown, tFileCache, gvo); } } gfOutputQuery(gvo, f); ++seqNumber; } carefulClose(&f); if (!allGenomes) { showAliPlaces(pslTn.forCgi, faTn.forCgi, NULL, serve->db, qType, tType, organism, feelingLucky); } -if(!feelingLucky && !allGenomes) +if ((!feelingLucky && !allGenomes) || (autoBigPsl && feelingLucky)) cartWebEnd(); gfFileCacheFree(&tFileCache); } void askForSeq(char *organism, char *db) /* Put up a little form that asks for sequence. * Call self.... */ { /* ignore struct serverTable* return, but can error out if not found */ findServer(db, FALSE); /* JavaScript to update form when org changes */ char *onChangeText = "" "document.mainForm.changeInfo.value='orgChange';" @@ -2410,22 +2639,24 @@ } /* Null terminated list of CGI Variables we don't want to save * permanently. */ char *excludeVars[] = {"Submit", "submit", "Clear", "Lucky", "type", "userSeq", "seqFile", "showPage", "changeInfo", NULL}; int main(int argc, char *argv[]) /* Process command line. */ { enteredMainTime = clock1000(); /* 0, 0, == use default 10 second for warning, 20 second for immediate exit */ issueBotWarning = earlyBotCheck(enteredMainTime, "hgBlat", delayFraction, 0, 0, "html"); oldVars = hashNew(10); cgiSpoof(&argc, argv); +autoBigPsl = cfgOptionBooleanDefault("autoBlatBigPsl", autoBigPsl); + /* org has precedence over db when changeInfo='orgChange' */ cartEmptyShell(doMiddle, hUserCookie(), excludeVars, oldVars); cgiExitTime("hgBlat", enteredMainTime); return 0; }