github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/welcome/welcome.go (about) 1 /* 2 Copyright 2018 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 welcome implements a prow plugin to welcome new contributors 18 package welcome 19 20 import ( 21 "bytes" 22 "fmt" 23 "html/template" 24 "strings" 25 26 "github.com/sirupsen/logrus" 27 28 "k8s.io/test-infra/prow/github" 29 "k8s.io/test-infra/prow/pluginhelp" 30 "k8s.io/test-infra/prow/plugins" 31 ) 32 33 const ( 34 pluginName = "welcome" 35 defaultWelcomeMessage = "Welcome @{{.AuthorLogin}}! It looks like this is your first PR to {{.Org}}/{{.Repo}} 🎉" 36 ) 37 38 // PRInfo contains info used provided to the welcome message template 39 type PRInfo struct { 40 Org string 41 Repo string 42 AuthorLogin string 43 AuthorName string 44 } 45 46 func init() { 47 plugins.RegisterPullRequestHandler(pluginName, handlePullRequest, helpProvider) 48 } 49 50 func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) { 51 welcomeConfig := map[string]string{} 52 for _, repo := range enabledRepos { 53 parts := strings.Split(repo, "/") 54 if len(parts) != 2 { 55 return nil, fmt.Errorf("invalid repo in enabledRepos: %q", repo) 56 } 57 messageTemplate := welcomeMessageForRepo(config, parts[0], parts[1]) 58 welcomeConfig[repo] = fmt.Sprintf("The welcome plugin is configured to post using following welcome template: %s.", messageTemplate) 59 } 60 61 // The {WhoCanUse, Usage, Examples} fields are omitted because this plugin is not triggered with commands. 62 return &pluginhelp.PluginHelp{ 63 Description: "The welcome plugin posts a welcoming message when it detects a user's first contribution to a repo.", 64 Config: welcomeConfig, 65 }, 66 nil 67 } 68 69 type githubClient interface { 70 CreateComment(owner, repo string, number int, comment string) error 71 FindIssues(query, sort string, asc bool) ([]github.Issue, error) 72 } 73 74 type client struct { 75 GitHubClient githubClient 76 Logger *logrus.Entry 77 } 78 79 func getClient(pc plugins.Agent) client { 80 return client{ 81 GitHubClient: pc.GitHubClient, 82 Logger: pc.Logger, 83 } 84 } 85 86 func handlePullRequest(pc plugins.Agent, pre github.PullRequestEvent) error { 87 return handlePR(getClient(pc), pre, welcomeMessageForRepo(pc.PluginConfig, pre.Repo.Owner.Login, pre.Repo.Name)) 88 } 89 90 func handlePR(c client, pre github.PullRequestEvent, welcomeTemplate string) error { 91 // Only consider newly opened PRs 92 if pre.Action != github.PullRequestActionOpened { 93 return nil 94 } 95 96 // search for PRs from the author in this repo 97 org := pre.PullRequest.Base.Repo.Owner.Login 98 repo := pre.PullRequest.Base.Repo.Name 99 user := pre.PullRequest.User.Login 100 query := fmt.Sprintf("is:pr repo:%s/%s author:%s", org, repo, user) 101 issues, err := c.GitHubClient.FindIssues(query, "", false) 102 if err != nil { 103 return err 104 } 105 106 // if there are no results, this is the first! post the welcome comment 107 if len(issues) == 0 || len(issues) == 1 && issues[0].Number == pre.Number { 108 // load the template, and run it over the PR info 109 parsedTemplate, err := template.New("welcome").Parse(welcomeTemplate) 110 if err != nil { 111 return err 112 } 113 var msgBuffer bytes.Buffer 114 err = parsedTemplate.Execute(&msgBuffer, PRInfo{ 115 Org: org, 116 Repo: repo, 117 AuthorLogin: user, 118 AuthorName: pre.PullRequest.User.Name, 119 }) 120 if err != nil { 121 return err 122 } 123 124 // actually post the comment 125 return c.GitHubClient.CreateComment(org, repo, pre.PullRequest.Number, msgBuffer.String()) 126 } 127 128 return nil 129 } 130 131 func welcomeMessageForRepo(config *plugins.Configuration, org, repo string) string { 132 opts := optionsForRepo(config, org, repo) 133 if opts.MessageTemplate != "" { 134 return opts.MessageTemplate 135 } 136 return defaultWelcomeMessage 137 } 138 139 // optionsForRepo gets the plugins.Welcome struct that is applicable to the indicated repo. 140 func optionsForRepo(config *plugins.Configuration, org, repo string) *plugins.Welcome { 141 fullName := fmt.Sprintf("%s/%s", org, repo) 142 143 // First search for repo config 144 for _, c := range config.Welcome { 145 if !strInSlice(fullName, c.Repos) { 146 continue 147 } 148 return &c 149 } 150 151 // If you don't find anything, loop again looking for an org config 152 for _, c := range config.Welcome { 153 if !strInSlice(org, c.Repos) { 154 continue 155 } 156 return &c 157 } 158 159 // Return an empty config, and default to defaultWelcomeMessage 160 return &plugins.Welcome{} 161 } 162 163 func strInSlice(str string, slice []string) bool { 164 for _, elem := range slice { 165 if elem == str { 166 return true 167 } 168 } 169 return false 170 }