83858083785a6b2b1f3c05f93e818e5a89318692
max
Tue Aug 25 01:42:34 2026 -0700
hgSession: show cytoBand and gene/locus annotations for each saved session; date-only Created/Last used columns with full timestamp on hover. Factor locusName lookup into reusable hLocusName/hLocusNameExpand in hdb.c and reuse from hgBlat. refs #38157
The Assembly column now shows band, gene/locus name and Mbp position (separated by whitespace, normal text size). hLocusNameExpand centralizes the ex:/in:/ig: and | to - expansion that hgBlat's results page did inline.
diff --git src/hg/js/hgSession.js src/hg/js/hgSession.js
index 090936a3cb4..cc2942efb23 100644
--- src/hg/js/hgSession.js
+++ src/hg/js/hgSession.js
@@ -46,35 +46,40 @@
return (typeof commify === 'function') ? commify(n) : String(n);
}
function sessRandomShareName() {
// Mirror the server's auto/anonymous share-name convention ("share_" + 8 URL-safe alphanumeric
// chars). hgSession.c's doSaveSessionJson generates the same style server-side for the top-right
// "Share a link"; we generate it here so the confirm dialog can show the name before saving.
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var s = '';
for (var i = 0; i < 8; i++) {
s += chars.charAt(Math.floor(Math.random() * chars.length));
}
return 'share_' + s;
}
-function sessCommifyPos(pos) {
- // Add thousands separators to each number in a position string ("chr7:155799529-155812871" ->
- // "chr7:155,799,529-155,812,871") using utils.js commify.
- if (typeof commify !== 'function') { return String(pos); }
- return String(pos).replace(/\d+/g, function(n) { return commify(n); });
+function sessMbpPos(pos) {
+ // Shorten a position to megabases to save horizontal space:
+ // "chr7:155799529-155812871" -> "chr7:155.80-155.81 Mbp". Falls back to the raw string
+ // if it doesn't parse.
+ var m = /^(.+):([0-9,]+)-([0-9,]+)$/.exec(String(pos));
+ if (!m) { return String(pos); }
+ var s = parseInt(m[2].replace(/,/g, ''), 10);
+ var e = parseInt(m[3].replace(/,/g, ''), 10);
+ if (isNaN(s) || isNaN(e)) { return String(pos); }
+ return m[1] + ':' + (s / 1e6).toFixed(2) + '-' + (e / 1e6).toFixed(2) + ' Mbp';
}
function sessMsg(text, cls) {
// Show a transient status line at the top of the app (cls: 'ok' | 'err' | '').
var el = document.getElementById('sessMsg');
if (!el) { return; }
el.className = 'sessMsg' + (cls ? ' ' + cls : '');
el.innerHTML = text ? sessEnc(text) : '';
}
// ---- AJAX ----------------------------------------------------------------
// Post an action to hgSession and get JSON back. Always carry the session id so the CGI loads the
// right cart. onOk(resp) is called for {success:true,...} or {name,url}; onErr(msg) for {error}.
function sessAjax(params, onOk, onErr) {
@@ -715,41 +720,49 @@
language: { searchPlaceholder: 'Search sessions', search: '', lengthMenu: 'Show _MENU_',
emptyTable: 'No saved sessions yet.' },
columns: [
{ title: '',
className: 'sessSelCol', data: null, orderable: false, searchable: false, visible: false,
render: function(d, type, row) {
return ''; } },
{ title: 'Session', className: 'sessNameCol', data: 'name',
render: function(d, type, row) {
return (type === 'display') ? sessNameCellHtml(row) : row.name; } },
{ title: 'Assembly', data: 'db',
render: function(d, type, row) {
if (type !== 'display') { return row.db || ''; }
var html = sessEnc(row.db || 'n/a');
- if (row.position) {
- html += ' ' + sessEnc(sessCommifyPos(row.position)) +
- '';
+ // Region: band, locus (gene) and the position shortened to Mbp, so it all fits.
+ var region = [];
+ if (row.band) { region.push(sessEnc(row.band)); }
+ if (row.locus) { region.push(sessEnc(row.locus)); }
+ if (row.position) { region.push(sessEnc(sessMbpPos(row.position))); }
+ if (region.length) {
+ html += ' ' + region.join(' ') + '';
}
return html; } },
{ title: 'Created', data: 'createdEpoch',
render: function(d, type, row) {
- return (type === 'display') ? sessEnc(row.created) : row.createdEpoch; } },
+ return (type === 'display') ?
+ '' +
+ sessEnc(row.created) + '' : row.createdEpoch; } },
{ title: 'Last used', data: 'lastUseEpoch',
render: function(d, type, row) {
- return (type === 'display') ? sessEnc(row.lastUse) : row.lastUseEpoch; } },
+ return (type === 'display') ?
+ '' +
+ sessEnc(row.lastUseDate || row.lastUse) + '' : row.lastUseEpoch; } },
{ title: 'Views', data: 'useCount',
render: function(d, type) { return (type === 'display') ? sessNum(d) : d; } },
{ title: 'Actions', className: 'sessActionsCol', data: null, orderable: false,
searchable: false,
render: function(d, type, row) { return (type === 'display') ? sessActionsHtml(row) : ''; } }
]
});
// Delegate the row action buttons.
$('#sessionAppTable tbody').on('click', 'button[data-act]', function() {
var act = this.getAttribute('data-act');
var row = sessByEnc(this.getAttribute('data-enc'));
if (!row) { return; }
if (act === 'overwrite') { sessOpenOverwrite(row); }
else if (act === 'share') { sessOpenShare(row); }