@@ -413,6 +413,80 @@ export class HighchartsService {
413413 } ;
414414 }
415415
416+ getLanguageTrendsChart ( metrics : CopilotMetrics [ ] ) : Highcharts . Options {
417+ const dailyData : Record < string , Record < string , { accepted : number , suggested : number } > > = { } ;
418+ const languageTotals : Record < string , number > = { } ;
419+
420+ for ( const entry of metrics ) {
421+ const dateStr = new Date ( entry . date ) . toISOString ( ) . split ( 'T' ) [ 0 ] ;
422+
423+ if ( ! entry . copilot_ide_code_completions ?. editors ) continue ;
424+
425+ for ( const editor of entry . copilot_ide_code_completions . editors ) {
426+ for ( const model of editor . models || [ ] ) {
427+ for ( const lang of model . languages || [ ] ) {
428+ if ( ! dailyData [ dateStr ] ) dailyData [ dateStr ] = { } ;
429+ if ( ! dailyData [ dateStr ] [ lang . name ] ) {
430+ dailyData [ dateStr ] [ lang . name ] = { accepted : 0 , suggested : 0 } ;
431+ }
432+
433+ dailyData [ dateStr ] [ lang . name ] . accepted += lang . total_code_lines_accepted || 0 ;
434+ dailyData [ dateStr ] [ lang . name ] . suggested += lang . total_code_lines_suggested || 0 ;
435+
436+ languageTotals [ lang . name ] = ( languageTotals [ lang . name ] || 0 ) + lang . total_code_lines_suggested ;
437+ }
438+ }
439+ }
440+ }
441+
442+ const topLanguages = Object . entries ( languageTotals )
443+ . filter ( ( [ lang ] ) => lang . toLowerCase ( ) !== 'unknown' ) // exclude "unknown"
444+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
445+ . slice ( 0 , 5 )
446+ . map ( ( [ lang ] ) => lang ) ;
447+
448+ const series : Highcharts . SeriesSplineOptions [ ] = topLanguages . map ( ( lang : string ) => ( {
449+ name : lang ,
450+ type : 'spline' ,
451+ color : this . getLanguageColor ( lang ) ,
452+ data : Object . entries ( dailyData ) . map ( ( [ date , langs ] ) => {
453+ const accepted = langs [ lang ] ?. accepted || 0 ;
454+ const suggested = langs [ lang ] ?. suggested || 0 ;
455+ const percent = suggested > 0 ? ( accepted / suggested ) * 100 : null ;
456+
457+ return {
458+ x : new Date ( date ) . getTime ( ) ,
459+ y : percent ,
460+ custom : { accepted, suggested }
461+ } ;
462+ } ) . filter ( p => p . y !== null )
463+ } ) ) ;
464+
465+ return {
466+ chart : { type : 'spline' } ,
467+ xAxis : { type : 'datetime' } ,
468+ yAxis : {
469+ min : 0 ,
470+ max : 100 ,
471+ title : { text : 'Acceptance Rate (%)' }
472+ } ,
473+ tooltip : {
474+ formatter : function ( this : Highcharts . TooltipFormatterContextObject & {
475+ point : Highcharts . Point & { custom ?: { accepted : number , suggested : number } }
476+ } ) {
477+ return `
478+ <b>${ this . series . name } </b><br/>
479+ ${ Highcharts . dateFormat ( '%b %e' , this . x as number ) } <br/>
480+ Accepted: ${ this . point . custom ?. accepted } <br/>
481+ Suggested: ${ this . point . custom ?. suggested } <br/>
482+ Acceptance: ${ ( this . y ?? 0 ) . toFixed ( 1 ) } %
483+ ` ;
484+ }
485+ } ,
486+ series
487+ } ;
488+ }
489+
416490 transformCopilotMetricsToBars ( data : CopilotMetrics , totalSeats : number ) : DashboardCardBarsInput [ ] {
417491 return [
418492 { name : 'IDE Code Completion' , icon : 'code' , value : data . copilot_ide_code_completions ?. total_engaged_users || 0 , maxValue : totalSeats } ,
0 commit comments