Skip to content

Commit 394935f

Browse files
committed
ui: air: add patch volume graph
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent a4a40c5 commit 394935f

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

ui/air.html

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,10 @@ <h3 style="margin-top: 40px;">Cost and Time (by feedback status)</h3>
739739
<div class="chart-container">
740740
<canvas id="averagesChart"></canvas>
741741
</div>
742+
<h3 style="margin-top: 40px;">Patches and Submissions</h3>
743+
<div class="chart-container">
744+
<canvas id="volumeChart"></canvas>
745+
</div>
742746
<h3 style="margin-top: 40px;">Feedback Breakdown (36-hour rolling)</h3>
743747
<div class="chart-container">
744748
<canvas id="feedbackChart"></canvas>
@@ -985,6 +989,7 @@ <h3 style="margin-top: 40px;">Feedback Breakdown (36-hour rolling)</h3>
985989
// Initialize and update review chart
986990
let reviewChart = null;
987991
let averagesChart = null;
992+
let volumeChart = null;
988993
let feedbackChart = null;
989994
loadCharts();
990995
// Charts/tables only refresh on user action (filter/limit changes), not periodically
@@ -1030,6 +1035,7 @@ <h3 style="margin-top: 40px;">Feedback Breakdown (36-hour rolling)</h3>
10301035
updateStatsTable(filteredReviews);
10311036
updateReviewChart(filteredReviews);
10321037
updateAveragesChart(filteredReviews);
1038+
updateVolumeChart(filteredReviews);
10331039
updateFeedbackChart(filteredReviews);
10341040

10351041
// Also update the reviews list
@@ -1794,6 +1800,109 @@ <h3 style="margin-top: 40px;">Feedback Breakdown (36-hour rolling)</h3>
17941800
}
17951801
}
17961802

1803+
function updateVolumeChart(reviews) {
1804+
const bucketSize = parseInt(document.getElementById('bucketSize').value);
1805+
1806+
// Group reviews by time buckets, counting submissions and patches
1807+
const dataByBucket = {};
1808+
1809+
reviews.forEach(review => {
1810+
if (review.status !== 'done') return;
1811+
1812+
const timestamp = parseUTCDate(review.date);
1813+
const bucketMs = bucketSize * 3600000;
1814+
const bucketStart = new Date(Math.floor(timestamp.getTime() / bucketMs) * bucketMs);
1815+
const bucketKey = bucketStart.toISOString();
1816+
1817+
if (!dataByBucket[bucketKey]) {
1818+
dataByBucket[bucketKey] = { submissions: 0, patches: 0 };
1819+
}
1820+
1821+
dataByBucket[bucketKey].submissions++;
1822+
dataByBucket[bucketKey].patches += review.patch_count || 0;
1823+
});
1824+
1825+
const buckets = Object.keys(dataByBucket).sort();
1826+
const submissionsData = buckets.map(b => ({ x: b, y: dataByBucket[b].submissions || null }));
1827+
const patchesData = buckets.map(b => ({ x: b, y: dataByBucket[b].patches || null }));
1828+
1829+
const ctx = document.getElementById('volumeChart');
1830+
1831+
if (volumeChart) {
1832+
volumeChart.data.datasets[0].data = submissionsData;
1833+
volumeChart.data.datasets[1].data = patchesData;
1834+
volumeChart.update();
1835+
} else {
1836+
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-primary');
1837+
const borderColor = getComputedStyle(document.documentElement).getPropertyValue('--border-color');
1838+
1839+
volumeChart = new Chart(ctx, {
1840+
type: 'line',
1841+
data: {
1842+
datasets: [
1843+
{
1844+
label: 'Submissions',
1845+
data: submissionsData,
1846+
borderColor: '#007bff',
1847+
backgroundColor: 'rgba(0, 123, 255, 0.1)',
1848+
tension: 0.1,
1849+
fill: true,
1850+
spanGaps: false,
1851+
yAxisID: 'y'
1852+
},
1853+
{
1854+
label: 'Patches',
1855+
data: patchesData,
1856+
borderColor: '#28a745',
1857+
backgroundColor: 'rgba(40, 167, 69, 0.1)',
1858+
tension: 0.1,
1859+
fill: true,
1860+
spanGaps: false,
1861+
yAxisID: 'y'
1862+
}
1863+
]
1864+
},
1865+
options: {
1866+
responsive: true,
1867+
maintainAspectRatio: false,
1868+
interaction: {
1869+
mode: 'index',
1870+
intersect: false,
1871+
},
1872+
plugins: {
1873+
legend: {
1874+
position: 'top',
1875+
labels: { color: textColor }
1876+
},
1877+
tooltip: {
1878+
mode: 'index',
1879+
intersect: false,
1880+
}
1881+
},
1882+
scales: {
1883+
x: {
1884+
type: 'time',
1885+
time: {
1886+
unit: 'hour',
1887+
stepSize: 6,
1888+
displayFormats: { hour: 'MMM d HH:mm' }
1889+
},
1890+
grid: { color: borderColor },
1891+
ticks: { color: textColor }
1892+
},
1893+
y: {
1894+
type: 'linear',
1895+
position: 'left',
1896+
beginAtZero: true,
1897+
grid: { color: borderColor },
1898+
ticks: { color: textColor, precision: 0 }
1899+
}
1900+
}
1901+
}
1902+
});
1903+
}
1904+
}
1905+
17971906
function updateFeedbackChart(reviews) {
17981907
// Only process completed reviews
17991908
const completedReviews = reviews.filter(r => r.status === 'done');
@@ -2628,6 +2737,15 @@ <h3 style="margin-top: 40px;">Feedback Breakdown (36-hour rolling)</h3>
26282737

26292738
averagesChart.update();
26302739
}
2740+
2741+
if (volumeChart) {
2742+
volumeChart.options.plugins.legend.labels.color = textColor;
2743+
volumeChart.options.scales.x.grid.color = borderColor;
2744+
volumeChart.options.scales.x.ticks.color = textColor;
2745+
volumeChart.options.scales.y.grid.color = borderColor;
2746+
volumeChart.options.scales.y.ticks.color = textColor;
2747+
volumeChart.update();
2748+
}
26312749
}
26322750

26332751
function updateThemeButton(theme) {

0 commit comments

Comments
 (0)