aboutsummaryrefslogtreecommitdiff
path: root/cmd/pr-analyzer/analyzer.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--cmd/pr-analyzer/analyzer.go122
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
+}