aboutsummaryrefslogtreecommitdiff
path: root/cmd/pr-analyzer/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/pr-analyzer/main.go')
-rw-r--r--cmd/pr-analyzer/main.go184
1 files changed, 184 insertions, 0 deletions
diff --git a/cmd/pr-analyzer/main.go b/cmd/pr-analyzer/main.go
new file mode 100644
index 0000000..b12be31
--- /dev/null
+++ b/cmd/pr-analyzer/main.go
@@ -0,0 +1,184 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+ "time"
+
+ "go.fcuny.net/x/internal/github"
+)
+
+type Config struct {
+ Token string
+ Repo string
+ StartDate time.Time
+ EndDate time.Time
+ APIURL string
+}
+
+func parseFlags() (*Config, error) {
+ config := &Config{}
+
+ flag.StringVar(&config.Token, "token", "", "GitHub personal access token (required)")
+ flag.StringVar(&config.Repo, "repo", "", "Repository in owner/repo format (required)")
+
+ startDateStr := flag.String(
+ "start",
+ time.Now().AddDate(0, -1, 0).Format("2006-01-02"),
+ "Start date (YYYY-MM-DD)",
+ )
+ endDateStr := flag.String("end", time.Now().Format("2006-01-02"), "End date (YYYY-MM-DD)")
+ flag.StringVar(
+ &config.APIURL,
+ "api-url",
+ "https://api.github.com",
+ "GitHub API URL (for GitHub Enterprise)",
+ )
+
+ flag.Parse()
+
+ if config.Token == "" {
+ return nil, fmt.Errorf("--token is required")
+ }
+
+ if config.Repo == "" {
+ return nil, fmt.Errorf("--repo is required")
+ }
+
+ startDate, err := time.Parse("2006-01-02", *startDateStr)
+ if err != nil {
+ return nil, fmt.Errorf("invalid start date format: %v", err)
+ }
+ config.StartDate = startDate
+
+ endDate, err := time.Parse("2006-01-02", *endDateStr)
+ if err != nil {
+ return nil, fmt.Errorf("invalid end date format: %v", err)
+ }
+ config.EndDate = endDate
+
+ return config, nil
+}
+
+func convertToAnalyzerTypes(
+ prs []github.PullRequest,
+ reviews map[int][]github.Review,
+ comments map[int][]github.ReviewComment,
+) []PRData {
+ var prData []PRData
+
+ for _, pr := range prs {
+ prReviews := reviews[pr.Number]
+ if prReviews == nil {
+ prReviews = []github.Review{}
+ }
+
+ prComments := comments[pr.Number]
+ if prComments == nil {
+ prComments = []github.ReviewComment{}
+ }
+
+ commentsByReview := make(map[int64][]CommentData)
+ for _, comment := range prComments {
+ commentsByReview[comment.ReviewID] = append(
+ commentsByReview[comment.ReviewID],
+ CommentData{
+ Reviewer: comment.User.Login,
+ CreatedAt: comment.CreatedAt,
+ Path: comment.Path,
+ Line: comment.Line,
+ },
+ )
+ }
+
+ var reviewData []ReviewData
+ for _, review := range prReviews {
+ reviewData = append(reviewData, ReviewData{
+ Reviewer: review.User.Login,
+ State: review.State,
+ SubmittedAt: review.SubmittedAt,
+ Comments: commentsByReview[review.ID],
+ })
+ }
+
+ prData = append(prData, PRData{
+ Number: pr.Number,
+ CreatedAt: pr.CreatedAt,
+ Reviews: reviewData,
+ })
+ }
+
+ return prData
+}
+
+func runAnalysis(ctx context.Context, config *Config) error {
+ client := github.NewClient(config.Token, config.APIURL)
+ analyzer := NewAnalyzer(config.StartDate, config.EndDate)
+ formatter := NewFormatter(config.Repo, config.StartDate, config.EndDate)
+
+ parts := strings.Split(config.Repo, "/")
+ if len(parts) != 2 {
+ return fmt.Errorf("invalid repo format: %s (expected owner/repo)", config.Repo)
+ }
+ owner, repo := parts[0], parts[1]
+
+ fmt.Print(formatter.FormatProgress("Fetching pull requests..."))
+ prs, err := client.GetPullRequests(ctx, owner, repo, config.StartDate, config.EndDate)
+ if err != nil {
+ return fmt.Errorf("fetching pull requests: %w", err)
+ }
+
+ reviews := make(map[int][]github.Review)
+ comments := make(map[int][]github.ReviewComment)
+
+ for i, pr := range prs {
+ fmt.Print(
+ formatter.FormatProgress(
+ fmt.Sprintf("Processing PR %d/%d: #%d", i+1, len(prs), pr.Number),
+ ),
+ )
+
+ prReviews, err := client.GetPullRequestReviews(ctx, owner, repo, pr.Number)
+ if err != nil {
+ return fmt.Errorf("fetching reviews for PR #%d: %w", pr.Number, err)
+ }
+ reviews[pr.Number] = prReviews
+
+ prComments, err := client.GetPullRequestReviewComments(ctx, owner, repo, pr.Number)
+ if err != nil {
+ return fmt.Errorf("fetching comments for PR #%d: %w", pr.Number, err)
+ }
+ comments[pr.Number] = prComments
+ }
+
+ prData := convertToAnalyzerTypes(prs, reviews, comments)
+
+ fmt.Print(formatter.FormatProgress("Analyzing PR data..."))
+ metrics, err := analyzer.AnalyzePRs(prData)
+ if err != nil {
+ return fmt.Errorf("analyzing PRs: %w", err)
+ }
+
+ fmt.Print(formatter.FormatTable(metrics))
+ return nil
+}
+
+func main() {
+ config, err := parseFlags()
+ if err != nil {
+ log.Printf("Error: %v\n", err)
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ ctx := context.Background()
+
+ if err := runAnalysis(ctx, config); err != nil {
+ log.Printf("Error: %v\n", err)
+ os.Exit(1)
+ }
+}