Skip to content

Commit

Permalink
Add no_newbies feature
Browse files Browse the repository at this point in the history
Perhaps you're tired of Hacktoberfest spam, and don't have a
good set of rules on how to detect invalid PRs? Closing
new PRs from unknown contributors can be an effective
way to do this.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
  • Loading branch information
alexellis committed Oct 1, 2020
1 parent ba1049b commit 561ccb7
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ All features are modular and can be enabled/disabled as required:
* Automatically label/flag PRs without a *Description*
* Label PRs from first-time contributors
* Detect spam PRs (from hacktoberfest)
* Automatically close PRs during hacktoberfest from first-time contributors

Self-host, or use the free managed version.

Expand Down
9 changes: 9 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ features:
- comments
- pr_description_required
- hacktoberfest
- no_newbies
- release_notes
```

Expand All @@ -69,6 +70,14 @@ Generated by Derek

If `pr_description_required` is specified in the feature list then Derek will inform you that a PR needs a description. He also adds the `invalid` label.

### Feature: `no_newbies`

This feature is designed to deter and intercept spam generated during the Hacktoberfest period.

Any new contributors will have their PRs closed with an empathetic comment and instructions to comment on whether their PR is "not spam". From there, you can remove the "invalid" label and merge the PR if required.

This feature is only advisable during the period of October each year.

### Feature: `hacktoberfest`

This feature is used as an effort to enforce the DigitalOcean Hacktoberfest [Quality Standards](https://hacktoberfest.digitalocean.com/details#quality-standards)
Expand Down
67 changes: 65 additions & 2 deletions handler/hacktoberfest_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,55 @@ import (
)

const (
hacktoberfestPRLabel = "invalid"
invalidLabel = "invalid"
)

func HandleFirstTimerPR(req types.PullRequestOuter, contributingURL string, config config.Config) (bool, error) {
ctx := context.Background()
token, tokenErr := getAccessToken(config, req.Installation.ID)

if tokenErr != nil {
fmt.Printf("Error getting installation token: %s\n", tokenErr.Error())
return false, tokenErr
}

client := factory.MakeClient(ctx, token, config)

if req.Action == openedPRAction {

if isFirstTimer(req) {
// Close PR first, to prevent other handlers from executing on "label" events
closeState := "close"
input := &github.IssueRequest{State: &closeState}

_, _, err := client.Issues.Edit(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, input)
if err != nil {
log.Fatalf("unable to close pull request %d: %s", req.PullRequest.Number, err)
return true, err
}

fmt.Println(fmt.Sprintf("Request to close issue #%d was successful.\n", req.PullRequest.Number))

_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number,
[]string{invalidLabel})
if assignLabelErr != nil {
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining)
return true, assignLabelErr
}

body := firstTimerComment(contributingURL)

if err = createPullRequestComment(ctx, body, req, client); err != nil {
log.Fatalf("unable to add comment on PR %d: %s", req.PullRequest.Number, err)
return true, err
}

return true, nil
}
}
return false, nil
}

// HandleHacktoberfestPR checks for opened PR, first time contributor. If only .MD files are changed, issue is closed and invalid label is added
// The goal of this function is to mark pull requests invalid and close them from people only making typo changes without signing their commit (flybys)
func HandleHacktoberfestPR(req types.PullRequestOuter, contributingURL string, config config.Config) (bool, error) {
Expand Down Expand Up @@ -47,7 +93,7 @@ func HandleHacktoberfestPR(req types.PullRequestOuter, contributingURL string, c

fmt.Println(fmt.Sprintf("Request to close issue #%d was successful.\n", req.PullRequest.Number))

_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{hacktoberfestPRLabel})
_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{invalidLabel})
if assignLabelErr != nil {
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining)
return true, assignLabelErr
Expand Down Expand Up @@ -78,6 +124,23 @@ See also [Hacktoberfest quality standards](https://hacktoberfest.digitalocean.co
`
}

func firstTimerComment(contributingURL string) string {
return `Thank you for your interest in this project.
Due to an unfortunate number of spam Pull Requests, all PRs from first-time
contributors are assumed spam until we have a chance to review them.
If your PR is genuine, then please bear with us and we will get to your
change as soon as we can. In the meantime, you can make life easier for
us by commenting "Not Spam" and by checking your PR against the
[contributing guidelines](` + contributingURL + `)
`
}

func isFirstTimer(req types.PullRequestOuter) bool {
return req.PullRequest.FirstTimeContributor()
}

func isHacktoberfestSpam(req types.PullRequestOuter, client *github.Client) bool {
commits, err := fetchPullRequestCommits(req, client)
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
deleted = "deleted"
prDescriptionRequired = "pr_description_required"
hacktoberfest = "hacktoberfest"
noNewbies = "no_newbies"
releaseNotes = "release_notes"
)

Expand Down Expand Up @@ -133,6 +134,12 @@ func handleEvent(eventType string, bytesIn []byte, config config.Config) error {

if req.Action != handler.ClosedConstant && req.PullRequest.State != handler.ClosedConstant {
contributingURL := getContributingURL(derekConfig.ContributingURL, req.Repository.Owner.Login, req.Repository.Name)
if handler.EnabledFeature(noNewbies, derekConfig) {
isSpamPR, _ := handler.HandleFirstTimerPR(req, contributingURL, config)
if isSpamPR {
return nil
}
}
if handler.EnabledFeature(hacktoberfest, derekConfig) {
isSpamPR, _ := handler.HandleHacktoberfestPR(req, contributingURL, config)
if isSpamPR {
Expand Down

0 comments on commit 561ccb7

Please sign in to comment.