Skip to content

Commit d936547

Browse files
authored
Merge branch 'main' into obscurerichard/remove-copilot-usage-api
2 parents 8d2beab + 9e3ccbb commit d936547

10 files changed

Lines changed: 152 additions & 10 deletions

File tree

backend/src/app.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import express, { Express } from 'express';
33
import rateLimit from 'express-rate-limit';
44
import bodyParser from 'body-parser';
55
import cors from 'cors';
6-
import path, { dirname } from 'path';
7-
import { fileURLToPath } from 'url';
6+
import path from 'path';
87
import * as http from 'http';
98
import Database from './database.js';
109
import logger, { expressLoggerMiddleware } from './services/logger.js';
@@ -133,7 +132,7 @@ class App {
133132
this.e.get('*', (_, res) => res.sendFile(path.join(frontendPath, 'index.html')));
134133

135134
this.eListener = this.e.listen(this.port, '0.0.0.0');
136-
logger.info(`eListener on port ${this.port}`);
135+
logger.info(`eListener on port ${this.port} (http://localhost:${this.port})`);
137136
}
138137

139138
private initializeSettings() {

backend/src/routes/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import adoptionController from '../controllers/adoption.controller.js';
1111
const router = Router();
1212

1313
router.get('/', (req: Request, res: Response) => {
14-
res.send('Hello World!');
14+
res.send('Hello github-value!');
1515
});
1616

1717
router.get('/survey', surveyController.getAllSurveys);

backend/src/services/query.service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class QueryService {
7979
}
8080

8181
await adoptionService.createAdoption(enterpriseAdoptionData);
82+
logger.info(`Task finished. Last ran at `, this.cronJob.lastDate());
8283
}
8384

8485
private async orgTask(octokit: Octokit, queryAt: Date, org: string) {
@@ -97,7 +98,7 @@ class QueryService {
9798
}
9899

99100
const queries = [
100-
this.queryCopilotUsageMetricsNew(octokit, org).then(result => {
101+
this.queryCopilotMetrics(octokit, org).then(result => {
101102
this.status.metrics = true;
102103
return result;
103104
}),
@@ -107,11 +108,11 @@ class QueryService {
107108
}),
108109
];
109110

110-
const [usageMetricsNew, copilotSeatAssignments] = await Promise.all(queries);
111+
const [copilotMetrics, copilotSeatAssignments] = await Promise.all(queries);
111112
this.status.dbInitialized = true;
112113

113114
return {
114-
usageMetricsNew,
115+
copilotMetrics,
115116
copilotSeatAssignments,
116117
teamsAndMembers
117118
}
@@ -121,7 +122,7 @@ class QueryService {
121122
logger.info(`${org} finished task`);
122123
}
123124

124-
public async queryCopilotUsageMetricsNew(octokit: Octokit, org: string, team?: string) {
125+
public async queryCopilotMetrics(octokit: Octokit, org: string, team?: string) {
125126
try {
126127
const metricsArray = await octokit.paginate<MetricDailyResponseType>(
127128
'GET /orgs/{org}/copilot/metrics',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<highcharts-chart
2+
*ngIf="options"
3+
[Highcharts]="Highcharts"
4+
[options]="options"
5+
style="width: 100%; height: 400px; display: block;">
6+
</highcharts-chart>

frontend/src/app/main/copilot/copilot-dashboard/dashboard-card/dashboard-card-line-chart/dashboard-card-line-chart.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { DashboardCardLineChartComponent } from './dashboard-card-line-chart.component';
4+
5+
describe('DashboardCardLineChartComponent', () => {
6+
let component: DashboardCardLineChartComponent;
7+
let fixture: ComponentFixture<DashboardCardLineChartComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [DashboardCardLineChartComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(DashboardCardLineChartComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, Input } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { HighchartsChartModule } from 'highcharts-angular';
4+
import * as Highcharts from 'highcharts';
5+
import { CopilotMetrics } from '../../../../../services/api/metrics.service.interfaces';
6+
import { HighchartsService } from '../../../../../services/highcharts.service';
7+
8+
@Component({
9+
selector: 'app-dashboard-card-line-chart',
10+
standalone: true,
11+
imports: [CommonModule, HighchartsChartModule],
12+
templateUrl: './dashboard-card-line-chart.component.html',
13+
styleUrls: ['./dashboard-card-line-chart.component.scss']
14+
})
15+
export class DashboardCardLineChartComponent {
16+
@Input() data?: CopilotMetrics[];
17+
18+
Highcharts: typeof Highcharts = Highcharts;
19+
options: Highcharts.Options | undefined;
20+
21+
constructor(private highchartsService: HighchartsService) {}
22+
23+
ngOnChanges(): void {
24+
if (this.data && this.data.length) {
25+
this.options = this.highchartsService.getLanguageTrendsChart(this.data);
26+
}
27+
}
28+
}

frontend/src/app/main/copilot/copilot-metrics/copilot-metrics.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ <h1>Metrics</h1>
4646
</ng-container>
4747
</mat-card-content>
4848
</mat-card>
49+
<mat-card appearance="outlined" id="line-chart" style="grid-column: span 2;">
50+
<mat-card-header>
51+
<mat-card-title>Language Acceptance Trends</mat-card-title>
52+
</mat-card-header>
53+
<mat-card-content>
54+
<ng-container *ngIf="metrics?.length; else loading">
55+
<app-dashboard-card-line-chart [data]="metrics"></app-dashboard-card-line-chart>
56+
</ng-container>
57+
</mat-card-content>
58+
</mat-card>
4959
</div>
5060
</div>
5161
<ng-template #loading>

frontend/src/app/main/copilot/copilot-metrics/copilot-metrics.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { CopilotMetrics } from '../../../services/api/metrics.service.interfaces
55
import { CopilotMetricsPieChartComponent } from './copilot-metrics-pie-chart/copilot-metrics-pie-chart.component';
66
import { MatCardModule } from '@angular/material/card';
77
import { Installation, InstallationsService } from '../../../services/api/installations.service';
8-
import { forkJoin, Subject, Subscription, takeUntil } from 'rxjs';
9-
import { DashboardCardBarsComponent } from '../copilot-dashboard/dashboard-card/dashboard-card-bars/dashboard-card-bars.component';
8+
import { Subject, Subscription, takeUntil } from 'rxjs';
109
import { DashboardCardDrilldownBarChartComponent } from '../copilot-dashboard/dashboard-card/dashboard-card-drilldown-bar-chart/dashboard-card-drilldown-bar-chart.component';
1110
import { ActiveUsersChartComponent } from '../copilot-dashboard/dashboard-card/active-users-chart/active-users-chart.component';
1211
import { SeatService } from '../../../services/api/seat.service';
1312
import { MembersService } from '../../../services/api/members.service';
1413
import { CommonModule } from '@angular/common';
1514
import { LoadingSpinnerComponent } from '../../../shared/loading-spinner/loading-spinner.component';
15+
import { DashboardCardLineChartComponent } from '../copilot-dashboard/dashboard-card/dashboard-card-line-chart/dashboard-card-line-chart.component';
1616

1717
@Component({
1818
selector: 'app-metrics',
@@ -26,6 +26,7 @@ import { LoadingSpinnerComponent } from '../../../shared/loading-spinner/loading
2626
ActiveUsersChartComponent,
2727
CommonModule,
2828
LoadingSpinnerComponent,
29+
DashboardCardLineChartComponent
2930
],
3031
templateUrl: './copilot-metrics.component.html',
3132
styleUrls: [

frontend/src/app/services/highcharts.service.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)