Skip to content

Commit 19b86e7

Browse files
committed
Generate footer from template
Signed-off-by: David Pordomingo <[email protected]>
1 parent 745123d commit 19b86e7

File tree

8 files changed

+129
-34
lines changed

8 files changed

+129
-34
lines changed

.helm-staging.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ providers:
2727
app_id: 17877
2828
private_key: /local/lookout/private-key.pem
2929
secretName: lookout-staging-github-key
30-
comment_footer: '_If you have feedback about this comment, please, [tell us](%s)._'
30+
comment_footer: "_{{if .Feedback}}If you have feedback about this comment made by the analyzer {{.Name}}, please, [tell us]({{.Feedback}}){{else}}Comment made by the analyzer {{.Name}}{{end}}._"
3131
installation_sync_interval: 5m
3232

3333
analyzers:

cmd/lookoutd/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func (c *queueConsumerCommand) initPoster(conf Config) (lookout.Poster, error) {
324324

325325
switch c.Provider {
326326
case github.Provider:
327-
return github.NewPoster(c.pool, conf.Providers.Github), nil
327+
return github.NewPoster(c.pool, conf.Providers.Github)
328328
case json.Provider:
329329
return json.NewPoster(os.Stdout), nil
330330
default:

config.yml.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ analyzers:
88

99
providers:
1010
github:
11-
comment_footer: '_If you have feedback about this comment, please, [tell us](%s)._'
11+
comment_footer: "_{{if .Feedback}}If you have feedback about this comment made by the analyzer {{.Name}}, please, [tell us]({{.Feedback}}){{else}}Comment made by the analyzer {{.Name}}{{end}}._"
1212
# The minimum watch interval to discover new pull requests and push events
1313
watch_min_interval: 2s
1414
# Authorization with GitHub App

docs/configuration.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ The `providers.github` key configures how **source{d} Lookout** will connect wit
3939
```yaml
4040
providers:
4141
github:
42-
comment_footer: "_If you have feedback about this comment, please, [tell us](%s)._"
42+
comment_footer: "_Comment made by '{{.Name}}'{{with .Feedback}}, [tell us]({{.}}){{end}}._"
4343
# app_id: 1234
4444
# private_key: ./key.pem
4545
# installation_sync_interval: 1h
4646
# watch_min_interval: 2s
4747
```
4848

49-
`comment_footer` key defines a format-string that will be used for custom messages for every message posted on GitHub; see how to [add a custom message to the posted comments](#add-a-custom-message-to-the-posted-comments)
49+
`comment_footer` key defines the [go template](https://golang.org/pkg/text/template) that will be used for custom messages for every message posted on GitHub; see how to [add a custom message to the posted comments](#add-a-custom-message-to-the-posted-comments)
5050

5151
### Authentication with GitHub
5252

@@ -141,17 +141,31 @@ analyzers:
141141

142142
### Add a Custom Message to the Posted Comments
143143

144-
You can configure **source{d} Lookout** to add a custom message to every comment that each analyzer returns. This custom message will be created following the rule:
145-
```
146-
sprintf(providers.github.comment_footer, feedback)
147-
```
148-
If any of those two keys are not defined, the custom message won't be added.
144+
You can configure **source{d} Lookout** to add a custom message to every comment that each analyzer returns. This custom message will be created from the template defined by `providers.github.comment_footer`, using the configuration set for each analyzer.
149145

150-
Example:
151-
```text
152-
"_If you have feedback about this comment, please, [tell us](%s)._"
146+
If the template (`providers.github.comment_footer`) is empty, or the analyzer configuration does not define any of the values that the template requires, the custom message won't be added.
147+
148+
For example, for this configuration, each analyzer needs to define `name` and `settings.email`:
149+
150+
```yaml
151+
providers:
152+
github:
153+
comment_footer: "Comment made by analyzer {{.Name}}, [email me]({{.Settings.email}})."
154+
155+
analyzers:
156+
- name: Fancy Analyzer
157+
addr: ipv4://localhost:9930
158+
settings:
159+
160+
- name: Awesome Analyzer
161+
addr: ipv4://localhost:9931
153162
```
154163

164+
Comments from `Fancy Analyzer` will have this footer appended:
165+
>_Comment made by analyzer Fancy Analyzer, [email me]([email protected])._
166+
167+
but comments from `Awesome Analyzer` wont have a footer message because in its configuration it's missing the `settings.email` value.
168+
155169

156170
## Timeouts
157171

provider/github/poster.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"reflect"
77
"strings"
8+
"text/template"
89

910
"github.com/src-d/lookout"
1011
"github.com/src-d/lookout/util/ctxlog"
@@ -32,18 +33,27 @@ const (
3233

3334
// Poster posts comments as Pull Request Reviews.
3435
type Poster struct {
35-
pool *ClientPool
36-
conf ProviderConfig
36+
pool *ClientPool
37+
conf ProviderConfig
38+
footerTemplate *template.Template
3739
}
3840

3941
var _ lookout.Poster = &Poster{}
4042

4143
// NewPoster creates a new poster for the GitHub API.
42-
func NewPoster(pool *ClientPool, conf ProviderConfig) *Poster {
43-
return &Poster{
44-
pool: pool,
45-
conf: conf,
44+
func NewPoster(pool *ClientPool, conf ProviderConfig) (*Poster, error) {
45+
tpl, err := newFooterTemplate(conf.CommentFooter)
46+
if ErrEmptyTemplate.Is(err) {
47+
log.DefaultLogger.Warningf("no footer template being used: %s", err)
48+
} else if err != nil {
49+
return nil, err
4650
}
51+
52+
return &Poster{
53+
pool: pool,
54+
conf: conf,
55+
footerTemplate: tpl,
56+
}, nil
4757
}
4858

4959
// Post posts comments as a Pull Request Review.
@@ -157,15 +167,11 @@ func (p *Poster) createReviewRequest(
157167
}
158168

159169
var bodyComments []string
160-
tmpl := p.conf.CommentFooter
161-
162170
for _, aComments := range aCommentsList {
163171
ctx, _ := ctxlog.WithLogFields(ctx, log.Fields{
164172
"analyzer": aComments.Config.Name,
165173
})
166174

167-
url := aComments.Config.Feedback
168-
169175
forBody, ghComments := convertComments(ctx, aComments.Comments, dl)
170176

171177
if len(postedComments) > 0 {
@@ -175,13 +181,13 @@ func (p *Poster) createReviewRequest(
175181
ghComments = mergeComments(ghComments)
176182

177183
for i, c := range ghComments {
178-
body := addFootnote(c.GetBody(), tmpl, url)
184+
body := addFootnote(ctx, c.GetBody(), p.footerTemplate, &aComments.Config)
179185
ghComments[i].Body = &body
180186
}
181187

182188
bodyComments = append(
183189
bodyComments,
184-
addFootnote(strings.Join(forBody, "\n\n"), tmpl, url),
190+
addFootnote(ctx, strings.Join(forBody, "\n\n"), p.footerTemplate, &aComments.Config),
185191
)
186192
req.Comments = append(req.Comments, ghComments...)
187193
}

provider/github/poster_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,10 @@ func (s *PosterTestSuite) TestPostFooter() {
205205
aComments := mockAnalyzerComments
206206
aComments[0].Config.Feedback = "https://foo.bar/feedback"
207207

208+
footerTpl, _ := newFooterTemplate("To post feedback go to {{.Feedback}}")
208209
p := &Poster{
209-
pool: s.pool,
210-
conf: ProviderConfig{
211-
CommentFooter: "To post feedback go to %s",
212-
},
210+
pool: s.pool,
211+
footerTemplate: footerTpl,
213212
}
214213
err := p.Post(context.Background(), mockEvent, aComments, false)
215214
s.NoError(err)
@@ -526,3 +525,22 @@ func intptr(v int) *int {
526525
func int64ptr(v int64) *int64 {
527526
return &v
528527
}
528+
529+
func (s *PosterTestSuite) TestCouldNotParseFooterTemplate() {
530+
emptyTemplateRaw := ""
531+
posterWithEmptyTemplate, err := NewPoster(nil, ProviderConfig{CommentFooter: emptyTemplateRaw})
532+
s.Nil(err, "NewPoster must return no error when parsing an empty template")
533+
emptyTemplate := posterWithEmptyTemplate.footerTemplate
534+
commentsEmptyTemplate := addFootnote(context.TODO(), "comments", emptyTemplate, nil)
535+
s.Equal("comments", commentsEmptyTemplate)
536+
537+
oldTemplateRaw := "Old template %s"
538+
posterWithOldTemplate, err := NewPoster(nil, ProviderConfig{CommentFooter: oldTemplateRaw})
539+
s.Nil(posterWithOldTemplate, "NewPoster must fail when parsing an old template config")
540+
s.True(ErrOldTemplate.Is(err), "Error should be 'ErrOldTemplate'")
541+
542+
wrongTemplateeRaw := "Old template {{{parseerror"
543+
posterWithWrongTemplate, err := NewPoster(nil, ProviderConfig{CommentFooter: wrongTemplateeRaw})
544+
s.Nil(posterWithWrongTemplate, "NewPoster must fail when parsing a wrong template config")
545+
s.True(ErrParseTemplate.Is(err), "Error should be 'ErrParseTemplate'")
546+
}

provider/github/review.go

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package github
22

33
import (
44
"context"
5-
"fmt"
65
"sort"
76
"strings"
7+
"text/template"
88
"time"
99

1010
"github.com/google/go-github/github"
1111
"github.com/src-d/lookout"
1212
"github.com/src-d/lookout/util/ctxlog"
13+
errors "gopkg.in/src-d/go-errors.v1"
1314
log "gopkg.in/src-d/go-log.v1"
1415
)
1516

@@ -29,6 +30,13 @@ const commentsSeparator = "\n<!-- lookout comment separator -->\n---\n"
2930
// comment can contain footer with link to the analyzer
3031
const footnoteSeparator = "\n<!-- lookout footnote separator -->\n"
3132

33+
var (
34+
ErrEmptyTemplate = errors.NewKind("empty footer template")
35+
ErrOldTemplate = errors.NewKind("old footer template: '%%s' placeholder is no longer supported: '%s'")
36+
ErrParseTemplate = errors.NewKind("error parsing footer template: %s")
37+
ErrTemplateError = errors.NewKind("error generating the footer: %s")
38+
)
39+
3240
// createReview creates pull request review on github using multiple http calls
3341
// in case of too many comments
3442
func createReview(
@@ -207,13 +215,53 @@ func splitReviewRequest(review *github.PullRequestReviewRequest, n int) []*githu
207215
return result
208216
}
209217

218+
func newFooterTemplate(tpl string) (*template.Template, error) {
219+
if tpl == "" {
220+
return nil, ErrEmptyTemplate.New()
221+
}
222+
223+
if strings.Index(tpl, "%s") >= 0 {
224+
return nil, ErrOldTemplate.New(tpl)
225+
}
226+
227+
template, err := template.New("footer").Parse(tpl)
228+
if err != nil {
229+
return nil, ErrParseTemplate.New(err)
230+
}
231+
232+
return template.Option("missingkey=error"), nil
233+
}
234+
210235
// addFootnote adds footnote link to text of a comment
211-
func addFootnote(text, tmpl, url string) string {
212-
if text == "" || tmpl == "" || url == "" {
213-
return text
236+
func addFootnote(
237+
ctx context.Context,
238+
comment string, tmpl *template.Template, analyzerConf *lookout.AnalyzerConfig,
239+
) string {
240+
if comment == "" || tmpl == nil {
241+
return comment
242+
}
243+
244+
footer, err := getFootnote(tmpl, analyzerConf)
245+
if err != nil {
246+
ctxlog.Get(ctx).Warningf("footer could not be generated: %s", err)
247+
return comment
248+
}
249+
250+
return comment + footer
251+
}
252+
253+
func getFootnote(tmpl *template.Template, analyzerConf *lookout.AnalyzerConfig) (string, error) {
254+
var footer strings.Builder
255+
if err := tmpl.Execute(&footer, analyzerConf); err != nil {
256+
return "", ErrTemplateError.New(err)
257+
}
258+
259+
footerTxt := footer.String()
260+
if footerTxt == "" {
261+
return "", nil
214262
}
215263

216-
return text + footnoteSeparator + fmt.Sprintf(tmpl, url)
264+
return footnoteSeparator + footer.String(), nil
217265
}
218266

219267
// removeFootnote removes footnote and returns only text of a comment

provider/github/review_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,12 @@ func TestConvertCommentsWrongFile(t *testing.T) {
225225
Body: strptr("Line comment"),
226226
}}, ghComments)
227227
}
228+
229+
func TestCouldNotExecuteFooterTemplate(t *testing.T) {
230+
require := require.New(t)
231+
232+
unkonwnDataTemplate, err := newFooterTemplate("Old template {{.UnknownData}}")
233+
require.Nil(err)
234+
commentsWrongTemplate := addFootnote(context.TODO(), "comments", unkonwnDataTemplate, nil)
235+
require.Equal("comments", commentsWrongTemplate)
236+
}

0 commit comments

Comments
 (0)