Skip to content

Commit 309354c

Browse files
authored
New webhook trigger for receiving Pull Request review requests (#24481)
close #16321 Provided a webhook trigger for requesting someone to review the Pull Request. Some modifications have been made to the returned `PullRequestPayload` based on the GitHub webhook settings, including: - add a description of the current reviewer object as `RequestedReviewer` . - setting the action to either **review_requested** or **review_request_removed** based on the operation. - adding the `RequestedReviewers` field to the issues_model.PullRequest. This field will be loaded into the PullRequest through `LoadRequestedReviewers()` when `ToAPIPullRequest` is called. After the Pull Request is merged, I will supplement the relevant documentation.
1 parent 93c6a9a commit 309354c

File tree

21 files changed

+304
-116
lines changed

21 files changed

+304
-116
lines changed

models/issues/pull.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,10 @@ type PullRequest struct {
175175

176176
ChangedProtectedFiles []string `xorm:"TEXT JSON"`
177177

178-
IssueID int64 `xorm:"INDEX"`
179-
Issue *Issue `xorm:"-"`
180-
Index int64
178+
IssueID int64 `xorm:"INDEX"`
179+
Issue *Issue `xorm:"-"`
180+
Index int64
181+
RequestedReviewers []*user_model.User `xorm:"-"`
181182

182183
HeadRepoID int64 `xorm:"INDEX"`
183184
HeadRepo *repo_model.Repository `xorm:"-"`
@@ -302,6 +303,29 @@ func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
302303
return nil
303304
}
304305

306+
// LoadRequestedReviewers loads the requested reviewers.
307+
func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
308+
if len(pr.RequestedReviewers) > 0 {
309+
return nil
310+
}
311+
312+
reviews, err := GetReviewsByIssueID(pr.Issue.ID)
313+
if err != nil {
314+
return err
315+
}
316+
317+
if len(reviews) > 0 {
318+
err = LoadReviewers(ctx, reviews)
319+
if err != nil {
320+
return err
321+
}
322+
for _, review := range reviews {
323+
pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer)
324+
}
325+
}
326+
return nil
327+
}
328+
305329
// LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned.
306330
func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
307331
if pr.BaseRepo != nil {

models/issues/pull_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"code.gitea.io/gitea/models/db"
1010
issues_model "code.gitea.io/gitea/models/issues"
1111
"code.gitea.io/gitea/models/unittest"
12+
user_model "code.gitea.io/gitea/models/user"
1213

1314
"github.com/stretchr/testify/assert"
1415
)
@@ -74,6 +75,34 @@ func TestPullRequestsNewest(t *testing.T) {
7475
}
7576
}
7677

78+
func TestLoadRequestedReviewers(t *testing.T) {
79+
assert.NoError(t, unittest.PrepareTestDatabase())
80+
81+
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
82+
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
83+
issue := pull.Issue
84+
assert.NoError(t, issue.LoadRepo(db.DefaultContext))
85+
assert.Len(t, pull.RequestedReviewers, 0)
86+
87+
user1, err := user_model.GetUserByID(db.DefaultContext, 1)
88+
assert.NoError(t, err)
89+
90+
comment, err := issues_model.AddReviewRequest(issue, user1, &user_model.User{})
91+
assert.NoError(t, err)
92+
assert.NotNil(t, comment)
93+
94+
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
95+
assert.Len(t, pull.RequestedReviewers, 1)
96+
97+
comment, err = issues_model.RemoveReviewRequest(issue, user1, &user_model.User{})
98+
assert.NoError(t, err)
99+
assert.NotNil(t, comment)
100+
101+
pull.RequestedReviewers = nil
102+
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
103+
assert.Empty(t, pull.RequestedReviewers)
104+
}
105+
77106
func TestPullRequestsOldest(t *testing.T) {
78107
assert.NoError(t, unittest.PrepareTestDatabase())
79108
prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{

models/issues/review.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,27 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {
162162
return err
163163
}
164164

165+
// LoadReviewers loads reviewers
166+
func LoadReviewers(ctx context.Context, reviews []*Review) (err error) {
167+
reviewerIds := make([]int64, len(reviews))
168+
for i := 0; i < len(reviews); i++ {
169+
reviewerIds[i] = reviews[i].ReviewerID
170+
}
171+
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIds)
172+
if err != nil {
173+
return err
174+
}
175+
176+
userMap := make(map[int64]*user_model.User, len(reviewers))
177+
for _, reviewer := range reviewers {
178+
userMap[reviewer.ID] = reviewer
179+
}
180+
for _, review := range reviews {
181+
review.Reviewer = userMap[review.ReviewerID]
182+
}
183+
return nil
184+
}
185+
165186
// LoadReviewerTeam loads reviewer team
166187
func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
167188
if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil {
@@ -520,8 +541,8 @@ func GetReviews(ctx context.Context, opts *GetReviewOptions) ([]*Review, error)
520541
return reviews, sess.Find(&reviews)
521542
}
522543

523-
// GetReviewersByIssueID gets the latest review of each reviewer for a pull request
524-
func GetReviewersByIssueID(issueID int64) ([]*Review, error) {
544+
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request
545+
func GetReviewsByIssueID(issueID int64) ([]*Review, error) {
525546
reviews := make([]*Review, 0, 10)
526547

527548
sess := db.GetEngine(db.DefaultContext)

models/issues/review_test.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,22 @@ func TestGetReviewersByIssueID(t *testing.T) {
132132
UpdatedUnix: 946684814,
133133
})
134134

135-
allReviews, err := issues_model.GetReviewersByIssueID(issue.ID)
136-
for _, reviewer := range allReviews {
137-
assert.NoError(t, reviewer.LoadReviewer(db.DefaultContext))
135+
allReviews, err := issues_model.GetReviewsByIssueID(issue.ID)
136+
assert.NoError(t, err)
137+
for _, review := range allReviews {
138+
assert.NoError(t, review.LoadReviewer(db.DefaultContext))
138139
}
140+
if assert.Len(t, allReviews, 3) {
141+
for i, review := range allReviews {
142+
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
143+
assert.Equal(t, expectedReviews[i].Type, review.Type)
144+
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
145+
}
146+
}
147+
148+
allReviews, err = issues_model.GetReviewsByIssueID(issue.ID)
139149
assert.NoError(t, err)
150+
assert.NoError(t, issues_model.LoadReviewers(db.DefaultContext, allReviews))
140151
if assert.Len(t, allReviews, 3) {
141152
for i, review := range allReviews {
142153
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)

models/user/user.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"code.gitea.io/gitea/modules/auth/openid"
2121
"code.gitea.io/gitea/modules/auth/password/hash"
2222
"code.gitea.io/gitea/modules/base"
23+
"code.gitea.io/gitea/modules/container"
2324
"code.gitea.io/gitea/modules/git"
2425
"code.gitea.io/gitea/modules/log"
2526
"code.gitea.io/gitea/modules/setting"
@@ -910,6 +911,15 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) {
910911
return u, nil
911912
}
912913

914+
// GetUserByIDs returns the user objects by given IDs if exists.
915+
func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
916+
users := make([]*User, 0, len(ids))
917+
err := db.GetEngine(ctx).In("id", ids).
918+
Table("user").
919+
Find(&users)
920+
return users, err
921+
}
922+
913923
// GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
914924
func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
915925
switch id {
@@ -924,6 +934,25 @@ func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
924934
}
925935
}
926936

937+
// GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0
938+
func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
939+
uniqueIDs := container.SetOf(ids...)
940+
users := make([]*User, 0, len(ids))
941+
_ = uniqueIDs.Remove(0)
942+
if uniqueIDs.Remove(-1) {
943+
users = append(users, NewGhostUser())
944+
}
945+
if uniqueIDs.Remove(ActionsUserID) {
946+
users = append(users, NewActionsUser())
947+
}
948+
res, err := GetUserByIDs(ctx, uniqueIDs.Values())
949+
if err != nil {
950+
return nil, err
951+
}
952+
users = append(users, res...)
953+
return users, nil
954+
}
955+
927956
// GetUserByNameCtx returns user by given name.
928957
func GetUserByName(ctx context.Context, name string) (*User, error) {
929958
if len(name) == 0 {

models/webhook/webhook.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,12 @@ func (w *Webhook) HasPackageEvent() bool {
298298
(w.ChooseEvents && w.HookEvents.Package)
299299
}
300300

301+
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
302+
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
303+
return w.SendEverything ||
304+
(w.ChooseEvents && w.HookEvents.PullRequestReviewRequest)
305+
}
306+
301307
// EventCheckers returns event checkers
302308
func (w *Webhook) EventCheckers() []struct {
303309
Has func() bool
@@ -329,6 +335,7 @@ func (w *Webhook) EventCheckers() []struct {
329335
{w.HasRepositoryEvent, webhook_module.HookEventRepository},
330336
{w.HasReleaseEvent, webhook_module.HookEventRelease},
331337
{w.HasPackageEvent, webhook_module.HookEventPackage},
338+
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
332339
}
333340
}
334341

models/webhook/webhook_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) {
7373
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
7474
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
7575
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
76-
"package",
76+
"package", "pull_request_review_request",
7777
},
7878
(&Webhook{
7979
HookEvent: &webhook_module.HookEvent{SendEverything: true},

modules/structs/hook.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ const (
342342
HookIssueDemilestoned HookIssueAction = "demilestoned"
343343
// HookIssueReviewed is an issue action for when a pull request is reviewed
344344
HookIssueReviewed HookIssueAction = "reviewed"
345+
// HookIssueReviewRequested is an issue action for when a reviewer is requested for a pull request.
346+
HookIssueReviewRequested HookIssueAction = "review_requested"
347+
// HookIssueReviewRequestRemoved is an issue action for removing a review request to someone on a pull request.
348+
HookIssueReviewRequestRemoved HookIssueAction = "review_request_removed"
345349
)
346350

347351
// IssuePayload represents the payload information that is sent along with an issue event.
@@ -381,14 +385,15 @@ type ChangesPayload struct {
381385

382386
// PullRequestPayload represents a payload information of pull request event.
383387
type PullRequestPayload struct {
384-
Action HookIssueAction `json:"action"`
385-
Index int64 `json:"number"`
386-
Changes *ChangesPayload `json:"changes,omitempty"`
387-
PullRequest *PullRequest `json:"pull_request"`
388-
Repository *Repository `json:"repository"`
389-
Sender *User `json:"sender"`
390-
CommitID string `json:"commit_id"`
391-
Review *ReviewPayload `json:"review"`
388+
Action HookIssueAction `json:"action"`
389+
Index int64 `json:"number"`
390+
Changes *ChangesPayload `json:"changes,omitempty"`
391+
PullRequest *PullRequest `json:"pull_request"`
392+
RequestedReviewer *User `json:"requested_reviewer"`
393+
Repository *Repository `json:"repository"`
394+
Sender *User `json:"sender"`
395+
CommitID string `json:"commit_id"`
396+
Review *ReviewPayload `json:"review"`
392397
}
393398

394399
// JSONPayload FIXME

modules/structs/pull.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ import (
99

1010
// PullRequest represents a pull request
1111
type PullRequest struct {
12-
ID int64 `json:"id"`
13-
URL string `json:"url"`
14-
Index int64 `json:"number"`
15-
Poster *User `json:"user"`
16-
Title string `json:"title"`
17-
Body string `json:"body"`
18-
Labels []*Label `json:"labels"`
19-
Milestone *Milestone `json:"milestone"`
20-
Assignee *User `json:"assignee"`
21-
Assignees []*User `json:"assignees"`
22-
State StateType `json:"state"`
23-
IsLocked bool `json:"is_locked"`
24-
Comments int `json:"comments"`
12+
ID int64 `json:"id"`
13+
URL string `json:"url"`
14+
Index int64 `json:"number"`
15+
Poster *User `json:"user"`
16+
Title string `json:"title"`
17+
Body string `json:"body"`
18+
Labels []*Label `json:"labels"`
19+
Milestone *Milestone `json:"milestone"`
20+
Assignee *User `json:"assignee"`
21+
Assignees []*User `json:"assignees"`
22+
RequestedReviewers []*User `json:"requested_reviewers"`
23+
State StateType `json:"state"`
24+
IsLocked bool `json:"is_locked"`
25+
Comments int `json:"comments"`
2526

2627
HTMLURL string `json:"html_url"`
2728
DiffURL string `json:"diff_url"`

modules/webhook/structs.go

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,27 @@ package webhook
55

66
// HookEvents is a set of web hook events
77
type HookEvents struct {
8-
Create bool `json:"create"`
9-
Delete bool `json:"delete"`
10-
Fork bool `json:"fork"`
11-
Issues bool `json:"issues"`
12-
IssueAssign bool `json:"issue_assign"`
13-
IssueLabel bool `json:"issue_label"`
14-
IssueMilestone bool `json:"issue_milestone"`
15-
IssueComment bool `json:"issue_comment"`
16-
Push bool `json:"push"`
17-
PullRequest bool `json:"pull_request"`
18-
PullRequestAssign bool `json:"pull_request_assign"`
19-
PullRequestLabel bool `json:"pull_request_label"`
20-
PullRequestMilestone bool `json:"pull_request_milestone"`
21-
PullRequestComment bool `json:"pull_request_comment"`
22-
PullRequestReview bool `json:"pull_request_review"`
23-
PullRequestSync bool `json:"pull_request_sync"`
24-
Wiki bool `json:"wiki"`
25-
Repository bool `json:"repository"`
26-
Release bool `json:"release"`
27-
Package bool `json:"package"`
8+
Create bool `json:"create"`
9+
Delete bool `json:"delete"`
10+
Fork bool `json:"fork"`
11+
Issues bool `json:"issues"`
12+
IssueAssign bool `json:"issue_assign"`
13+
IssueLabel bool `json:"issue_label"`
14+
IssueMilestone bool `json:"issue_milestone"`
15+
IssueComment bool `json:"issue_comment"`
16+
Push bool `json:"push"`
17+
PullRequest bool `json:"pull_request"`
18+
PullRequestAssign bool `json:"pull_request_assign"`
19+
PullRequestLabel bool `json:"pull_request_label"`
20+
PullRequestMilestone bool `json:"pull_request_milestone"`
21+
PullRequestComment bool `json:"pull_request_comment"`
22+
PullRequestReview bool `json:"pull_request_review"`
23+
PullRequestSync bool `json:"pull_request_sync"`
24+
PullRequestReviewRequest bool `json:"pull_request_review_request"`
25+
Wiki bool `json:"wiki"`
26+
Repository bool `json:"repository"`
27+
Release bool `json:"release"`
28+
Package bool `json:"package"`
2829
}
2930

3031
// HookEvent represents events that will delivery hook.

modules/webhook/type.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
2727
HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
2828
HookEventPullRequestSync HookEventType = "pull_request_sync"
29+
HookEventPullRequestReviewRequest HookEventType = "pull_request_review_request"
2930
HookEventWiki HookEventType = "wiki"
3031
HookEventRepository HookEventType = "repository"
3132
HookEventRelease HookEventType = "release"
@@ -46,7 +47,7 @@ func (h HookEventType) Event() string {
4647
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
4748
return "issues"
4849
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
49-
HookEventPullRequestSync:
50+
HookEventPullRequestSync, HookEventPullRequestReviewRequest:
5051
return "pull_request"
5152
case HookEventIssueComment, HookEventPullRequestComment:
5253
return "issue_comment"

options/locale/locale_en-US.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,8 @@ settings.event_pull_request_review = Pull Request Reviewed
21182118
settings.event_pull_request_review_desc = Pull request approved, rejected, or review comment.
21192119
settings.event_pull_request_sync = Pull Request Synchronized
21202120
settings.event_pull_request_sync_desc = Pull request synchronized.
2121+
settings.event_pull_request_review_request = Pull Request Review Requested
2122+
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
21212123
settings.event_pull_request_approvals = Pull Request Approvals
21222124
settings.event_pull_request_merge = Pull Request Merge
21232125
settings.event_package = Package

0 commit comments

Comments
 (0)