// --------------------------------------------------------------------------- // Table // --------------------------------------------------------------------------- export function renderTable(el: HTMLElement, rows: Record[], columns?: string[]): void { const table = el.createEl('table', { cls: 'logfire-table' }); const allKeys = columns ?? Object.keys(rows[0]); const thead = table.createEl('thead'); const headerRow = thead.createEl('tr'); for (const key of allKeys) { headerRow.createEl('th', { text: key }); } const tbody = table.createEl('tbody'); for (const row of rows) { const tr = tbody.createEl('tr'); for (const key of allKeys) { tr.createEl('td', { text: formatValue(row[key]) }); } } } // --------------------------------------------------------------------------- // Timeline // --------------------------------------------------------------------------- export function renderTimeline(el: HTMLElement, rows: Record[]): void { const list = el.createEl('ul', { cls: 'logfire-timeline' }); for (const row of rows) { const ts = typeof row.timestamp === 'number' ? new Date(row.timestamp).toLocaleTimeString() : ''; const type = String(row.type ?? ''); const source = String(row.source ?? ''); list.createEl('li', { text: `${ts} ${type} ${source}` }); } } // --------------------------------------------------------------------------- // Summary // --------------------------------------------------------------------------- export function renderSummary(el: HTMLElement, rows: Record[]): void { const container = el.createDiv({ cls: 'logfire-summary' }); for (const row of rows) { for (const [key, value] of Object.entries(row)) { const line = container.createDiv(); line.createEl('strong', { text: `${key}: ` }); line.appendText(formatValue(value)); } } } // --------------------------------------------------------------------------- // Metric (single big number) // --------------------------------------------------------------------------- export function renderMetric(el: HTMLElement, rows: Record[]): void { const record = rows[0]; const values = Object.values(record); const value = values.length > 0 ? values[values.length - 1] : 0; const div = el.createEl('div', { text: formatValue(value), cls: 'logfire-metric', }); div.style.fontSize = '2em'; } // --------------------------------------------------------------------------- // List // --------------------------------------------------------------------------- export function renderList(el: HTMLElement, rows: Record[]): void { const list = el.createEl('ul', { cls: 'logfire-list' }); for (const row of rows) { const text = Object.values(row).map(formatValue).join(' | '); list.createEl('li', { text }); } } // --------------------------------------------------------------------------- // Heatmap (text-based bar chart) // --------------------------------------------------------------------------- export function renderHeatmap(el: HTMLElement, rows: Record[]): void { const container = el.createDiv({ cls: 'logfire-heatmap' }); for (const row of rows) { const group = String(row.group ?? ''); const count = Number(row.count ?? 0); const bar = '\u2588'.repeat(Math.min(count, 50)); container.createDiv({ text: `${group} ${bar} ${count}` }); } } // --------------------------------------------------------------------------- // Markdown table generation (for copy/export) // --------------------------------------------------------------------------- export function toMarkdownTable(keys: string[], rows: Record[]): string { const header = `| ${keys.join(' | ')} |`; const separator = `| ${keys.map(() => '---').join(' | ')} |`; const body = rows.map(row => `| ${keys.map(k => { const v = row[k]; return v === null || v === undefined ? '' : String(v); }).join(' | ')} |` ).join('\n'); return `${header}\n${separator}\n${body}`; } // --------------------------------------------------------------------------- // Value formatting // --------------------------------------------------------------------------- export function formatValue(value: unknown): string { if (value === null || value === undefined) return ''; if (typeof value === 'number') { if (value > 1e12) return new Date(value).toLocaleString(); return value.toLocaleString(); } return String(value); }