From 510e89c22be770a80256751c2273fc55219bbd9b Mon Sep 17 00:00:00 2001 From: "j. mccann" Date: Tue, 16 Jul 2019 01:59:38 -0400 Subject: [PATCH 1/4] Include thread related headers in issue/coment mail Make it so mail programs will group comments from an issue into the same thread by setting Message-ID on initial issue and then using In-Reply-To and References headers to reference that later on. --- models/issue.go | 12 ++++++++++++ models/mail.go | 17 ++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/models/issue.go b/models/issue.go index 63074cd40ce8c..bde5758b02693 100644 --- a/models/issue.go +++ b/models/issue.go @@ -472,6 +472,18 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { } } +// ReplyReference returns tokenized address to use for email reply headers +func (issue *Issue) ReplyReference() string { + var path string + if issue.IsPull { + path = "pulls" + } else { + path = "issues" + } + + return fmt.Sprintf("%s/%s/%d@%s", issue.Repo.FullName(), path, issue.Index, setting.Domain) +} + func (issue *Issue) addLabel(e *xorm.Session, label *Label, doer *User) error { return newIssueLabel(e, issue, label, doer) } diff --git a/models/mail.go b/models/mail.go index 2bb07607a4119..7dd61fb387ae1 100644 --- a/models/mail.go +++ b/models/mail.go @@ -156,7 +156,13 @@ func composeTplData(subject, body, link string) map[string]interface{} { } func composeIssueCommentMessage(issue *Issue, doer *User, content string, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { - subject := issue.mailSubject() + var subject string + if comment != nil { + subject = "Re: " + issue.mailSubject() + } else { + subject = issue.mailSubject() + } + err := issue.LoadRepo() if err != nil { log.Error("LoadRepo: %v", err) @@ -179,6 +185,15 @@ func composeIssueCommentMessage(issue *Issue, doer *User, content string, commen msg := mailer.NewMessageFrom(tos, doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String()) msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) + + // Set Message-ID on first message so replies know what to reference + if comment == nil { + msg.SetHeader("Message-ID", "<"+issue.ReplyReference()+">") + } + + msg.SetHeader("In-Reply-To", "<"+issue.ReplyReference()+">") + msg.SetHeader("References", "<"+issue.ReplyReference()+">") + return msg } From b854c652855b7cfe1c3d0646f5e1307dce702a9a Mon Sep 17 00:00:00 2001 From: "j. mccann" Date: Tue, 16 Jul 2019 19:51:39 -0400 Subject: [PATCH 2/4] Add tests --- models/mail_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 models/mail_test.go diff --git a/models/mail_test.go b/models/mail_test.go new file mode 100644 index 0000000000000..022af52529e5a --- /dev/null +++ b/models/mail_test.go @@ -0,0 +1,75 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "html/template" + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +const tmpl = ` + + + + + {{.Subject}} + + + +

{{.Body}}

+

+ --- +
+ View it on Gitea. +

+ + +` + +func TestComposeIssueCommentMessage(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + var MailService setting.Mailer + + MailService.From = "test@gitea.com" + setting.MailService = &MailService + + doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, Owner: doer}).(*Repository) + issue := AssertExistsAndLoadBean(t, &Issue{ID: 1, Repo: repo, Poster: doer}).(*Issue) + comment := AssertExistsAndLoadBean(t, &Comment{ID: 2, Issue: issue}).(*Comment) + + email := template.Must(template.New("issue/comment").Parse(tmpl)) + InitMailRender(email) + + tos := []string{"test@gitea.com", "test2@gitea.com"} + msg := composeIssueCommentMessage(issue, doer, "test body", comment, mailIssueComment, tos, "issue comment") + subject := msg.GetHeader("Subject") + assert.Equal(t, subject[0], "Re: "+issue.mailSubject(), "Comment reply subject should contain Re:") + +} + +func TestComposeIssueMessage(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + var MailService setting.Mailer + + MailService.From = "test@gitea.com" + setting.MailService = &MailService + + doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, Owner: doer}).(*Repository) + issue := AssertExistsAndLoadBean(t, &Issue{ID: 1, Repo: repo, Poster: doer}).(*Issue) + + email := template.Must(template.New("issue/comment").Parse(tmpl)) + InitMailRender(email) + + tos := []string{"test@gitea.com", "test2@gitea.com"} + msg := composeIssueCommentMessage(issue, doer, "test body", nil, mailIssueComment, tos, "issue create") + subject := msg.GetHeader("Subject") + assert.Equal(t, subject[0], issue.mailSubject(), "Subject not equal to issue.mailSubject()") +} From c44a0c3864853426a3f5eb3edf4103fa1c403378 Mon Sep 17 00:00:00 2001 From: "j. mccann" Date: Tue, 16 Jul 2019 20:27:37 -0400 Subject: [PATCH 3/4] more tests --- models/mail.go | 6 +++--- models/mail_test.go | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/models/mail.go b/models/mail.go index 7dd61fb387ae1..cd4e4bc804c2a 100644 --- a/models/mail.go +++ b/models/mail.go @@ -189,11 +189,11 @@ func composeIssueCommentMessage(issue *Issue, doer *User, content string, commen // Set Message-ID on first message so replies know what to reference if comment == nil { msg.SetHeader("Message-ID", "<"+issue.ReplyReference()+">") + } else { + msg.SetHeader("In-Reply-To", "<"+issue.ReplyReference()+">") + msg.SetHeader("References", "<"+issue.ReplyReference()+">") } - msg.SetHeader("In-Reply-To", "<"+issue.ReplyReference()+">") - msg.SetHeader("References", "<"+issue.ReplyReference()+">") - return msg } diff --git a/models/mail_test.go b/models/mail_test.go index 022af52529e5a..f52a09c40ad4c 100644 --- a/models/mail_test.go +++ b/models/mail_test.go @@ -49,8 +49,14 @@ func TestComposeIssueCommentMessage(t *testing.T) { tos := []string{"test@gitea.com", "test2@gitea.com"} msg := composeIssueCommentMessage(issue, doer, "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: "+issue.mailSubject(), "Comment reply subject should contain Re:") + assert.Equal(t, inreplyTo[0], "", "In-Reply-To header doesn't match") + assert.Equal(t, references[0], "", "References header doesn't match") } @@ -70,6 +76,12 @@ func TestComposeIssueMessage(t *testing.T) { tos := []string{"test@gitea.com", "test2@gitea.com"} msg := composeIssueCommentMessage(issue, doer, "test body", nil, mailIssueComment, tos, "issue create") + subject := msg.GetHeader("Subject") + messageID := msg.GetHeader("Message-ID") + assert.Equal(t, subject[0], issue.mailSubject(), "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], "", "References header doesn't match") } From 99e3ea0ccbaf69862c6bac88843c844ecf2edc9f Mon Sep 17 00:00:00 2001 From: "j. mccann" Date: Tue, 16 Jul 2019 20:29:03 -0400 Subject: [PATCH 4/4] fix typo --- models/mail_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/mail_test.go b/models/mail_test.go index f52a09c40ad4c..51c52427f027e 100644 --- a/models/mail_test.go +++ b/models/mail_test.go @@ -83,5 +83,5 @@ func TestComposeIssueMessage(t *testing.T) { assert.Equal(t, subject[0], issue.mailSubject(), "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], "", "References header doesn't match") + assert.Equal(t, messageID[0], "", "Message-ID header doesn't match") }