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
@@ -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:"-"` | |||
@@ -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 { | |||
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 { |
@@ -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" | |||
) | |||
@@ -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{ |
@@ -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 { | |||
@@ -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) |
@@ -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) |
@@ -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" | |||
@@ -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 { | |||
@@ -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 { |
@@ -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 | |||
@@ -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}, | |||
} | |||
} | |||
@@ -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}, |
@@ -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. | |||
@@ -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 |
@@ -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"` |
@@ -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. |
@@ -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" | |||
@@ -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" |
@@ -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 |
@@ -179,25 +179,26 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI | |||
HookEvent: &webhook_module.HookEvent{ | |||
ChooseEvents: true, | |||
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, | |||
}, | |||
@@ -379,6 +380,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh | |||
w.PullRequestMilestone = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)) | |||
w.PullRequestComment = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)) | |||
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)) | |||
if err := w.UpdateEvent(); err != nil { |
@@ -576,7 +576,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is | |||
} | |||
ctx.Data["OriginalReviews"] = originalAuthorReviews | |||
reviews, err := issues_model.GetReviewersByIssueID(issue.ID) | |||
reviews, err := issues_model.GetReviewsByIssueID(issue.ID) | |||
if err != nil { | |||
ctx.ServerError("GetReviewersByIssueID", err) | |||
return |
@@ -160,26 +160,27 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent { | |||
SendEverything: form.SendEverything(), | |||
ChooseEvents: form.ChooseEvents(), | |||
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, | |||
} |
@@ -88,6 +88,14 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u | |||
}, | |||
} | |||
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 { | |||
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr() | |||
} |
@@ -228,30 +228,31 @@ func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) bin | |||
// WebhookForm form for changing web hook | |||
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 |
@@ -719,6 +719,34 @@ func (m *webhookNotifier) NotifyPullRequestReview(ctx context.Context, pr *issue | |||
} | |||
} | |||
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) { | |||
apiPusher := convert.ToUser(ctx, pusher, nil) | |||
apiRepo := convert.ToRepo(ctx, repo, perm.AccessModeNone) |
@@ -43,7 +43,7 @@ func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module. | |||
case webhook_module.HookEventPush: | |||
return s.Push(p.(*api.PushPayload)) | |||
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)) | |||
case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment: | |||
return s.Review(p.(*api.PullRequestPayload), event) |
@@ -238,6 +238,16 @@ | |||
</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> | |||
@@ -19934,6 +19934,13 @@ | |||
"type": "string", | |||
"x-go-name": "PatchURL" | |||
}, | |||
"requested_reviewers": { | |||
"type": "array", | |||
"items": { | |||
"$ref": "#/definitions/User" | |||
}, | |||
"x-go-name": "RequestedReviewers" | |||
}, | |||
"state": { | |||
"$ref": "#/definitions/StateType" | |||
}, |