diff options
Diffstat (limited to 'cmd/pr-analyzer/analyzer.go')
| -rw-r--r-- | cmd/pr-analyzer/analyzer.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/cmd/pr-analyzer/analyzer.go b/cmd/pr-analyzer/analyzer.go new file mode 100644 index 0000000..a793509 --- /dev/null +++ b/cmd/pr-analyzer/analyzer.go @@ -0,0 +1,122 @@ +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 +} |
