Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 115 additions & 67 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import (

// AppConfig holds the application configuration
type AppConfig struct {
Token string
RepoDir string
PR int
Repo string
Verbose bool
Token string
RepoDir string
PR int
Repo string
Verbose bool
AddComments bool
}

// App represents the application with its dependencies
Expand All @@ -38,23 +39,26 @@ type App struct {

// Flags holds the command line flags
type Flags struct {
Token *string
RepoDir *string
PR *int
Repo *string
Verbose *bool
Token *string
RepoDir *string
PR *int
Repo *string
Verbose *bool
AddComments *bool
}

var (
flags = &Flags{
Token: flag.String("token", getEnv("INPUT_GITHUB-TOKEN", ""), "GitHub authentication token"),
RepoDir: flag.String("dir", getEnv("GITHUB_WORKSPACE", "/"), "Path to local Git repo"),
PR: flag.Int("pr", ignoreError(strconv.Atoi(getEnv("INPUT_PR", ""))), "Pull Request number"),
Repo: flag.String("repo", getEnv("INPUT_REPOSITORY", ""), "GitHub repo name"),
Verbose: flag.Bool("v", ignoreError(strconv.ParseBool(getEnv("INPUT_VERBOSE", "0"))), "Verbose output"),
}
WarningBuffer = bytes.NewBuffer([]byte{})
InfoBuffer = bytes.NewBuffer([]byte{})
Token: flag.String("token", getEnv("INPUT_GITHUB-TOKEN", ""), "GitHub authentication token"),
RepoDir: flag.String("dir", getEnv("GITHUB_WORKSPACE", "/"), "Path to local Git repo"),
PR: flag.Int("pr", ignoreError(strconv.Atoi(getEnv("INPUT_PR", ""))), "Pull Request number"),
Repo: flag.String("repo", getEnv("INPUT_REPOSITORY", ""), "GitHub repo name"),
Verbose: flag.Bool("v", ignoreError(strconv.ParseBool(getEnv("INPUT_VERBOSE", "0"))), "Verbose output"),
AddComments: flag.Bool("comments", ignoreError(strconv.ParseBool(getEnv("INPUT_ADD_COMMENTS", "1"))), "Add comments to Pull Request"),
}
WarningBuffer = bytes.NewBuffer([]byte{})
InfoBuffer = bytes.NewBuffer([]byte{})
QUIET_MODE_TEST = true
)

// initFlags initializes and parses command line flags
Expand Down Expand Up @@ -211,51 +215,13 @@ func (a *App) processApprovalsAndReviewers() (bool, string, error) {
}

// Add comments to the PR if necessary
if len(unapprovedOwners) > 0 {
// Comment on the PR with the codeowner teams that have not approved the PR
comment := allRequiredOwners.ToCommentString()
hasHighPriority, err := a.client.IsInLabels(a.conf.HighPriorityLabels)
if err != nil {
fmt.Fprintf(WarningBuffer, "WARNING: Error checking high priority labels: %v\n", err)
} else if hasHighPriority {
comment = "❗High Prio❗\n\n" + comment
}
if maxReviewsMet {
comment += "\n\n"
comment += "The PR has received the max number of required reviews. No further action is required."
}
fiveDaysAgo := time.Now().AddDate(0, 0, -5)
found, err := a.client.IsInComments(comment, &fiveDaysAgo)
if err != nil {
return false, message, fmt.Errorf("IsInComments Error: %v\n", err)
}
if !found {
err = a.client.AddComment(comment)
if err != nil {
return false, message, fmt.Errorf("AddComment Error: %v\n", err)
}
}
err = a.addReviewStatusComment(allRequiredOwners, unapprovedOwners, maxReviewsMet)
if err != nil {
return false, message, fmt.Errorf("failed to add review status comment: %w", err)
}
if len(allOptionalReviewerNames) > 0 {
var isInCommentsError error = nil
// Add CC comment to the PR with the optional reviewers that have not already been mentioned in the PR comments
viewersToPing := f.Filtered(allOptionalReviewerNames, func(name string) bool {
found, err := a.client.IsSubstringInComments(name, nil)
if err != nil {
isInCommentsError = err
}
return !found
})
if isInCommentsError != nil {
return false, message, fmt.Errorf("IsInComments Error: %v\n", err)
}
if len(viewersToPing) > 0 {
comment := fmt.Sprintf("cc %s", strings.Join(viewersToPing, " "))
err = a.client.AddComment(comment)
if err != nil {
return false, message, fmt.Errorf("AddComment Error: %v\n", err)
}
}
err = a.addOptionalCcComment(allOptionalReviewerNames)
if err != nil {
return false, message, fmt.Errorf("failed to add optional CC comment: %w", err)
}

// Exit if there are any unapproved codeowner teams
Expand Down Expand Up @@ -293,6 +259,87 @@ func (a *App) processApprovalsAndReviewers() (bool, string, error) {
return true, message, nil
}

func (a *App) addReviewStatusComment(allRequiredOwners, unapprovedOwners codeowners.ReviewerGroups, maxReviewsMet bool) error {
// Comment on the PR with the codeowner teams that have not approved the PR

if !a.config.AddComments || len(unapprovedOwners) == 0 {
printDebug("Skipping review status comment (disabled or no unapproved owners).\n")
return nil
}

comment := allRequiredOwners.ToCommentString()
hasHighPriority, err := a.client.IsInLabels(a.conf.HighPriorityLabels)
if err != nil {
printWarning("WARNING: Error checking high priority labels: %v\n", err)
} else if hasHighPriority {
comment = "❗High Prio❗\n\n" + comment
}

if maxReviewsMet {
comment += "\n\nThe PR has received the max number of required reviews. No further action is required."
}

fiveDaysAgo := time.Now().AddDate(0, 0, -5)
found, err := a.client.IsInComments(comment, &fiveDaysAgo)
if err != nil {
return fmt.Errorf("IsInComments Error: %v\n", err)
}

// Add the comment if it wasn't found recently
if !found {
printDebug("Adding review status comment: %q\n", comment)
err = a.client.AddComment(comment)
if err != nil {
return fmt.Errorf("AddComment Error: %v\n", err)
}
} else {
printDebug("Similar review status comment already exists.\n")
}

return nil
}

func (a *App) addOptionalCcComment(allOptionalReviewerNames []string) error {
// Add CC comment to the PR with the optional reviewers that have not already been mentioned in the PR comments

if !a.config.AddComments || len(allOptionalReviewerNames) == 0 {
printDebug("Skipping optional CC comment (disabled or no optional reviewers).\n")
return nil
}

var isInCommentsError error
viewersToPing := f.Filtered(allOptionalReviewerNames, func(name string) bool {
if isInCommentsError != nil {
return false
}
found, err := a.client.IsSubstringInComments(name, nil)
if err != nil {
printWarning("WARNING: Error checking comments for substring '%s': %v\n", name, err)
isInCommentsError = err
return false
}
return !found
})

if isInCommentsError != nil {
return fmt.Errorf("IsInComments Error: %v\n", isInCommentsError)
}

// Add the CC comment if there are any viewers to ping
if len(viewersToPing) > 0 {
comment := fmt.Sprintf("cc %s", strings.Join(viewersToPing, " "))
printDebug("Adding CC comment: %q\n", comment)
err := a.client.AddComment(comment)
if err != nil {
return fmt.Errorf("AddComment Error: %v\n", err)
}
} else {
printDebug("No new optional reviewers to CC.\n")
}

return nil
}

func (a *App) processTokenOwnerApproval() (*gh.CurrentApproval, error) {
tokenOwner, err := a.client.GetTokenUser()
if err != nil {
Expand Down Expand Up @@ -419,11 +466,12 @@ func main() {
}

cfg := AppConfig{
Token: *flags.Token,
RepoDir: *flags.RepoDir,
PR: *flags.PR,
Repo: *flags.Repo,
Verbose: *flags.Verbose,
Token: *flags.Token,
RepoDir: *flags.RepoDir,
PR: *flags.PR,
Repo: *flags.Repo,
Verbose: *flags.Verbose,
AddComments: *flags.AddComments,
}

app, err := NewApp(cfg)
Expand Down
Loading