Skip to content

Commit

Permalink
Add pr_description_required to features
Browse files Browse the repository at this point in the history
This commit adds a new feature for pr_description_required
This feature will verify that the incoming PR has
something in the description (aka body)

If there is no description, the invalid label will be applied
and a comment will be added to the PR with a link to the
contributing guide.

Signed-off-by: Burton Rheutan <[email protected]>
  • Loading branch information
burtonr authored and alexellis committed Nov 22, 2018
1 parent 4979cb2 commit 7aeec85
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 18 deletions.
4 changes: 4 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ redirect: https://raw.githubusercontent.com/openfaas/faas/master/.DEREK.yml

If `dco_check` is specified in the feature list then Derek will inform you when a PR is submitted with commits which have no sign-off. He also adds a label `no-dco`.

### Feature: `pr_description_required`

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: `comments`

If `comments` is given in the `features` list then this enables all commenting features:
Expand Down
85 changes: 71 additions & 14 deletions handler/pullRequestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,25 @@ import (
"github.com/google/go-github/github"
)

const (
prDescriptionRequiredLabel = "invalid"
openedPRAction = "opened"
)

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

token := os.Getenv("personal_access_token")
if len(token) == 0 {

newToken, tokenErr := auth.MakeAccessTokenForInstallation(
config.ApplicationID,
req.Installation.ID,
config.PrivateKey)

if tokenErr != nil {
log.Fatalln(tokenErr.Error())
}

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

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

hasUnsignedCommits, err := hasUnsigned(req, client)

if req.Action == "opened" {
if req.Action == openedPRAction {
if req.PullRequest.FirstTimeContributor() == true {
_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{"new-contributor"})
if assignLabelErr != nil {
Expand Down Expand Up @@ -101,6 +97,63 @@ That's something we need before your Pull Request can be merged. Please see our
}
}

// VerifyPullRequestDescription checks that the PR has anything in the body.
// If there is no body, a label is added and comment posted to the PR with a link to the contributing guide.
func VerifyPullRequestDescription(req types.PullRequestOuter, contributingURL string, config config.Config) {
ctx := context.Background()
token, tokenErr := getAccessToken(config, req.Installation.ID)

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

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

if req.Action == openedPRAction {
if !hasDescription(req.PullRequest) {
fmt.Printf("Applying label: %s", prDescriptionRequiredLabel)
_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{prDescriptionRequiredLabel})
if assignLabelErr != nil {
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining)
}

body := `Thank you for your contribution. I've just checked and your Pull Request doesn't appear to have any description.
That's something we need before your Pull Request can be merged. Please see our [contributing guide](` + contributingURL + `).`

comment := &github.IssueComment{
Body: &body,
}

comment, resp, err := client.Issues.CreateComment(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, comment)
if err != nil {
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, resp.Limit, resp.Remaining)
log.Fatal(err)
}
fmt.Println(comment, resp.Rate)
}
}
}

func getAccessToken(config config.Config, installationID int) (string, error) {
token := os.Getenv("personal_access_token")
if len(token) == 0 {

installationToken, tokenErr := auth.MakeAccessTokenForInstallation(
config.ApplicationID,
installationID,
config.PrivateKey)

if tokenErr != nil {
return "", tokenErr
}

token = installationToken
}

return token, nil
}

func hasNoDcoLabel(issue *github.Issue) bool {
if issue != nil {
for _, label := range issue.Labels {
Expand Down Expand Up @@ -143,3 +196,7 @@ func hasUnsigned(req types.PullRequestOuter, client *github.Client) (bool, error
func isSigned(msg string) bool {
return strings.Contains(msg, "Signed-off-by:")
}

func hasDescription(pr types.PullRequest) bool {
return len(strings.TrimSpace(pr.Body)) > 0
}
29 changes: 29 additions & 0 deletions handler/pullRequestHandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package handler
import (
"testing"

"github.com/alexellis/derek/types"
"github.com/google/go-github/github"
)

Expand Down Expand Up @@ -95,3 +96,31 @@ func Test_hasNoDcoLabel(t *testing.T) {
})
}
}

func Test_hasDescription(t *testing.T) {
var pr = []struct {
title string
body string
expectedBool bool
}{
{
title: "PR with body",
body: "This PR has a body",
expectedBool: true,
},
{
title: "This PR has no body",
body: "",
expectedBool: false,
},
}

for _, test := range pr {
testPr := types.PullRequest{Body: test.body}
hasDescription := hasDescription(testPr)

if hasDescription != test.expectedBool {
t.Errorf("PR missing body - wanted: %t, found: %t", test.expectedBool, hasDescription)
}
}
}
12 changes: 8 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (
)

const (
dcoCheck = "dco_check"
comments = "comments"
deleted = "deleted"
dcoCheck = "dco_check"
comments = "comments"
deleted = "deleted"
prDescriptionRequired = "pr_description_required"
)

func main() {
Expand Down Expand Up @@ -80,10 +81,13 @@ func handleEvent(eventType string, bytesIn []byte, config config.Config) error {
return fmt.Errorf("Unable to access maintainers file at: %s/%s", req.Repository.Owner.Login, req.Repository.Name)
}
if req.Action != handler.ClosedConstant {
contributingURL := getContributingURL(derekConfig.ContributingURL, req.Repository.Owner.Login, req.Repository.Name)
if handler.EnabledFeature(dcoCheck, derekConfig) {
contributingURL := getContributingURL(derekConfig.ContributingURL, req.Repository.Owner.Login, req.Repository.Name)
handler.HandlePullRequest(req, contributingURL, config)
}
if handler.EnabledFeature(prDescriptionRequired, derekConfig) {
handler.VerifyPullRequestDescription(req, contributingURL, config)
}
}
break

Expand Down
1 change: 1 addition & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Owner struct {
type PullRequest struct {
Number int `json:"number"`
AuthorAssociation string `json:"author_association"`
Body string `json:"body"`
}

type InstallationRequest struct {
Expand Down

0 comments on commit 7aeec85

Please sign in to comment.