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+
207278function 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+
221309function 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+
233385async 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
290448init ( ) . catch ( err => {
0 commit comments