Browse Source

New webhook trigger for receiving Pull Request review requests (#24481)

close https://github.com/go-gitea/gitea/issues/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.
tags/v1.20.0-rc0
谈笑风生间 1 year ago
parent
commit
309354c70e
No account linked to committer's email address

+ 27
- 3
models/issues/pull.go View File



ChangedProtectedFiles []string `xorm:"TEXT JSON"` 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"` HeadRepoID int64 `xorm:"INDEX"`
HeadRepo *repo_model.Repository `xorm:"-"` HeadRepo *repo_model.Repository `xorm:"-"`
return nil 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 {
pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer)
}
}
return nil
}

// LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned. // LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned.
func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) { func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
if pr.BaseRepo != nil { if pr.BaseRepo != nil {

+ 29
- 0
models/issues/pull_test.go View File

"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"


"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
} }
} }


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) { func TestPullRequestsOldest(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{ prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{

+ 23
- 2
models/issues/review.go View File

return err 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 // LoadReviewerTeam loads reviewer team
func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) { func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil { if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil {
return reviews, sess.Find(&reviews) 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) reviews := make([]*Review, 0, 10)


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

+ 14
- 3
models/issues/review_test.go View File

UpdatedUnix: 946684814, 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, err)
assert.NoError(t, issues_model.LoadReviewers(db.DefaultContext, allReviews))
if assert.Len(t, allReviews, 3) { if assert.Len(t, allReviews, 3) {
for i, review := range allReviews { for i, review := range allReviews {
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer) assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)

+ 29
- 0
models/user/user.go View File

"code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
return u, nil 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 // GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) { func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
switch id { switch id {
} }
} }


// 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. // GetUserByNameCtx returns user by given name.
func GetUserByName(ctx context.Context, name string) (*User, error) { func GetUserByName(ctx context.Context, name string) (*User, error) {
if len(name) == 0 { if len(name) == 0 {

+ 7
- 0
models/webhook/webhook.go View File

(w.ChooseEvents && w.HookEvents.Package) (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 // EventCheckers returns event checkers
func (w *Webhook) EventCheckers() []struct { func (w *Webhook) EventCheckers() []struct {
Has func() bool Has func() bool
{w.HasRepositoryEvent, webhook_module.HookEventRepository}, {w.HasRepositoryEvent, webhook_module.HookEventRepository},
{w.HasReleaseEvent, webhook_module.HookEventRelease}, {w.HasReleaseEvent, webhook_module.HookEventRelease},
{w.HasPackageEvent, webhook_module.HookEventPackage}, {w.HasPackageEvent, webhook_module.HookEventPackage},
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
} }
} }



+ 1
- 1
models/webhook/webhook_test.go View File

"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone", "pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected", "pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release", "pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
"package",
"package", "pull_request_review_request",
}, },
(&Webhook{ (&Webhook{
HookEvent: &webhook_module.HookEvent{SendEverything: true}, HookEvent: &webhook_module.HookEvent{SendEverything: true},

+ 13
- 8
modules/structs/hook.go View File

HookIssueDemilestoned HookIssueAction = "demilestoned" HookIssueDemilestoned HookIssueAction = "demilestoned"
// HookIssueReviewed is an issue action for when a pull request is reviewed // HookIssueReviewed is an issue action for when a pull request is reviewed
HookIssueReviewed HookIssueAction = "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. // IssuePayload represents the payload information that is sent along with an issue event.


// PullRequestPayload represents a payload information of pull request event. // PullRequestPayload represents a payload information of pull request event.
type PullRequestPayload struct { 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 // JSONPayload FIXME

+ 14
- 13
modules/structs/pull.go View File



// PullRequest represents a pull request // PullRequest represents a pull request
type PullRequest struct { 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"` HTMLURL string `json:"html_url"`
DiffURL string `json:"diff_url"` DiffURL string `json:"diff_url"`

+ 21
- 20
modules/webhook/structs.go View File



// HookEvents is a set of web hook events // HookEvents is a set of web hook events
type HookEvents struct { 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. // HookEvent represents events that will delivery hook.

+ 2
- 1
modules/webhook/type.go View File

HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected" HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment" HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
HookEventPullRequestSync HookEventType = "pull_request_sync" HookEventPullRequestSync HookEventType = "pull_request_sync"
HookEventPullRequestReviewRequest HookEventType = "pull_request_review_request"
HookEventWiki HookEventType = "wiki" HookEventWiki HookEventType = "wiki"
HookEventRepository HookEventType = "repository" HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release" HookEventRelease HookEventType = "release"
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone: case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
return "issues" return "issues"
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone, case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
HookEventPullRequestSync:
HookEventPullRequestSync, HookEventPullRequestReviewRequest:
return "pull_request" return "pull_request"
case HookEventIssueComment, HookEventPullRequestComment: case HookEventIssueComment, HookEventPullRequestComment:
return "issue_comment" return "issue_comment"

+ 2
- 0
options/locale/locale_en-US.ini View File

settings.event_pull_request_review_desc = Pull request approved, rejected, or review comment. 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 = Pull Request Synchronized
settings.event_pull_request_sync_desc = 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_approvals = Pull Request Approvals
settings.event_pull_request_merge = Pull Request Merge settings.event_pull_request_merge = Pull Request Merge
settings.event_package = Package settings.event_package = Package

+ 21
- 19
routers/api/v1/utils/hook.go View File

HookEvent: &webhook_module.HookEvent{ HookEvent: &webhook_module.HookEvent{
ChooseEvents: true, ChooseEvents: true,
HookEvents: webhook_module.HookEvents{ HookEvents: webhook_module.HookEvents{
Create: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
Delete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
Fork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
Issues: issuesHook(form.Events, "issues_only"),
IssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
IssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
IssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
IssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
Push: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
PullRequest: pullHook(form.Events, "pull_request_only"),
PullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
PullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
PullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
PullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
PullRequestReview: pullHook(form.Events, "pull_request_review"),
PullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
Release: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
Create: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
Delete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
Fork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
Issues: issuesHook(form.Events, "issues_only"),
IssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
IssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
IssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
IssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
Push: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
PullRequest: pullHook(form.Events, "pull_request_only"),
PullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
PullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
PullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
PullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
PullRequestReview: pullHook(form.Events, "pull_request_review"),
PullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
PullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
Wiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
Repository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
Release: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
}, },
BranchFilter: form.BranchFilter, BranchFilter: form.BranchFilter,
}, },
w.PullRequestMilestone = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)) w.PullRequestMilestone = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
w.PullRequestComment = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)) w.PullRequestComment = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
w.PullRequestReview = pullHook(form.Events, "pull_request_review") w.PullRequestReview = pullHook(form.Events, "pull_request_review")
w.PullRequestReviewRequest = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
w.PullRequestSync = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)) w.PullRequestSync = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))


if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {

+ 1
- 1
routers/web/repo/issue.go View File

} }
ctx.Data["OriginalReviews"] = originalAuthorReviews ctx.Data["OriginalReviews"] = originalAuthorReviews


reviews, err := issues_model.GetReviewersByIssueID(issue.ID)
reviews, err := issues_model.GetReviewsByIssueID(issue.ID)
if err != nil { if err != nil {
ctx.ServerError("GetReviewersByIssueID", err) ctx.ServerError("GetReviewersByIssueID", err)
return return

+ 21
- 20
routers/web/repo/webhook.go View File

SendEverything: form.SendEverything(), SendEverything: form.SendEverything(),
ChooseEvents: form.ChooseEvents(), ChooseEvents: form.ChooseEvents(),
HookEvents: webhook_module.HookEvents{ HookEvents: webhook_module.HookEvents{
Create: form.Create,
Delete: form.Delete,
Fork: form.Fork,
Issues: form.Issues,
IssueAssign: form.IssueAssign,
IssueLabel: form.IssueLabel,
IssueMilestone: form.IssueMilestone,
IssueComment: form.IssueComment,
Release: form.Release,
Push: form.Push,
PullRequest: form.PullRequest,
PullRequestAssign: form.PullRequestAssign,
PullRequestLabel: form.PullRequestLabel,
PullRequestMilestone: form.PullRequestMilestone,
PullRequestComment: form.PullRequestComment,
PullRequestReview: form.PullRequestReview,
PullRequestSync: form.PullRequestSync,
Wiki: form.Wiki,
Repository: form.Repository,
Package: form.Package,
Create: form.Create,
Delete: form.Delete,
Fork: form.Fork,
Issues: form.Issues,
IssueAssign: form.IssueAssign,
IssueLabel: form.IssueLabel,
IssueMilestone: form.IssueMilestone,
IssueComment: form.IssueComment,
Release: form.Release,
Push: form.Push,
PullRequest: form.PullRequest,
PullRequestAssign: form.PullRequestAssign,
PullRequestLabel: form.PullRequestLabel,
PullRequestMilestone: form.PullRequestMilestone,
PullRequestComment: form.PullRequestComment,
PullRequestReview: form.PullRequestReview,
PullRequestSync: form.PullRequestSync,
PullRequestReviewRequest: form.PullRequestReviewRequest,
Wiki: form.Wiki,
Repository: form.Repository,
Package: form.Package,
}, },
BranchFilter: form.BranchFilter, BranchFilter: form.BranchFilter,
} }

+ 8
- 0
services/convert/pull.go View File

}, },
} }


if err = pr.LoadRequestedReviewers(ctx); err != nil {
log.Error("LoadRequestedReviewers[%d]: %v", pr.ID, err)
return nil
}
for _, reviewer := range pr.RequestedReviewers {
apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil))
}

if pr.Issue.ClosedUnix != 0 { if pr.Issue.ClosedUnix != 0 {
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr() apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
} }

+ 25
- 24
services/forms/repo_form.go View File



// WebhookForm form for changing web hook // WebhookForm form for changing web hook
type WebhookForm struct { type WebhookForm struct {
Events string
Create bool
Delete bool
Fork bool
Issues bool
IssueAssign bool
IssueLabel bool
IssueMilestone bool
IssueComment bool
Release bool
Push bool
PullRequest bool
PullRequestAssign bool
PullRequestLabel bool
PullRequestMilestone bool
PullRequestComment bool
PullRequestReview bool
PullRequestSync bool
Wiki bool
Repository bool
Package bool
Active bool
BranchFilter string `binding:"GlobPattern"`
AuthorizationHeader string
Events string
Create bool
Delete bool
Fork bool
Issues bool
IssueAssign bool
IssueLabel bool
IssueMilestone bool
IssueComment bool
Release bool
Push bool
PullRequest bool
PullRequestAssign bool
PullRequestLabel bool
PullRequestMilestone bool
PullRequestComment bool
PullRequestReview bool
PullRequestSync bool
PullRequestReviewRequest bool
Wiki bool
Repository bool
Package bool
Active bool
BranchFilter string `binding:"GlobPattern"`
AuthorizationHeader string
} }


// PushOnly if the hook will be triggered when push // PushOnly if the hook will be triggered when push

+ 28
- 0
services/webhook/notifier.go View File

} }
} }


func (m *webhookNotifier) NotifyPullReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
if !issue.IsPull {
log.Warn("NotifyPullReviewRequest: issue is not a pull request: %v", issue.ID)
return
}
mode, _ := access_model.AccessLevelUnit(ctx, doer, issue.Repo, unit.TypePullRequests)
if err := issue.LoadPullRequest(ctx); err != nil {
log.Error("LoadPullRequest failed: %v", err)
return
}
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
Repository: convert.ToRepo(ctx, issue.Repo, mode),
Sender: convert.ToUser(ctx, doer, nil),
}
if isRequest {
apiPullRequest.Action = api.HookIssueReviewRequested
} else {
apiPullRequest.Action = api.HookIssueReviewRequestRemoved
}
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestReviewRequest, apiPullRequest); err != nil {
log.Error("PrepareWebhooks [review_requested: %v]: %v", isRequest, err)
return
}
}

func (m *webhookNotifier) NotifyCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) { func (m *webhookNotifier) NotifyCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refType, refFullName, refID string) {
apiPusher := convert.ToUser(ctx, pusher, nil) apiPusher := convert.ToUser(ctx, pusher, nil)
apiRepo := convert.ToRepo(ctx, repo, perm.AccessModeNone) apiRepo := convert.ToRepo(ctx, repo, perm.AccessModeNone)

+ 1
- 1
services/webhook/payloader.go View File

case webhook_module.HookEventPush: case webhook_module.HookEventPush:
return s.Push(p.(*api.PushPayload)) return s.Push(p.(*api.PushPayload))
case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestLabel, case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestMilestone, webhook_module.HookEventPullRequestSync:
webhook_module.HookEventPullRequestMilestone, webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestReviewRequest:
return s.PullRequest(p.(*api.PullRequestPayload)) return s.PullRequest(p.(*api.PullRequestPayload))
case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment: case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
return s.Review(p.(*api.PullRequestPayload), event) return s.Review(p.(*api.PullRequestPayload), event)

+ 10
- 0
templates/repo/settings/webhook/settings.tmpl View File

</div> </div>
</div> </div>
</div> </div>
<!-- Pull Request Review Request -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input name="pull_request_review_request" type="checkbox" tabindex="0" {{if .Webhook.PullRequestReviewRequest}}checked{{end}}>
<label>{{.locale.Tr "repo.settings.event_pull_request_review_request"}}</label>
<span class="help">{{.locale.Tr "repo.settings.event_pull_request_review_request_desc"}}</span>
</div>
</div>
</div>
</div> </div>
</div> </div>



+ 7
- 0
templates/swagger/v1_json.tmpl View File

"type": "string", "type": "string",
"x-go-name": "PatchURL" "x-go-name": "PatchURL"
}, },
"requested_reviewers": {
"type": "array",
"items": {
"$ref": "#/definitions/User"
},
"x-go-name": "RequestedReviewers"
},
"state": { "state": {
"$ref": "#/definitions/StateType" "$ref": "#/definitions/StateType"
}, },

Loading…
Cancel
Save