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

New webhook trigger for receiving Pull Request review requests #24481

Merged
merged 27 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e693713
feat: new webhook for receiving Pull Request review requests
Makonike May 2, 2023
4882266
fix: not issue trigger
Makonike May 2, 2023
22f0ae5
fix: dup reviewer
Makonike May 2, 2023
2617c4c
fix: update swagger
Makonike May 2, 2023
c5a9f4b
fix: no newline at end of file
Makonike May 2, 2023
439a847
feat: convertPayloader
Makonike May 2, 2023
ee0187d
feat: template button
Makonike May 2, 2023
988c136
fix: move reviewers field to pull
Makonike May 9, 2023
9c9dbde
fix: remove reviewers in issue
Makonike May 9, 2023
cc11f8f
fix: update v1_json.tmpl
Makonike May 9, 2023
57964f4
fix: ci lint error
Makonike May 9, 2023
e2a743c
fix: ci lint error
Makonike May 9, 2023
3568067
fix: ci lint error
Makonike May 10, 2023
13dad91
fix: load reviewers return if existed
Makonike May 11, 2023
c5a553f
fix: rename event
Makonike May 11, 2023
332b187
Merge branch 'main' into main
techknowlogick May 11, 2023
161270c
fix: ci lint error
Makonike May 11, 2023
d22436d
fix: batch load reviewers
Makonike May 24, 2023
ff20eee
docs: add comments
Makonike May 24, 2023
018d95d
fix: rename GetReviewersByIssueID to GetReviewsByIssueID
Makonike May 24, 2023
d5f9b9d
fix
Makonike May 24, 2023
49b8888
fix: add translation keys
Makonike May 24, 2023
063c813
fix
Makonike May 24, 2023
64fbc53
Merge branch 'main' into main
GiteaBot May 24, 2023
d058c60
Merge branch 'main' into main
GiteaBot May 24, 2023
7b195d0
Merge branch 'main' into main
GiteaBot May 25, 2023
8364df5
Merge branch 'main' into main
GiteaBot May 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions models/issues/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,10 @@ type PullRequest struct {

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

IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
Index int64
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
Index int64
RequestedReviewers []*user_model.User `xorm:"-"`

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

// LoadRequestedReviewers loads the requested reviewers.
func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
if len(pr.RequestedReviewers) > 0 {
return nil
}

reviews, err := GetReviewsByIssueID(pr.Issue.ID)
if err != nil {
return err
}

if len(reviews) > 0 {
err = LoadReviewers(ctx, reviews)
if err != nil {
return err
}
for _, review := range reviews {
lunny marked this conversation as resolved.
Show resolved Hide resolved
pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer)
}
}
return nil
}

// LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned.
func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
if pr.BaseRepo != nil {
Expand Down
29 changes: 29 additions & 0 deletions models/issues/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -74,6 +75,34 @@ func TestPullRequestsNewest(t *testing.T) {
}
}

func TestLoadRequestedReviewers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue
assert.NoError(t, issue.LoadRepo(db.DefaultContext))
assert.Len(t, pull.RequestedReviewers, 0)

user1, err := user_model.GetUserByID(db.DefaultContext, 1)
assert.NoError(t, err)

comment, err := issues_model.AddReviewRequest(issue, user1, &user_model.User{})
assert.NoError(t, err)
assert.NotNil(t, comment)

assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
assert.Len(t, pull.RequestedReviewers, 1)

comment, err = issues_model.RemoveReviewRequest(issue, user1, &user_model.User{})
assert.NoError(t, err)
assert.NotNil(t, comment)

pull.RequestedReviewers = nil
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
assert.Empty(t, pull.RequestedReviewers)
}

func TestPullRequestsOldest(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{
Expand Down
25 changes: 23 additions & 2 deletions models/issues/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,27 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {
return err
}

// LoadReviewers loads reviewers
func LoadReviewers(ctx context.Context, reviews []*Review) (err error) {
reviewerIds := make([]int64, len(reviews))
for i := 0; i < len(reviews); i++ {
reviewerIds[i] = reviews[i].ReviewerID
}
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIds)
if err != nil {
return err
}

userMap := make(map[int64]*user_model.User, len(reviewers))
for _, reviewer := range reviewers {
userMap[reviewer.ID] = reviewer
}
for _, review := range reviews {
review.Reviewer = userMap[review.ReviewerID]
}
return nil
}

// LoadReviewerTeam loads reviewer team
func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil {
Expand Down Expand Up @@ -520,8 +541,8 @@ func GetReviews(ctx context.Context, opts *GetReviewOptions) ([]*Review, error)
return reviews, sess.Find(&reviews)
}

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

sess := db.GetEngine(db.DefaultContext)
Expand Down
17 changes: 14 additions & 3 deletions models/issues/review_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,22 @@ func TestGetReviewersByIssueID(t *testing.T) {
UpdatedUnix: 946684814,
})

allReviews, err := issues_model.GetReviewersByIssueID(issue.ID)
for _, reviewer := range allReviews {
assert.NoError(t, reviewer.LoadReviewer(db.DefaultContext))
allReviews, err := issues_model.GetReviewsByIssueID(issue.ID)
assert.NoError(t, err)
for _, review := range allReviews {
assert.NoError(t, review.LoadReviewer(db.DefaultContext))
}
if assert.Len(t, allReviews, 3) {
for i, review := range allReviews {
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
assert.Equal(t, expectedReviews[i].Type, review.Type)
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
}
}

allReviews, err = issues_model.GetReviewsByIssueID(issue.ID)
assert.NoError(t, err)
assert.NoError(t, issues_model.LoadReviewers(db.DefaultContext, allReviews))
if assert.Len(t, allReviews, 3) {
for i, review := range allReviews {
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
Expand Down
29 changes: 29 additions & 0 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -910,6 +911,15 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) {
return u, nil
}

// GetUserByIDs returns the user objects by given IDs if exists.
func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
users := make([]*User, 0, len(ids))
err := db.GetEngine(ctx).In("id", ids).
Table("user").
Find(&users)
return users, err
}

// GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
switch id {
Expand All @@ -924,6 +934,25 @@ func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
}
}

// GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0
func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
uniqueIDs := container.SetOf(ids...)
users := make([]*User, 0, len(ids))
_ = uniqueIDs.Remove(0)
if uniqueIDs.Remove(-1) {
users = append(users, NewGhostUser())
}
if uniqueIDs.Remove(ActionsUserID) {
users = append(users, NewActionsUser())
}
res, err := GetUserByIDs(ctx, uniqueIDs.Values())
if err != nil {
return nil, err
}
users = append(users, res...)
return users, nil
}

// GetUserByNameCtx returns user by given name.
func GetUserByName(ctx context.Context, name string) (*User, error) {
if len(name) == 0 {
Expand Down
7 changes: 7 additions & 0 deletions models/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ func (w *Webhook) HasPackageEvent() bool {
(w.ChooseEvents && w.HookEvents.Package)
}

// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestReviewRequest)
}

// EventCheckers returns event checkers
func (w *Webhook) EventCheckers() []struct {
Has func() bool
Expand Down Expand Up @@ -329,6 +335,7 @@ func (w *Webhook) EventCheckers() []struct {
{w.HasRepositoryEvent, webhook_module.HookEventRepository},
{w.HasReleaseEvent, webhook_module.HookEventRelease},
{w.HasPackageEvent, webhook_module.HookEventPackage},
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
}
}

Expand Down
2 changes: 1 addition & 1 deletion models/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) {
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
"package",
"package", "pull_request_review_request",
},
(&Webhook{
HookEvent: &webhook_module.HookEvent{SendEverything: true},
Expand Down
21 changes: 13 additions & 8 deletions modules/structs/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ const (
HookIssueDemilestoned HookIssueAction = "demilestoned"
// HookIssueReviewed is an issue action for when a pull request is reviewed
HookIssueReviewed HookIssueAction = "reviewed"
// HookIssueReviewRequested is an issue action for when a reviewer is requested for a pull request.
HookIssueReviewRequested HookIssueAction = "review_requested"
// HookIssueReviewRequestRemoved is an issue action for removing a review request to someone on a pull request.
HookIssueReviewRequestRemoved HookIssueAction = "review_request_removed"
)

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

// PullRequestPayload represents a payload information of pull request event.
type PullRequestPayload struct {
Action HookIssueAction `json:"action"`
Index int64 `json:"number"`
Changes *ChangesPayload `json:"changes,omitempty"`
PullRequest *PullRequest `json:"pull_request"`
Repository *Repository `json:"repository"`
Sender *User `json:"sender"`
CommitID string `json:"commit_id"`
Review *ReviewPayload `json:"review"`
Action HookIssueAction `json:"action"`
Index int64 `json:"number"`
Changes *ChangesPayload `json:"changes,omitempty"`
PullRequest *PullRequest `json:"pull_request"`
RequestedReviewer *User `json:"requested_reviewer"`
Repository *Repository `json:"repository"`
Sender *User `json:"sender"`
CommitID string `json:"commit_id"`
Review *ReviewPayload `json:"review"`
}

// JSONPayload FIXME
Expand Down
27 changes: 14 additions & 13 deletions modules/structs/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ import (

// PullRequest represents a pull request
type PullRequest struct {
ID int64 `json:"id"`
URL string `json:"url"`
Index int64 `json:"number"`
Poster *User `json:"user"`
Title string `json:"title"`
Body string `json:"body"`
Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"`
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`
ID int64 `json:"id"`
URL string `json:"url"`
Index int64 `json:"number"`
Poster *User `json:"user"`
Title string `json:"title"`
Body string `json:"body"`
Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"`
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
RequestedReviewers []*User `json:"requested_reviewers"`
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`

HTMLURL string `json:"html_url"`
DiffURL string `json:"diff_url"`
Expand Down
41 changes: 21 additions & 20 deletions modules/webhook/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@ package webhook

// HookEvents is a set of web hook events
type HookEvents struct {
Create bool `json:"create"`
Delete bool `json:"delete"`
Fork bool `json:"fork"`
Issues bool `json:"issues"`
IssueAssign bool `json:"issue_assign"`
IssueLabel bool `json:"issue_label"`
IssueMilestone bool `json:"issue_milestone"`
IssueComment bool `json:"issue_comment"`
Push bool `json:"push"`
PullRequest bool `json:"pull_request"`
PullRequestAssign bool `json:"pull_request_assign"`
PullRequestLabel bool `json:"pull_request_label"`
PullRequestMilestone bool `json:"pull_request_milestone"`
PullRequestComment bool `json:"pull_request_comment"`
PullRequestReview bool `json:"pull_request_review"`
PullRequestSync bool `json:"pull_request_sync"`
Wiki bool `json:"wiki"`
Repository bool `json:"repository"`
Release bool `json:"release"`
Package bool `json:"package"`
Create bool `json:"create"`
Delete bool `json:"delete"`
Fork bool `json:"fork"`
Issues bool `json:"issues"`
IssueAssign bool `json:"issue_assign"`
IssueLabel bool `json:"issue_label"`
IssueMilestone bool `json:"issue_milestone"`
IssueComment bool `json:"issue_comment"`
Push bool `json:"push"`
PullRequest bool `json:"pull_request"`
PullRequestAssign bool `json:"pull_request_assign"`
PullRequestLabel bool `json:"pull_request_label"`
PullRequestMilestone bool `json:"pull_request_milestone"`
PullRequestComment bool `json:"pull_request_comment"`
PullRequestReview bool `json:"pull_request_review"`
PullRequestSync bool `json:"pull_request_sync"`
PullRequestReviewRequest bool `json:"pull_request_review_request"`
Wiki bool `json:"wiki"`
Repository bool `json:"repository"`
Release bool `json:"release"`
Package bool `json:"package"`
}

// HookEvent represents events that will delivery hook.
Expand Down
3 changes: 2 additions & 1 deletion modules/webhook/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
HookEventPullRequestSync HookEventType = "pull_request_sync"
HookEventPullRequestReviewRequest HookEventType = "pull_request_review_request"
HookEventWiki HookEventType = "wiki"
HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release"
Expand All @@ -46,7 +47,7 @@ func (h HookEventType) Event() string {
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
return "issues"
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
HookEventPullRequestSync:
HookEventPullRequestSync, HookEventPullRequestReviewRequest:
return "pull_request"
case HookEventIssueComment, HookEventPullRequestComment:
return "issue_comment"
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2118,6 +2118,8 @@ settings.event_pull_request_review = Pull Request Reviewed
settings.event_pull_request_review_desc = Pull request approved, rejected, or review comment.
settings.event_pull_request_sync = Pull Request Synchronized
settings.event_pull_request_sync_desc = Pull request synchronized.
settings.event_pull_request_review_request = Pull Request Review Requested
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
settings.event_pull_request_approvals = Pull Request Approvals
settings.event_pull_request_merge = Pull Request Merge
settings.event_package = Package
Expand Down
Loading