github.com/abayer/test-infra@v0.0.5/prow/plugins/help/help.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package help 18 19 import ( 20 "regexp" 21 "strings" 22 23 "github.com/sirupsen/logrus" 24 25 "k8s.io/test-infra/prow/github" 26 "k8s.io/test-infra/prow/pluginhelp" 27 "k8s.io/test-infra/prow/plugins" 28 ) 29 30 const pluginName = "help" 31 32 var ( 33 helpLabel = "help wanted" 34 goodFirstIssueLabel = "good first issue" 35 helpRe = regexp.MustCompile(`(?mi)^/help\s*$`) 36 helpRemoveRe = regexp.MustCompile(`(?mi)^/remove-help\s*$`) 37 helpGoodFirstIssueRe = regexp.MustCompile(`(?mi)^/good-first-issue\s*$`) 38 helpGoodFirstIssueRemoveRe = regexp.MustCompile(`(?mi)^/remove-good-first-issue\s*$`) 39 helpGuidelinesURL = "https://git.k8s.io/community/contributors/devel/help-wanted.md" 40 helpMsgPruneMatch = "This request has been marked as needing help from a contributor." 41 helpMsg = ` 42 This request has been marked as needing help from a contributor. 43 44 Please ensure the request meets the requirements listed [here](` + helpGuidelinesURL + `). 45 46 If this request no longer meets these requirements, the label can be removed 47 by commenting with the ` + "`/remove-help`" + ` command. 48 ` 49 goodFirstIssueMsgPruneMatch = "This request has been marked as suitable for new contributors." 50 goodFirstIssueMsg = ` 51 This request has been marked as suitable for new contributors. 52 53 Please ensure the request meets the requirements listed [here](` + helpGuidelinesURL + "#good-first-issue" + `). 54 55 If this request no longer meets these requirements, the label can be removed 56 by commenting with the ` + "`/remove-good-first-issue`" + ` command. 57 ` 58 ) 59 60 func init() { 61 plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider) 62 } 63 64 func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) { 65 // The Config field is omitted because this plugin is not configurable. 66 pluginHelp := &pluginhelp.PluginHelp{ 67 Description: "The help plugin provides commands that add or remove the '" + helpLabel + "' and the '" + goodFirstIssueLabel + "' labels from issues.", 68 } 69 pluginHelp.AddCommand(pluginhelp.Command{ 70 Usage: "/[remove-](help|good-first-issue)", 71 Description: "Applies or removes the '" + helpLabel + "' and '" + goodFirstIssueLabel + "' labels to an issue.", 72 Featured: false, 73 WhoCanUse: "Anyone can trigger this command on a PR.", 74 Examples: []string{"/help", "/remove-help", "/good-first-issue", "/remove-good-first-issue"}, 75 }) 76 return pluginHelp, nil 77 } 78 79 type githubClient interface { 80 BotName() (string, error) 81 CreateComment(owner, repo string, number int, comment string) error 82 AddLabel(owner, repo string, number int, label string) error 83 RemoveLabel(owner, repo string, number int, label string) error 84 GetIssueLabels(org, repo string, number int) ([]github.Label, error) 85 } 86 87 type commentPruner interface { 88 PruneComments(shouldPrune func(github.IssueComment) bool) 89 } 90 91 func handleGenericComment(pc plugins.PluginClient, e github.GenericCommentEvent) error { 92 return handle(pc.GitHubClient, pc.Logger, pc.CommentPruner, &e) 93 } 94 95 func handle(gc githubClient, log *logrus.Entry, cp commentPruner, e *github.GenericCommentEvent) error { 96 org := e.Repo.Owner.Login 97 repo := e.Repo.Name 98 commentAuthor := e.User.Login 99 100 // Determine if the issue has the help and the good-first-issue label 101 labels, err := gc.GetIssueLabels(org, repo, e.Number) 102 if err != nil { 103 log.WithError(err).Errorf("Failed to get issue labels.") 104 } 105 hasHelp := github.HasLabel(helpLabel, labels) 106 hasGoodFirstIssue := github.HasLabel(goodFirstIssueLabel, labels) 107 108 // If PR has help label and we're asking for it to be removed, remove label 109 if hasHelp && helpRemoveRe.MatchString(e.Body) { 110 if err := gc.RemoveLabel(org, repo, e.Number, helpLabel); err != nil { 111 log.WithError(err).Errorf("Github failed to remove the following label: %s", helpLabel) 112 } 113 114 botName, err := gc.BotName() 115 if err != nil { 116 log.WithError(err).Errorf("Failed to get bot name.") 117 } 118 cp.PruneComments(shouldPrune(log, botName, helpMsgPruneMatch)) 119 120 // if it has the good-first-issue label, remove it too 121 if hasGoodFirstIssue { 122 if err := gc.RemoveLabel(org, repo, e.Number, goodFirstIssueLabel); err != nil { 123 log.WithError(err).Errorf("Github failed to remove the following label: %s", goodFirstIssueLabel) 124 } 125 cp.PruneComments(shouldPrune(log, botName, goodFirstIssueMsgPruneMatch)) 126 } 127 128 return nil 129 } 130 131 // If PR does not have the good-first-issue label and we are asking for it to be added, 132 // add both the good-first-issue and help labels 133 if !hasGoodFirstIssue && helpGoodFirstIssueRe.MatchString(e.Body) { 134 if err := gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.IssueHTMLURL, commentAuthor, goodFirstIssueMsg)); err != nil { 135 log.WithError(err).Errorf("Failed to create comment \"%s\".", goodFirstIssueMsg) 136 } 137 138 if err := gc.AddLabel(org, repo, e.Number, goodFirstIssueLabel); err != nil { 139 log.WithError(err).Errorf("Github failed to add the following label: %s", goodFirstIssueLabel) 140 } 141 142 if !hasHelp { 143 if err := gc.AddLabel(org, repo, e.Number, helpLabel); err != nil { 144 log.WithError(err).Errorf("Github failed to add the following label: %s", helpLabel) 145 } 146 } 147 148 return nil 149 } 150 151 // If PR does not have the help label and we're asking it to be added, 152 // add the label 153 if !hasHelp && helpRe.MatchString(e.Body) { 154 if err := gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.IssueHTMLURL, commentAuthor, helpMsg)); err != nil { 155 log.WithError(err).Errorf("Failed to create comment \"%s\".", helpMsg) 156 } 157 if err := gc.AddLabel(org, repo, e.Number, helpLabel); err != nil { 158 log.WithError(err).Errorf("Github failed to add the following label: %s", helpLabel) 159 } 160 161 return nil 162 } 163 164 // If PR has good-first-issue label and we are asking for it to be removed, 165 // remove just the good-first-issue label 166 if hasGoodFirstIssue && helpGoodFirstIssueRemoveRe.MatchString(e.Body) { 167 if err := gc.RemoveLabel(org, repo, e.Number, goodFirstIssueLabel); err != nil { 168 log.WithError(err).Errorf("Github failed to remove the following label: %s", goodFirstIssueLabel) 169 } 170 171 botName, err := gc.BotName() 172 if err != nil { 173 log.WithError(err).Errorf("Failed to get bot name.") 174 } 175 cp.PruneComments(shouldPrune(log, botName, goodFirstIssueMsgPruneMatch)) 176 177 return nil 178 } 179 180 return nil 181 } 182 183 // shouldPrune finds comments left by this plugin. 184 func shouldPrune(log *logrus.Entry, botName, msgPruneMatch string) func(github.IssueComment) bool { 185 return func(comment github.IssueComment) bool { 186 if comment.User.Login != botName { 187 return false 188 } 189 return strings.Contains(comment.Body, msgPruneMatch) 190 } 191 }