Table-Renderer: Render-Funktionen fuer Query-Ergebnisse

Tabelle, Timeline, Summary, Metric, Liste, Heatmap sowie
Markdown-Export und Wertformatierung (Timestamps, Zahlen).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-12 11:02:45 +01:00
parent 00446c4227
commit b2fc5b8f6b

125
src/viz/table-renderer.ts Normal file
View file

@ -0,0 +1,125 @@
// ---------------------------------------------------------------------------
// Table
// ---------------------------------------------------------------------------
export function renderTable(el: HTMLElement, rows: Record<string, unknown>[], 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<string, unknown>[]): 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<string, unknown>[]): 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<string, unknown>[]): 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<string, unknown>[]): 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<string, unknown>[]): 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, unknown>[]): 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);
}