Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added replay of webhooks #18191

Merged
merged 1 commit into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions models/webhook/hooktask.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,13 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) {
// CreateHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent.
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()
if err != nil {
return err
}
t.UUID = gouuid.New().String()
t.PayloadContent = string(data)
_, err = e.Insert(t)
return err
return db.Insert(db.DefaultContext, t)
}

// UpdateHookTask updates information of hook task.
Expand All @@ -195,6 +190,38 @@ func UpdateHookTask(t *HookTask) error {
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
func FindUndeliveredHookTasks() ([]*HookTask, error) {
tasks := make([]*HookTask, 0, 10)
Expand Down
16 changes: 16 additions & 0 deletions models/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ func (err ErrWebhookNotExist) Error() string {
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
type HookContentType int

Expand Down
3 changes: 2 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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.test_delivery = Test Delivery
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.response = Response
settings.webhook.headers = Headers
settings.webhook.payload = Content
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.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
Expand Down
24 changes: 23 additions & 1 deletion routers/web/repo/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,11 +1189,33 @@ func TestWebhook(ctx *context.Context) {
ctx.Flash.Error("PrepareWebhook: " + err.Error())
ctx.Status(500)
} 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)
}
}

// 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
func DeleteWebhook(ctx *context.Context) {
if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
Expand Down
17 changes: 13 additions & 4 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,10 @@ func RegisterRoutes(m *web.Route) {
m.Group("/hooks", func() {
m.Get("", admin.DefaultOrSystemWebhooks)
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("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
Expand Down Expand Up @@ -559,7 +562,10 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
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("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
Expand Down Expand Up @@ -653,8 +659,11 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Get("/{id}", repo.WebHooksEdit)
m.Post("/{id}/test", repo.TestWebhook)
m.Group("/{id}", func() {
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("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
Expand Down
12 changes: 12 additions & 0 deletions services/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,15 @@ func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT
}
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
}
2 changes: 1 addition & 1 deletion templates/admin/hook_new.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin new webhook">
<div class="page-content admin settings new webhook">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
Expand Down
8 changes: 8 additions & 0 deletions templates/repo/settings/webhook/history.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
<span class="ui label">N/A</span>
{{end}}
</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 class="ui bottom attached tab segment active" data-tab="request-{{.ID}}">
{{if .RequestInfo}}
Expand Down