chore: update src/lib/analytics.ts
swadhinbiswas committed 8 months ago
d1173c4
1 parent 421bc6b
commit d1173c4
1 file changed
+31 -25
Showing 1 changed file+31-25
▼src/lib/analytics.ts
+31-25
| @@ -2,7 +2,7 @@ | ||
2 | 2 | import { getDatabase } from "../db"; |
3 | 3 | import { type NodePgDatabase } from "drizzle-orm/node-postgres"; |
4 | 4 | import * as schema from "../db/schema"; |
5 | -import { eq, and, gte, desc, sql } from "drizzle-orm"; | |
5 | +import { eq, and, gte, desc, inArray } from "drizzle-orm"; | |
6 | 6 | import { pullRequests, pullRequestReviews } from "../db/schema/pull-requests"; |
7 | 7 | |
8 | 8 | const db = getDatabase() as NodePgDatabase<typeof schema>; |
| @@ -36,20 +36,17 @@ export async function getRepoStats(repoId: string, days = 30): Promise<DailyStat | ||
36 | 36 | ) |
37 | 37 | .orderBy(desc(pullRequests.mergedAt)); |
38 | 38 | |
39 | - // Also fetch reviews for review velocity | |
40 | - const reviews = await db | |
41 | - .select({ | |
42 | - prId: pullRequestReviews.pullRequestId, | |
43 | - submittedAt: pullRequestReviews.submittedAt, | |
44 | - }) | |
45 | - .from(pullRequestReviews) | |
46 | - .innerJoin(pullRequests, eq(pullRequestReviews.pullRequestId, pullRequests.id)) | |
47 | - .where( | |
48 | - and( | |
49 | - eq(pullRequests.repositoryId, repoId), | |
50 | - gte(pullRequestReviews.submittedAt, startDate) | |
51 | - ) | |
52 | - ); | |
39 | + const prIds = prs.map((pr) => pr.id); | |
40 | + const reviews = prIds.length | |
41 | + ? await db | |
42 | + .select({ | |
43 | + prId: pullRequestReviews.pullRequestId, | |
44 | + submittedAt: pullRequestReviews.submittedAt, | |
45 | + createdAt: pullRequestReviews.createdAt, | |
46 | + }) | |
47 | + .from(pullRequestReviews) | |
48 | + .where(inArray(pullRequestReviews.pullRequestId, prIds)) | |
49 | + : []; | |
53 | 50 | |
54 | 51 | // Group by day |
55 | 52 | const statsMap = new Map<string, { totalCycle: number; totalMerge: number; totalReview: number; reviewCounts: number }>(); |
| @@ -62,6 +59,17 @@ export async function getRepoStats(repoId: string, days = 30): Promise<DailyStat | ||
62 | 59 | statsMap.set(dateStr, { totalCycle: 0, totalMerge: 0, totalReview: 0, reviewCounts: 0 }); |
63 | 60 | } |
64 | 61 | |
62 | + const firstReviewByPr = new Map<string, Date>(); | |
63 | + | |
64 | + for (const review of reviews) { | |
65 | + const reviewTime = review.submittedAt || review.createdAt; | |
66 | + if (!reviewTime) continue; | |
67 | + const current = firstReviewByPr.get(review.prId); | |
68 | + if (!current || reviewTime < current) { | |
69 | + firstReviewByPr.set(review.prId, reviewTime); | |
70 | + } | |
71 | + } | |
72 | + | |
65 | 73 | // Calculate Metrics |
66 | 74 | prs.forEach((pr) => { |
67 | 75 | if (!pr.mergedAt || !pr.createdAt) return; |
| @@ -72,24 +80,22 @@ export async function getRepoStats(repoId: string, days = 30): Promise<DailyStat | ||
72 | 80 | if (dayStats) { |
73 | 81 | dayStats.totalCycle += hours; |
74 | 82 | dayStats.totalMerge += 1; |
83 | + | |
84 | + const firstReviewAt = firstReviewByPr.get(pr.id); | |
85 | + if (firstReviewAt && firstReviewAt >= pr.createdAt) { | |
86 | + const reviewHours = (firstReviewAt.getTime() - pr.createdAt.getTime()) / (1000 * 60 * 60); | |
87 | + dayStats.totalReview += reviewHours; | |
88 | + dayStats.reviewCounts += 1; | |
89 | + } | |
75 | 90 | } |
76 | 91 | }); |
77 | 92 | |
78 | - // Calculate Review Velocity (rough approx: merged PRs or any PR with review?) | |
79 | - // Let's use the reviews we fetched. | |
80 | - // We need to match review to its PR creation time. | |
81 | - // This is a bit complex in one go. For MVP, let's simplify review velocity: | |
82 | - // Average time for *merged* PRs to get their first review? | |
83 | - // Let's iterate reviews and match to PRs if we have them. | |
84 | - // Ideally we need `pullRequests` creation time for the reviews. | |
85 | - // Let's stick to Cycle Time and Merge Frequency for MVP robustness. | |
86 | - | |
87 | 93 | return Array.from(statsMap.entries()) |
88 | 94 | .map(([date, data]) => ({ |
89 | 95 | date, |
90 | 96 | cycleTime: data.totalMerge ? Math.round((data.totalCycle / data.totalMerge) * 100) / 100 : 0, |
91 | 97 | mergeCount: data.totalMerge, |
92 | - reviewTime: 0 // Placeholder | |
98 | + reviewTime: data.reviewCounts ? Math.round((data.totalReview / data.reviewCounts) * 100) / 100 : 0 | |
93 | 99 | })) |
94 | 100 | .sort((a, b) => a.date.localeCompare(b.date)); |
95 | 101 | } |
96 | 102 | |