Add replay of webhooks. (#18191)

pull/18075/head^2
KN4CK3R 3 years ago committed by GitHub
parent a38ba634a4
commit bf7b083cfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      models/webhook/hooktask.go
  2. 16
      models/webhook/webhook.go
  3. 3
      options/locale/locale_en-US.ini
  4. 24
      routers/web/repo/webhook.go
  5. 17
      routers/web/web.go
  6. 12
      services/webhook/webhook.go
  7. 2
      templates/admin/hook_new.tmpl
  8. 8
      templates/repo/settings/webhook/history.tmpl

@ -175,18 +175,13 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) {
// CreateHookTask creates a new hook task, // CreateHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent. // it handles conversion from Payload to PayloadContent.
func CreateHookTask(t *HookTask) error { func CreateHookTask(t *HookTask) error {
return createHookTask(db.GetEngine(db.DefaultContext), t)
}
func createHookTask(e db.Engine, t *HookTask) error {
data, err := t.Payloader.JSONPayload() data, err := t.Payloader.JSONPayload()
if err != nil { if err != nil {
return err return err
} }
t.UUID = gouuid.New().String() t.UUID = gouuid.New().String()
t.PayloadContent = string(data) t.PayloadContent = string(data)
_, err = e.Insert(t) return db.Insert(db.DefaultContext, t)
return err
} }
// UpdateHookTask updates information of hook task. // UpdateHookTask updates information of hook task.
@ -195,6 +190,38 @@ func UpdateHookTask(t *HookTask) error {
return err return err
} }
// ReplayHookTask copies a hook task to get re-delivered
func ReplayHookTask(hookID int64, uuid string) (*HookTask, error) {
var newTask *HookTask
err := db.WithTx(func(ctx context.Context) error {
task := &HookTask{
HookID: hookID,
UUID: uuid,
}
has, err := db.GetByBean(ctx, task)
if err != nil {
return err
} else if !has {
return ErrHookTaskNotExist{
HookID: hookID,
UUID: uuid,
}
}
newTask = &HookTask{
UUID: gouuid.New().String(),
RepoID: task.RepoID,
HookID: task.HookID,
PayloadContent: task.PayloadContent,
EventType: task.EventType,
}
return db.Insert(ctx, newTask)
})
return newTask, err
}
// FindUndeliveredHookTasks represents find the undelivered hook tasks // FindUndeliveredHookTasks represents find the undelivered hook tasks
func FindUndeliveredHookTasks() ([]*HookTask, error) { func FindUndeliveredHookTasks() ([]*HookTask, error) {
tasks := make([]*HookTask, 0, 10) tasks := make([]*HookTask, 0, 10)

@ -41,6 +41,22 @@ func (err ErrWebhookNotExist) Error() string {
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID) return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
} }
// ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
type ErrHookTaskNotExist struct {
HookID int64
UUID string
}
// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
func IsErrHookTaskNotExist(err error) bool {
_, ok := err.(ErrHookTaskNotExist)
return ok
}
func (err ErrHookTaskNotExist) Error() string {
return fmt.Sprintf("hook task does not exist [hook: %d, uuid: %s]", err.HookID, err.UUID)
}
// HookContentType is the content type of a web hook // HookContentType is the content type of a web hook
type HookContentType int type HookContentType int

@ -1831,12 +1831,13 @@ settings.webhook_deletion_desc = Removing a webhook deletes its settings and del
settings.webhook_deletion_success = The webhook has been removed. settings.webhook_deletion_success = The webhook has been removed.
settings.webhook.test_delivery = Test Delivery settings.webhook.test_delivery = Test Delivery
settings.webhook.test_delivery_desc = Test this webhook with a fake event. settings.webhook.test_delivery_desc = Test this webhook with a fake event.
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.
settings.webhook.request = Request settings.webhook.request = Request
settings.webhook.response = Response settings.webhook.response = Response
settings.webhook.headers = Headers settings.webhook.headers = Headers
settings.webhook.payload = Content settings.webhook.payload = Content
settings.webhook.body = Body settings.webhook.body = Body
settings.webhook.replay.description = Replay this webhook.
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.
settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations." settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations."
settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
settings.githook_name = Hook Name settings.githook_name = Hook Name

@ -1189,11 +1189,33 @@ func TestWebhook(ctx *context.Context) {
ctx.Flash.Error("PrepareWebhook: " + err.Error()) ctx.Flash.Error("PrepareWebhook: " + err.Error())
ctx.Status(500) ctx.Status(500)
} else { } else {
ctx.Flash.Info(ctx.Tr("repo.settings.webhook.test_delivery_success")) ctx.Flash.Info(ctx.Tr("repo.settings.webhook.delivery.success"))
ctx.Status(200) ctx.Status(200)
} }
} }
// ReplayWebhook replays a webhook
func ReplayWebhook(ctx *context.Context) {
hookTaskUUID := ctx.Params(":uuid")
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
if err := webhook_service.ReplayHookTask(w, hookTaskUUID); err != nil {
if webhook.IsErrHookTaskNotExist(err) {
ctx.NotFound("ReplayHookTask", nil)
} else {
ctx.ServerError("ReplayHookTask", err)
}
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.webhook.delivery.success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// DeleteWebhook delete a webhook // DeleteWebhook delete a webhook
func DeleteWebhook(ctx *context.Context) { func DeleteWebhook(ctx *context.Context) {
if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {

@ -435,7 +435,10 @@ func RegisterRoutes(m *web.Route) {
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Get("", admin.DefaultOrSystemWebhooks) m.Get("", admin.DefaultOrSystemWebhooks)
m.Post("/delete", admin.DeleteDefaultOrSystemWebhook) m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
m.Get("/{id}", repo.WebHooksEdit) m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/replay/{uuid}", repo.ReplayWebhook)
})
m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
@ -559,7 +562,10 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Get("/{id}", repo.WebHooksEdit) m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/replay/{uuid}", repo.ReplayWebhook)
})
m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
@ -653,8 +659,11 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Get("/{id}", repo.WebHooksEdit) m.Group("/{id}", func() {
m.Post("/{id}/test", repo.TestWebhook) m.Get("", repo.WebHooksEdit)
m.Post("/test", repo.TestWebhook)
m.Post("/replay/{uuid}", repo.ReplayWebhook)
})
m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)

@ -229,3 +229,15 @@ func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT
} }
return nil return nil
} }
// ReplayHookTask replays a webhook task
func ReplayHookTask(w *webhook_model.Webhook, uuid string) error {
t, err := webhook_model.ReplayHookTask(w.ID, uuid)
if err != nil {
return err
}
go hookQueue.Add(t.RepoID)
return nil
}

@ -1,5 +1,5 @@
{{template "base/head" .}} {{template "base/head" .}}
<div class="page-content admin new webhook"> <div class="page-content admin settings new webhook">
{{template "admin/navbar" .}} {{template "admin/navbar" .}}
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}

@ -40,6 +40,14 @@
<span class="ui label">N/A</span> <span class="ui label">N/A</span>
{{end}} {{end}}
</a> </a>
{{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin}}
<div class="right menu">
<form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post">
{{$.CsrfTokenHtml}}
<button class="ui tiny button tooltip" data-content="{{$.i18n.Tr "repo.settings.webhook.replay.description"}}" data-variation="inverted tiny">{{svg "octicon-sync"}}</button>
</form>
</div>
{{end}}
</div> </div>
<div class="ui bottom attached tab segment active" data-tab="request-{{.ID}}"> <div class="ui bottom attached tab segment active" data-tab="request-{{.ID}}">
{{if .RequestInfo}} {{if .RequestInfo}}

Loading…
Cancel
Save