sachin1801 commited on
Commit
78ccf41
·
1 Parent(s): 2fc42e1

refactor: replace batch result modal with full results page navigation

Browse files

- Change row click to navigate to /batch/{job_id}/sequence/{index}
- Remove unused modal HTML from batch_result.html
- Remove modal-related JS functions (showDetail, closeDetailModal, interpretPsi, createForcePlot)
- Update cache-busting version to v=3

webapp/static/js/batch-result.js CHANGED
@@ -20,12 +20,6 @@ const jobTitleEl = document.getElementById('job-title');
20
  const resultsTableBody = document.getElementById('results-table-body');
21
  const searchInput = document.getElementById('search-results');
22
 
23
- // Stats elements
24
- const statTotal = document.getElementById('stat-total');
25
- const statSuccess = document.getElementById('stat-success');
26
- const statInvalid = document.getElementById('stat-invalid');
27
- const statAvgPsi = document.getElementById('stat-avg-psi');
28
-
29
  // Pagination elements
30
  const pageStart = document.getElementById('page-start');
31
  const pageEnd = document.getElementById('page-end');
@@ -148,9 +142,6 @@ async function loadResults() {
148
 
149
  const data = await response.json();
150
 
151
- // Update stats
152
- updateStats(data);
153
-
154
  // Update table
155
  totalResults = data.total;
156
  totalPages = data.total_pages;
@@ -167,21 +158,6 @@ async function loadResults() {
167
  }
168
  }
169
 
170
- /**
171
- * Update stats display
172
- */
173
- function updateStats(data) {
174
- statTotal.textContent = data.total_sequences || 0;
175
- statSuccess.textContent = data.successful_count || 0;
176
- statInvalid.textContent = data.invalid_count || 0;
177
-
178
- if (data.average_psi !== null && data.average_psi !== undefined) {
179
- statAvgPsi.textContent = data.average_psi.toFixed(3);
180
- } else {
181
- statAvgPsi.textContent = '-';
182
- }
183
- }
184
-
185
  /**
186
  * Render results table
187
  */
@@ -191,7 +167,9 @@ function renderResults(results) {
191
  for (const result of results) {
192
  const row = document.createElement('tr');
193
  row.className = 'hover:bg-gray-50 cursor-pointer';
194
- row.onclick = () => showDetail(result.index);
 
 
195
 
196
  const statusBadge = getStatusBadge(result.status);
197
  const psiDisplay = result.status === 'success' && result.psi !== null
@@ -320,228 +298,6 @@ function renderPagination() {
320
  pageButtons.appendChild(nextBtn);
321
  }
322
 
323
- /**
324
- * Show sequence detail modal
325
- */
326
- async function showDetail(index) {
327
- const modal = document.getElementById('detail-modal');
328
- const detailTitle = document.getElementById('detail-title');
329
- const detailContent = document.getElementById('detail-content');
330
- const detailLoading = document.getElementById('detail-loading');
331
-
332
- // Show modal with loading
333
- modal.classList.remove('hidden');
334
- detailLoading.classList.remove('hidden');
335
- detailContent.innerHTML = '';
336
- detailContent.appendChild(detailLoading);
337
-
338
- try {
339
- const response = await fetch(`/api/batch/${jobId}/sequence/${index}`, {
340
- cache: 'no-store'
341
- });
342
-
343
- if (!response.ok) {
344
- const error = await response.json();
345
- throw new Error(error.detail || 'Failed to load sequence details');
346
- }
347
-
348
- const data = await response.json();
349
- detailLoading.classList.add('hidden');
350
-
351
- // Update title
352
- detailTitle.textContent = data.name || `Sequence ${index + 1}`;
353
-
354
- // Build detail content
355
- let html = '';
356
-
357
- if (data.status === 'invalid') {
358
- // Show invalid sequence info
359
- html = `
360
- <div class="bg-red-50 border border-red-200 rounded-lg p-4 text-center">
361
- <svg class="h-12 w-12 text-red-400 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
362
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
363
- </svg>
364
- <h4 class="mt-4 text-lg font-medium text-red-800">Invalid Sequence</h4>
365
- <p class="mt-2 text-red-700">${escapeHtml(data.validation_error || 'Sequence validation failed')}</p>
366
- </div>
367
- <div class="bg-gray-50 rounded-lg p-4">
368
- <h4 class="text-sm font-medium text-gray-500 uppercase mb-2">Sequence</h4>
369
- <p class="font-mono text-sm text-gray-900 break-all">${escapeHtml(data.sequence)}</p>
370
- </div>
371
- `;
372
- } else {
373
- // Show successful prediction details
374
- const psi = data.psi;
375
- const interp = interpretPsi(psi);
376
-
377
- html = `
378
- <!-- PSI Value -->
379
- <div class="rounded-lg p-6 text-center ${interp.colorClass}">
380
- <p class="text-sm font-medium text-gray-500 uppercase tracking-wide">Predicted PSI</p>
381
- <p class="mt-2 text-4xl font-bold ${interp.textClass}">${psi.toFixed(3)}</p>
382
- <p class="mt-2 text-sm ${interp.textClass}">${interp.text}: ${interp.description}</p>
383
- </div>
384
-
385
- <!-- Info Grid -->
386
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
387
- <div class="bg-gray-50 rounded-lg p-4">
388
- <h4 class="text-sm font-medium text-gray-500 uppercase mb-2">RNA Secondary Structure</h4>
389
- <p class="font-mono text-sm text-gray-900 break-all">${escapeHtml(data.structure || 'N/A')}</p>
390
- </div>
391
- <div class="bg-gray-50 rounded-lg p-4">
392
- <h4 class="text-sm font-medium text-gray-500 uppercase mb-2">Minimum Free Energy</h4>
393
- <p class="text-2xl font-bold text-gray-900">${data.mfe ? data.mfe.toFixed(2) : 'N/A'} <span class="text-base font-normal text-gray-500">kcal/mol</span></p>
394
- </div>
395
- </div>
396
-
397
- <!-- Sequence -->
398
- <div class="bg-gray-50 rounded-lg p-4">
399
- <h4 class="text-sm font-medium text-gray-500 uppercase mb-2">Sequence</h4>
400
- <p class="font-mono text-sm text-gray-900 break-all">${escapeHtml(data.sequence)}</p>
401
- </div>
402
-
403
- <!-- Force Plot -->
404
- <div class="bg-white rounded-lg border border-gray-200 p-4">
405
- <h4 class="text-sm font-medium text-gray-500 uppercase mb-4">
406
- Position-wise Contribution to PSI
407
- <span class="ml-2 text-gray-400 font-normal normal-case">
408
- (green = promotes inclusion, red = promotes skipping)
409
- </span>
410
- </h4>
411
- <div id="detail-force-plot" class="w-full" style="height: 350px;"></div>
412
- </div>
413
- `;
414
- }
415
-
416
- detailContent.innerHTML = html;
417
-
418
- // Create force plot if we have data
419
- if (data.status === 'success' && data.force_plot_data && data.force_plot_data.length > 0) {
420
- setTimeout(() => createForcePlot('detail-force-plot', data.force_plot_data), 100);
421
- }
422
-
423
- } catch (error) {
424
- console.error('Error loading detail:', error);
425
- detailLoading.classList.add('hidden');
426
- detailContent.innerHTML = `
427
- <div class="bg-red-50 border border-red-200 rounded-lg p-4 text-center">
428
- <p class="text-red-700">${escapeHtml(error.message)}</p>
429
- </div>
430
- `;
431
- }
432
- }
433
-
434
- /**
435
- * Close detail modal
436
- */
437
- function closeDetailModal() {
438
- document.getElementById('detail-modal').classList.add('hidden');
439
- }
440
-
441
- // Close modal on escape key
442
- document.addEventListener('keydown', (e) => {
443
- if (e.key === 'Escape') {
444
- closeDetailModal();
445
- }
446
- });
447
-
448
- // Close modal on backdrop click
449
- document.getElementById('detail-modal').addEventListener('click', (e) => {
450
- if (e.target === e.currentTarget) {
451
- closeDetailModal();
452
- }
453
- });
454
-
455
- /**
456
- * Interpret PSI value
457
- */
458
- function interpretPsi(psi) {
459
- if (psi >= 0.8) {
460
- return {
461
- text: 'High Inclusion',
462
- description: 'This exon is predicted to be included in most transcripts.',
463
- colorClass: 'bg-green-50 border border-green-200',
464
- textClass: 'text-green-600'
465
- };
466
- } else if (psi >= 0.3) {
467
- return {
468
- text: 'Variable/Regulated',
469
- description: 'This exon shows intermediate inclusion, suggesting regulation.',
470
- colorClass: 'bg-yellow-50 border border-yellow-200',
471
- textClass: 'text-yellow-600'
472
- };
473
- } else {
474
- return {
475
- text: 'High Skipping',
476
- description: 'This exon is predicted to be skipped in most transcripts.',
477
- colorClass: 'bg-red-50 border border-red-200',
478
- textClass: 'text-red-600'
479
- };
480
- }
481
- }
482
-
483
- /**
484
- * Create force plot using Plotly
485
- */
486
- function createForcePlot(elementId, forceData) {
487
- const element = document.getElementById(elementId);
488
- if (!element) return;
489
-
490
- const positions = Array.from({ length: forceData.length }, (_, i) => i + 1);
491
- const colors = forceData.map(v => v >= 0 ? 'rgba(34, 197, 94, 0.8)' : 'rgba(239, 68, 68, 0.8)');
492
-
493
- const trace = {
494
- x: positions,
495
- y: forceData,
496
- type: 'bar',
497
- marker: { color: colors },
498
- hovertemplate: 'Position %{x}<br>Contribution: %{y:.4f}<extra></extra>'
499
- };
500
-
501
- const layout = {
502
- margin: { t: 20, r: 20, b: 50, l: 50 },
503
- xaxis: {
504
- title: { text: 'Position', font: { size: 11 } },
505
- tickmode: 'linear',
506
- dtick: 10,
507
- range: [0, 91]
508
- },
509
- yaxis: {
510
- title: { text: 'Contribution to PSI', font: { size: 11 } },
511
- zeroline: true,
512
- zerolinecolor: '#888',
513
- zerolinewidth: 1
514
- },
515
- shapes: [{
516
- type: 'rect',
517
- xref: 'x',
518
- yref: 'paper',
519
- x0: 10.5,
520
- x1: 80.5,
521
- y0: 0,
522
- y1: 1,
523
- fillcolor: 'rgba(59, 130, 246, 0.05)',
524
- line: { width: 0 }
525
- }],
526
- annotations: [
527
- { x: 5, y: 1.05, xref: 'x', yref: 'paper', text: "5' flank", showarrow: false, font: { size: 9, color: '#888' } },
528
- { x: 45, y: 1.05, xref: 'x', yref: 'paper', text: 'Exon', showarrow: false, font: { size: 9, color: '#3b82f6' } },
529
- { x: 85, y: 1.05, xref: 'x', yref: 'paper', text: "3' flank", showarrow: false, font: { size: 9, color: '#888' } }
530
- ],
531
- paper_bgcolor: 'rgba(0,0,0,0)',
532
- plot_bgcolor: 'rgba(0,0,0,0)',
533
- font: { family: 'system-ui, -apple-system, sans-serif' }
534
- };
535
-
536
- const config = {
537
- responsive: true,
538
- displayModeBar: true,
539
- modeBarButtonsToRemove: ['lasso2d', 'select2d'],
540
- displaylogo: false
541
- };
542
-
543
- Plotly.newPlot(element, [trace], layout, config);
544
- }
545
 
546
  /**
547
  * Show error state
@@ -673,8 +429,6 @@ function restoreNameDisplay(parent, index, name) {
673
 
674
  // Make functions available globally
675
  window.copyLink = copyLink;
676
- window.closeDetailModal = closeDetailModal;
677
- window.showDetail = showDetail;
678
  window.startEditName = startEditName;
679
  window.handleEditKeydown = handleEditKeydown;
680
  window.handleEditBlur = handleEditBlur;
 
20
  const resultsTableBody = document.getElementById('results-table-body');
21
  const searchInput = document.getElementById('search-results');
22
 
 
 
 
 
 
 
23
  // Pagination elements
24
  const pageStart = document.getElementById('page-start');
25
  const pageEnd = document.getElementById('page-end');
 
142
 
143
  const data = await response.json();
144
 
 
 
 
145
  // Update table
146
  totalResults = data.total;
147
  totalPages = data.total_pages;
 
158
  }
159
  }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  /**
162
  * Render results table
163
  */
 
167
  for (const result of results) {
168
  const row = document.createElement('tr');
169
  row.className = 'hover:bg-gray-50 cursor-pointer';
170
+ row.onclick = () => {
171
+ window.location.href = `/batch/${jobId}/sequence/${result.index}`;
172
+ };
173
 
174
  const statusBadge = getStatusBadge(result.status);
175
  const psiDisplay = result.status === 'success' && result.psi !== null
 
298
  pageButtons.appendChild(nextBtn);
299
  }
300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  /**
303
  * Show error state
 
429
 
430
  // Make functions available globally
431
  window.copyLink = copyLink;
 
 
432
  window.startEditName = startEditName;
433
  window.handleEditKeydown = handleEditKeydown;
434
  window.handleEditBlur = handleEditBlur;
webapp/templates/batch_result.html CHANGED
@@ -48,26 +48,6 @@
48
 
49
  <!-- Results Container (hidden initially) -->
50
  <div id="results-container" class="hidden space-y-6">
51
- <!-- Summary Stats -->
52
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
53
- <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 text-center">
54
- <p class="text-sm font-medium text-gray-500">Total Sequences</p>
55
- <p id="stat-total" class="mt-1 text-2xl font-bold text-gray-900">0</p>
56
- </div>
57
- <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 text-center">
58
- <p class="text-sm font-medium text-gray-500">Successful</p>
59
- <p id="stat-success" class="mt-1 text-2xl font-bold text-green-600">0</p>
60
- </div>
61
- <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 text-center">
62
- <p class="text-sm font-medium text-gray-500">Invalid</p>
63
- <p id="stat-invalid" class="mt-1 text-2xl font-bold text-red-600">0</p>
64
- </div>
65
- <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 text-center">
66
- <p class="text-sm font-medium text-gray-500">Average PSI</p>
67
- <p id="stat-avg-psi" class="mt-1 text-2xl font-bold text-primary-600">-</p>
68
- </div>
69
- </div>
70
-
71
  <!-- Search and Export -->
72
  <div class="flex flex-wrap gap-4 items-center justify-between">
73
  <div class="flex-1 min-w-[200px] max-w-md">
@@ -139,34 +119,11 @@
139
  </div>
140
  </div>
141
 
142
- <!-- Detail Modal -->
143
- <div id="detail-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
144
- <div class="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto">
145
- <div class="sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center">
146
- <h3 id="detail-title" class="text-lg font-medium text-gray-900">Sequence Details</h3>
147
- <button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-500">
148
- <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
149
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
150
- </svg>
151
- </button>
152
- </div>
153
- <div id="detail-content" class="p-6 space-y-6">
154
- <!-- Loading spinner for detail -->
155
- <div id="detail-loading" class="text-center py-8">
156
- <svg class="animate-spin h-8 w-8 text-primary-600 mx-auto" fill="none" viewBox="0 0 24 24">
157
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
158
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
159
- </svg>
160
- </div>
161
- <!-- Detail will be populated here -->
162
- </div>
163
- </div>
164
- </div>
165
  {% endblock %}
166
 
167
  {% block scripts %}
168
  <script>
169
  const jobId = "{{ job_id }}";
170
  </script>
171
- <script src="/static/js/batch-result.js"></script>
172
  {% endblock %}
 
48
 
49
  <!-- Results Container (hidden initially) -->
50
  <div id="results-container" class="hidden space-y-6">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  <!-- Search and Export -->
52
  <div class="flex flex-wrap gap-4 items-center justify-between">
53
  <div class="flex-1 min-w-[200px] max-w-md">
 
119
  </div>
120
  </div>
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  {% endblock %}
123
 
124
  {% block scripts %}
125
  <script>
126
  const jobId = "{{ job_id }}";
127
  </script>
128
+ <script src="/static/js/batch-result.js?v=3"></script>
129
  {% endblock %}