Skip to content

Commit 3963625

Browse files
authored
Webhook for Wiki changes (#20219)
Add support for triggering webhook notifications on wiki changes. This PR contains frontend and backend for webhook notifications on wiki actions (create a new page, rename a page, edit a page and delete a page). The frontend got a new checkbox under the Custom Event -> Repository Events section. There is only one checkbox for create/edit/rename/delete actions, because it makes no sense to separate it and others like releases or packages follow the same schema. ![image](https://user-images.githubusercontent.com/121972/177018803-26851196-831f-4fde-9a4c-9e639b0e0d6b.png) The actions itself are separated, so that different notifications will be executed (with the "action" field). All the webhook receivers implement the new interface method (Wiki) and the corresponding tests. When implementing this, I encounter a little bug on editing a wiki page. Creating and editing a wiki page is technically the same action and will be handled by the ```updateWikiPage``` function. But the function need to know if it is a new wiki page or just a change. This distinction is done by the ```action``` parameter, but this will not be sent by the frontend (on form submit). This PR will fix this by adding the ```action``` parameter with the values ```_new``` or ```_edit```, which will be used by the ```updateWikiPage``` function. I've done integration tests with matrix and gitea (http). ![image](https://user-images.githubusercontent.com/121972/177018795-eb5cdc01-9ba3-483e-a6b7-ed0e313a71fb.png) Fix #16457 Signed-off-by: Aaron Fischer <[email protected]>
1 parent 8b0aaa5 commit 3963625

37 files changed

+618
-9
lines changed

models/webhook/hooktask.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const (
4747
HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
4848
HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
4949
HookEventPullRequestSync HookEventType = "pull_request_sync"
50+
HookEventWiki HookEventType = "wiki"
5051
HookEventRepository HookEventType = "repository"
5152
HookEventRelease HookEventType = "release"
5253
HookEventPackage HookEventType = "package"
@@ -76,6 +77,8 @@ func (h HookEventType) Event() string {
7677
return "pull_request_rejected"
7778
case HookEventPullRequestReviewComment:
7879
return "pull_request_comment"
80+
case HookEventWiki:
81+
return "wiki"
7982
case HookEventRepository:
8083
return "repository"
8184
case HookEventRelease:

models/webhook/webhook.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ type HookEvents struct {
132132
PullRequestComment bool `json:"pull_request_comment"`
133133
PullRequestReview bool `json:"pull_request_review"`
134134
PullRequestSync bool `json:"pull_request_sync"`
135+
Wiki bool `json:"wiki"`
135136
Repository bool `json:"repository"`
136137
Release bool `json:"release"`
137138
Package bool `json:"package"`
@@ -328,6 +329,12 @@ func (w *Webhook) HasPullRequestSyncEvent() bool {
328329
(w.ChooseEvents && w.HookEvents.PullRequestSync)
329330
}
330331

332+
// HasWikiEvent returns true if hook enabled wiki event.
333+
func (w *Webhook) HasWikiEvent() bool {
334+
return w.SendEverything ||
335+
(w.ChooseEvents && w.HookEvent.Wiki)
336+
}
337+
331338
// HasReleaseEvent returns if hook enabled release event.
332339
func (w *Webhook) HasReleaseEvent() bool {
333340
return w.SendEverything ||
@@ -373,6 +380,7 @@ func (w *Webhook) EventCheckers() []struct {
373380
{w.HasPullRequestRejectedEvent, HookEventPullRequestReviewRejected},
374381
{w.HasPullRequestCommentEvent, HookEventPullRequestReviewComment},
375382
{w.HasPullRequestSyncEvent, HookEventPullRequestSync},
383+
{w.HasWikiEvent, HookEventWiki},
376384
{w.HasRepositoryEvent, HookEventRepository},
377385
{w.HasReleaseEvent, HookEventRelease},
378386
{w.HasPackageEvent, HookEventPackage},

models/webhook/webhook_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestWebhook_EventsArray(t *testing.T) {
7171
"issues", "issue_assign", "issue_label", "issue_milestone", "issue_comment",
7272
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
7373
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
74-
"pull_request_review_comment", "pull_request_sync", "repository", "release",
74+
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
7575
"package",
7676
},
7777
(&Webhook{

modules/notification/base/notifier.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type Notifier interface {
4545
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User)
4646
NotifyUpdateComment(*user_model.User, *issues_model.Comment, string)
4747
NotifyDeleteComment(*user_model.User, *issues_model.Comment)
48+
NotifyNewWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string)
49+
NotifyEditWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string)
50+
NotifyDeleteWikiPage(doer *user_model.User, repo *repo_model.Repository, page string)
4851
NotifyNewRelease(rel *repo_model.Release)
4952
NotifyUpdateRelease(doer *user_model.User, rel *repo_model.Release)
5053
NotifyDeleteRelease(doer *user_model.User, rel *repo_model.Release)

modules/notification/base/null.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ func (*NullNotifier) NotifyUpdateComment(doer *user_model.User, c *issues_model.
7878
func (*NullNotifier) NotifyDeleteComment(doer *user_model.User, c *issues_model.Comment) {
7979
}
8080

81+
// NotifyNewWikiPage places a place holder function
82+
func (*NullNotifier) NotifyNewWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string) {
83+
}
84+
85+
// NotifyEditWikiPage places a place holder function
86+
func (*NullNotifier) NotifyEditWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string) {
87+
}
88+
89+
// NotifyDeleteWikiPage places a place holder function
90+
func (*NullNotifier) NotifyDeleteWikiPage(doer *user_model.User, repo *repo_model.Repository, page string) {
91+
}
92+
8193
// NotifyNewRelease places a place holder function
8294
func (*NullNotifier) NotifyNewRelease(rel *repo_model.Release) {
8395
}

modules/notification/notification.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ func NewContext() {
4040
RegisterNotifier(mirror.NewNotifier())
4141
}
4242

43+
// NotifyNewWikiPage notifies creating new wiki pages to notifiers
44+
func NotifyNewWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string) {
45+
for _, notifier := range notifiers {
46+
notifier.NotifyNewWikiPage(doer, repo, page, comment)
47+
}
48+
}
49+
50+
// NotifyEditWikiPage notifies editing or renaming wiki pages to notifiers
51+
func NotifyEditWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string) {
52+
for _, notifier := range notifiers {
53+
notifier.NotifyEditWikiPage(doer, repo, page, comment)
54+
}
55+
}
56+
57+
// NotifyDeleteWikiPage notifies deleting wiki pages to notifiers
58+
func NotifyDeleteWikiPage(doer *user_model.User, repo *repo_model.Repository, page string) {
59+
for _, notifier := range notifiers {
60+
notifier.NotifyDeleteWikiPage(doer, repo, page)
61+
}
62+
}
63+
4364
// NotifyCreateIssueComment notifies issue comment related message to notifiers
4465
func NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
4566
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,

modules/notification/webhook/webhook.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,44 @@ func (m *webhookNotifier) NotifyDeleteComment(doer *user_model.User, comment *is
501501
}
502502
}
503503

504+
func (m *webhookNotifier) NotifyNewWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string) {
505+
// Add to hook queue for created wiki page.
506+
if err := webhook_services.PrepareWebhooks(repo, webhook.HookEventWiki, &api.WikiPayload{
507+
Action: api.HookWikiCreated,
508+
Repository: convert.ToRepo(repo, perm.AccessModeOwner),
509+
Sender: convert.ToUser(doer, nil),
510+
Page: page,
511+
Comment: comment,
512+
}); err != nil {
513+
log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
514+
}
515+
}
516+
517+
func (m *webhookNotifier) NotifyEditWikiPage(doer *user_model.User, repo *repo_model.Repository, page, comment string) {
518+
// Add to hook queue for edit wiki page.
519+
if err := webhook_services.PrepareWebhooks(repo, webhook.HookEventWiki, &api.WikiPayload{
520+
Action: api.HookWikiEdited,
521+
Repository: convert.ToRepo(repo, perm.AccessModeOwner),
522+
Sender: convert.ToUser(doer, nil),
523+
Page: page,
524+
Comment: comment,
525+
}); err != nil {
526+
log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
527+
}
528+
}
529+
530+
func (m *webhookNotifier) NotifyDeleteWikiPage(doer *user_model.User, repo *repo_model.Repository, page string) {
531+
// Add to hook queue for edit wiki page.
532+
if err := webhook_services.PrepareWebhooks(repo, webhook.HookEventWiki, &api.WikiPayload{
533+
Action: api.HookWikiDeleted,
534+
Repository: convert.ToRepo(repo, perm.AccessModeOwner),
535+
Sender: convert.ToUser(doer, nil),
536+
Page: page,
537+
}); err != nil {
538+
log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
539+
}
540+
}
541+
504542
func (m *webhookNotifier) NotifyIssueChangeLabels(doer *user_model.User, issue *issues_model.Issue,
505543
addedLabels, removedLabels []*issues_model.Label,
506544
) {

modules/structs/hook.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,39 @@ type ReviewPayload struct {
397397
Content string `json:"content"`
398398
}
399399

400+
// __ __.__ __ .__
401+
// / \ / \__| | _|__|
402+
// \ \/\/ / | |/ / |
403+
// \ /| | <| |
404+
// \__/\ / |__|__|_ \__|
405+
// \/ \/
406+
407+
// HookWikiAction an action that happens to a wiki page
408+
type HookWikiAction string
409+
410+
const (
411+
// HookWikiCreated created
412+
HookWikiCreated HookWikiAction = "created"
413+
// HookWikiEdited edited
414+
HookWikiEdited HookWikiAction = "edited"
415+
// HookWikiDeleted deleted
416+
HookWikiDeleted HookWikiAction = "deleted"
417+
)
418+
419+
// WikiPayload payload for repository webhooks
420+
type WikiPayload struct {
421+
Action HookWikiAction `json:"action"`
422+
Repository *Repository `json:"repository"`
423+
Sender *User `json:"sender"`
424+
Page string `json:"page"`
425+
Comment string `json:"comment"`
426+
}
427+
428+
// JSONPayload JSON representation of the payload
429+
func (p *WikiPayload) JSONPayload() ([]byte, error) {
430+
return json.MarshalIndent(p, "", " ")
431+
}
432+
400433
//__________ .__ __
401434
//\______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
402435
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |

options/locale/locale_en-US.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,8 @@ settings.event_delete = Delete
19561956
settings.event_delete_desc = Branch or tag deleted.
19571957
settings.event_fork = Fork
19581958
settings.event_fork_desc = Repository forked.
1959+
settings.event_wiki = Wiki
1960+
settings.event_wiki_desc = Wiki page created, renamed, edited or deleted.
19591961
settings.event_release = Release
19601962
settings.event_release_desc = Release published, updated or deleted in a repository.
19611963
settings.event_push = Push

routers/api/v1/repo/wiki.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"code.gitea.io/gitea/modules/context"
1515
"code.gitea.io/gitea/modules/convert"
1616
"code.gitea.io/gitea/modules/git"
17+
"code.gitea.io/gitea/modules/notification"
1718
"code.gitea.io/gitea/modules/setting"
1819
api "code.gitea.io/gitea/modules/structs"
1920
"code.gitea.io/gitea/modules/util"
@@ -85,6 +86,7 @@ func NewWikiPage(ctx *context.APIContext) {
8586
wikiPage := getWikiPage(ctx, wikiName)
8687

8788
if !ctx.Written() {
89+
notification.NotifyNewWikiPage(ctx.Doer, ctx.Repo.Repository, wikiName, form.Message)
8890
ctx.JSON(http.StatusCreated, wikiPage)
8991
}
9092
}
@@ -152,6 +154,7 @@ func EditWikiPage(ctx *context.APIContext) {
152154
wikiPage := getWikiPage(ctx, newWikiName)
153155

154156
if !ctx.Written() {
157+
notification.NotifyEditWikiPage(ctx.Doer, ctx.Repo.Repository, newWikiName, form.Message)
155158
ctx.JSON(http.StatusOK, wikiPage)
156159
}
157160
}
@@ -242,6 +245,8 @@ func DeleteWikiPage(ctx *context.APIContext) {
242245
return
243246
}
244247

248+
notification.NotifyDeleteWikiPage(ctx.Doer, ctx.Repo.Repository, wikiName)
249+
245250
ctx.Status(http.StatusNoContent)
246251
}
247252

routers/api/v1/utils/hook.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
126126
PullRequestComment: pullHook(form.Events, string(webhook.HookEventPullRequestComment)),
127127
PullRequestReview: pullHook(form.Events, "pull_request_review"),
128128
PullRequestSync: pullHook(form.Events, string(webhook.HookEventPullRequestSync)),
129+
Wiki: util.IsStringInSlice(string(webhook.HookEventWiki), form.Events, true),
129130
Repository: util.IsStringInSlice(string(webhook.HookEventRepository), form.Events, true),
130131
Release: util.IsStringInSlice(string(webhook.HookEventRelease), form.Events, true),
131132
},
@@ -249,6 +250,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
249250
w.Delete = util.IsStringInSlice(string(webhook.HookEventDelete), form.Events, true)
250251
w.Fork = util.IsStringInSlice(string(webhook.HookEventFork), form.Events, true)
251252
w.Repository = util.IsStringInSlice(string(webhook.HookEventRepository), form.Events, true)
253+
w.Wiki = util.IsStringInSlice(string(webhook.HookEventWiki), form.Events, true)
252254
w.Release = util.IsStringInSlice(string(webhook.HookEventRelease), form.Events, true)
253255
w.BranchFilter = form.BranchFilter
254256

routers/web/repo/webhook.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook.HookEvent {
178178
PullRequestComment: form.PullRequestComment,
179179
PullRequestReview: form.PullRequestReview,
180180
PullRequestSync: form.PullRequestSync,
181+
Wiki: form.Wiki,
181182
Repository: form.Repository,
182183
Package: form.Package,
183184
},

routers/web/repo/wiki.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"code.gitea.io/gitea/modules/log"
2626
"code.gitea.io/gitea/modules/markup"
2727
"code.gitea.io/gitea/modules/markup/markdown"
28+
"code.gitea.io/gitea/modules/notification"
2829
"code.gitea.io/gitea/modules/setting"
2930
"code.gitea.io/gitea/modules/timeutil"
3031
"code.gitea.io/gitea/modules/util"
@@ -705,6 +706,8 @@ func NewWikiPost(ctx *context.Context) {
705706
return
706707
}
707708

709+
notification.NotifyNewWikiPage(ctx.Doer, ctx.Repo.Repository, wikiName, form.Message)
710+
708711
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(wikiName))
709712
}
710713

@@ -747,6 +750,8 @@ func EditWikiPost(ctx *context.Context) {
747750
return
748751
}
749752

753+
notification.NotifyEditWikiPage(ctx.Doer, ctx.Repo.Repository, newWikiName, form.Message)
754+
750755
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(newWikiName))
751756
}
752757

@@ -762,6 +767,8 @@ func DeleteWikiPagePost(ctx *context.Context) {
762767
return
763768
}
764769

770+
notification.NotifyDeleteWikiPage(ctx.Doer, ctx.Repo.Repository, wikiName)
771+
765772
ctx.JSON(http.StatusOK, map[string]interface{}{
766773
"redirect": ctx.Repo.RepoLink + "/wiki/",
767774
})

services/forms/repo_form.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,12 @@ func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) bin
215215
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
216216
}
217217

218-
// __ __ ___. .__ .__ __
219-
// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
220-
// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
221-
// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
222-
// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
223-
// \/ \/ \/ \/ \/ \/
218+
// __ __ ___. .__ __
219+
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
220+
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
221+
// \ /\ ___/| \_\ \ Y ( <_> | <_> ) <
222+
// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \
223+
// \/ \/ \/ \/ \/
224224

225225
// WebhookForm form for changing web hook
226226
type WebhookForm struct {
@@ -242,6 +242,7 @@ type WebhookForm struct {
242242
PullRequestComment bool
243243
PullRequestReview bool
244244
PullRequestSync bool
245+
Wiki bool
245246
Repository bool
246247
Package bool
247248
Active bool

services/webhook/dingtalk.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ func (d *DingtalkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
107107
return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view issue", p.Issue.HTMLURL), nil
108108
}
109109

110+
// Wiki implements PayloadConvertor Wiki method
111+
func (d *DingtalkPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
112+
text, _, _ := getWikiPayloadInfo(p, noneLinkFormatter, true)
113+
url := p.Repository.HTMLURL + "/wiki/" + url.PathEscape(p.Page)
114+
115+
return createDingtalkPayload(text, text, "view wiki", url), nil
116+
}
117+
110118
// IssueComment implements PayloadConvertor IssueComment method
111119
func (d *DingtalkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
112120
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true)

services/webhook/dingtalk_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,44 @@ func TestDingTalkPayload(t *testing.T) {
189189
assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
190190
})
191191

192+
t.Run("Wiki", func(t *testing.T) {
193+
p := wikiTestPayload()
194+
195+
d := new(DingtalkPayload)
196+
p.Action = api.HookWikiCreated
197+
pl, err := d.Wiki(p)
198+
require.NoError(t, err)
199+
require.NotNil(t, pl)
200+
require.IsType(t, &DingtalkPayload{}, pl)
201+
202+
assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Text)
203+
assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Title)
204+
assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle)
205+
assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
206+
207+
p.Action = api.HookWikiEdited
208+
pl, err = d.Wiki(p)
209+
require.NoError(t, err)
210+
require.NotNil(t, pl)
211+
require.IsType(t, &DingtalkPayload{}, pl)
212+
213+
assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Text)
214+
assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.(*DingtalkPayload).ActionCard.Title)
215+
assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle)
216+
assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
217+
218+
p.Action = api.HookWikiDeleted
219+
pl, err = d.Wiki(p)
220+
require.NoError(t, err)
221+
require.NotNil(t, pl)
222+
require.IsType(t, &DingtalkPayload{}, pl)
223+
224+
assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*DingtalkPayload).ActionCard.Text)
225+
assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.(*DingtalkPayload).ActionCard.Title)
226+
assert.Equal(t, "view wiki", pl.(*DingtalkPayload).ActionCard.SingleTitle)
227+
assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
228+
})
229+
192230
t.Run("Release", func(t *testing.T) {
193231
p := pullReleaseTestPayload()
194232

0 commit comments

Comments
 (0)