package main import ( "fmt" "sort" "time" ) // AnalyzePRs processes a list of PRs and generates metrics for each reviewer. func (a *Analyzer) AnalyzePRs(prs []PRData) ([]ReviewerMetrics, error) { reviewerPRs := make(map[string][]PRMetrics) for _, pr := range prs { metrics, err := a.analyzePR(pr) if err != nil { return nil, fmt.Errorf("analyzing PR %d: %w", pr.Number, err) } for _, reviewer := range metrics.Reviewers { reviewerPRs[reviewer] = append(reviewerPRs[reviewer], metrics) } } var results []ReviewerMetrics for reviewer, prs := range reviewerPRs { metrics := a.calculateReviewerMetrics(reviewer, prs) results = append(results, metrics) } sort.Slice(results, func(i, j int) bool { return results[i].PRsReviewed > results[j].PRsReviewed }) return results, nil } func (a *Analyzer) analyzePR(pr PRData) (PRMetrics, error) { metrics := PRMetrics{ Number: pr.Number, CreatedAt: pr.CreatedAt, CommentsPerFile: make(map[string]int), Reviewers: make([]string, 0), } sort.Slice(pr.Reviews, func(i, j int) bool { return pr.Reviews[i].SubmittedAt.Before(pr.Reviews[j].SubmittedAt) }) for _, review := range pr.Reviews { metrics.Reviewers = append(metrics.Reviewers, review.Reviewer) pattern := ReviewPattern{ FirstReviewState: review.State, TimeToFirstReview: review.SubmittedAt.Sub(pr.CreatedAt), } for _, comment := range review.Comments { metrics.CommentsPerFile[comment.Path]++ } metrics.ReviewPatterns = append(metrics.ReviewPatterns, pattern) } if len(pr.Reviews) > 0 { metrics.TimeToFirstReview = pr.Reviews[0].SubmittedAt.Sub(pr.CreatedAt) } if len(metrics.ReviewPatterns) > 0 { lastReview := pr.Reviews[len(pr.Reviews)-1] metrics.ReviewPatterns[len(metrics.ReviewPatterns)-1].FinalReviewState = lastReview.State metrics.ReviewPatterns[len(metrics.ReviewPatterns)-1].TimeToFinalReview = lastReview.SubmittedAt.Sub( pr.CreatedAt, ) } return metrics, nil } func (a *Analyzer) calculateReviewerMetrics(reviewer string, prs []PRMetrics) ReviewerMetrics { metrics := ReviewerMetrics{ Username: reviewer, PRsReviewed: len(prs), } var totalReviewSpeed time.Duration var totalComments int var immediateApprovals int for _, pr := range prs { var pattern ReviewPattern for _, p := range pr.ReviewPatterns { if p.FirstReviewState != "" { pattern = p break } } if pattern.TimeToFirstReview > 0 { totalReviewSpeed += pattern.TimeToFirstReview } for _, count := range pr.CommentsPerFile { totalComments += count } if pattern.FirstReviewState == "APPROVED" { immediateApprovals++ } } if metrics.PRsReviewed > 0 { metrics.AverageReviewSpeed = totalReviewSpeed.Hours() / float64(metrics.PRsReviewed) metrics.AverageComments = float64(totalComments) / float64(metrics.PRsReviewed) metrics.ImmediateApprovals = float64( immediateApprovals, ) / float64( metrics.PRsReviewed, ) * 100 } return metrics }