1db5c89d97fdd3c3580c4ba9b18eba6b5029c97d
max
Thu Sep 17 05:09:56 2026 -0700
hgSearch: highlight MANE transcript(s) in RefSeq protein-position search results, refs #38285
#Preview2 week - bugs introduced now will need a build patch to fix
A gene-symbol + codon-range search (e.g. "BRCA1 100-200") maps to every
RefSeq isoform prediction sharing that genomic footprint, which can be
dozens of near-identical NP_ accessions with no indication of which one
is the clinically-relevant MANE transcript. Cross-reference the mane
bigGenePred track by genomic overlap + protein accession and call out
the MANE Select/Plus Clinical transcript(s) in their own section above
the rest, gated behind showManeInSearch (default off) in hg.conf.
diff --git src/hg/js/hgSearch.js src/hg/js/hgSearch.js
index e3fee756915..c775ea590b9 100644
--- src/hg/js/hgSearch.js
+++ src/hg/js/hgSearch.js
@@ -350,31 +350,31 @@
});
parentDiv.css('height', "auto");
}
function updateFilters(uiState) {
if (typeof uiState.categs !== "undefined") {
filtersToJstree();
makeCategoryTree();
}
}
function clearOldFacetCounts() {
$("[id*='extraInfo']").remove();
}
- function printMatches(list, matches, title, searchDesc, doShowMore) {
+ function printMatches(list, matches, title, searchDesc, doShowMore, maneSection) {
var printCount = 0;
_.each(matches, function(match, printCount) {
var position = match.position.split(':');
var url, matchTitle;
if (title === "helpDocs") {
url = position[0];
matchTitle = "" + position[1].replace(/_/g, " ") + "";
} else if (title === "publicHubs") {
var hubUrl = position[0] + ":" + position[1];
var dbName = position[2];
var track = position[3];
var hubShortLabel = position[4];
var hubLongLabel = position[5];
url = "hgTrackUi?hubUrl=" + hubUrl + "&g=" + track + "&db=" + dbName;
matchTitle = "" + hubShortLabel + "";
@@ -397,33 +397,40 @@
if (goToHgTracks) {
url = "hgTracks?db=" + db + "&" + hgTracksTitle + "=pack&position=" + match.position;
if (match.hgFindMatches) {
url += "&hgFind.matches=" + match.hgFindMatches;
}
if (match.extraSel) {
url += "&" + match.extraSel;
}
if (match.highlight) {
url += url[url.length-1] !== '&' ? '&' : '';
url += "addHighlight=" + encodeURIComponent(match.highlight);
}
} else {
url = "hgc?db=" + db + "&g=" + hgcTitle + "&i=" + match.position + "&c=0&o=0&l=0&r=0" ;
}
- matchTitle = match.posName;
- //if (match.canonical === true)
- matchTitle = "" + matchTitle + "";
+ if (maneSection && match.maneProtAcc) {
+ // match.posName can be a "/"-joined group of transcripts that all
+ // share this genomic footprint; show only the actual MANE one.
+ matchTitle = "" + match.maneProtAcc + "";
+ } else {
+ matchTitle = "" + match.posName + "";
+ }
+ if (maneSection && match.maneStatus) {
+ matchTitle = match.maneStatus + ": " + matchTitle;
+ }
}
if (printCount < 500) {
let newListObj = document.createElement("li");
let className = "searchResult ";
if (doShowMore || printCount + 1 > 10) {
className += title + "_hidden";
if (!doShowMore) {
newListObj.style.display = "none";
}
}
newListObj.className = className;
let a = document.createElement("a");
newListObj.appendChild(a);
a.href = url;
a.innerHTML = matchTitle; // need the bold in the title so use innerHTML here
@@ -507,39 +514,47 @@
function collapseNode() {
var toCollapse = this.parentNode.childNodes[3];
var isHidden = toCollapse.style.display === "none";
if (isHidden)
{
toCollapse.style = 'display:';
this.src = "../images/remove_sm.gif";
}
else
{
toCollapse.style = 'display: none';
this.src = "../images/add_sm.gif";
}
}
+ function nonManeMatches(matches) {
+ /* If any of matches are MANE transcripts, they get their own section elsewhere
+ * (see updateSearchResults), so exclude them here to avoid showing the same
+ * transcript twice. If none are MANE, return matches unchanged. */
+ let maneMatches = matches.filter(function(m) { return m.maneStatus; });
+ return maneMatches.length > 0 ? matches.filter(function(m) { return !m.maneStatus; }) : matches;
+ }
+
function printMoreResults(trackName, nodeAfter) {
/* Print the 11-500 result before nodeAfter */
let results = uiState.resultHash[trackName];
let title = results.name;
let searchDesc = results.description;
// show the 11-500th elements
// after this, only CSS is used to show hide them, since they are part of the
// page already
- printMatches(nodeAfter, results.matches.slice(10), title, searchDesc, true);
+ printMatches(nodeAfter, nonManeMatches(results.matches).slice(10), title, searchDesc, true);
}
function updateSearchResults(uiState) {
var parentDiv = $("#searchResults");
if (uiState && typeof uiState.search !== "undefined") {
$("#searchBarSearchString").val(uiState.search);
} else {
// back button all the way to the beginning
$("#searchBarSearchString").val("");
}
if (uiState && uiState.positionMatches && uiState.positionMatches.length > 0) {
// clear the old search results if there were any:
parentDiv.empty();
// create the elements that will hold results:
@@ -566,60 +581,83 @@
noLiStyle.value = "liNoStyle";
newListObj.setAttributeNode(noLiStyle);
let inp = document.createElement("input");
inp.type = "hidden";
inp.id = idKey + categoryCount;
inp.value = "0";
newListObj.appendChild(inp);
let ctrlImg = document.createElement("img");
ctrlImg.height = "18";
ctrlImg.width = "18";
ctrlImg.id = idKey + categoryCount + "_button";
ctrlImg.src = "../images/remove_sm.gif";
newListObj.appendChild(ctrlImg);
let descText = document.createTextNode(" " + searchDesc + ":");
newListObj.appendChild(descText);
+ // If any of the matches are MANE transcripts (RefSeq protein-position
+ // searches only), call them out in their own small section above the
+ // full list, since with many isoform predictions the MANE transcript(s)
+ // can otherwise be hard to spot.
+ let maneMatches = matches.filter(function(m) { return m.maneStatus; });
+ if (maneMatches.length > 0) {
+ let maneHeader = document.createElement("div");
+ maneHeader.className = "maneSectionHeader";
+ maneHeader.textContent = "MANE transcript" + (maneMatches.length > 1 ? "s" : "");
+ newListObj.appendChild(maneHeader);
+ let maneList = document.createElement("ul");
+ printMatches(maneList, maneMatches, title, searchDesc, false, true);
+ newListObj.appendChild(maneList);
+ }
+ // The rest of the matches, excluding any already shown above as MANE
+ // transcripts (if there were none, this is just the full match list).
+ let otherMatches = nonManeMatches(matches);
+ if (maneMatches.length > 0 && otherMatches.length > 0) {
+ let otherHeader = document.createElement("div");
+ otherHeader.className = "maneSectionHeader";
+ otherHeader.textContent = "Other transcripts";
+ newListObj.appendChild(otherHeader);
+ }
// Now loop through each actual hit on this table and unpack onto list
let subList = document.createElement("ul");
// only print the first 10 at first
- printMatches(subList, matches.slice(0,10), title, searchDesc, false);
- if (matches.length > 10) {
+ printMatches(subList, otherMatches.slice(0,10), title, searchDesc, false);
+ if (otherMatches.length > 10) {
let idStr = idKey + "_" + categoryCount;
let showMoreLi = document.createElement("li");
showMoreLi.id = idStr;
showMoreLi.classList.add("liNoStyle","searchResult");
let showMoreInp = document.createElement("input");
showMoreInp.type = "hidden";
showMoreInp.value = '0';
showMoreInp.id = showMoreLi.id + "showMore";
showMoreLi.appendChild(showMoreInp);
let showMoreImg = document.createElement("img");
showMoreImg.height = "18";
showMoreImg.width = "18";
showMoreImg.id = showMoreLi.id + "_showMoreButton";
showMoreImg.src = "../images/add_sm.gif";
showMoreLi.appendChild(showMoreImg);
let showMoreDiv = document.createElement("div");
showMoreDiv.id = idStr + "_showMoreDiv";
showMoreDiv.classList.add("showMoreDiv");
let showMoreA = document.createElement("a");
showMoreA.id = idStr + "_showMoreLink";
let newText = "";
- if (matches.length > 500) {
- newText = " Show 490 (out of " + (matches.length) + " total) more matches for " + searchDesc;
+ if (otherMatches.length > 500) {
+ newText = " Show 490 (out of " + (otherMatches.length) + " total) more matches for " + searchDesc;
} else {
- newText = " Show " + (matches.length - 10) + " more matches for " + searchDesc;
+ newText = " Show " + (otherMatches.length - 10) + " more matches for " + searchDesc;
}
showMoreA.textContent = newText;
showMoreDiv.appendChild(showMoreA);
showMoreLi.appendChild(showMoreDiv);
subList.appendChild(showMoreLi);
}
newListObj.append(subList);
newList.append(newListObj);
// make result list collapsible:
$('#'+idKey+categoryCount+"_button").click(collapseNode);
$('#'+idKey+"_" +categoryCount+"_showMoreButton").click(showMoreResults);
$('#'+idKey + "_" + categoryCount + "_showMoreLink").click(showMoreResults);
categoryCount += 1;
});