[refactor] mailer service (#15072)

* Unexport SendUserMail

* Instead of "[]*models.User" or "[]string" lists infent "[]*MailRecipient" for mailer

* adopt

* code format

* TODOs for "i18n"

* clean

* no fallback for lang -> just use english

* lint

* exec testComposeIssueCommentMessage per lang and use only emails

* rm MailRecipient

* Dont reload from users from db if you alredy have in ram

* nits

* minimize diff

Signed-off-by: 6543 <6543@obermui.de>

* localize subjects

* linter ...

* Tr extend

* start tmpl edit ...

* Apply suggestions from code review

* use translation.Locale

* improve mailIssueCommentBatch

Signed-off-by: Andrew Thornton <art27@cantab.net>

* add i18n to datas

Signed-off-by: Andrew Thornton <art27@cantab.net>

* a comment

Co-authored-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
6543 2021-04-02 12:25:13 +02:00 committed by GitHub
parent cc2d540092
commit 80d6c6d7de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 191 additions and 151 deletions

View file

@ -23,11 +23,16 @@ type mailCommentContext struct {
Comment *models.Comment
}
const (
// MailBatchSize set the batch size used in mailIssueCommentBatch
MailBatchSize = 100
)
// 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(ctx *mailCommentContext, mentions []int64) error {
func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*models.User) error {
// Required by the mail composer; make sure to load these before calling the async function
if err := ctx.Issue.LoadRepo(); err != nil {
@ -94,78 +99,72 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) e
visited[i] = true
}
if err = mailIssueCommentBatch(ctx, unfiltered, visited, false); err != nil {
unfilteredUsers, err := models.GetMaileableUsersByIDs(unfiltered, false)
if err != nil {
return err
}
if err = mailIssueCommentBatch(ctx, unfilteredUsers, visited, false); err != nil {
return fmt.Errorf("mailIssueCommentBatch(): %v", err)
}
return nil
}
func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int64]bool, fromMention bool) error {
const batchSize = 100
for i := 0; i < len(ids); i += batchSize {
var last int
if i+batchSize < len(ids) {
last = i + batchSize
} else {
last = len(ids)
}
unique := make([]int64, 0, last-i)
for j := i; j < last; j++ {
id := ids[j]
if _, ok := visited[id]; !ok {
unique = append(unique, id)
visited[id] = true
}
}
recipients, err := models.GetMaileableUsersByIDs(unique, fromMention)
if err != nil {
return err
}
checkUnit := models.UnitTypeIssues
if ctx.Issue.IsPull {
checkUnit = models.UnitTypePullRequests
}
// Make sure all recipients can still see the issue
idx := 0
for _, r := range recipients {
if ctx.Issue.Repo.CheckUnitUser(r, checkUnit) {
recipients[idx] = r
idx++
}
}
recipients = recipients[:idx]
// TODO: Separate recipients by language for i18n mail templates
tos := make([]string, len(recipients))
for i := range recipients {
tos[i] = recipients[i].Email
}
SendAsyncs(composeIssueCommentMessages(ctx, tos, fromMention, "issue comments"))
func mailIssueCommentBatch(ctx *mailCommentContext, users []*models.User, visited map[int64]bool, fromMention bool) error {
checkUnit := models.UnitTypeIssues
if ctx.Issue.IsPull {
checkUnit = models.UnitTypePullRequests
}
langMap := make(map[string][]string)
for _, user := range users {
// At this point we exclude:
// user that don't have all mails enabled or users only get mail on mention and this is one ...
if !(user.EmailNotificationsPreference == models.EmailNotificationsEnabled ||
fromMention && user.EmailNotificationsPreference == models.EmailNotificationsOnMention) {
continue
}
// if we have already visited this user we exclude them
if _, ok := visited[user.ID]; ok {
continue
}
// now mark them as visited
visited[user.ID] = true
// test if this user is allowed to see the issue/pull
if !ctx.Issue.Repo.CheckUnitUser(user, checkUnit) {
continue
}
langMap[user.Language] = append(langMap[user.Language], user.Email)
}
for lang, receivers := range langMap {
// because we know that the len(receivers) > 0 and we don't care about the order particularly
// working backwards from the last (possibly) incomplete batch. If len(receivers) can be 0 this
// starting condition will need to be changed slightly
for i := ((len(receivers) - 1) / MailBatchSize) * MailBatchSize; i >= 0; i -= MailBatchSize {
SendAsyncs(composeIssueCommentMessages(ctx, lang, receivers[i:], fromMention, "issue comments"))
receivers = receivers[:i]
}
}
return nil
}
// MailParticipants sends new issue thread created emails to repository watchers
// and mentioned people.
func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) error {
return mailParticipants(issue, doer, opType, mentions)
}
func mailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) (err error) {
mentionedIDs := make([]int64, len(mentions))
for i, u := range mentions {
mentionedIDs[i] = u.ID
}
if err = mailIssueCommentToParticipants(
if err := mailIssueCommentToParticipants(
&mailCommentContext{
Issue: issue,
Doer: doer,
ActionType: opType,
Content: issue.Content,
Comment: nil,
}, mentionedIDs); err != nil {
}, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
return nil