( data: Record<string, number>, color: string, maxItems = 6, fixedOrder?: string[], )
| 1822 | ] |
| 1823 | |
| 1824 | function generateBarChart( |
| 1825 | data: Record<string, number>, |
| 1826 | color: string, |
| 1827 | maxItems = 6, |
| 1828 | fixedOrder?: string[], |
| 1829 | ): string { |
| 1830 | let entries: [string, number][] |
| 1831 | |
| 1832 | if (fixedOrder) { |
| 1833 | // Use fixed order, only including items that exist in data |
| 1834 | entries = fixedOrder |
| 1835 | .filter(key => key in data && (data[key] ?? 0) > 0) |
| 1836 | .map(key => [key, data[key] ?? 0] as [string, number]) |
| 1837 | } else { |
| 1838 | // Sort by count descending |
| 1839 | entries = Object.entries(data) |
| 1840 | .sort((a, b) => b[1] - a[1]) |
| 1841 | .slice(0, maxItems) |
| 1842 | } |
| 1843 | |
| 1844 | if (entries.length === 0) return '<p class="empty">No data</p>' |
| 1845 | |
| 1846 | const maxVal = Math.max(...entries.map(e => e[1])) |
| 1847 | return entries |
| 1848 | .map(([label, count]) => { |
| 1849 | const pct = (count / maxVal) * 100 |
| 1850 | // Use LABEL_MAP if available, otherwise clean up underscores and title case |
| 1851 | const cleanLabel = |
| 1852 | LABEL_MAP[label] || |
| 1853 | label.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) |
| 1854 | return `<div class="bar-row"> |
| 1855 | <div class="bar-label">${escapeHtml(cleanLabel)}</div> |
| 1856 | <div class="bar-track"><div class="bar-fill" style="width:${pct}%;background:${color}"></div></div> |
| 1857 | <div class="bar-value">${count}</div> |
| 1858 | </div>` |
| 1859 | }) |
| 1860 | .join('\n') |
| 1861 | } |
| 1862 | |
| 1863 | function generateResponseTimeHistogram(times: number[]): string { |
| 1864 | if (times.length === 0) return '<p class="empty">No response time data</p>' |
no test coverage detected