From 9e178a27df36fc4628830e360884dde39744c22e Mon Sep 17 00:00:00 2001 From: Burton Rheutan Date: Sat, 10 Nov 2018 15:18:48 -0600 Subject: [PATCH] Add pr_description_required to features 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 --- USER_GUIDE.md | 4 ++ handler/pullRequestHandler.go | 85 +++++++++++++++++++++++++----- handler/pullRequestHandler_test.go | 29 ++++++++++ main.go | 12 +++-- types/types.go | 1 + 5 files changed, 113 insertions(+), 18 deletions(-) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 62c79a9..0fd3564 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -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: diff --git a/handler/pullRequestHandler.go b/handler/pullRequestHandler.go index c3ff350..c0ee35f 100644 --- a/handler/pullRequestHandler.go +++ b/handler/pullRequestHandler.go @@ -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 { @@ -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 { @@ -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 +} diff --git a/handler/pullRequestHandler_test.go b/handler/pullRequestHandler_test.go index e18a28c..77020a2 100644 --- a/handler/pullRequestHandler_test.go +++ b/handler/pullRequestHandler_test.go @@ -6,6 +6,7 @@ package handler import ( "testing" + "github.com/alexellis/derek/types" "github.com/google/go-github/github" ) @@ -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) + } + } +} diff --git a/main.go b/main.go index ae5ac18..9f76671 100644 --- a/main.go +++ b/main.go @@ -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() { @@ -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 diff --git a/types/types.go b/types/types.go index 3548fcd..60a593a 100644 --- a/types/types.go +++ b/types/types.go @@ -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 {