Skip to content

Commit d8a07a3

Browse files
feat: add status bar, language/editor breakdown to Copilot metrics dashboard
Agent-Logs-Url: https://github.com/DewDropstempest/psl/sessions/532df43f-8706-4e8a-ad06-cd9d46d098a9 Co-authored-by: DewDropstempest <123912597+DewDropstempest@users.noreply.github.com>
1 parent 9288a38 commit d8a07a3

2 files changed

Lines changed: 174 additions & 1 deletion

File tree

.github/workflows/copilot-metrics.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ jobs:
7070
entry["total_acceptances_count"] = day.get("total_acceptances_count", 0)
7171
entry["total_lines_suggested"] = day.get("total_lines_suggested", 0)
7272
entry["total_lines_accepted"] = day.get("total_lines_accepted", 0)
73+
entry["breakdown"] = day.get("breakdown", [])
7374
elif "copilot_ide_code_completions" in day:
7475
# v2 / newer shape
7576
cc = day.get("copilot_ide_code_completions") or {}
@@ -78,9 +79,23 @@ jobs:
7879
entry["total_acceptances_count"] = cc.get("total_acceptances_count", 0)
7980
entry["total_lines_suggested"] = cc.get("total_lines_suggested", 0)
8081
entry["total_lines_accepted"] = cc.get("total_lines_accepted", 0)
82+
# Normalise v2 breakdown into the same shape as v1
83+
breakdown = []
84+
for lang_entry in (cc.get("languages") or []):
85+
for editor_entry in (lang_entry.get("editors") or []):
86+
breakdown.append({
87+
"language": lang_entry.get("name", "unknown"),
88+
"editor": editor_entry.get("name", "unknown"),
89+
"suggestions_count": editor_entry.get("total_suggestions_count", 0),
90+
"acceptances_count": editor_entry.get("total_acceptances_count", 0),
91+
"lines_suggested": editor_entry.get("total_lines_suggested", 0),
92+
"lines_accepted": editor_entry.get("total_lines_accepted", 0),
93+
"active_users": editor_entry.get("total_active_users", 0),
94+
})
95+
entry["breakdown"] = breakdown
8196
else:
8297
# Fallback – store whatever we got so the dashboard can adapt
83-
entry.update({k: v for k, v in day.items() if k != "breakdown"})
98+
entry.update({k: v for k, v in day.items()})
8499
normalised.append(entry)
85100
86101
out_path = "copilot-dashboard/data/metrics.json"

copilot-dashboard/index.html

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,46 @@
115115
border-radius: 4px;
116116
font-size: 0.85em;
117117
}
118+
119+
/* Status bar */
120+
#status-bar {
121+
display: flex;
122+
align-items: center;
123+
gap: 10px;
124+
background: #161b22;
125+
border: 1px solid #30363d;
126+
border-radius: 8px;
127+
padding: 10px 16px;
128+
margin-bottom: 24px;
129+
font-size: 0.82rem;
130+
color: #8b949e;
131+
}
132+
133+
#status-bar .status-dot {
134+
width: 10px;
135+
height: 10px;
136+
border-radius: 50%;
137+
flex-shrink: 0;
138+
}
139+
140+
#status-bar .status-dot.green { background: #3fb950; box-shadow: 0 0 6px #3fb950aa; }
141+
#status-bar .status-dot.yellow { background: #e3b341; box-shadow: 0 0 6px #e3b341aa; }
142+
#status-bar .status-dot.red { background: #f85149; box-shadow: 0 0 6px #f85149aa; }
143+
144+
#status-bar .status-text { color: #c9d1d9; font-weight: 500; }
145+
#status-bar .status-sep { color: #30363d; }
146+
147+
/* Section headings */
148+
.section-heading {
149+
font-size: 0.75rem;
150+
font-weight: 600;
151+
text-transform: uppercase;
152+
letter-spacing: 0.08em;
153+
color: #8b949e;
154+
margin: 32px 0 16px;
155+
padding-bottom: 8px;
156+
border-bottom: 1px solid #21262d;
157+
}
118158
</style>
119159
</head>
120160
<body>
@@ -144,6 +184,13 @@ <h2>No data yet</h2>
144184
</p>
145185
</div>
146186

187+
<div id="status-bar" style="display:none">
188+
<span class="status-dot" id="status-dot"></span>
189+
<span class="status-text" id="status-text"></span>
190+
<span class="status-sep">·</span>
191+
<span id="status-detail"></span>
192+
</div>
193+
147194
<div id="dashboard" style="display:none">
148195
<div class="kpi-grid">
149196
<div class="kpi">
@@ -191,6 +238,20 @@ <h2>Lines Suggested vs Accepted</h2>
191238
<canvas id="chart-lines"></canvas>
192239
</div>
193240
</div>
241+
242+
<div id="breakdown-section" style="display:none">
243+
<div class="section-heading">Developer Breakdown</div>
244+
<div class="charts-grid">
245+
<div class="chart-card">
246+
<h2>Acceptances by Language</h2>
247+
<canvas id="chart-lang"></canvas>
248+
</div>
249+
<div class="chart-card">
250+
<h2>Acceptances by Editor</h2>
251+
<canvas id="chart-editor"></canvas>
252+
</div>
253+
</div>
254+
</div>
194255
</div>
195256

196257
<script>
@@ -204,6 +265,16 @@ <h2>Lines Suggested vs Accepted</h2>
204265
}
205266
};
206267

268+
const BAR_DEFAULTS = {
269+
responsive: true,
270+
maintainAspectRatio: true,
271+
plugins: { legend: { display: false } },
272+
scales: {
273+
x: { ticks: { color: '#8b949e' }, grid: { color: '#21262d' } },
274+
y: { ticks: { color: '#8b949e' }, grid: { color: '#21262d' }, beginAtZero: true }
275+
}
276+
};
277+
207278
function fmt(n) {
208279
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
209280
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';
@@ -218,6 +289,23 @@ <h2>Lines Suggested vs Accepted</h2>
218289
});
219290
}
220291

292+
function makeBar(ctx, labels, values, color) {
293+
return new Chart(ctx, {
294+
type: 'bar',
295+
data: {
296+
labels,
297+
datasets: [{
298+
data: values,
299+
backgroundColor: color + 'cc',
300+
borderColor: color,
301+
borderWidth: 1,
302+
borderRadius: 4,
303+
}]
304+
},
305+
options: { ...BAR_DEFAULTS }
306+
});
307+
}
308+
221309
function lineDs(label, data, color) {
222310
return {
223311
label,
@@ -230,6 +318,70 @@ <h2>Lines Suggested vs Accepted</h2>
230318
};
231319
}
232320

321+
function setStatusBar(latestDay) {
322+
const bar = document.getElementById('status-bar');
323+
const dot = document.getElementById('status-dot');
324+
const text = document.getElementById('status-text');
325+
const detail = document.getElementById('status-detail');
326+
327+
bar.style.display = 'flex';
328+
329+
const now = new Date();
330+
const latest = new Date(latestDay + 'T00:00:00Z');
331+
const diffDays = Math.floor((now - latest) / 86_400_000);
332+
333+
if (diffDays <= 1) {
334+
dot.className = 'status-dot green';
335+
text.textContent = 'Data current';
336+
detail.textContent = `Latest entry: ${latestDay}`;
337+
} else if (diffDays <= 3) {
338+
dot.className = 'status-dot yellow';
339+
text.textContent = 'Data slightly stale';
340+
detail.textContent = `Latest entry: ${latestDay} (${diffDays} days ago) — workflow may not have run yet today`;
341+
} else {
342+
dot.className = 'status-dot red';
343+
text.textContent = 'Data stale';
344+
detail.textContent = `Latest entry: ${latestDay} (${diffDays} days ago) — check the Copilot Metrics Collector workflow`;
345+
}
346+
}
347+
348+
function renderBreakdown(days) {
349+
// Aggregate breakdown across all days
350+
const byLang = {};
351+
const byEditor = {};
352+
353+
for (const day of days) {
354+
for (const b of (day.breakdown || [])) {
355+
const lang = b.language || 'unknown';
356+
const ed = b.editor || 'unknown';
357+
byLang[lang] = (byLang[lang] || 0) + (b.acceptances_count || 0);
358+
byEditor[ed] = (byEditor[ed] || 0) + (b.acceptances_count || 0);
359+
}
360+
}
361+
362+
if (Object.keys(byLang).length === 0) return;
363+
364+
document.getElementById('breakdown-section').style.display = 'block';
365+
366+
// Sort descending, take top 10
367+
const topLangs = Object.entries(byLang).sort((a, b) => b[1] - a[1]).slice(0, 10);
368+
const topEditors = Object.entries(byEditor).sort((a, b) => b[1] - a[1]).slice(0, 10);
369+
370+
makeBar(
371+
document.getElementById('chart-lang'),
372+
topLangs.map(e => e[0]),
373+
topLangs.map(e => e[1]),
374+
'#3fb950'
375+
);
376+
377+
makeBar(
378+
document.getElementById('chart-editor'),
379+
topEditors.map(e => e[0]),
380+
topEditors.map(e => e[1]),
381+
'#58a6ff'
382+
);
383+
}
384+
233385
async function init() {
234386
const res = await fetch('./data/metrics.json');
235387
const days = await res.json();
@@ -254,6 +406,9 @@ <h2>Lines Suggested vs Accepted</h2>
254406
s > 0 ? +((acceptances[i] / s) * 100).toFixed(1) : 0
255407
);
256408

409+
// Status bar
410+
setStatusBar(labels[labels.length - 1]);
411+
257412
// KPIs
258413
const totalSugg = suggestions.reduce((a, b) => a + b, 0);
259414
const totalAcc = acceptances.reduce((a, b) => a + b, 0);
@@ -285,6 +440,9 @@ <h2>Lines Suggested vs Accepted</h2>
285440
lineDs('Lines Suggested', linesSugg, '#3fb950'),
286441
lineDs('Lines Accepted', linesAcc, '#58a6ff'),
287442
]);
443+
444+
// Developer breakdown (language + editor)
445+
renderBreakdown(days);
288446
}
289447

290448
init().catch(err => {

0 commit comments

Comments
 (0)