From 309354c70ee994a1e8f261d7bc24e7473e601d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=88=E7=AC=91=E9=A3=8E=E7=94=9F=E9=97=B4?= Date: Thu, 25 May 2023 10:06:27 +0800 Subject: [PATCH] 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. --- models/issues/pull.go | 30 ++++++++++-- models/issues/pull_test.go | 29 +++++++++++ models/issues/review.go | 25 +++++++++- models/issues/review_test.go | 19 +++++-- models/user/user.go | 29 +++++++++++ models/webhook/webhook.go | 7 +++ models/webhook/webhook_test.go | 2 +- modules/structs/hook.go | 21 +++++--- modules/structs/pull.go | 27 +++++----- modules/webhook/structs.go | 41 ++++++++-------- modules/webhook/type.go | 3 +- options/locale/locale_en-US.ini | 2 + routers/api/v1/utils/hook.go | 40 ++++++++------- routers/web/repo/issue.go | 2 +- routers/web/repo/webhook.go | 41 ++++++++-------- services/convert/pull.go | 8 +++ services/forms/repo_form.go | 49 ++++++++++--------- services/webhook/notifier.go | 28 +++++++++++ services/webhook/payloader.go | 2 +- templates/repo/settings/webhook/settings.tmpl | 10 ++++ templates/swagger/v1_json.tmpl | 7 +++ 21 files changed, 305 insertions(+), 117 deletions(-) diff --git a/models/issues/pull.go b/models/issues/pull.go index 218a265741..3f37d8d243 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -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 { diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 0b06095213..7a183ac312 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -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{ diff --git a/models/issues/review.go b/models/issues/review.go index ed30bce149..06cf132a48 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -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) diff --git a/models/issues/review_test.go b/models/issues/review_test.go index 3221496577..0cb621812c 100644 --- a/models/issues/review_test.go +++ b/models/issues/review_test.go @@ -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) diff --git a/models/user/user.go b/models/user/user.go index 57b2117bb9..07d8177b6a 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -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 { diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index e3f6b593d9..fc2bbed083 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -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}, } } diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index e05dcaba01..de6568c321 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -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}, diff --git a/modules/structs/hook.go b/modules/structs/hook.go index df5da6790f..cd91d4bc46 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -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 diff --git a/modules/structs/pull.go b/modules/structs/pull.go index f64bb83af9..a4a6f60b05 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -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"` diff --git a/modules/webhook/structs.go b/modules/webhook/structs.go index 96012de352..927a91a74c 100644 --- a/modules/webhook/structs.go +++ b/modules/webhook/structs.go @@ -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. diff --git a/modules/webhook/type.go b/modules/webhook/type.go index db4ab17931..7042d391b7 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -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" diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d863cc72bf..458c99f7fe 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 44625cc9b8..b62d20a18a 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -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 { diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index d7ea3f12f1..1511716c1e 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -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 diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 46b0f49108..5139c0b091 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -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, } diff --git a/services/convert/pull.go b/services/convert/pull.go index 4989e82cd4..598187ca6e 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -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() } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index cacfb64b17..8108a55f7a 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -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 diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index 1f7cb8d988..bd25e20805 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -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) diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go index 9eff25628b..d53e65fa5e 100644 --- a/services/webhook/payloader.go +++ b/services/webhook/payloader.go @@ -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) diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl index 57745c0401..8b03a3c959 100644 --- a/templates/repo/settings/webhook/settings.tmpl +++ b/templates/repo/settings/webhook/settings.tmpl @@ -238,6 +238,16 @@ + +
+
+
+ + + {{.locale.Tr "repo.settings.event_pull_request_review_request_desc"}} +
+
+
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 15402ecdfd..03a65184c3 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -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" },