* Send notifications to partecipants in issue comments Closes #1216 Includes test (still failing) * Do not include "labelers" to participants Fix test to expect what GetParticipants returntags/v1.2.0-rc1
id: 1 | id: 1 | ||||
type: 7 # label | type: 7 # label | ||||
poster_id: 2 | poster_id: 2 | ||||
issue_id: 1 | |||||
issue_id: 1 # in repo_id 1 | |||||
label_id: 1 | label_id: 1 | ||||
content: "1" | content: "1" | ||||
- | |||||
id: 2 | |||||
type: 0 # comment | |||||
poster_id: 3 # user not watching (see watch.yml) | |||||
issue_id: 1 # in repo_id 1 | |||||
content: "good work!" | |||||
- | |||||
id: 3 | |||||
type: 0 # comment | |||||
poster_id: 5 # user not watching (see watch.yml) | |||||
issue_id: 1 # in repo_id 1 | |||||
content: "meh..." |
content: content1 | content: content1 | ||||
is_closed: false | is_closed: false | ||||
is_pull: false | is_pull: false | ||||
num_comments: 0 | |||||
num_comments: 2 | |||||
created_unix: 946684800 | created_unix: 946684800 | ||||
updated_unix: 978307200 | updated_unix: 978307200 | ||||
return issues, nil | return issues, nil | ||||
} | } | ||||
// GetParticipantsByIssueID returns all users who are participated in comments of an issue. | |||||
func GetParticipantsByIssueID(issueID int64) ([]*User, error) { | |||||
userIDs := make([]int64, 0, 5) | |||||
if err := x.Table("comment").Cols("poster_id"). | |||||
Where("issue_id = ?", issueID). | |||||
And("type = ?", CommentTypeComment). | |||||
Distinct("poster_id"). | |||||
Find(&userIDs); err != nil { | |||||
return nil, fmt.Errorf("get poster IDs: %v", err) | |||||
} | |||||
if len(userIDs) == 0 { | |||||
return nil, nil | |||||
} | |||||
users := make([]*User, 0, len(userIDs)) | |||||
return users, x.In("id", userIDs).Find(&users) | |||||
} | |||||
// UpdateIssueMentions extracts mentioned people from content and | // UpdateIssueMentions extracts mentioned people from content and | ||||
// updates issue-user relations for them. | // updates issue-user relations for them. | ||||
func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error { | func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error { |
} | } | ||||
// mailIssueCommentToParticipants can be used for both new issue creation and comment. | // 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 *Issue, doer *User, mentions []string) error { | func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error { | ||||
if !setting.Service.EnableNotifyMail { | if !setting.Service.EnableNotifyMail { | ||||
return nil | return nil | ||||
} | } | ||||
// Mail watchers. | |||||
watchers, err := GetWatchers(issue.RepoID) | watchers, err := GetWatchers(issue.RepoID) | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err) | |||||
return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err) | |||||
} | |||||
participants, err := GetParticipantsByIssueID(issue.ID) | |||||
if err != nil { | |||||
return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err) | |||||
} | |||||
// In case the issue poster is not watching the repository, | |||||
// even if we have duplicated in watchers, can be safely filtered out. | |||||
if issue.PosterID != doer.ID { | |||||
participants = append(participants, issue.Poster) | |||||
} | } | ||||
tos := make([]string, 0, len(watchers)) // List of email addresses. | tos := make([]string, 0, len(watchers)) // List of email addresses. | ||||
tos = append(tos, to.Email) | tos = append(tos, to.Email) | ||||
names = append(names, to.Name) | names = append(names, to.Name) | ||||
} | } | ||||
for i := range participants { | |||||
if participants[i].ID == doer.ID { | |||||
continue | |||||
} else if com.IsSliceContainsStr(names, participants[i].Name) { | |||||
continue | |||||
} | |||||
tos = append(tos, participants[i].Email) | |||||
names = append(names, participants[i].Name) | |||||
} | |||||
SendIssueCommentMail(issue, doer, tos) | SendIssueCommentMail(issue, doer, tos) | ||||
// Mail mentioned people and exclude watchers. | // Mail mentioned people and exclude watchers. |
package models | package models | ||||
import ( | import ( | ||||
"sort" | |||||
"testing" | "testing" | ||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
testSuccess([]int64{1, 2, 3}, []int64{}) | testSuccess([]int64{1, 2, 3}, []int64{}) | ||||
testSuccess([]int64{1, 2, 3}, []int64{NonexistentID}) | testSuccess([]int64{1, 2, 3}, []int64{NonexistentID}) | ||||
} | } | ||||
func TestGetParticipantsByIssueID(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
checkPartecipants := func(issueID int64, userIDs []int) { | |||||
partecipants, err := GetParticipantsByIssueID(issueID) | |||||
if assert.NoError(t, err) { | |||||
partecipantsIDs := make([]int,len(partecipants)) | |||||
for i,u := range partecipants { partecipantsIDs[i] = int(u.ID) } | |||||
sort.Ints(partecipantsIDs) | |||||
sort.Ints(userIDs) | |||||
assert.Equal(t, userIDs, partecipantsIDs) | |||||
} | |||||
} | |||||
// User 1 is issue1 poster (see fixtures/issue.yml) | |||||
// User 2 only labeled issue1 (see fixtures/comment.yml) | |||||
// Users 3 and 5 made actual comments (see fixtures/comment.yml) | |||||
checkPartecipants(1, []int{3,5}) | |||||
} |