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

add request review from specific reviewers feature in pull request #10756

Merged
merged 10 commits into from
Apr 6, 2020
Next Next commit
add request review feature in pull request
add a way to notify specific reviewers to review like github , by add  or delet a  special type
review . The acton is  is similar to Assign ,  so many code reuse the function and items of
Assignee, but the meaning and result is different.

The Permission style is is similar to github, that only writer can add a review request from Reviewers,
but the poster can recall and remove a review request after a reviwer has revied even if he don't have
Write Premission. only manager , the poster and reviewer of a request review can remove it.

The reviewers can be requested to review contain all readers for private repo , for public, contain
all writers and watchers.

The offical Review Request will block merge if Reject can block it.

an other change: add ui otify for Assignees.

Co-authored-by: guillep2k <[email protected]>
Co-authored-by: Lauris BH <[email protected]>

Signed-off-by: a1012112796 <[email protected]>
  • Loading branch information
a1012112796 committed Apr 2, 2020
commit ff596262fd9d847dfcfd59f2cb3127b87ae1daeb
3 changes: 2 additions & 1 deletion models/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,13 @@ func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest)
}

// MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews
// An official ReviewRequest should also block Merge like Reject
func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullRequest) bool {
if !protectBranch.BlockOnRejectedReviews {
return false
}
rejectExist, err := x.Where("issue_id = ?", pr.IssueID).
And("type = ?", ReviewTypeReject).
And("type in ( ?, ?)", ReviewTypeReject, ReviewTypeRequest).
And("official = ?", true).
Exist(new(Review))
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions models/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const (
CommentTypeChangeTargetBranch
// Delete time manual for time tracking
CommentTypeDeleteTimeManual
// add or remove Request from one
CommentTypeReviewRequest
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
)

// CommentTag defines comment tag type
Expand Down
77 changes: 43 additions & 34 deletions models/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,64 +118,73 @@ func GetNotifications(opts FindNotificationOptions) (NotificationList, error) {

// CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists
func CreateOrUpdateIssueNotifications(issueID, commentID int64, notificationAuthorID int64) error {
// receiverID > 0 just send to reciver, else send to all watcher
func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID, receiverID int64) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

if err := createOrUpdateIssueNotifications(sess, issueID, commentID, notificationAuthorID); err != nil {
if err := createOrUpdateIssueNotifications(sess, issueID, commentID, notificationAuthorID, receiverID); err != nil {
return err
}

return sess.Commit()
}

func createOrUpdateIssueNotifications(e Engine, issueID, commentID int64, notificationAuthorID int64) error {
func createOrUpdateIssueNotifications(e Engine, issueID, commentID, notificationAuthorID, receiverID int64) error {
// init
toNotify := make(map[int64]struct{}, 32)
var toNotify map[int64]struct{}
notifications, err := getNotificationsByIssueID(e, issueID)

if err != nil {
return err
}

issue, err := getIssueByID(e, issueID)
if err != nil {
return err
}

issueWatches, err := getIssueWatchersIDs(e, issueID, true)
if err != nil {
return err
}
for _, id := range issueWatches {
toNotify[id] = struct{}{}
}
if receiverID > 0 {
toNotify = make(map[int64]struct{}, 1)
toNotify[receiverID] = struct{}{}
} else {
toNotify = make(map[int64]struct{}, 32)
issueWatches, err := getIssueWatchersIDs(e, issueID, true)
if err != nil {
return err
}
for _, id := range issueWatches {
toNotify[id] = struct{}{}
}

repoWatches, err := getRepoWatchersIDs(e, issue.RepoID)
if err != nil {
return err
}
for _, id := range repoWatches {
toNotify[id] = struct{}{}
}
issueParticipants, err := issue.getParticipantIDsByIssue(e)
if err != nil {
return err
}
for _, id := range issueParticipants {
toNotify[id] = struct{}{}
}
repoWatches, err := getRepoWatchersIDs(e, issue.RepoID)
if err != nil {
return err
}
for _, id := range repoWatches {
toNotify[id] = struct{}{}
}
issueParticipants, err := issue.getParticipantIDsByIssue(e)
if err != nil {
return err
}
for _, id := range issueParticipants {
toNotify[id] = struct{}{}
}

// dont notify user who cause notification
delete(toNotify, notificationAuthorID)
// explicit unwatch on issue
issueUnWatches, err := getIssueWatchersIDs(e, issueID, false)
if err != nil {
return err
}
for _, id := range issueUnWatches {
delete(toNotify, id)
// dont notify user who cause notification
delete(toNotify, notificationAuthorID)
// explicit unwatch on issue
issueUnWatches, err := getIssueWatchersIDs(e, issueID, false)
if err != nil {
return err
}
for _, id := range issueUnWatches {
delete(toNotify, id)
}
}

err = issue.loadRepo(e)
Expand Down
2 changes: 1 addition & 1 deletion models/notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)

assert.NoError(t, CreateOrUpdateIssueNotifications(issue.ID, 0, 2))
assert.NoError(t, CreateOrUpdateIssueNotifications(issue.ID, 0, 2, 0))

// User 9 is inactive, thus notifications for user 1 and 4 are created
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
Expand Down
83 changes: 83 additions & 0 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,89 @@ func (repo *Repository) GetAssignees() (_ []*User, err error) {
return repo.getAssignees(x)
}

func (repo *Repository) getReviewersPrivate(e Engine, doerID, posterID int64) (users []*User, err error) {
users = make([]*User, 0, 20)

if err = e.
SQL("SELECT * FROM user WHERE id in (SELECT user_id FROM access WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?))",
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
repo.ID, AccessModeRead,
doerID, posterID).
Find(&users); err != nil {
return nil, err
}

return users, nil
}

func (repo *Repository) getReviewersPublic(e Engine, doerID, posterID int64) (users []*User, err error) {

userIDs := make([]int64, 0, 20)

if err = e.
SQL("SELECT user_id FROM access WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?)",
repo.ID, AccessModeRead,
doerID, posterID).
Find(&userIDs); err != nil {
return nil, err
}

getNum := len(userIDs)

watcherIDs := make([]int64, 0)

if err = e.SQL("SELECT user_id FROM watch WHERE repo_id = ? AND user_id NOT IN ( ?, ?)",
repo.ID, doerID, posterID).Find(&watcherIDs); err != nil {
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}

for _, watcherID := range watcherIDs {
isHave := false
for index, userID := range userIDs {
if index >= getNum {
break
}
if userID == watcherID {
isHave = true
break
}
}

if !isHave {
userIDs = append(userIDs, watcherID)
}
}

users = make([]*User, 0, len(userIDs))
if err = e.In("id", userIDs).Find(&users); err != nil {
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}

return users, nil
}

func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) (users []*User, err error) {
if err = repo.getOwner(e); err != nil {
return nil, err
}

if repo.IsPrivate ||
(repo.Owner.IsOrganization() && repo.Owner.Visibility == api.VisibleTypePrivate) {
users, err = repo.getReviewersPrivate(x, doerID, posterID)
} else {
users, err = repo.getReviewersPublic(x, doerID, posterID)
}
return
}

// GetReviewers get all users can be requested to review
// for private rpo , that return all users that have read access or higher to the repository.
// but for public rpo, that return all users that have write access or higher to the repository,
// and all repo watchers.
// TODO: may be we should hava a busy choice for users to block review request to them.
func (repo *Repository) GetReviewers(doerID, posterID int64) (_ []*User, err error) {
return repo.getReviewers(x, doerID, posterID)
}

// GetMilestoneByID returns the milestone belongs to repository by given ID.
func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) {
return GetMilestoneByRepoID(repo.ID, milestoneID)
Expand Down
Loading