-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Mail assignee when issue/pull request is assigned #8546
Changes from 21 commits
fb0bb14
385c14d
27bbc9d
b063676
1374db5
a3556c9
630dceb
18187fa
99f8db4
ba31fed
04450f9
fea106d
5f6f77f
15b84ed
a7562d1
075c7c6
e0b2648
0d5202c
e761790
abc21a5
430a3c8
4c10b70
152fef9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,8 +58,11 @@ func getAssigneesByIssue(e Engine, issue *Issue) (assignees []*User, err error) | |
|
||
// IsUserAssignedToIssue returns true when the user is assigned to the issue | ||
func IsUserAssignedToIssue(issue *Issue, user *User) (isAssigned bool, err error) { | ||
isAssigned, err = x.Exist(&IssueAssignees{IssueID: issue.ID, AssigneeID: user.ID}) | ||
return | ||
return isUserAssignedToIssue(x, issue, user) | ||
} | ||
|
||
func isUserAssignedToIssue(e Engine, issue *Issue, user *User) (isAssigned bool, err error) { | ||
return e.Get(&IssueAssignees{IssueID: issue.ID, AssigneeID: user.ID}) | ||
} | ||
|
||
// DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array | ||
|
@@ -78,7 +81,7 @@ func DeleteNotPassedAssignee(issue *Issue, doer *User, assignees []*User) (err e | |
|
||
if !found { | ||
// This function also does comments and hooks, which is why we call it seperatly instead of directly removing the assignees here | ||
if err := UpdateAssignee(issue, doer, assignee.ID); err != nil { | ||
if _, _, err := issue.ToggleAssignee(doer, assignee.ID); err != nil { | ||
return err | ||
} | ||
} | ||
|
@@ -110,73 +113,56 @@ func clearAssigneeByUserID(sess *xorm.Session, userID int64) (err error) { | |
return | ||
} | ||
|
||
// AddAssigneeIfNotAssigned adds an assignee only if he isn't aleady assigned to the issue | ||
func AddAssigneeIfNotAssigned(issue *Issue, doer *User, assigneeID int64) (err error) { | ||
// Check if the user is already assigned | ||
isAssigned, err := IsUserAssignedToIssue(issue, &User{ID: assigneeID}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !isAssigned { | ||
return issue.ChangeAssignee(doer, assigneeID) | ||
} | ||
return nil | ||
} | ||
|
||
// UpdateAssignee deletes or adds an assignee to an issue | ||
func UpdateAssignee(issue *Issue, doer *User, assigneeID int64) (err error) { | ||
return issue.ChangeAssignee(doer, assigneeID) | ||
} | ||
|
||
// ChangeAssignee changes the Assignee of this issue. | ||
func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { | ||
// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. | ||
func (issue *Issue) ToggleAssignee(doer *User, assigneeID int64) (removed bool, comment *Comment, err error) { | ||
sess := x.NewSession() | ||
davidsvantesson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
defer sess.Close() | ||
|
||
if err := sess.Begin(); err != nil { | ||
return err | ||
return false, nil, err | ||
} | ||
|
||
if err := issue.changeAssignee(sess, doer, assigneeID, false); err != nil { | ||
return err | ||
removed, comment, err = issue.toggleAssignee(sess, doer, assigneeID, false) | ||
if err != nil { | ||
return false, nil, err | ||
} | ||
|
||
if err := sess.Commit(); err != nil { | ||
return err | ||
return false, nil, err | ||
} | ||
|
||
go HookQueue.Add(issue.RepoID) | ||
return nil | ||
|
||
return removed, comment, nil | ||
} | ||
|
||
func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID int64, isCreate bool) (err error) { | ||
// Update the assignee | ||
removed, err := updateIssueAssignee(sess, issue, assigneeID) | ||
func (issue *Issue) toggleAssignee(sess *xorm.Session, doer *User, assigneeID int64, isCreate bool) (removed bool, comment *Comment, err error) { | ||
removed, err = toggleUserAssignee(sess, issue, assigneeID) | ||
if err != nil { | ||
return fmt.Errorf("UpdateIssueUserByAssignee: %v", err) | ||
return false, nil, fmt.Errorf("UpdateIssueUserByAssignee: %v", err) | ||
} | ||
|
||
// Repo infos | ||
if err = issue.loadRepo(sess); err != nil { | ||
return fmt.Errorf("loadRepo: %v", err) | ||
return false, nil, fmt.Errorf("loadRepo: %v", err) | ||
} | ||
|
||
// Comment | ||
if _, err = createAssigneeComment(sess, doer, issue.Repo, issue, assigneeID, removed); err != nil { | ||
return fmt.Errorf("createAssigneeComment: %v", err) | ||
comment, err = createAssigneeComment(sess, doer, issue.Repo, issue, assigneeID, removed) | ||
if err != nil { | ||
return false, nil, fmt.Errorf("createAssigneeComment: %v", err) | ||
} | ||
|
||
// if pull request is in the middle of creation - don't call webhook | ||
if isCreate { | ||
return nil | ||
return removed, comment, err | ||
} | ||
|
||
if issue.IsPull { | ||
mode, _ := accessLevelUnit(sess, doer, issue.Repo, UnitTypePullRequests) | ||
|
||
if err = issue.loadPullRequest(sess); err != nil { | ||
return fmt.Errorf("loadPullRequest: %v", err) | ||
return false, nil, fmt.Errorf("loadPullRequest: %v", err) | ||
} | ||
issue.PullRequest.Issue = issue | ||
apiPullRequest := &api.PullRequestPayload{ | ||
|
@@ -190,9 +176,10 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |
} else { | ||
apiPullRequest.Action = api.HookIssueAssigned | ||
} | ||
// Assignee comment triggers a webhook | ||
if err := prepareWebhooks(sess, issue.Repo, HookEventPullRequest, apiPullRequest); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The webhooks part doesn't seem related with changing assignees. I thihk it should be moved to another function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it is, below is help text for PR trigger event. 😉
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oh, that's not what I meant. 😄 What I meant is that this code seems to belong to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aha, yes you are probably right, but I will leave out that refactoring from this PR. I think we should have some design guidelines what type of code should be where. I think I've started to understand Gitea's code structure now.... |
||
log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err) | ||
return nil | ||
return false, nil, err | ||
} | ||
} else { | ||
mode, _ := accessLevelUnit(sess, doer, issue.Repo, UnitTypeIssues) | ||
|
@@ -208,67 +195,50 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |
} else { | ||
apiIssue.Action = api.HookIssueAssigned | ||
} | ||
// Assignee comment triggers a webhook | ||
if err := prepareWebhooks(sess, issue.Repo, HookEventIssues, apiIssue); err != nil { | ||
log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err) | ||
return nil | ||
return false, nil, err | ||
} | ||
} | ||
return nil | ||
return removed, comment, nil | ||
} | ||
|
||
// UpdateAPIAssignee is a helper function to add or delete one or multiple issue assignee(s) | ||
// Deleting is done the GitHub way (quote from their api documentation): | ||
// https://developer.github.com/v3/issues/#edit-an-issue | ||
// "assignees" (array): Logins for Users to assign to this issue. | ||
// Pass one or more user logins to replace the set of assignees on this Issue. | ||
// Send an empty array ([]) to clear all assignees from the Issue. | ||
func UpdateAPIAssignee(issue *Issue, oneAssignee string, multipleAssignees []string, doer *User) (err error) { | ||
var allNewAssignees []*User | ||
// toggles user assignee state in database | ||
func toggleUserAssignee(e *xorm.Session, issue *Issue, assigneeID int64) (removed bool, err error) { | ||
|
||
// Keep the old assignee thingy for compatibility reasons | ||
if oneAssignee != "" { | ||
// Prevent double adding assignees | ||
var isDouble bool | ||
for _, assignee := range multipleAssignees { | ||
if assignee == oneAssignee { | ||
isDouble = true | ||
break | ||
} | ||
} | ||
|
||
if !isDouble { | ||
multipleAssignees = append(multipleAssignees, oneAssignee) | ||
} | ||
// Check if the user exists | ||
assignee, err := getUserByID(e, assigneeID) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
// Loop through all assignees to add them | ||
for _, assigneeName := range multipleAssignees { | ||
assignee, err := GetUserByName(assigneeName) | ||
if err != nil { | ||
return err | ||
// Check if the submitted user is already assigned, if yes delete him otherwise add him | ||
var i int | ||
for i = 0; i < len(issue.Assignees); i++ { | ||
if issue.Assignees[i].ID == assigneeID { | ||
break | ||
} | ||
|
||
allNewAssignees = append(allNewAssignees, assignee) | ||
} | ||
|
||
// Delete all old assignees not passed | ||
if err = DeleteNotPassedAssignee(issue, doer, allNewAssignees); err != nil { | ||
return err | ||
} | ||
assigneeIn := IssueAssignees{AssigneeID: assigneeID, IssueID: issue.ID} | ||
|
||
// Add all new assignees | ||
// Update the assignee. The function will check if the user exists, is already | ||
// assigned (which he shouldn't as we deleted all assignees before) and | ||
// has access to the repo. | ||
for _, assignee := range allNewAssignees { | ||
// Extra method to prevent double adding (which would result in removing) | ||
err = AddAssigneeIfNotAssigned(issue, doer, assignee.ID) | ||
toBeDeleted := i < len(issue.Assignees) | ||
if toBeDeleted { | ||
issue.Assignees = append(issue.Assignees[:i], issue.Assignees[i:]...) | ||
_, err = e.Delete(assigneeIn) | ||
if err != nil { | ||
return err | ||
return toBeDeleted, err | ||
} | ||
} else { | ||
issue.Assignees = append(issue.Assignees, assignee) | ||
_, err = e.Insert(assigneeIn) | ||
if err != nil { | ||
return toBeDeleted, err | ||
} | ||
} | ||
|
||
return | ||
return toBeDeleted, nil | ||
} | ||
|
||
// MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs | ||
|
@@ -292,7 +262,7 @@ func MakeIDsFromAPIAssigneesToAdd(oneAssignee string, multipleAssignees []string | |
} | ||
|
||
// Get the IDs of all assignees | ||
assigneeIDs = GetUserIDsByNames(multipleAssignees) | ||
assigneeIDs, err = GetUserIDsByNames(multipleAssignees, false) | ||
|
||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why removed this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved adding assignees outside the creation of new issues. Reason is that each added assignee creates a new comment (issue event) which is used in the notification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think adding assignee comment should be in the transaction but not in the notifications?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I understand now. The added assignee comment is used when sending the notification (the comment hash is included in the mail).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lunny If I've read that right, it's not a comment in the sense of a row in thecomment
table but a message for the contents of the e-mail. @davidsvantesson, perhaps it should be renamed tomessage
to avoid confusion with the existing model/structure?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if I still misunderstand you. Adding an assignee creates a Comment in the database of type CommentTypeAssignees. CommentTypeComment is another type. Maybe IssueEvent would have been a better name to reflect it is not only comments. Anyhow the Comment (of type Assignees) is used when constructing the e-mail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidsvantesson OK, sorry, I got confused about that. I understand @lunny's concern then. However, I'm not sure I'd block the creation of the issue because of an error while doing the assignments. That's why I think the error Flash is useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The input data is validated before issue creation, but it is of course possible it can still fail because of unconsidered reasons / bugs. I think it is a good idea to use error flash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added an error flash now specifically for the case if add assignees fail when creating new issues/pr. Other error flashes I think can be for another PR.
I am not sure how to handle it for API creation. Right now it can potentially give error although the issue itself is created.