Skip to content

Commit bf7b083

Browse files
authored
Add replay of webhooks. (#18191)
1 parent a38ba63 commit bf7b083

File tree

8 files changed

+108
-13
lines changed

8 files changed

+108
-13
lines changed

models/webhook/hooktask.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,13 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) {
175175
// CreateHookTask creates a new hook task,
176176
// it handles conversion from Payload to PayloadContent.
177177
func CreateHookTask(t *HookTask) error {
178-
return createHookTask(db.GetEngine(db.DefaultContext), t)
179-
}
180-
181-
func createHookTask(e db.Engine, t *HookTask) error {
182178
data, err := t.Payloader.JSONPayload()
183179
if err != nil {
184180
return err
185181
}
186182
t.UUID = gouuid.New().String()
187183
t.PayloadContent = string(data)
188-
_, err = e.Insert(t)
189-
return err
184+
return db.Insert(db.DefaultContext, t)
190185
}
191186

192187
// UpdateHookTask updates information of hook task.
@@ -195,6 +190,38 @@ func UpdateHookTask(t *HookTask) error {
195190
return err
196191
}
197192

193+
// ReplayHookTask copies a hook task to get re-delivered
194+
func ReplayHookTask(hookID int64, uuid string) (*HookTask, error) {
195+
var newTask *HookTask
196+
197+
err := db.WithTx(func(ctx context.Context) error {
198+
task := &HookTask{
199+
HookID: hookID,
200+
UUID: uuid,
201+
}
202+
has, err := db.GetByBean(ctx, task)
203+
if err != nil {
204+
return err
205+
} else if !has {
206+
return ErrHookTaskNotExist{
207+
HookID: hookID,
208+
UUID: uuid,
209+
}
210+
}
211+
212+
newTask = &HookTask{
213+
UUID: gouuid.New().String(),
214+
RepoID: task.RepoID,
215+
HookID: task.HookID,
216+
PayloadContent: task.PayloadContent,
217+
EventType: task.EventType,
218+
}
219+
return db.Insert(ctx, newTask)
220+
})
221+
222+
return newTask, err
223+
}
224+
198225
// FindUndeliveredHookTasks represents find the undelivered hook tasks
199226
func FindUndeliveredHookTasks() ([]*HookTask, error) {
200227
tasks := make([]*HookTask, 0, 10)

models/webhook/webhook.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ func (err ErrWebhookNotExist) Error() string {
4141
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
4242
}
4343

44+
// ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
45+
type ErrHookTaskNotExist struct {
46+
HookID int64
47+
UUID string
48+
}
49+
50+
// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
51+
func IsErrHookTaskNotExist(err error) bool {
52+
_, ok := err.(ErrHookTaskNotExist)
53+
return ok
54+
}
55+
56+
func (err ErrHookTaskNotExist) Error() string {
57+
return fmt.Sprintf("hook task does not exist [hook: %d, uuid: %s]", err.HookID, err.UUID)
58+
}
59+
4460
// HookContentType is the content type of a web hook
4561
type HookContentType int
4662

options/locale/locale_en-US.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1831,12 +1831,13 @@ settings.webhook_deletion_desc = Removing a webhook deletes its settings and del
18311831
settings.webhook_deletion_success = The webhook has been removed.
18321832
settings.webhook.test_delivery = Test Delivery
18331833
settings.webhook.test_delivery_desc = Test this webhook with a fake event.
1834-
settings.webhook.test_delivery_success = A fake event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.
18351834
settings.webhook.request = Request
18361835
settings.webhook.response = Response
18371836
settings.webhook.headers = Headers
18381837
settings.webhook.payload = Content
18391838
settings.webhook.body = Body
1839+
settings.webhook.replay.description = Replay this webhook.
1840+
settings.webhook.delivery.success = An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.
18401841
settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations."
18411842
settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
18421843
settings.githook_name = Hook Name

routers/web/repo/webhook.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1189,11 +1189,33 @@ func TestWebhook(ctx *context.Context) {
11891189
ctx.Flash.Error("PrepareWebhook: " + err.Error())
11901190
ctx.Status(500)
11911191
} else {
1192-
ctx.Flash.Info(ctx.Tr("repo.settings.webhook.test_delivery_success"))
1192+
ctx.Flash.Info(ctx.Tr("repo.settings.webhook.delivery.success"))
11931193
ctx.Status(200)
11941194
}
11951195
}
11961196

1197+
// ReplayWebhook replays a webhook
1198+
func ReplayWebhook(ctx *context.Context) {
1199+
hookTaskUUID := ctx.Params(":uuid")
1200+
1201+
orCtx, w := checkWebhook(ctx)
1202+
if ctx.Written() {
1203+
return
1204+
}
1205+
1206+
if err := webhook_service.ReplayHookTask(w, hookTaskUUID); err != nil {
1207+
if webhook.IsErrHookTaskNotExist(err) {
1208+
ctx.NotFound("ReplayHookTask", nil)
1209+
} else {
1210+
ctx.ServerError("ReplayHookTask", err)
1211+
}
1212+
return
1213+
}
1214+
1215+
ctx.Flash.Success(ctx.Tr("repo.settings.webhook.delivery.success"))
1216+
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
1217+
}
1218+
11971219
// DeleteWebhook delete a webhook
11981220
func DeleteWebhook(ctx *context.Context) {
11991221
if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {

routers/web/web.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,10 @@ func RegisterRoutes(m *web.Route) {
435435
m.Group("/hooks", func() {
436436
m.Get("", admin.DefaultOrSystemWebhooks)
437437
m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
438-
m.Get("/{id}", repo.WebHooksEdit)
438+
m.Group("/{id}", func() {
439+
m.Get("", repo.WebHooksEdit)
440+
m.Post("/replay/{uuid}", repo.ReplayWebhook)
441+
})
439442
m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
440443
m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
441444
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
@@ -559,7 +562,10 @@ func RegisterRoutes(m *web.Route) {
559562
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
560563
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
561564
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
562-
m.Get("/{id}", repo.WebHooksEdit)
565+
m.Group("/{id}", func() {
566+
m.Get("", repo.WebHooksEdit)
567+
m.Post("/replay/{uuid}", repo.ReplayWebhook)
568+
})
563569
m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
564570
m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
565571
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
@@ -653,8 +659,11 @@ func RegisterRoutes(m *web.Route) {
653659
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
654660
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
655661
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
656-
m.Get("/{id}", repo.WebHooksEdit)
657-
m.Post("/{id}/test", repo.TestWebhook)
662+
m.Group("/{id}", func() {
663+
m.Get("", repo.WebHooksEdit)
664+
m.Post("/test", repo.TestWebhook)
665+
m.Post("/replay/{uuid}", repo.ReplayWebhook)
666+
})
658667
m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
659668
m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
660669
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)

services/webhook/webhook.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,15 @@ func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT
229229
}
230230
return nil
231231
}
232+
233+
// ReplayHookTask replays a webhook task
234+
func ReplayHookTask(w *webhook_model.Webhook, uuid string) error {
235+
t, err := webhook_model.ReplayHookTask(w.ID, uuid)
236+
if err != nil {
237+
return err
238+
}
239+
240+
go hookQueue.Add(t.RepoID)
241+
242+
return nil
243+
}

templates/admin/hook_new.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{template "base/head" .}}
2-
<div class="page-content admin new webhook">
2+
<div class="page-content admin settings new webhook">
33
{{template "admin/navbar" .}}
44
<div class="ui container">
55
{{template "base/alert" .}}

templates/repo/settings/webhook/history.tmpl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@
4040
<span class="ui label">N/A</span>
4141
{{end}}
4242
</a>
43+
{{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin}}
44+
<div class="right menu">
45+
<form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post">
46+
{{$.CsrfTokenHtml}}
47+
<button class="ui tiny button tooltip" data-content="{{$.i18n.Tr "repo.settings.webhook.replay.description"}}" data-variation="inverted tiny">{{svg "octicon-sync"}}</button>
48+
</form>
49+
</div>
50+
{{end}}
4351
</div>
4452
<div class="ui bottom attached tab segment active" data-tab="request-{{.ID}}">
4553
{{if .RequestInfo}}

0 commit comments

Comments
 (0)