"use strict"; /** * UI Logic for Max-Outlier Visualization * Handles rendering of table, chart, and filter interactions. * Relies on api.js for data fetching. */ import { fetchMaxOutlierResults } from './api.js'; const ui = (() => { const tableContainer = document.getElementById('outlier-table'); const chartContainer = document.getElementById('outlier-chart'); const filters = document.querySelectorAll('.filter-input'); /** * Render results table from API data. * @param {Array} data */ const renderTable = (data) => { if (!tableContainer) return; const table = document.createElement('table'); table.className = 'outlier__table'; const header = document.createElement('thead'); header.innerHTML = ` Run ID Parallelism Stratum Δt p95 p99 Retry Status `; table.appendChild(header); const body = document.createElement('tbody'); data.forEach((row) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${row.run_id ?? '-'} ${row.parallelism ?? '-'} ${row.stratum ?? '-'} ${row.delta_t ?? '-'} ${row.p95 ?? '-'} ${row.p99 ?? '-'} ${row.retry_status ?? '-'} `; body.appendChild(tr); }); table.appendChild(body); tableContainer.innerHTML = ''; tableContainer.appendChild(table); }; /** * Render chart for p95/p99 visualization. * @param {Array} data */ const renderChart = (data) => { if (!chartContainer) return; const ctx = chartContainer.getContext('2d'); if (!ctx) return; const labels = data.map((d) => `${d.run_id}`); const p95Values = data.map((d) => d.p95); const p99Values = data.map((d) => d.p99); if (chartContainer.chartInstance) { chartContainer.chartInstance.destroy(); } // Basic chart setup using Chart.js style interface if loaded chartContainer.chartInstance = new Chart(ctx, { type: 'line', data: { labels, datasets: [ { label: 'p95', data: p95Values, borderColor: 'var(--primary)', backgroundColor: 'rgba(0, 128, 255, 0.2)', fill: true, tension: 0.2, }, { label: 'p99', data: p99Values, borderColor: 'var(--accent)', backgroundColor: 'rgba(255, 64, 64, 0.2)', fill: true, tension: 0.2, }, ], }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { title: { display: true, text: 'Run ID' } }, y: { title: { display: true, text: 'Metric Value' } } }, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false } } } }); }; /** * Handle filter input change, fetch new data and re-render views. * @param {Event} e */ const handleFilterChange = async (e) => { e?.preventDefault(); const params = {}; filters.forEach((input) => { if (input.value.trim()) { params[input.name] = input.value.trim(); } }); try { const results = await fetchMaxOutlierResults(params); renderTable(results); renderChart(results); } catch (err) { console.error('Failed to update data:', err); } }; // Binding filter events on page load const bindFilterEvents = () => { filters.forEach((input) => { input.addEventListener('change', handleFilterChange); }); }; // Public API return { renderTable, renderChart, handleFilterChange, bindFilterEvents }; })(); export default ui;