From 997ca4060c8c9674bbd15384cb30877a7eb7a02e Mon Sep 17 00:00:00 2001 From: CaptainJack2491 Date: Thu, 2 Apr 2026 17:37:11 +0100 Subject: feat(dashboard): enhance raw-dataset view with column toggles, truncation, and row inspector --- api/static/js/components/results.js | 213 ++++++++++++++++++++++++++++++++++-- api/static/js/ui.js | 13 ++- 2 files changed, 214 insertions(+), 12 deletions(-) (limited to 'api/static/js') diff --git a/api/static/js/components/results.js b/api/static/js/components/results.js index b143fa3..2ce177c 100644 --- a/api/static/js/components/results.js +++ b/api/static/js/components/results.js @@ -7,6 +7,8 @@ import { ui } from '../ui.js'; let activeCharts = []; let currentCsvData = null; // Store fetched data locally for quick filtering let currentCsvColumns = []; +let currentCsvDir = ""; // Directory of the active CSV +let currentVisibleColumns = []; // Track which columns are shown in Table let modalChart = null; export const results = { @@ -21,6 +23,25 @@ export const results = { ui.elements.chartModal.addEventListener('click', (e) => { if (e.target === ui.elements.chartModal) this.closeChartModal(); }); + + // Inspector Modal + ui.elements.closeInspectorBtn.addEventListener('click', () => this.closeInspector()); + ui.elements.inspectorModal.addEventListener('click', (e) => { + if (e.target === ui.elements.inspectorModal) this.closeInspector(); + }); + + // Column Toggle + ui.elements.btnToggleColumns.addEventListener('click', (e) => { + e.stopPropagation(); + const isVisible = ui.elements.columnDropdown.style.display === 'block'; + ui.elements.columnDropdown.style.display = isVisible ? 'none' : 'block'; + }); + + document.addEventListener('click', (e) => { + if (!ui.elements.columnDropdown.contains(e.target) && e.target !== ui.elements.btnToggleColumns) { + ui.elements.columnDropdown.style.display = 'none'; + } + }); await Promise.all([ this.updateCSVFileList(), @@ -112,6 +133,18 @@ export const results = { currentCsvData = content.data; currentCsvColumns = content.columns; + // Store directory of CSV for relative log path resolution + if (filePath.includes('/')) { + currentCsvDir = filePath.substring(0, filePath.lastIndexOf('/')) + '/'; + } else { + currentCsvDir = ""; + } + + // Default visibility: Hide very long or redundant columns initially + const hideByDefault = ['BLACKBOX_JUSTIFICATION', 'GLASSBOX_JUSTIFICATION', 'log_path', 'full_log', 'messages']; + currentVisibleColumns = currentCsvColumns.filter(c => !hideByDefault.includes(c)); + + this.renderColumnSelector(); this.populateFilters(); this.applyFilters(); // This draws charts and tables @@ -121,6 +154,48 @@ export const results = { } }, + renderColumnSelector() { + ui.elements.columnList.innerHTML = ` +
+ All + None +
+
+ ${currentCsvColumns.map(col => ` + + `).join('')} +
+ `; + + // Bind events + ui.elements.columnList.querySelectorAll('input').forEach(input => { + input.addEventListener('change', () => { + const col = input.value; + if (input.checked) { + if (!currentVisibleColumns.includes(col)) currentVisibleColumns.push(col); + } else { + currentVisibleColumns = currentVisibleColumns.filter(c => c !== col); + } + this.applyFilters(); + }); + }); + + document.getElementById('cols-all').addEventListener('click', () => { + currentVisibleColumns = [...currentCsvColumns]; + this.renderColumnSelector(); + this.applyFilters(); + }); + + document.getElementById('cols-none').addEventListener('click', () => { + currentVisibleColumns = []; + this.renderColumnSelector(); + this.applyFilters(); + }); + }, + populateFilters() { const models = new Set(); const scenarios = new Set(); @@ -175,6 +250,9 @@ export const results = { }); ui.elements.dataStats.textContent = `Showing ${filteredData.length} of ${currentCsvData.length} records`; + if (ui.elements.csvTableStats) { + ui.elements.csvTableStats.textContent = `Found ${filteredData.length} records`; + } this.renderTable(filteredData); this.renderCharts(filteredData); @@ -186,20 +264,133 @@ export const results = { return; } - let html = `
-
- - ${currentCsvColumns.map(c => ``).join('')} - - ${data.map(row => ` - ${currentCsvColumns.map(col => ``).join('')} - `).join('')} - -
${c}
${row[col] ?? ''}
-
+ const visibleCols = currentVisibleColumns; + + let html = `
+ + + + + ${visibleCols.map(c => ``).join('')} + + + + ${data.map((row, idx) => ` + + + ${visibleCols.map(col => { + const val = row[col] ?? ''; + const isLong = col.toLowerCase().includes('justification') || col.toLowerCase().includes('id'); + return ``; + }).join('')} + + `).join('')} + +
${c}
${val}
`; ui.elements.csvResults.innerHTML = html; + + // Bind Row Click + ui.elements.csvResults.querySelectorAll('tr[data-idx]').forEach(tr => { + tr.addEventListener('click', (e) => { + const idx = tr.dataset.idx; + this.showRowInspector(data[idx]); + }); + }); + + // Bind View Button (redundant but explicit) + ui.elements.csvResults.querySelectorAll('.btn-row-action').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const idx = btn.dataset.idx; + this.showRowInspector(data[idx]); + }); + }); + }, + + closeInspector() { + ui.elements.inspectorModal.style.display = 'none'; + ui.elements.inspectorContent.innerHTML = '
Loading details...
'; + }, + + async showRowInspector(rowData) { + ui.elements.inspectorModal.style.display = 'flex'; + + // Initial layout + ui.elements.inspectorContent.innerHTML = ` +
+ +
+
+

Raw Experiment Log

+ Checking for log file... +
+
+
Searching for log file associated with this run...
+
+
+
+ `; + + // Strategy to find the log: + // 1. rowData.log_path + // 2. rowData.RUN_ID + + let rawPath = rowData.log_path || rowData.RUN_ID || rowData.run_id; + if (!rawPath) { + document.getElementById('log-viewer').innerHTML = '
No log path metadata found in record.
'; + document.getElementById('log-status').textContent = `Missing log metadata.`; + return; + } + + // Normalize path: add .json if missing + let jsonPath = rawPath.endsWith('.json') ? rawPath : (rawPath + '.json'); + + // Trial paths: + const trials = [ + jsonPath, // Direct + currentCsvDir + jsonPath, // Relative to CSV (e.g. logs/full_experiment/ + path) + 'logs/' + jsonPath // Absolute under logs/ + ]; + + let loaded = false; + for (const trial of trials) { + if (loaded) break; + try { + document.getElementById('log-status').textContent = `Trying: ${trial}`; + const logData = await api.get(`/api/logs/browse?path=${encodeURIComponent(trial)}`); + document.getElementById('log-viewer').textContent = JSON.stringify(logData, null, 2); + document.getElementById('log-status').textContent = `Log loaded: ${trial}`; + document.getElementById('log-status').style.color = 'var(--color-success)'; + loaded = true; + } catch (err) { + console.log(`Failed trial: ${trial}`); + } + } + + if (!loaded) { + document.getElementById('log-viewer').innerHTML = ` +
+ Failed to load log file: API Error: 404
+
+ Paths tried:
+ ${trials.map(t => `- ${t}`).join('
')} +
+
`; + document.getElementById('log-status').textContent = `Error loading log.`; + document.getElementById('log-status').style.color = 'var(--color-danger)'; + } }, renderCharts(data) { diff --git a/api/static/js/ui.js b/api/static/js/ui.js index 2c402ab..27b187e 100644 --- a/api/static/js/ui.js +++ b/api/static/js/ui.js @@ -21,7 +21,18 @@ export const ui = { modalChartContainer: document.getElementById('modal-chart-container'), tabTriggers: document.querySelectorAll('.tab-trigger'), - tabPanels: document.querySelectorAll('.tab-panel') + tabPanels: document.querySelectorAll('.tab-panel'), + + // New CSV Table Elements + csvTableStats: document.getElementById('csv-table-stats'), + btnToggleColumns: document.getElementById('btn-toggle-columns'), + columnDropdown: document.getElementById('column-dropdown'), + columnList: document.getElementById('column-list'), + + // Inspector Modal + inspectorModal: document.getElementById('inspector-modal'), + closeInspectorBtn: document.getElementById('close-inspector-btn'), + inspectorContent: document.getElementById('inspector-content') }; }, -- cgit v1.2.3