diff options
Diffstat (limited to 'cmd/pr-analyzer/main.go')
| -rw-r--r-- | cmd/pr-analyzer/main.go | 184 |
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) + } +} |
