Granular webhook events (#9626)

* Initial work

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add PR reviews and API coverage

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Split up events

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add migration and locale

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Format

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Revert IsPull

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix comments

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix tests

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix PR reviews

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix issue_comment

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Make fmt

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Migrations

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Backwards compatible API

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix feishu

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Move session commit

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
This commit is contained in:
John Olheiser 2020-03-05 23:10:48 -06:00 committed by GitHub
parent 80db44267c
commit 3f1c0841cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 624 additions and 173 deletions

View File

@ -192,6 +192,8 @@ var migrations = []Migration{
NewMigration("fix merge base for pull requests", fixMergeBase),
// v129 -> v130
NewMigration("remove dependencies from deleted repositories", purgeUnusedDependencies),
// v130 -> v131
NewMigration("Expand webhooks for more granularity", expandWebhooks),
}
// Migrate database to current version

101
models/migrations/v130.go Normal file
View File

@ -0,0 +1,101 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"encoding/json"
"xorm.io/xorm"
)
func expandWebhooks(x *xorm.Engine) error {
type ChooseEvents struct {
Issues bool `json:"issues"`
IssueAssign bool `json:"issue_assign"`
IssueLabel bool `json:"issue_label"`
IssueMilestone bool `json:"issue_milestone"`
IssueComment bool `json:"issue_comment"`
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"`
}
type Events struct {
PushOnly bool `json:"push_only"`
SendEverything bool `json:"send_everything"`
ChooseEvents bool `json:"choose_events"`
BranchFilter string `json:"branch_filter"`
Events ChooseEvents `json:"events"`
}
type Webhook struct {
ID int64
Events string
}
var events Events
var bytes []byte
var last int
const batchSize = 50
sess := x.NewSession()
defer sess.Close()
for {
if err := sess.Begin(); err != nil {
return err
}
var results = make([]Webhook, 0, batchSize)
err := x.OrderBy("id").
Limit(batchSize, last).
Find(&results)
if err != nil {
return err
}
if len(results) == 0 {
break
}
last += len(results)
for _, res := range results {
if err = json.Unmarshal([]byte(res.Events), &events); err != nil {
return err
}
if events.Events.Issues {
events.Events.IssueAssign = true
events.Events.IssueLabel = true
events.Events.IssueMilestone = true
events.Events.IssueComment = true
}
if events.Events.PullRequest {
events.Events.PullRequestAssign = true
events.Events.PullRequestLabel = true
events.Events.PullRequestMilestone = true
events.Events.PullRequestComment = true
events.Events.PullRequestReview = true
events.Events.PullRequestSync = true
}
if bytes, err = json.Marshal(&events); err != nil {
return err
}
_, err = sess.Exec("UPDATE webhook SET events = ? WHERE id = ?", string(bytes), res.ID)
if err != nil {
return err
}
}
if err := sess.Commit(); err != nil {
return err
}
}
return nil
}

View File

@ -61,9 +61,18 @@ type HookEvents struct {
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"`
Repository bool `json:"repository"`
Release bool `json:"release"`
}
@ -154,6 +163,24 @@ func (w *Webhook) HasIssuesEvent() bool {
(w.ChooseEvents && w.HookEvents.Issues)
}
// HasIssuesAssignEvent returns true if hook enabled issues assign event.
func (w *Webhook) HasIssuesAssignEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.IssueAssign)
}
// HasIssuesLabelEvent returns true if hook enabled issues label event.
func (w *Webhook) HasIssuesLabelEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.IssueLabel)
}
// HasIssuesMilestoneEvent returns true if hook enabled issues milestone event.
func (w *Webhook) HasIssuesMilestoneEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.IssueMilestone)
}
// HasIssueCommentEvent returns true if hook enabled issue_comment event.
func (w *Webhook) HasIssueCommentEvent() bool {
return w.SendEverything ||
@ -172,6 +199,54 @@ func (w *Webhook) HasPullRequestEvent() bool {
(w.ChooseEvents && w.HookEvents.PullRequest)
}
// HasPullRequestAssignEvent returns true if hook enabled pull request assign event.
func (w *Webhook) HasPullRequestAssignEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestAssign)
}
// HasPullRequestLabelEvent returns true if hook enabled pull request label event.
func (w *Webhook) HasPullRequestLabelEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestLabel)
}
// HasPullRequestMilestoneEvent returns true if hook enabled pull request milestone event.
func (w *Webhook) HasPullRequestMilestoneEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestMilestone)
}
// HasPullRequestCommentEvent returns true if hook enabled pull_request_comment event.
func (w *Webhook) HasPullRequestCommentEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestComment)
}
// HasPullRequestApprovedEvent returns true if hook enabled pull request review event.
func (w *Webhook) HasPullRequestApprovedEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestReview)
}
// HasPullRequestRejectedEvent returns true if hook enabled pull request review event.
func (w *Webhook) HasPullRequestRejectedEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestReview)
}
// HasPullRequestReviewCommentEvent returns true if hook enabled pull request review event.
func (w *Webhook) HasPullRequestReviewCommentEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestReview)
}
// HasPullRequestSyncEvent returns true if hook enabled pull request sync event.
func (w *Webhook) HasPullRequestSyncEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequestSync)
}
// HasReleaseEvent returns if hook enabled release event.
func (w *Webhook) HasReleaseEvent() bool {
return w.SendEverything ||
@ -198,8 +273,19 @@ func (w *Webhook) EventCheckers() []struct {
{w.HasForkEvent, HookEventFork},
{w.HasPushEvent, HookEventPush},
{w.HasIssuesEvent, HookEventIssues},
{w.HasIssuesAssignEvent, HookEventIssueAssign},
{w.HasIssuesLabelEvent, HookEventIssueLabel},
{w.HasIssuesMilestoneEvent, HookEventIssueMilestone},
{w.HasIssueCommentEvent, HookEventIssueComment},
{w.HasPullRequestEvent, HookEventPullRequest},
{w.HasPullRequestAssignEvent, HookEventPullRequestAssign},
{w.HasPullRequestLabelEvent, HookEventPullRequestLabel},
{w.HasPullRequestMilestoneEvent, HookEventPullRequestMilestone},
{w.HasPullRequestCommentEvent, HookEventPullRequestComment},
{w.HasPullRequestApprovedEvent, HookEventPullRequestReviewApproved},
{w.HasPullRequestRejectedEvent, HookEventPullRequestReviewRejected},
{w.HasPullRequestCommentEvent, HookEventPullRequestReviewComment},
{w.HasPullRequestSyncEvent, HookEventPullRequestSync},
{w.HasRepositoryEvent, HookEventRepository},
{w.HasReleaseEvent, HookEventRelease},
}
@ -503,15 +589,55 @@ const (
HookEventFork HookEventType = "fork"
HookEventPush HookEventType = "push"
HookEventIssues HookEventType = "issues"
HookEventIssueAssign HookEventType = "issue_assign"
HookEventIssueLabel HookEventType = "issue_label"
HookEventIssueMilestone HookEventType = "issue_milestone"
HookEventIssueComment HookEventType = "issue_comment"
HookEventPullRequest HookEventType = "pull_request"
HookEventPullRequestAssign HookEventType = "pull_request_assign"
HookEventPullRequestLabel HookEventType = "pull_request_label"
HookEventPullRequestMilestone HookEventType = "pull_request_milestone"
HookEventPullRequestComment HookEventType = "pull_request_comment"
HookEventPullRequestReviewApproved HookEventType = "pull_request_review_approved"
HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
HookEventPullRequestSync HookEventType = "pull_request_sync"
HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release"
HookEventPullRequestApproved HookEventType = "pull_request_approved"
HookEventPullRequestRejected HookEventType = "pull_request_rejected"
HookEventPullRequestComment HookEventType = "pull_request_comment"
)
// Event returns the HookEventType as an event string
func (h HookEventType) Event() string {
switch h {
case HookEventCreate:
return "create"
case HookEventDelete:
return "delete"
case HookEventFork:
return "fork"
case HookEventPush:
return "push"
case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
return "issues"
case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
HookEventPullRequestSync:
return "pull_request"
case HookEventIssueComment, HookEventPullRequestComment:
return "issue_comment"
case HookEventPullRequestReviewApproved:
return "pull_request_approved"
case HookEventPullRequestReviewRejected:
return "pull_request_rejected"
case HookEventPullRequestReviewComment:
return "pull_request_comment"
case HookEventRepository:
return "repository"
case HookEventRelease:
return "release"
}
return ""
}
// HookRequest represents hook task request information.
type HookRequest struct {
Headers map[string]string `json:"headers"`

View File

@ -61,7 +61,11 @@ func TestWebhook_UpdateEvent(t *testing.T) {
}
func TestWebhook_EventsArray(t *testing.T) {
assert.Equal(t, []string{"create", "delete", "fork", "push", "issues", "issue_comment", "pull_request", "repository", "release"},
assert.Equal(t, []string{"create", "delete", "fork", "push",
"issues", "issue_assign", "issue_label", "issue_milestone", "issue_comment",
"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", "repository", "release"},
(&Webhook{
HookEvent: &HookEvent{SendEverything: true},
}).EventsArray(),

View File

@ -195,10 +195,19 @@ type WebhookForm struct {
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
Repository bool
Active bool
BranchFilter string `binding:"GlobPattern"`

View File

@ -48,7 +48,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model
return
}
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{
Action: api.HookIssueLabelCleared,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
@ -56,7 +56,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model
Sender: doer.APIFormat(),
})
} else {
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{
Action: api.HookIssueLabelCleared,
Index: issue.Index,
Issue: convert.ToAPIIssue(issue),
@ -147,7 +147,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo
apiPullRequest.Action = api.HookIssueAssigned
}
// Assignee comment triggers a webhook
if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest); err != nil {
if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestAssign, apiPullRequest); err != nil {
log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
return
}
@ -165,7 +165,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo
apiIssue.Action = api.HookIssueAssigned
}
// Assignee comment triggers a webhook
if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue); err != nil {
if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueAssign, apiIssue); err != nil {
log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
return
}
@ -338,22 +338,25 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod
}
func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
if err := c.LoadPoster(); err != nil {
var err error
if err = c.LoadPoster(); err != nil {
log.Error("LoadPoster: %v", err)
return
}
if err := c.LoadIssue(); err != nil {
if err = c.LoadIssue(); err != nil {
log.Error("LoadIssue: %v", err)
return
}
if err := c.Issue.LoadAttributes(); err != nil {
if err = c.Issue.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return
}
mode, _ := models.AccessLevel(doer, c.Issue.Repo)
if err := webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
if c.Issue.IsPull {
err = webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited,
Issue: convert.ToAPIIssue(c.Issue),
Comment: c.APIFormat(),
@ -364,8 +367,25 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme
},
Repository: c.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
IsPull: c.Issue.IsPull,
}); err != nil {
IsPull: true,
})
} else {
err = webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited,
Issue: convert.ToAPIIssue(c.Issue),
Comment: c.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
From: oldContent,
},
},
Repository: c.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
IsPull: false,
})
}
if err != nil {
log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
}
}
@ -373,45 +393,76 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme
func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
mode, _ := models.AccessLevel(doer, repo)
if err := webhook_module.PrepareWebhooks(repo, models.HookEventIssueComment, &api.IssueCommentPayload{
var err error
if issue.IsPull {
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(issue),
Comment: comment.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(),
IsPull: issue.IsPull,
}); err != nil {
IsPull: true,
})
} else {
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(issue),
Comment: comment.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(),
IsPull: false,
})
}
if err != nil {
log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
}
}
func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models.Comment) {
if err := comment.LoadPoster(); err != nil {
var err error
if err = comment.LoadPoster(); err != nil {
log.Error("LoadPoster: %v", err)
return
}
if err := comment.LoadIssue(); err != nil {
if err = comment.LoadIssue(); err != nil {
log.Error("LoadIssue: %v", err)
return
}
if err := comment.Issue.LoadAttributes(); err != nil {
if err = comment.Issue.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return
}
mode, _ := models.AccessLevel(doer, comment.Issue.Repo)
if err := webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
if comment.Issue.IsPull {
err = webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted,
Issue: convert.ToAPIIssue(comment.Issue),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
IsPull: comment.Issue.IsPull,
}); err != nil {
IsPull: true,
})
} else {
err = webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted,
Issue: convert.ToAPIIssue(comment.Issue),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
IsPull: false,
})
}
if err != nil {
log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
}
}
func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
@ -438,7 +489,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode
log.Error("LoadIssue: %v", err)
return
}
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{
Action: api.HookIssueLabelUpdated,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
@ -446,7 +497,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode
Sender: doer.APIFormat(),
})
} else {
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Index: issue.Index,
Issue: convert.ToAPIIssue(issue),
@ -480,7 +531,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m
log.Error("LoadIssue: %v", err)
return
}
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestMilestone, &api.PullRequestPayload{
Action: hookAction,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
@ -488,7 +539,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m
Sender: doer.APIFormat(),
})
} else {
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueMilestone, &api.IssuePayload{
Action: hookAction,
Index: issue.Index,
Issue: convert.ToAPIIssue(issue),
@ -597,11 +648,11 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
switch review.Type {
case models.ReviewTypeApprove:
reviewHookType = models.HookEventPullRequestApproved
reviewHookType = models.HookEventPullRequestReviewApproved
case models.ReviewTypeComment:
reviewHookType = models.HookEventPullRequestComment
case models.ReviewTypeReject:
reviewHookType = models.HookEventPullRequestRejected
reviewHookType = models.HookEventPullRequestReviewRejected
default:
// unsupported review webhook type here
log.Error("Unsupported review webhook type")
@ -619,7 +670,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
return
}
if err := webhook_module.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Action: api.HookIssueReviewed,
Index: review.Issue.Index,
PullRequest: convert.ToAPIPullRequest(pr),
Repository: review.Issue.Repo.APIFormat(mode),
@ -673,7 +724,7 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m
return
}
if err := webhook_module.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
if err := webhook_module.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequestSync, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: pr.Issue.Index,
PullRequest: convert.ToAPIPullRequest(pr),

View File

@ -374,6 +374,8 @@ const (
HookIssueMilestoned HookIssueAction = "milestoned"
// HookIssueDemilestoned is an issue action for when a milestone is cleared on an issue.
HookIssueDemilestoned HookIssueAction = "demilestoned"
// HookIssueReviewed is an issue action for when a pull request is reviewed
HookIssueReviewed HookIssueAction = "reviewed"
)
// IssuePayload represents the payload information that is sent along with an issue event.

View File

@ -74,13 +74,13 @@ func Deliver(t *models.HookTask) error {
}
req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", string(t.EventType))
req.Header.Add("X-Gitea-Event", t.EventType.Event())
req.Header.Add("X-Gitea-Signature", t.Signature)
req.Header.Add("X-Gogs-Delivery", t.UUID)
req.Header.Add("X-Gogs-Event", string(t.EventType))
req.Header.Add("X-Gogs-Event", t.EventType.Event())
req.Header.Add("X-Gogs-Signature", t.Signature)
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{string(t.EventType)}
req.Header["X-GitHub-Event"] = []string{t.EventType.Event()}
// Record delivery information.
t.RequestInfo = &models.HookRequest{

View File

@ -181,7 +181,7 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload,
func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*DingtalkPayload, error) {
var text, title string
switch p.Action {
case api.HookIssueSynchronized:
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
return nil, err
@ -261,15 +261,16 @@ func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string
return getDingtalkDeletePayload(p.(*api.DeletePayload))
case models.HookEventFork:
return getDingtalkForkPayload(p.(*api.ForkPayload))
case models.HookEventIssues:
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getDingtalkIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment:
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
case models.HookEventPush:
return getDingtalkPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest:
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
models.HookEventPullRequestMilestone, models.HookEventPullRequestSync:
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPullRequestApproved, models.HookEventPullRequestRejected, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewComment:
return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case models.HookEventRepository:
return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))

View File

@ -296,7 +296,7 @@ func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *Disco
var text, title string
var color int
switch p.Action {
case api.HookIssueSynchronized:
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
return nil, err
@ -306,9 +306,9 @@ func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *Disco
text = p.Review.Content
switch event {
case models.HookEventPullRequestApproved:
case models.HookEventPullRequestReviewApproved:
color = greenColor
case models.HookEventPullRequestRejected:
case models.HookEventPullRequestReviewRejected:
color = redColor
case models.HookEventPullRequestComment:
color = greyColor
@ -405,15 +405,16 @@ func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string)
return getDiscordDeletePayload(p.(*api.DeletePayload), discord)
case models.HookEventFork:
return getDiscordForkPayload(p.(*api.ForkPayload), discord)
case models.HookEventIssues:
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
case models.HookEventIssueComment:
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
case models.HookEventPush:
return getDiscordPushPayload(p.(*api.PushPayload), discord)
case models.HookEventPullRequest:
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
models.HookEventPullRequestMilestone, models.HookEventPullRequestSync:
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment:
return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event)
case models.HookEventRepository:
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
@ -428,9 +429,9 @@ func parseHookPullRequestEventType(event models.HookEventType) (string, error) {
switch event {
case models.HookEventPullRequestApproved:
case models.HookEventPullRequestReviewApproved:
return "approved", nil
case models.HookEventPullRequestRejected:
case models.HookEventPullRequestReviewRejected:
return "rejected", nil
case models.HookEventPullRequestComment:
return "comment", nil

View File

@ -189,7 +189,7 @@ func GetFeishuPayload(p api.Payloader, event models.HookEventType, meta string)
return getFeishuPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest:
return getFeishuPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPullRequestApproved, models.HookEventPullRequestRejected, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestComment:
return getFeishuPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case models.HookEventRepository:
return getFeishuRepositoryPayload(p.(*api.RepositoryPayload))

View File

@ -395,7 +395,7 @@ func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event model
var text, title string
var color int
switch p.Action {
case api.HookIssueSynchronized:
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
return nil, err
@ -405,9 +405,9 @@ func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event model
text = p.Review.Content
switch event {
case models.HookEventPullRequestApproved:
case models.HookEventPullRequestReviewApproved:
color = greenColor
case models.HookEventPullRequestRejected:
case models.HookEventPullRequestReviewRejected:
color = redColor
case models.HookEventPullRequestComment:
color = greyColor
@ -555,15 +555,16 @@ func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string)
return getMSTeamsDeletePayload(p.(*api.DeletePayload))
case models.HookEventFork:
return getMSTeamsForkPayload(p.(*api.ForkPayload))
case models.HookEventIssues:
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getMSTeamsIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment:
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getMSTeamsIssueCommentPayload(p.(*api.IssueCommentPayload))
case models.HookEventPush:
return getMSTeamsPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest:
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
models.HookEventPullRequestMilestone, models.HookEventPullRequestSync:
return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment:
return getMSTeamsPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case models.HookEventRepository:
return getMSTeamsRepositoryPayload(p.(*api.RepositoryPayload))

View File

@ -271,7 +271,7 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM
var text string
switch p.Action {
case api.HookIssueSynchronized:
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
return nil, err
@ -324,15 +324,16 @@ func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (
return getSlackDeletePayload(p.(*api.DeletePayload), slack)
case models.HookEventFork:
return getSlackForkPayload(p.(*api.ForkPayload), slack)
case models.HookEventIssues:
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
case models.HookEventIssueComment:
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
case models.HookEventPush:
return getSlackPushPayload(p.(*api.PushPayload), slack)
case models.HookEventPullRequest:
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
models.HookEventPullRequestMilestone, models.HookEventPullRequestSync:
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment:
return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event)
case models.HookEventRepository:
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)

View File

@ -151,7 +151,7 @@ func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload,
func getTelegramPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*TelegramPayload, error) {
var text, attachmentText string
switch p.Action {
case api.HookIssueSynchronized:
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
return nil, err
@ -203,15 +203,16 @@ func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string
return getTelegramDeletePayload(p.(*api.DeletePayload))
case models.HookEventFork:
return getTelegramForkPayload(p.(*api.ForkPayload))
case models.HookEventIssues:
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getTelegramIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment:
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getTelegramIssueCommentPayload(p.(*api.IssueCommentPayload))
case models.HookEventPush:
return getTelegramPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest:
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,
models.HookEventPullRequestMilestone, models.HookEventPullRequestSync:
return getTelegramPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment:
return getTelegramPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case models.HookEventRepository:
return getTelegramRepositoryPayload(p.(*api.RepositoryPayload))

View File

@ -1380,26 +1380,47 @@ settings.event_desc = Trigger On:
settings.event_push_only = Push Events
settings.event_send_everything = All Events
settings.event_choose = Custom Events…
settings.event_header_repository = Repository Events
settings.event_create = Create
settings.event_create_desc = Branch or tag created.
settings.event_delete = Delete
settings.event_delete_desc = Branch or tag deleted
settings.event_delete_desc = Branch or tag deleted.
settings.event_fork = Fork
settings.event_fork_desc = Repository forked
settings.event_issues = Issues
settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned.
settings.event_issue_comment = Issue Comment
settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
settings.event_fork_desc = Repository forked.
settings.event_release = Release
settings.event_release_desc = Release published, updated or deleted in a repository.
settings.event_pull_request = Pull Request
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized.
settings.event_push = Push
settings.event_push_desc = Git push to a repository.
settings.branch_filter = Branch filter
settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or <code>*</code>, events for all branches are reported. See <a href="https://godoc.org/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for syntax. Examples: <code>master</code>, <code>{master,release*}</code>.
settings.event_repository = Repository
settings.event_repository_desc = Repository created or deleted.
settings.event_header_issue = Issue Events
settings.event_issues = Issues
settings.event_issues_desc = Issue opened, closed, reopened, or edited.
settings.event_issue_assign = Issue Assigned
settings.event_issue_assign_desc = Issue assigned or unassigned.
settings.event_issue_label = Issue Labeled
settings.event_issue_label_desc = Issue labels updated or cleared.
settings.event_issue_milestone = Issue Milestoned
settings.event_issue_milestone_desc = Issue milestoned or demilestoned.
settings.event_issue_comment = Issue Comment
settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
settings.event_header_pull_request = Pull Request Events
settings.event_pull_request = Pull Request
settings.event_pull_request_desc = Pull request opened, closed, reopened, or edited.
settings.event_pull_request_assign = Pull Request Assigned
settings.event_pull_request_assign_desc = Pull request assigned or unassigned.
settings.event_pull_request_label = Pull Request Labeled
settings.event_pull_request_label_desc = Pull request labels updated or cleared.
settings.event_pull_request_milestone = Pull Request Milestoned
settings.event_pull_request_milestone_desc = Pull request milestoned or demilestoned.
settings.event_pull_request_comment = Pull Request Comment
settings.event_pull_request_comment_desc = Pull request comment created, edited, or deleted.
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.branch_filter = Branch filter
settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or <code>*</code>, events for all branches are reported. See <a href="https://godoc.org/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for syntax. Examples: <code>master</code>, <code>{master,release*}</code>.
settings.active = Active
settings.active_helper = Information about triggered events will be sent to this webhook URL.
settings.add_hook_success = The webhook has been added.

View File

@ -87,6 +87,14 @@ func AddRepoHook(ctx *context.APIContext, form *api.CreateHookOption) {
}
}
func issuesHook(events []string, event string) bool {
return com.IsSliceContainsStr(events, event) || com.IsSliceContainsStr(events, string(models.HookEventIssues))
}
func pullHook(events []string, event string) bool {
return com.IsSliceContainsStr(events, event) || com.IsSliceContainsStr(events, string(models.HookEventPullRequest))
}
// addHook add the hook specified by `form`, `orgID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID int64) (*models.Webhook, bool) {
@ -106,10 +114,19 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)),
Delete: com.IsSliceContainsStr(form.Events, string(models.HookEventDelete)),
Fork: com.IsSliceContainsStr(form.Events, string(models.HookEventFork)),
Issues: com.IsSliceContainsStr(form.Events, string(models.HookEventIssues)),
IssueComment: com.IsSliceContainsStr(form.Events, string(models.HookEventIssueComment)),
Issues: issuesHook(form.Events, "issues_only"),
IssueAssign: issuesHook(form.Events, string(models.HookEventIssueAssign)),
IssueLabel: issuesHook(form.Events, string(models.HookEventIssueLabel)),
IssueMilestone: issuesHook(form.Events, string(models.HookEventIssueMilestone)),
IssueComment: issuesHook(form.Events, string(models.HookEventIssueComment)),
Push: com.IsSliceContainsStr(form.Events, string(models.HookEventPush)),
PullRequest: com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)),
PullRequest: pullHook(form.Events, "pull_request_only"),
PullRequestAssign: pullHook(form.Events, string(models.HookEventPullRequestAssign)),
PullRequestLabel: pullHook(form.Events, string(models.HookEventPullRequestLabel)),
PullRequestMilestone: pullHook(form.Events, string(models.HookEventPullRequestMilestone)),
PullRequestComment: pullHook(form.Events, string(models.HookEventPullRequestComment)),
PullRequestReview: pullHook(form.Events, "pull_request_review"),
PullRequestSync: pullHook(form.Events, string(models.HookEventPullRequestSync)),
Repository: com.IsSliceContainsStr(form.Events, string(models.HookEventRepository)),
Release: com.IsSliceContainsStr(form.Events, string(models.HookEventRelease)),
},

View File

@ -140,10 +140,19 @@ func ParseHookEvent(form auth.WebhookForm) *models.HookEvent {
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,
Repository: form.Repository,
},
BranchFilter: form.BranchFilter,

View File

@ -23,6 +23,10 @@
</div>
<div class="events fields ui grid" {{if not .Webhook.ChooseEvents}}style="display:none"{{end}}>
<!-- Repository Events -->
<div class="fourteen wide column">
<label>{{.i18n.Tr "repo.settings.event_header_repository"}}</label>
</div>
<!-- Create -->
<div class="seven wide column">
<div class="field">
@ -63,36 +67,6 @@
</div>
</div>
</div>
<!-- Issues -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issues" type="checkbox" tabindex="0" {{if .Webhook.Issues}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issues"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issues_desc"}}</span>
</div>
</div>
</div>
<!-- Issue Comment -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issue_comment" type="checkbox" tabindex="0" {{if .Webhook.IssueComment}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue_comment"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_comment_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request" type="checkbox" tabindex="0" {{if .Webhook.PullRequest}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_desc"}}</span>
</div>
</div>
</div>
<!-- Repository -->
<div class="seven wide column">
<div class="field">
@ -113,6 +87,136 @@
</div>
</div>
</div>
<!-- Issue Events -->
<div class="fourteen wide column">
<label>{{.i18n.Tr "repo.settings.event_header_issue"}}</label>
</div>
<!-- Issues -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issues" type="checkbox" tabindex="0" {{if .Webhook.Issues}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issues"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issues_desc"}}</span>
</div>
</div>
</div>
<!-- Issue Assign -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issue_assign" type="checkbox" tabindex="0" {{if .Webhook.IssueAssign}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue_assign"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_assign_desc"}}</span>
</div>
</div>
</div>
<!-- Issue Label -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issue_label" type="checkbox" tabindex="0" {{if .Webhook.IssueLabel}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue_label"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_label_desc"}}</span>
</div>
</div>
</div>
<!-- Issue Milestone -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issue_milestone" type="checkbox" tabindex="0" {{if .Webhook.IssueMilestone}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue_milestone"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_milestone_desc"}}</span>
</div>
</div>
</div>
<!-- Issue Comment -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issue_comment" type="checkbox" tabindex="0" {{if .Webhook.IssueComment}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue_comment"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_comment_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Events -->
<div class="fourteen wide column">
<label>{{.i18n.Tr "repo.settings.event_header_pull_request"}}</label>
</div>
<!-- Pull Request -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request" type="checkbox" tabindex="0" {{if .Webhook.PullRequest}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Assign -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request_assign" type="checkbox" tabindex="0" {{if .Webhook.PullRequestAssign}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request_assign"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_assign_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Label -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request_label" type="checkbox" tabindex="0" {{if .Webhook.PullRequestLabel}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request_label"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_label_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Milestone -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request_milestone" type="checkbox" tabindex="0" {{if .Webhook.PullRequestMilestone}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request_milestone"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_milestone_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Comment -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request_comment" type="checkbox" tabindex="0" {{if .Webhook.PullRequestComment}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request_comment"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_comment_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Review -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request_review" type="checkbox" tabindex="0" {{if .Webhook.PullRequestReview}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request_review"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_review_desc"}}</span>
</div>
</div>
</div>
<!-- Pull Request Sync -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="pull_request_sync" type="checkbox" tabindex="0" {{if .Webhook.PullRequestSync}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_pull_request_sync"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_pull_request_sync_desc"}}</span>
</div>
</div>
</div>
</div>
</div>