From f1e045f4bd96c674f651b19357aeb04cf08a8eb0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:47:56 +0000 Subject: [PATCH 1/2] Initial plan From 4bf32ad7467947e112860027505864ebc6b2d097 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:52:45 +0000 Subject: [PATCH 2/2] fix: include team pull request metrics in report data Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com> --- shared/utils/metrics-util-v2.ts | 53 ++++++++++++++++++-- tests/metrics-util-v2-team-path.spec.ts | 64 +++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/shared/utils/metrics-util-v2.ts b/shared/utils/metrics-util-v2.ts index 8bc9f031..23a77065 100644 --- a/shared/utils/metrics-util-v2.ts +++ b/shared/utils/metrics-util-v2.ts @@ -41,6 +41,50 @@ function sortMetricsDataResult(result: MetricsDataResult): MetricsDataResult { }; } +async function enrichTeamReportWithPullRequests( + report: OrgReport, + options: Options, + headers: Headers, + identifier: string, +): Promise { + if (!options.githubTeam || report.day_totals.length === 0) { + return report; + } + + try { + const teamReport = await fetchLatestReport( + { + scope: options.scope!, + identifier, + teamSlug: options.githubTeam, + isMocked: options.isDataMocked, + }, + headers, + ); + + const pullRequestsByDay = new Map( + (teamReport.day_totals || []) + .filter(day => day.pull_requests != null) + .map(day => [day.day, day.pull_requests] as const), + ); + + if (pullRequestsByDay.size === 0) { + return report; + } + + return { + ...report, + day_totals: report.day_totals.map(day => { + const pullRequests = pullRequestsByDay.get(day.day); + return pullRequests ? { ...day, pull_requests: pullRequests } : day; + }), + }; + } catch (error) { + console.info('Team pull request report data is unavailable; continuing without PR day_totals enrichment.', error); + return report; + } +} + /** * Returns true ONLY when USE_LEGACY_API is explicitly set to "true". * Default behavior is new API — no legacy calls unless opted in. @@ -165,7 +209,8 @@ export async function getMetricsDataV2(event: H3Event): Pro if (userDayRecords.length > 0) { logger.info(`Aggregating team metrics from ${userDayRecords.length} per-day user DB records`); const report = aggregateTeamMetrics(userDayRecords, teamLogins); - return buildFilteredResult(report, options); + const enrichedReport = await enrichTeamReportWithPullRequests(report, options, event.context.headers, identifier); + return buildFilteredResult(enrichedReport, options); } // No per-day data in DB — fetch from API, persist all user records, then aggregate @@ -181,7 +226,8 @@ export async function getMetricsDataV2(event: H3Event): Pro logger.error('Failed to store per-day user records:', err); } const report = aggregateTeamMetrics(liveUserDayRecords, teamLogins); - return buildFilteredResult(report, options); + const enrichedReport = await enrichTeamReportWithPullRequests(report, options, event.context.headers, identifier); + return buildFilteredResult(enrichedReport, options); } else { // Org/Enterprise path: serve pre-aggregated metrics from DB @@ -253,7 +299,8 @@ export async function getMetricsDataV2(event: H3Event): Pro const userDayRecords = await fetchRawUserDayRecords(request, event.context.headers); logger.info(`Aggregating team metrics from ${userDayRecords.length} user-day records (${teamMembers.length} team members)`); const report = aggregateTeamMetrics(userDayRecords, teamLogins); - return buildFilteredResult(report, options); + const enrichedReport = await enrichTeamReportWithPullRequests(report, options, event.context.headers, identifier); + return buildFilteredResult(enrichedReport, options); } logger.info('Using new Copilot Metrics API (direct, no DB)'); diff --git a/tests/metrics-util-v2-team-path.spec.ts b/tests/metrics-util-v2-team-path.spec.ts index dd5fdb75..0a5acccf 100644 --- a/tests/metrics-util-v2-team-path.spec.ts +++ b/tests/metrics-util-v2-team-path.spec.ts @@ -40,6 +40,8 @@ let _mockQuery: Record = {}; const mockGetUserDayMetrics = vi.fn(); const mockSaveUserDayBatch = vi.fn(); +const mockFetchLatestReport = vi.fn(); +const mockFetchRawUserDayRecords = vi.fn(); vi.mock('../server/storage/user-day-metrics-storage', () => ({ getUserDayMetricsByDateRange: (...args: any[]) => mockGetUserDayMetrics(...args), @@ -47,6 +49,11 @@ vi.mock('../server/storage/user-day-metrics-storage', () => ({ hasUserDayMetricsForDate: vi.fn(async () => false), })); +vi.mock('../server/services/github-copilot-usage-api', () => ({ + fetchLatestReport: (...args: any[]) => mockFetchLatestReport(...args), + fetchRawUserDayRecords: (...args: any[]) => mockFetchRawUserDayRecords(...args), +})); + const mockFetchAllTeamMembers = vi.fn(); vi.mock('../server/api/seats', () => ({ @@ -124,6 +131,48 @@ describe('getMetricsDataV2 — historical mode team path (regression for 500 bug { login: 'octocat', id: 1 }, { login: 'octokitten', id: 2 }, ]); + mockFetchLatestReport.mockResolvedValue({ + report_start_day: '2026-03-01', + report_end_day: '2026-03-28', + organization_id: '100001', + enterprise_id: '', + created_at: '2026-03-29T00:00:00.000Z', + day_totals: [ + { + day: '2026-03-15', + organization_id: '100001', + enterprise_id: '', + daily_active_users: 2, + weekly_active_users: 2, + monthly_active_users: 2, + user_initiated_interaction_count: 0, + code_generation_activity_count: 0, + code_acceptance_activity_count: 0, + totals_by_ide: [], + totals_by_feature: [], + totals_by_language_feature: [], + totals_by_language_model: [], + totals_by_model_feature: [], + loc_suggested_to_add_sum: 0, + loc_suggested_to_delete_sum: 0, + loc_added_sum: 0, + loc_deleted_sum: 0, + pull_requests: { + total_created: 4, + total_reviewed: 5, + total_merged: 3, + total_suggestions: 2, + total_applied_suggestions: 1, + total_created_by_copilot: 1, + total_reviewed_by_copilot: 1, + total_merged_created_by_copilot: 1, + total_copilot_suggestions: 1, + total_copilot_applied_suggestions: 1, + }, + }, + ], + }); + mockFetchRawUserDayRecords.mockResolvedValue([]); }); afterEach(() => { @@ -180,4 +229,19 @@ describe('getMetricsDataV2 — historical mode team path (regression for 500 bug // DB should NOT be queried when team is empty expect(mockGetUserDayMetrics).not.toHaveBeenCalled(); }); + + it('includes pull request day_totals data in team reportData when available from report API', async () => { + mockGetUserDayMetrics.mockResolvedValue([ + makeDayRecord('octocat', '2026-03-15'), + makeDayRecord('octokitten', '2026-03-15'), + ]); + + const { getMetricsDataV2 } = await import('../shared/utils/metrics-util-v2'); + const result = await getMetricsDataV2(makeEvent(true)); + + expect(result.reportData).toHaveLength(1); + expect(result.reportData[0]!.day).toBe('2026-03-15'); + expect(result.reportData[0]!.pull_requests).toBeDefined(); + expect(result.reportData[0]!.pull_requests?.total_created).toBe(4); + }); });