mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	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.
This commit is contained in:
		| @@ -178,6 +178,7 @@ type PullRequest struct { | |||||||
| 	IssueID            int64  `xorm:"INDEX"` | 	IssueID            int64  `xorm:"INDEX"` | ||||||
| 	Issue              *Issue `xorm:"-"` | 	Issue              *Issue `xorm:"-"` | ||||||
| 	Index              int64 | 	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:"-"` | ||||||
| @@ -302,6 +303,29 @@ func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) { | |||||||
| 	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 { | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"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" | ||||||
| ) | ) | ||||||
| @@ -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) { | 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{ | ||||||
|   | |||||||
| @@ -162,6 +162,27 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) { | |||||||
| 	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 { | ||||||
| @@ -520,8 +541,8 @@ func GetReviews(ctx context.Context, opts *GetReviewOptions) ([]*Review, error) | |||||||
| 	return reviews, sess.Find(&reviews) | 	return reviews, sess.Find(&reviews) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetReviewersByIssueID gets the latest review of each reviewer for a pull request | // GetReviewsByIssueID gets the latest review of each reviewer for a pull request | ||||||
| func GetReviewersByIssueID(issueID int64) ([]*Review, error) { | 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) | ||||||
|   | |||||||
| @@ -132,11 +132,22 @@ func TestGetReviewersByIssueID(t *testing.T) { | |||||||
| 			UpdatedUnix: 946684814, | 			UpdatedUnix: 946684814, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 	allReviews, err := issues_model.GetReviewersByIssueID(issue.ID) | 	allReviews, err := issues_model.GetReviewsByIssueID(issue.ID) | ||||||
| 	for _, reviewer := range allReviews { |  | ||||||
| 		assert.NoError(t, reviewer.LoadReviewer(db.DefaultContext)) |  | ||||||
| 	} |  | ||||||
| 	assert.NoError(t, err) | 	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) { | 	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) | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	"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" | ||||||
| @@ -910,6 +911,15 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) { | |||||||
| 	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 { | ||||||
| @@ -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. | // 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 { | ||||||
|   | |||||||
| @@ -298,6 +298,12 @@ func (w *Webhook) HasPackageEvent() bool { | |||||||
| 		(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 | ||||||
| @@ -329,6 +335,7 @@ func (w *Webhook) EventCheckers() []struct { | |||||||
| 		{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}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) { | |||||||
| 		"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}, | ||||||
|   | |||||||
| @@ -342,6 +342,10 @@ const ( | |||||||
| 	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. | ||||||
| @@ -385,6 +389,7 @@ type PullRequestPayload struct { | |||||||
| 	Index             int64           `json:"number"` | 	Index             int64           `json:"number"` | ||||||
| 	Changes           *ChangesPayload `json:"changes,omitempty"` | 	Changes           *ChangesPayload `json:"changes,omitempty"` | ||||||
| 	PullRequest       *PullRequest    `json:"pull_request"` | 	PullRequest       *PullRequest    `json:"pull_request"` | ||||||
|  | 	RequestedReviewer *User           `json:"requested_reviewer"` | ||||||
| 	Repository        *Repository     `json:"repository"` | 	Repository        *Repository     `json:"repository"` | ||||||
| 	Sender            *User           `json:"sender"` | 	Sender            *User           `json:"sender"` | ||||||
| 	CommitID          string          `json:"commit_id"` | 	CommitID          string          `json:"commit_id"` | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ type PullRequest struct { | |||||||
| 	Milestone          *Milestone `json:"milestone"` | 	Milestone          *Milestone `json:"milestone"` | ||||||
| 	Assignee           *User      `json:"assignee"` | 	Assignee           *User      `json:"assignee"` | ||||||
| 	Assignees          []*User    `json:"assignees"` | 	Assignees          []*User    `json:"assignees"` | ||||||
|  | 	RequestedReviewers []*User    `json:"requested_reviewers"` | ||||||
| 	State              StateType  `json:"state"` | 	State              StateType  `json:"state"` | ||||||
| 	IsLocked           bool       `json:"is_locked"` | 	IsLocked           bool       `json:"is_locked"` | ||||||
| 	Comments           int        `json:"comments"` | 	Comments           int        `json:"comments"` | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ type HookEvents struct { | |||||||
| 	PullRequestComment       bool `json:"pull_request_comment"` | 	PullRequestComment       bool `json:"pull_request_comment"` | ||||||
| 	PullRequestReview        bool `json:"pull_request_review"` | 	PullRequestReview        bool `json:"pull_request_review"` | ||||||
| 	PullRequestSync          bool `json:"pull_request_sync"` | 	PullRequestSync          bool `json:"pull_request_sync"` | ||||||
|  | 	PullRequestReviewRequest bool `json:"pull_request_review_request"` | ||||||
| 	Wiki                     bool `json:"wiki"` | 	Wiki                     bool `json:"wiki"` | ||||||
| 	Repository               bool `json:"repository"` | 	Repository               bool `json:"repository"` | ||||||
| 	Release                  bool `json:"release"` | 	Release                  bool `json:"release"` | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ const ( | |||||||
| 	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" | ||||||
| @@ -46,7 +47,7 @@ func (h HookEventType) Event() string { | |||||||
| 	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" | ||||||
|   | |||||||
| @@ -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_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 | ||||||
|   | |||||||
| @@ -194,6 +194,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI | |||||||
| 				PullRequestMilestone:     pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)), | 				PullRequestMilestone:     pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)), | ||||||
| 				PullRequestComment:       pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)), | 				PullRequestComment:       pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)), | ||||||
| 				PullRequestReview:        pullHook(form.Events, "pull_request_review"), | 				PullRequestReview:        pullHook(form.Events, "pull_request_review"), | ||||||
|  | 				PullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)), | ||||||
| 				PullRequestSync:          pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)), | 				PullRequestSync:          pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)), | ||||||
| 				Wiki:                     util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true), | 				Wiki:                     util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true), | ||||||
| 				Repository:               util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true), | 				Repository:               util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true), | ||||||
| @@ -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.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 { | ||||||
|   | |||||||
| @@ -576,7 +576,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is | |||||||
| 	} | 	} | ||||||
| 	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 | ||||||
|   | |||||||
| @@ -177,6 +177,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent { | |||||||
| 			PullRequestComment:       form.PullRequestComment, | 			PullRequestComment:       form.PullRequestComment, | ||||||
| 			PullRequestReview:        form.PullRequestReview, | 			PullRequestReview:        form.PullRequestReview, | ||||||
| 			PullRequestSync:          form.PullRequestSync, | 			PullRequestSync:          form.PullRequestSync, | ||||||
|  | 			PullRequestReviewRequest: form.PullRequestReviewRequest, | ||||||
| 			Wiki:                     form.Wiki, | 			Wiki:                     form.Wiki, | ||||||
| 			Repository:               form.Repository, | 			Repository:               form.Repository, | ||||||
| 			Package:                  form.Package, | 			Package:                  form.Package, | ||||||
|   | |||||||
| @@ -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 { | 	if pr.Issue.ClosedUnix != 0 { | ||||||
| 		apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr() | 		apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr() | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -246,6 +246,7 @@ type WebhookForm struct { | |||||||
| 	PullRequestComment       bool | 	PullRequestComment       bool | ||||||
| 	PullRequestReview        bool | 	PullRequestReview        bool | ||||||
| 	PullRequestSync          bool | 	PullRequestSync          bool | ||||||
|  | 	PullRequestReviewRequest bool | ||||||
| 	Wiki                     bool | 	Wiki                     bool | ||||||
| 	Repository               bool | 	Repository               bool | ||||||
| 	Package                  bool | 	Package                  bool | ||||||
|   | |||||||
| @@ -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) { | 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) | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module. | |||||||
| 	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) | ||||||
|   | |||||||
| @@ -238,6 +238,16 @@ | |||||||
| 				</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
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @@ -19934,6 +19934,13 @@ | |||||||
|           "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" | ||||||
|         }, |         }, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user