From e94f37f95e286ba3b982700744e7f55fdb31f046 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Thu, 23 Jan 2025 10:53:06 -0800
Subject: [PATCH] Refactor webhook events (#33337)

Extract from #33320

This PR uses a map instead of a struct to store webhook event
information. It removes many duplicated functions and makes the logic
clearer.
---
 models/webhook/webhook.go                     | 201 +++---------------
 models/webhook/webhook_test.go                |  12 +-
 modules/webhook/events.go                     |  20 ++
 modules/webhook/structs.go                    |  39 ----
 modules/webhook/type.go                       |  53 +++--
 routers/api/v1/utils/hook.go                  |  81 ++++---
 routers/web/repo/setting/webhook.go           |  42 ++--
 services/forms/repo_form.go                   |  11 +-
 services/webhook/webhook.go                   |  10 +-
 templates/repo/settings/webhook/settings.tmpl |  42 ++--
 10 files changed, 176 insertions(+), 335 deletions(-)
 create mode 100644 modules/webhook/events.go
 delete mode 100644 modules/webhook/structs.go

diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index 894357e36a..97ad373027 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -167,186 +167,39 @@ func (w *Webhook) UpdateEvent() error {
 	return err
 }
 
-// HasCreateEvent returns true if hook enabled create event.
-func (w *Webhook) HasCreateEvent() bool {
-	return w.SendEverything ||
-		(w.ChooseEvents && w.HookEvents.Create)
-}
-
-// HasDeleteEvent returns true if hook enabled delete event.
-func (w *Webhook) HasDeleteEvent() bool {
-	return w.SendEverything ||
-		(w.ChooseEvents && w.HookEvents.Delete)
-}
-
-// HasForkEvent returns true if hook enabled fork event.
-func (w *Webhook) HasForkEvent() bool {
-	return w.SendEverything ||
-		(w.ChooseEvents && w.HookEvents.Fork)
-}
-
-// HasIssuesEvent returns true if hook enabled issues event.
-func (w *Webhook) HasIssuesEvent() bool {
-	return w.SendEverything ||
-		(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 ||
-		(w.ChooseEvents && w.HookEvents.IssueComment)
-}
-
-// HasPushEvent returns true if hook enabled push event.
-func (w *Webhook) HasPushEvent() bool {
-	return w.PushOnly || w.SendEverything ||
-		(w.ChooseEvents && w.HookEvents.Push)
-}
-
-// HasPullRequestEvent returns true if hook enabled pull request event.
-func (w *Webhook) HasPullRequestEvent() bool {
-	return w.SendEverything ||
-		(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)
-}
-
-// HasWikiEvent returns true if hook enabled wiki event.
-func (w *Webhook) HasWikiEvent() bool {
-	return w.SendEverything ||
-		(w.ChooseEvents && w.HookEvent.Wiki)
-}
-
-// HasReleaseEvent returns if hook enabled release event.
-func (w *Webhook) HasReleaseEvent() bool {
-	return w.SendEverything ||
-		(w.ChooseEvents && w.HookEvents.Release)
-}
-
-// HasRepositoryEvent returns if hook enabled repository event.
-func (w *Webhook) HasRepositoryEvent() bool {
-	return w.SendEverything ||
-		(w.ChooseEvents && w.HookEvents.Repository)
-}
-
-// HasPackageEvent returns if hook enabled package event.
-func (w *Webhook) HasPackageEvent() bool {
-	return w.SendEverything ||
-		(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
-	Type webhook_module.HookEventType
-} {
-	return []struct {
-		Has  func() bool
-		Type webhook_module.HookEventType
-	}{
-		{w.HasCreateEvent, webhook_module.HookEventCreate},
-		{w.HasDeleteEvent, webhook_module.HookEventDelete},
-		{w.HasForkEvent, webhook_module.HookEventFork},
-		{w.HasPushEvent, webhook_module.HookEventPush},
-		{w.HasIssuesEvent, webhook_module.HookEventIssues},
-		{w.HasIssuesAssignEvent, webhook_module.HookEventIssueAssign},
-		{w.HasIssuesLabelEvent, webhook_module.HookEventIssueLabel},
-		{w.HasIssuesMilestoneEvent, webhook_module.HookEventIssueMilestone},
-		{w.HasIssueCommentEvent, webhook_module.HookEventIssueComment},
-		{w.HasPullRequestEvent, webhook_module.HookEventPullRequest},
-		{w.HasPullRequestAssignEvent, webhook_module.HookEventPullRequestAssign},
-		{w.HasPullRequestLabelEvent, webhook_module.HookEventPullRequestLabel},
-		{w.HasPullRequestMilestoneEvent, webhook_module.HookEventPullRequestMilestone},
-		{w.HasPullRequestCommentEvent, webhook_module.HookEventPullRequestComment},
-		{w.HasPullRequestApprovedEvent, webhook_module.HookEventPullRequestReviewApproved},
-		{w.HasPullRequestRejectedEvent, webhook_module.HookEventPullRequestReviewRejected},
-		{w.HasPullRequestCommentEvent, webhook_module.HookEventPullRequestReviewComment},
-		{w.HasPullRequestSyncEvent, webhook_module.HookEventPullRequestSync},
-		{w.HasWikiEvent, webhook_module.HookEventWiki},
-		{w.HasRepositoryEvent, webhook_module.HookEventRepository},
-		{w.HasReleaseEvent, webhook_module.HookEventRelease},
-		{w.HasPackageEvent, webhook_module.HookEventPackage},
-		{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
+func (w *Webhook) HasEvent(evt webhook_module.HookEventType) bool {
+	if w.SendEverything {
+		return true
 	}
+	if w.PushOnly {
+		return evt == webhook_module.HookEventPush
+	}
+	checkEvt := evt
+	switch evt {
+	case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
+		checkEvt = webhook_module.HookEventPullRequestReview
+	}
+	return w.HookEvents[checkEvt]
 }
 
 // EventsArray returns an array of hook events
 func (w *Webhook) EventsArray() []string {
-	events := make([]string, 0, 7)
+	if w.SendEverything {
+		events := make([]string, 0, len(webhook_module.AllEvents()))
+		for _, evt := range webhook_module.AllEvents() {
+			events = append(events, string(evt))
+		}
+		return events
+	}
 
-	for _, c := range w.EventCheckers() {
-		if c.Has() {
-			events = append(events, string(c.Type))
+	if w.PushOnly {
+		return []string{string(webhook_module.HookEventPush)}
+	}
+
+	events := make([]string, 0, len(w.HookEvents))
+	for event, enabled := range w.HookEvents {
+		if enabled {
+			events = append(events, string(event))
 		}
 	}
 	return events
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index c6c3f40d46..ee53d6da92 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -54,9 +54,9 @@ func TestWebhook_UpdateEvent(t *testing.T) {
 		SendEverything: false,
 		ChooseEvents:   false,
 		HookEvents: webhook_module.HookEvents{
-			Create:      false,
-			Push:        true,
-			PullRequest: false,
+			webhook_module.HookEventCreate:      false,
+			webhook_module.HookEventPush:        true,
+			webhook_module.HookEventPullRequest: false,
 		},
 	}
 	webhook.HookEvent = hookEvent
@@ -68,13 +68,13 @@ func TestWebhook_UpdateEvent(t *testing.T) {
 }
 
 func TestWebhook_EventsArray(t *testing.T) {
-	assert.Equal(t, []string{
+	assert.EqualValues(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", "wiki", "repository", "release",
-		"package", "pull_request_review_request",
+		"pull_request_review_comment", "pull_request_sync", "pull_request_review_request", "wiki", "repository", "release",
+		"package", "status",
 	},
 		(&Webhook{
 			HookEvent: &webhook_module.HookEvent{SendEverything: true},
diff --git a/modules/webhook/events.go b/modules/webhook/events.go
new file mode 100644
index 0000000000..f4dfff0294
--- /dev/null
+++ b/modules/webhook/events.go
@@ -0,0 +1,20 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package webhook
+
+type HookEvents map[HookEventType]bool
+
+func (he HookEvents) Get(evt HookEventType) bool {
+	return he[evt]
+}
+
+// HookEvent represents events that will delivery hook.
+type HookEvent struct {
+	PushOnly       bool   `json:"push_only"`
+	SendEverything bool   `json:"send_everything"`
+	ChooseEvents   bool   `json:"choose_events"`
+	BranchFilter   string `json:"branch_filter"`
+
+	HookEvents `json:"events"`
+}
diff --git a/modules/webhook/structs.go b/modules/webhook/structs.go
deleted file mode 100644
index 927a91a74c..0000000000
--- a/modules/webhook/structs.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-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"`
-	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.
-type HookEvent struct {
-	PushOnly       bool   `json:"push_only"`
-	SendEverything bool   `json:"send_everything"`
-	ChooseEvents   bool   `json:"choose_events"`
-	BranchFilter   string `json:"branch_filter"`
-
-	HookEvents `json:"events"`
-}
diff --git a/modules/webhook/type.go b/modules/webhook/type.go
index aa4de45eb4..b244bb0cff 100644
--- a/modules/webhook/type.go
+++ b/modules/webhook/type.go
@@ -31,21 +31,47 @@ const (
 	HookEventRepository                HookEventType = "repository"
 	HookEventRelease                   HookEventType = "release"
 	HookEventPackage                   HookEventType = "package"
-	HookEventSchedule                  HookEventType = "schedule"
 	HookEventStatus                    HookEventType = "status"
+	// once a new event added here, please also added to AllEvents() function
+
+	// FIXME: This event should be a group of pull_request_review_xxx events
+	HookEventPullRequestReview HookEventType = "pull_request_review"
+	// Actions event only
+	HookEventSchedule HookEventType = "schedule"
 )
 
+func AllEvents() []HookEventType {
+	return []HookEventType{
+		HookEventCreate,
+		HookEventDelete,
+		HookEventFork,
+		HookEventPush,
+		HookEventIssues,
+		HookEventIssueAssign,
+		HookEventIssueLabel,
+		HookEventIssueMilestone,
+		HookEventIssueComment,
+		HookEventPullRequest,
+		HookEventPullRequestAssign,
+		HookEventPullRequestLabel,
+		HookEventPullRequestMilestone,
+		HookEventPullRequestComment,
+		HookEventPullRequestReviewApproved,
+		HookEventPullRequestReviewRejected,
+		HookEventPullRequestReviewComment,
+		HookEventPullRequestSync,
+		HookEventPullRequestReviewRequest,
+		HookEventWiki,
+		HookEventRepository,
+		HookEventRelease,
+		HookEventPackage,
+		HookEventStatus,
+	}
+}
+
 // 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,
@@ -59,14 +85,9 @@ func (h HookEventType) Event() string {
 		return "pull_request_rejected"
 	case HookEventPullRequestReviewComment:
 		return "pull_request_comment"
-	case HookEventWiki:
-		return "wiki"
-	case HookEventRepository:
-		return "repository"
-	case HookEventRelease:
-		return "release"
+	default:
+		return string(h)
 	}
-	return ""
 }
 
 func (h HookEventType) IsPullRequest() bool {
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 4328878e19..148fe64992 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -185,26 +185,27 @@ 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"),
-				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),
+				webhook_module.HookEventCreate:                   util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
+				webhook_module.HookEventDelete:                   util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
+				webhook_module.HookEventFork:                     util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
+				webhook_module.HookEventIssues:                   issuesHook(form.Events, "issues_only"),
+				webhook_module.HookEventIssueAssign:              issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
+				webhook_module.HookEventIssueLabel:               issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
+				webhook_module.HookEventIssueMilestone:           issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
+				webhook_module.HookEventIssueComment:             issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
+				webhook_module.HookEventPush:                     util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
+				webhook_module.HookEventPullRequest:              pullHook(form.Events, "pull_request_only"),
+				webhook_module.HookEventPullRequestAssign:        pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
+				webhook_module.HookEventPullRequestLabel:         pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
+				webhook_module.HookEventPullRequestMilestone:     pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
+				webhook_module.HookEventPullRequestComment:       pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
+				webhook_module.HookEventPullRequestReview:        pullHook(form.Events, "pull_request_review"),
+				webhook_module.HookEventPullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
+				webhook_module.HookEventPullRequestSync:          pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
+				webhook_module.HookEventWiki:                     util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
+				webhook_module.HookEventRepository:               util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
+				webhook_module.HookEventRelease:                  util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
+				webhook_module.HookEventStatus:                   util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
 			},
 			BranchFilter: form.BranchFilter,
 		},
@@ -356,14 +357,13 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
 	w.PushOnly = false
 	w.SendEverything = false
 	w.ChooseEvents = true
-	w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
-	w.Push = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
-	w.Create = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
-	w.Delete = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
-	w.Fork = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
-	w.Repository = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
-	w.Wiki = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
-	w.Release = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
+	w.HookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
+	w.HookEvents[webhook_module.HookEventPush] = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
+	w.HookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
+	w.HookEvents[webhook_module.HookEventFork] = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
+	w.HookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
+	w.HookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
+	w.HookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
 	w.BranchFilter = form.BranchFilter
 
 	err := w.SetHeaderAuthorization(form.AuthorizationHeader)
@@ -373,21 +373,20 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
 	}
 
 	// Issues
-	w.Issues = issuesHook(form.Events, "issues_only")
-	w.IssueAssign = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
-	w.IssueLabel = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
-	w.IssueMilestone = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
-	w.IssueComment = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
+	w.HookEvents[webhook_module.HookEventIssues] = issuesHook(form.Events, "issues_only")
+	w.HookEvents[webhook_module.HookEventIssueAssign] = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
+	w.HookEvents[webhook_module.HookEventIssueLabel] = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
+	w.HookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
+	w.HookEvents[webhook_module.HookEventIssueComment] = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
 
 	// Pull requests
-	w.PullRequest = pullHook(form.Events, "pull_request_only")
-	w.PullRequestAssign = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
-	w.PullRequestLabel = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
-	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))
+	w.HookEvents[webhook_module.HookEventPullRequest] = pullHook(form.Events, "pull_request_only")
+	w.HookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
+	w.HookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
+	w.HookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
+	w.HookEvents[webhook_module.HookEventPullRequestReview] = pullHook(form.Events, "pull_request_review")
+	w.HookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
+	w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
 
 	if err := w.UpdateEvent(); err != nil {
 		ctx.Error(http.StatusInternalServerError, "UpdateEvent", err)
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index ce67ea3c01..997145b507 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -163,27 +163,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,
-			PullRequestReviewRequest: form.PullRequestReviewRequest,
-			Wiki:                     form.Wiki,
-			Repository:               form.Repository,
-			Package:                  form.Package,
+			webhook_module.HookEventCreate:                   form.Create,
+			webhook_module.HookEventDelete:                   form.Delete,
+			webhook_module.HookEventFork:                     form.Fork,
+			webhook_module.HookEventIssues:                   form.Issues,
+			webhook_module.HookEventIssueAssign:              form.IssueAssign,
+			webhook_module.HookEventIssueLabel:               form.IssueLabel,
+			webhook_module.HookEventIssueMilestone:           form.IssueMilestone,
+			webhook_module.HookEventIssueComment:             form.IssueComment,
+			webhook_module.HookEventRelease:                  form.Release,
+			webhook_module.HookEventPush:                     form.Push,
+			webhook_module.HookEventPullRequest:              form.PullRequest,
+			webhook_module.HookEventPullRequestAssign:        form.PullRequestAssign,
+			webhook_module.HookEventPullRequestLabel:         form.PullRequestLabel,
+			webhook_module.HookEventPullRequestMilestone:     form.PullRequestMilestone,
+			webhook_module.HookEventPullRequestComment:       form.PullRequestComment,
+			webhook_module.HookEventPullRequestReview:        form.PullRequestReview,
+			webhook_module.HookEventPullRequestSync:          form.PullRequestSync,
+			webhook_module.HookEventPullRequestReviewRequest: form.PullRequestReviewRequest,
+			webhook_module.HookEventWiki:                     form.Wiki,
+			webhook_module.HookEventRepository:               form.Repository,
+			webhook_module.HookEventPackage:                  form.Package,
 		},
 		BranchFilter: form.BranchFilter,
 	}
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 4f9806dc93..1a1c6585db 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -219,26 +219,18 @@ type ProtectBranchPriorityForm struct {
 	IDs []int64
 }
 
-//  __      __      ___.   .__                   __
-// /  \    /  \ ____\_ |__ |  |__   ____   ____ |  | __
-// \   \/\/   // __ \| __ \|  |  \ /  _ \ /  _ \|  |/ /
-//  \        /\  ___/| \_\ \   Y  (  <_> |  <_> )    <
-//   \__/\  /  \___  >___  /___|  /\____/ \____/|__|_ \
-//        \/       \/    \/     \/                   \/
-
 // WebhookForm form for changing web hook
 type WebhookForm struct {
 	Events                   string
 	Create                   bool
 	Delete                   bool
 	Fork                     bool
+	Push                     bool
 	Issues                   bool
 	IssueAssign              bool
 	IssueLabel               bool
 	IssueMilestone           bool
 	IssueComment             bool
-	Release                  bool
-	Push                     bool
 	PullRequest              bool
 	PullRequestAssign        bool
 	PullRequestLabel         bool
@@ -249,6 +241,7 @@ type WebhookForm struct {
 	PullRequestReviewRequest bool
 	Wiki                     bool
 	Repository               bool
+	Release                  bool
 	Package                  bool
 	Active                   bool
 	BranchFilter             string `binding:"GlobPattern"`
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index e0e8fa2fc1..b4609e8a51 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -137,14 +137,8 @@ func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook
 		return nil
 	}
 
-	for _, e := range w.EventCheckers() {
-		if event == e.Type {
-			if !e.Has() {
-				return nil
-			}
-
-			break
-		}
+	if !w.HasEvent(event) {
+		return nil
 	}
 
 	// Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.).
diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl
index cf3b0eb053..1a01a6aea8 100644
--- a/templates/repo/settings/webhook/settings.tmpl
+++ b/templates/repo/settings/webhook/settings.tmpl
@@ -31,7 +31,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="create" type="checkbox" {{if .Webhook.Create}}checked{{end}}>
+					<input name="create" type="checkbox" {{if .Webhook.HookEvents.Get "create"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_create"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_create_desc"}}</span>
 				</div>
@@ -41,7 +41,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="delete" type="checkbox" {{if .Webhook.Delete}}checked{{end}}>
+					<input name="delete" type="checkbox" {{if .Webhook.HookEvents.Get "delete"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_delete"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_delete_desc"}}</span>
 				</div>
@@ -51,7 +51,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="fork" type="checkbox" {{if .Webhook.Fork}}checked{{end}}>
+					<input name="fork" type="checkbox" {{if .Webhook.HookEvents.Get "fork"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_fork"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_fork_desc"}}</span>
 				</div>
@@ -61,7 +61,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="push" type="checkbox" {{if .Webhook.Push}}checked{{end}}>
+					<input name="push" type="checkbox" {{if .Webhook.HookEvents.Get "push"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_push"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_push_desc"}}</span>
 				</div>
@@ -71,7 +71,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="repository" type="checkbox" {{if .Webhook.Repository}}checked{{end}}>
+					<input name="repository" type="checkbox" {{if .Webhook.HookEvents.Get "repository"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_repository"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_repository_desc"}}</span>
 				</div>
@@ -81,7 +81,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="release" type="checkbox" {{if .Webhook.Release}}checked{{end}}>
+					<input name="release" type="checkbox" {{if .Webhook.HookEvents.Get "release"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_release"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_release_desc"}}</span>
 				</div>
@@ -91,7 +91,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="package" type="checkbox" {{if .Webhook.Package}}checked{{end}}>
+					<input name="package" type="checkbox" {{if .Webhook.HookEvents.Get "package"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_package"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_package_desc"}}</span>
 				</div>
@@ -102,7 +102,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="wiki" type="checkbox" {{if .Webhook.Wiki}}checked{{end}}>
+					<input name="wiki" type="checkbox" {{if .Webhook.HookEvents.Get "wiki"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_wiki"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_wiki_desc"}}</span>
 				</div>
@@ -117,7 +117,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="issues" type="checkbox" {{if .Webhook.Issues}}checked{{end}}>
+					<input name="issues" type="checkbox" {{if .Webhook.HookEvents.Get "issues"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_issues"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_issues_desc"}}</span>
 				</div>
@@ -127,7 +127,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="issue_assign" type="checkbox" {{if .Webhook.IssueAssign}}checked{{end}}>
+					<input name="issue_assign" type="checkbox" {{if .Webhook.HookEvents.Get "issue_assign"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_issue_assign"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_issue_assign_desc"}}</span>
 				</div>
@@ -137,7 +137,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="issue_label" type="checkbox" {{if .Webhook.IssueLabel}}checked{{end}}>
+					<input name="issue_label" type="checkbox" {{if .Webhook.HookEvents.Get "issue_label"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_issue_label"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_issue_label_desc"}}</span>
 				</div>
@@ -147,7 +147,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="issue_milestone" type="checkbox" {{if .Webhook.IssueMilestone}}checked{{end}}>
+					<input name="issue_milestone" type="checkbox" {{if .Webhook.HookEvents.Get "issue_milestone"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_issue_milestone"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_issue_milestone_desc"}}</span>
 				</div>
@@ -157,7 +157,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="issue_comment" type="checkbox" {{if .Webhook.IssueComment}}checked{{end}}>
+					<input name="issue_comment" type="checkbox" {{if .Webhook.HookEvents.Get "issue_comment"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_issue_comment"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_issue_comment_desc"}}</span>
 				</div>
@@ -172,7 +172,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request" type="checkbox" {{if .Webhook.PullRequest}}checked{{end}}>
+					<input name="pull_request" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_desc"}}</span>
 				</div>
@@ -182,7 +182,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_assign" type="checkbox" {{if .Webhook.PullRequestAssign}}checked{{end}}>
+					<input name="pull_request_assign" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_assign"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_assign"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_assign_desc"}}</span>
 				</div>
@@ -192,7 +192,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_label" type="checkbox" {{if .Webhook.PullRequestLabel}}checked{{end}}>
+					<input name="pull_request_label" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_label"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_label"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_label_desc"}}</span>
 				</div>
@@ -202,7 +202,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_milestone" type="checkbox" {{if .Webhook.PullRequestMilestone}}checked{{end}}>
+					<input name="pull_request_milestone" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_milestone"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_milestone"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_milestone_desc"}}</span>
 				</div>
@@ -212,7 +212,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_comment" type="checkbox" {{if .Webhook.PullRequestComment}}checked{{end}}>
+					<input name="pull_request_comment" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_comment"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_comment"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_comment_desc"}}</span>
 				</div>
@@ -222,7 +222,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_review" type="checkbox" {{if .Webhook.PullRequestReview}}checked{{end}}>
+					<input name="pull_request_review" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_review"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_review"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_review_desc"}}</span>
 				</div>
@@ -232,7 +232,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_sync" type="checkbox" {{if .Webhook.PullRequestSync}}checked{{end}}>
+					<input name="pull_request_sync" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_sync"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_sync"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_sync_desc"}}</span>
 				</div>
@@ -242,7 +242,7 @@
 		<div class="seven wide column">
 			<div class="field">
 				<div class="ui checkbox">
-					<input name="pull_request_review_request" type="checkbox" {{if .Webhook.PullRequestReviewRequest}}checked{{end}}>
+					<input name="pull_request_review_request" type="checkbox" {{if .Webhook.HookEvents.Get "pull_request_review_request"}}checked{{end}}>
 					<label>{{ctx.Locale.Tr "repo.settings.event_pull_request_review_request"}}</label>
 					<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_review_request_desc"}}</span>
 				</div>