Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use templates for issue e-mail subject and body #8329

Merged
merged 57 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9dbabcd
Merge go-gitea/master into master
guillep2k Sep 14, 2019
889c619
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 14, 2019
d7c46c8
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 15, 2019
8eaacbf
Merge remote-tracking branch 'refs/remotes/origin/master'
guillep2k Sep 19, 2019
de5aa64
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 19, 2019
80c6f2b
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 20, 2019
ac40f7f
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 22, 2019
f6ac46b
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 23, 2019
b563158
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 25, 2019
6f55d1e
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 25, 2019
188e164
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 26, 2019
fd6fe8c
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 27, 2019
54624dd
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 29, 2019
e2a6388
Merge branch 'master' of github.com:go-gitea/gitea
guillep2k Sep 30, 2019
2714a5d
Add template capability for issue mail subject
guillep2k Oct 1, 2019
c4f6c1d
Remove test string
guillep2k Oct 1, 2019
83624a9
Fix trim subject length
guillep2k Oct 1, 2019
c68bbd6
Add comment to template and run make fmt
guillep2k Oct 1, 2019
3d25d11
Add information for the template
guillep2k Oct 1, 2019
a366af6
Merge branch 'master' into fix-email-subject
guillep2k Oct 3, 2019
78b2e34
merge from latest master
guillep2k Oct 10, 2019
6049215
Rename defaultMailSubject() to fallbackMailSubject()
guillep2k Oct 16, 2019
39a6094
Merge branch 'master' into fix-email-subject
guillep2k Oct 21, 2019
70d1d44
General rewrite of the mail template code
guillep2k Oct 21, 2019
120ff55
Merge branch 'master' into fix-email-subject
guillep2k Oct 21, 2019
679338b
Fix .Doer name
guillep2k Oct 21, 2019
24967be
Use text/template for subject instead of html
guillep2k Oct 21, 2019
1ce2530
Fix subject Re: prefix
guillep2k Oct 21, 2019
2bba363
Fix mail tests
guillep2k Oct 22, 2019
6aa4433
Fix static templates
guillep2k Oct 22, 2019
b31fb25
Merge branch 'master' into fix-email-subject
guillep2k Oct 22, 2019
8383b95
[skip ci] Updated translations via Crowdin
GiteaBot Oct 21, 2019
3db7c21
Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool pa…
zeripath Oct 21, 2019
ab1ab0f
Prevent .code-view from overriding font on icon fonts (#8614)
zeripath Oct 21, 2019
7d5074b
Correct some outdated statements in the contributing guidelines (#8612)
lukbukkit Oct 21, 2019
9140b3b
Remove TrN due to lack of lang context
guillep2k Oct 22, 2019
554850e
Redo templates to match previous code
guillep2k Oct 22, 2019
fe6f873
Merge branch 'fix-email-subject' of github.com:guillep2k/gitea into f…
guillep2k Oct 23, 2019
f65e5d2
Fix extra character in template
guillep2k Oct 23, 2019
111a036
Unify PR & Issue tempaltes, fix format
guillep2k Oct 23, 2019
fa9e93b
Remove default subject
guillep2k Oct 23, 2019
cbf32f1
Add template tests
guillep2k Oct 23, 2019
d823b73
Fix template
guillep2k Oct 23, 2019
083f26e
Remove replaced function
guillep2k Oct 23, 2019
1a40a79
Merge branch 'master' into fix-email-subject
guillep2k Oct 23, 2019
74d9c3b
Provide User as models.User for better consistency
guillep2k Oct 23, 2019
e02aa11
Add docs
guillep2k Oct 23, 2019
e1ab7c4
Fix doc inaccuracies, improve examples
guillep2k Oct 23, 2019
e4065fc
Merge branch 'master' into fix-email-subject
guillep2k Oct 23, 2019
c84a489
Change mail footer to math AppName
guillep2k Oct 24, 2019
1d0302d
Merge branch 'master' into fix-email-subject
guillep2k Oct 25, 2019
2cad1b1
Add test for mail subject/body template separation
guillep2k Oct 25, 2019
507ef55
Add support for code review comments
guillep2k Oct 25, 2019
52966ee
Merge master and resolve conflicts
guillep2k Oct 25, 2019
93e8cfa
Update docs/content/doc/advanced/mail-templates-us.md
guillep2k Nov 6, 2019
21d9de0
Merge branch 'master' into fix-email-subject
lunny Nov 6, 2019
79f891f
Merge branch 'master' into fix-email-subject
lunny Nov 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1909,3 +1909,20 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) {
}
return
}

// ComposeSubjectTplData composes a map of metas for properly rendering a mail subject.
func (issue *Issue) ComposeSubjectTplData(doer *User, comment *Comment, actionType ActionType, fromMention bool) (map[string]interface{}, error) {
if err := issue.LoadPullRequest(); err != nil {
return nil, err
}
return map[string]interface{}{
"Issue": issue,
"Comment": comment,
"IsPull": issue.IsPull,
"User": issue.Repo.MustOwner().Name,
"Repo": issue.Repo.FullName(),
"Doer": doer.Name,
"Action": actionType,
"FromMention": fromMention,
}, nil
}
56 changes: 44 additions & 12 deletions services/mailer/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
"bytes"
"fmt"
"html/template"
"mime"
"path"
"regexp"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
Expand All @@ -28,13 +31,18 @@ const (
mailAuthResetPassword base.TplName = "auth/reset_passwd"
mailAuthRegisterNotify base.TplName = "auth/register_notify"

mailIssueSubject base.TplName = "issue/subject"
mailIssueComment base.TplName = "issue/comment"
mailIssueMention base.TplName = "issue/mention"

mailNotifyCollaborator base.TplName = "notify/collaborator"

// There's no actual limit for subject in RFC 5322
mailMaxSubjectRunes = 256
)

var templates *template.Template
var subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)

// InitMailRender initializes the mail renderer
func InitMailRender(tmpls *template.Template) {
Expand Down Expand Up @@ -163,20 +171,35 @@ func composeTplData(subject, body, link string) map[string]interface{} {
return data
}

func composeIssueCommentMessage(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tplName base.TplName, tos []string, info string) *Message {
func composeIssueCommentMessage(issue *models.Issue, doer *models.User, actionType models.ActionType, fromMention bool,
content string, comment *models.Comment, tplBody base.TplName, tos []string, info string) *Message {
var subject string
if comment != nil {
subject = "Re: " + mailSubject(issue)
} else {
subject = mailSubject(issue)
}
err := issue.LoadRepo()
if err != nil {
log.Error("LoadRepo: %v", err)
}

var mailSubject bytes.Buffer
if subjectData, err := issue.ComposeSubjectTplData(doer, comment, actionType, fromMention); err == nil {
if err = templates.ExecuteTemplate(&mailSubject, string(mailIssueSubject), subjectData); err == nil {
subject = sanitizeSubject(mailSubject.String())
}
}
if subject == "" {
if err != nil {
log.Error("Template: %v", err)
}
// Default subject
if comment != nil {
subject = "Re: " + defaultMailSubject(issue)
} else {
subject = defaultMailSubject(issue)
}
}

body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))

var data = make(map[string]interface{}, 10)
var data map[string]interface{}
if comment != nil {
data = composeTplData(subject, body, issue.HTMLURL()+"#"+comment.HashTag())
} else {
Expand All @@ -186,7 +209,7 @@ func composeIssueCommentMessage(issue *models.Issue, doer *models.User, content

var mailBody bytes.Buffer

if err := templates.ExecuteTemplate(&mailBody, string(tplName), data); err != nil {
if err := templates.ExecuteTemplate(&mailBody, string(tplBody), data); err != nil {
log.Error("Template: %v", err)
}

Expand All @@ -204,19 +227,28 @@ func composeIssueCommentMessage(issue *models.Issue, doer *models.User, content
return msg
}

func sanitizeSubject(subject string) string {
runes := []rune(strings.TrimSpace(subjectRemoveSpaces.ReplaceAllLiteralString(subject, " ")))
if len(runes) > mailMaxSubjectRunes {
runes = runes[:mailMaxSubjectRunes]
}
// Encode non-ASCII characters
return mime.QEncoding.Encode("utf-8", string(runes))
}

// SendIssueCommentMail composes and sends issue comment emails to target receivers.
func SendIssueCommentMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
func SendIssueCommentMail(issue *models.Issue, doer *models.User, actionType models.ActionType, content string, comment *models.Comment, tos []string) {
if len(tos) == 0 {
return
}

SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueComment, tos, "issue comment"))
SendAsync(composeIssueCommentMessage(issue, doer, actionType, false, content, comment, mailIssueComment, tos, "issue comment"))
}

// SendIssueMentionMail composes and sends issue mention emails to target receivers.
func SendIssueMentionMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
func SendIssueMentionMail(issue *models.Issue, doer *models.User, actionType models.ActionType, content string, comment *models.Comment, tos []string) {
if len(tos) == 0 {
return
}
SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueMention, tos, "issue mention"))
SendAsync(composeIssueCommentMessage(issue, doer, actionType, true, content, comment, mailIssueMention, tos, "issue mention"))
}
6 changes: 3 additions & 3 deletions services/mailer/mail_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ func mailParticipantsComment(ctx models.DBContext, c *models.Comment, opType mod
}

if len(c.Content) > 0 {
if err = mailIssueCommentToParticipants(issue, c.Poster, c.Content, c, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, c.Poster, opType, c.Content, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}

switch opType {
case models.ActionCloseIssue:
ct := fmt.Sprintf("Closed #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, c.Poster, ct, c, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, c.Poster, opType, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
case models.ActionReopenIssue:
ct := fmt.Sprintf("Reopened #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, c.Poster, ct, c, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, c.Poster, opType, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
Expand Down
16 changes: 8 additions & 8 deletions services/mailer/mail_issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ import (
"github.com/unknwon/com"
)

func mailSubject(issue *models.Issue) string {
func defaultMailSubject(issue *models.Issue) string {
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index)
}

// mailIssueCommentToParticipants can be used for both new issue creation and comment.
// This function sends two list of emails:
// 1. Repository watchers and users who are participated in comments.
// 2. Users who are not in 1. but get mentioned in current issue/comment.
func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, content string, comment *models.Comment, mentions []string) error {
func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, actionType models.ActionType, content string, comment *models.Comment, mentions []string) error {
if !setting.Service.EnableNotifyMail {
return nil
}
Expand Down Expand Up @@ -93,7 +93,7 @@ func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, cont
}

for _, to := range tos {
SendIssueCommentMail(issue, doer, content, comment, []string{to})
SendIssueCommentMail(issue, doer, actionType, content, comment, []string{to})
}

// Mail mentioned people and exclude watchers.
Expand All @@ -110,7 +110,7 @@ func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, cont
emails := models.GetUserEmailsByNames(tos)

for _, to := range emails {
SendIssueMentionMail(issue, doer, content, comment, []string{to})
SendIssueMentionMail(issue, doer, actionType, content, comment, []string{to})
}

return nil
Expand All @@ -130,7 +130,7 @@ func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.Us
}

if len(issue.Content) > 0 {
if err = mailIssueCommentToParticipants(issue, doer, issue.Content, nil, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, doer, opType, issue.Content, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
Expand All @@ -139,18 +139,18 @@ func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.Us
case models.ActionCreateIssue, models.ActionCreatePullRequest:
if len(issue.Content) == 0 {
ct := fmt.Sprintf("Created #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, doer, ct, nil, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, doer, opType, ct, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
case models.ActionCloseIssue, models.ActionClosePullRequest:
ct := fmt.Sprintf("Closed #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, doer, ct, nil, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, doer, opType, ct, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
case models.ActionReopenIssue, models.ActionReopenPullRequest:
ct := fmt.Sprintf("Reopened #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, doer, ct, nil, mentions); err != nil {
if err = mailIssueCommentToParticipants(issue, doer, opType, ct, nil, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}
Expand Down
8 changes: 4 additions & 4 deletions services/mailer/mail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ func TestComposeIssueCommentMessage(t *testing.T) {
InitMailRender(email)

tos := []string{"test@gitea.com", "test2@gitea.com"}
msg := composeIssueCommentMessage(issue, doer, "test body", comment, mailIssueComment, tos, "issue comment")
msg := composeIssueCommentMessage(issue, doer, models.ActionCommentIssue, false, "test body", comment, mailIssueComment, tos, "issue comment")

subject := msg.GetHeader("Subject")
inreplyTo := msg.GetHeader("In-Reply-To")
references := msg.GetHeader("References")

assert.Equal(t, subject[0], "Re: "+mailSubject(issue), "Comment reply subject should contain Re:")
assert.Equal(t, subject[0], "Re: "+defaultMailSubject(issue), "Comment reply subject should contain Re:")
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, inreplyTo[0], "<user2/repo1/issues/1@localhost>", "In-Reply-To header doesn't match")
assert.Equal(t, references[0], "<user2/repo1/issues/1@localhost>", "References header doesn't match")
}
Expand All @@ -79,12 +79,12 @@ func TestComposeIssueMessage(t *testing.T) {
InitMailRender(email)

tos := []string{"test@gitea.com", "test2@gitea.com"}
msg := composeIssueCommentMessage(issue, doer, "test body", nil, mailIssueComment, tos, "issue create")
msg := composeIssueCommentMessage(issue, doer, models.ActionCreateIssue, false, "test body", nil, mailIssueComment, tos, "issue create")

subject := msg.GetHeader("Subject")
messageID := msg.GetHeader("Message-ID")

assert.Equal(t, subject[0], mailSubject(issue), "Subject not equal to issue.mailSubject()")
assert.Equal(t, subject[0], defaultMailSubject(issue), "Subject not equal to issue.mailSubject()")
assert.Nil(t, msg.GetHeader("In-Reply-To"))
assert.Nil(t, msg.GetHeader("References"))
assert.Equal(t, messageID[0], "<user2/repo1/issues/1@localhost>", "Message-ID header doesn't match")
Expand Down
19 changes: 19 additions & 0 deletions templates/mail/issue/subject.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[{{.Repo}}] {{.Doer}}
{{if eq .Action 6}}
created issue #{{.Issue.Index}}
{{else if eq .Action 7}}
created PR #{{.Issue.Index}}
{{else if eq .Action 10}}
commented on #{{.Issue.Index}}
{{else if eq .Action 12}}
closed issue #{{.Issue.Index}}
{{else if eq .Action 13}}
reopened issue #{{.Issue.Index}}
{{else if eq .Action 14}}
closed PR #{{.Issue.Index}}
{{else if eq .Action 15}}
reopened PR #{{.Issue.Index}}
{{else}}
commented/acted upon #{{.Issue.Index}}
{{end}}
- {{.Issue.Title}}