Browse Source

repo/webhook: able to retrigger delivery history (#2187)

pull/4330/head
Unknwon 8 years ago
parent
commit
2807274e2d
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
  1. 8
      cmd/web.go
  2. 2
      conf/locale/locale_en-US.ini
  3. 2
      gogs.go
  4. 20
      models/error.go
  5. 34
      models/errors/webhook.go
  6. 20
      models/webhook.go
  7. 4
      modules/bindata/bindata.go
  8. 9
      public/css/gogs.css
  9. 138
      public/js/gogs.js
  10. 15
      public/less/_repository.less
  11. 3
      routers/api/v1/repo/hook.go
  12. 45
      routers/repo/webhook.go
  13. 2
      templates/.VERSION
  14. 4
      templates/org/settings/webhook_new.tmpl
  15. 11
      templates/repo/settings/webhook/history.tmpl

8
cmd/web.go

@ -460,12 +460,16 @@ func runWeb(ctx *cli.Context) error {
m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebHooksNewPost) m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebHooksNewPost)
m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksNewPost) m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksNewPost) m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksNewPost)
m.Get("/:id", repo.WebHooksEdit)
m.Post("/:id/test", repo.TestWebhook)
m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebHooksEditPost) m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebHooksEditPost)
m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksEditPost) m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksEditPost)
m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksEditPost) m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksEditPost)
m.Group("/:id", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/test", repo.TestWebhook)
m.Post("/redelivery", repo.RedeliveryWebhook)
})
m.Group("/git", func() { m.Group("/git", func() {
m.Get("", repo.SettingsGitHooks) m.Get("", repo.SettingsGitHooks)
m.Combo("/:name").Get(repo.SettingsGitHooksEdit). m.Combo("/:name").Get(repo.SettingsGitHooksEdit).

2
conf/locale/locale_en-US.ini

@ -742,6 +742,8 @@ settings.webhook_deletion_success = Webhook has been deleted successfully!
settings.webhook.test_delivery = Test Delivery settings.webhook.test_delivery = Test Delivery
settings.webhook.test_delivery_desc = Send a fake push event delivery to test your webhook settings settings.webhook.test_delivery_desc = Send a fake push event delivery to test your webhook settings
settings.webhook.test_delivery_success = Test webhook has been added to delivery queue. It may take few seconds before it shows up in the delivery history. settings.webhook.test_delivery_success = Test webhook has been added to delivery queue. It may take few seconds before it shows up in the delivery history.
settings.webhook.redelivery = Redelivery
settings.webhook.redelivery_success = Hook task '%s' has been readded to delivery queue. It may take few seconds to update delivery status in 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

2
gogs.go

@ -16,7 +16,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.10.23.0318" const APP_VER = "0.10.24.0319"
func init() { func init() {
setting.AppVer = APP_VER setting.AppVer = APP_VER

20
models/error.go

@ -436,26 +436,6 @@ func (err ErrBranchNotExist) Error() string {
return fmt.Sprintf("branch does not exist [name: %s]", err.Name) return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
} }
// __ __ ___. .__ __
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
// \ /\ ___/| \_\ \ Y ( <_> | <_> ) <
// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \
// \/ \/ \/ \/ \/
type ErrWebhookNotExist struct {
ID int64
}
func IsErrWebhookNotExist(err error) bool {
_, ok := err.(ErrWebhookNotExist)
return ok
}
func (err ErrWebhookNotExist) Error() string {
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
}
// .___ // .___
// | | ______ ________ __ ____ // | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \ // | |/ ___// ___/ | \_/ __ \

34
models/errors/webhook.go

@ -0,0 +1,34 @@
// Copyright 2017 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package errors
import "fmt"
type WebhookNotExist struct {
ID int64
}
func IsWebhookNotExist(err error) bool {
_, ok := err.(WebhookNotExist)
return ok
}
func (err WebhookNotExist) Error() string {
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
}
type HookTaskNotExist struct {
HookID int64
UUID string
}
func IsHookTaskNotExist(err error) bool {
_, ok := err.(HookTaskNotExist)
return ok
}
func (err HookTaskNotExist) Error() string {
return fmt.Sprintf("hook task does not exist [hook_id: %d, uuid: %s]", err.HookID, err.UUID)
}

20
models/webhook.go

@ -21,6 +21,7 @@ import (
api "github.com/gogits/go-gogs-client" api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/models/errors"
"github.com/gogits/gogs/modules/httplib" "github.com/gogits/gogs/modules/httplib"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
"github.com/gogits/gogs/modules/sync" "github.com/gogits/gogs/modules/sync"
@ -241,7 +242,7 @@ func getWebhook(bean *Webhook) (*Webhook, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, ErrWebhookNotExist{bean.ID} return nil, errors.WebhookNotExist{bean.ID}
} }
return bean, nil return bean, nil
} }
@ -494,6 +495,21 @@ func createHookTask(e Engine, t *HookTask) error {
return err return err
} }
// GetHookTaskOfWebhookByUUID returns hook task of given webhook by UUID.
func GetHookTaskOfWebhookByUUID(webhookID int64, uuid string) (*HookTask, error) {
hookTask := &HookTask{
HookID: webhookID,
UUID: uuid,
}
has, err := x.Get(hookTask)
if err != nil {
return nil, err
} else if !has {
return nil, errors.HookTaskNotExist{webhookID, uuid}
}
return hookTask, nil
}
// UpdateHookTask updates information of hook task. // UpdateHookTask updates information of hook task.
func UpdateHookTask(t *HookTask) error { func UpdateHookTask(t *HookTask) error {
_, err := x.Id(t.ID).AllCols().Update(t) _, err := x.Id(t.ID).AllCols().Update(t)
@ -725,7 +741,7 @@ func DeliverHooks() {
HookQueue.Remove(repoID) HookQueue.Remove(repoID)
tasks = make([]*HookTask, 0, 5) tasks = make([]*HookTask, 0, 5)
if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { if err := x.Where("repo_id = ?", repoID).And("is_delivered = ?", false).Find(&tasks); err != nil {
log.Error(4, "Get repository [%s] hook tasks: %v", repoID, err) log.Error(4, "Get repository [%s] hook tasks: %v", repoID, err)
continue continue
} }

4
modules/bindata/bindata.go

File diff suppressed because one or more lines are too long

9
public/css/gogs.css

@ -2375,6 +2375,15 @@ footer .ui.language .menu {
margin-left: 26px; margin-left: 26px;
padding-top: 0; padding-top: 0;
} }
.webhook .hook.history.list .right.menu .redelivery.button {
font-size: 12px;
margin-top: 6px;
height: 30px;
}
.webhook .hook.history.list .right.menu .redelivery.button .octicon {
font: normal normal normal 13px/1 Octicons;
width: 12px;
}
.user-cards .list { .user-cards .list {
padding: 0; padding: 0;
} }

138
public/js/gogs.js

@ -513,20 +513,6 @@ function initRepository() {
} }
} }
function initRepositoryCollaboration() {
console.log('initRepositoryCollaboration');
// Change collaborator access mode
$('.access-mode.menu .item').click(function () {
var $menu = $(this).parent();
$.post($menu.data('url'), {
"_csrf": csrf,
"uid": $menu.data('uid'),
"mode": $(this).data('value')
})
});
}
function initWikiForm() { function initWikiForm() {
var $editArea = $('.repository.wiki textarea#edit_area'); var $editArea = $('.repository.wiki textarea#edit_area');
if ($editArea.length > 0) { if ($editArea.length > 0) {
@ -828,61 +814,6 @@ function initOrganization() {
} }
} }
function initUserSettings() {
console.log('initUserSettings');
// Options
if ($('.user.settings.profile').length > 0) {
$('#username').keyup(function () {
var $prompt = $('#name-change-prompt');
if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) {
$prompt.show();
} else {
$prompt.hide();
}
});
}
}
function initWebhook() {
if ($('.new.webhook').length == 0) {
return;
}
$('.events.checkbox input').change(function () {
if ($(this).is(':checked')) {
$('.events.fields').show();
}
});
$('.non-events.checkbox input').change(function () {
if ($(this).is(':checked')) {
$('.events.fields').hide();
}
});
// Highlight payload on first click
$('.hook.history.list .toggle.button').click(function () {
$($(this).data('target') + ' .nohighlight').each(function () {
var $this = $(this);
$this.removeClass('nohighlight');
setTimeout(function(){ hljs.highlightBlock($this[0]) }, 500);
})
})
// Test delivery
$('#test-delivery').click(function () {
var $this = $(this);
$this.addClass('loading disabled');
$.post($this.data('link'), {
"_csrf": csrf
}).done(
setTimeout(function () {
window.location.href = $this.data('redirect');
}, 5000)
)
});
}
function initAdmin() { function initAdmin() {
if ($('.admin').length == 0) { if ($('.admin').length == 0) {
return; return;
@ -1152,6 +1083,71 @@ function initCodeView() {
} }
} }
function initUserSettings() {
console.log('initUserSettings');
// Options
if ($('.user.settings.profile').length > 0) {
$('#username').keyup(function () {
var $prompt = $('#name-change-prompt');
if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) {
$prompt.show();
} else {
$prompt.hide();
}
});
}
}
function initRepositoryCollaboration() {
console.log('initRepositoryCollaboration');
// Change collaborator access mode
$('.access-mode.menu .item').click(function () {
var $menu = $(this).parent();
$.post($menu.data('url'), {
"_csrf": csrf,
"uid": $menu.data('uid'),
"mode": $(this).data('value')
})
});
}
function initWebhookSettings() {
$('.events.checkbox input').change(function () {
if ($(this).is(':checked')) {
$('.events.fields').show();
}
});
$('.non-events.checkbox input').change(function () {
if ($(this).is(':checked')) {
$('.events.fields').hide();
}
});
// Highlight payload on first click
$('.hook.history.list .toggle.button').click(function () {
$($(this).data('target') + ' .nohighlight').each(function () {
var $this = $(this);
$this.removeClass('nohighlight');
setTimeout(function(){ hljs.highlightBlock($this[0]) }, 500);
})
})
// Trigger delivery
$('.delivery.button, .redelivery.button').click(function () {
var $this = $(this);
$this.addClass('loading disabled');
$.post($this.data('link'), {
"_csrf": csrf
}).done(
setTimeout(function () {
window.location.href = $this.data('redirect');
}, 5000)
);
});
}
$(document).ready(function () { $(document).ready(function () {
csrf = $('meta[name=_csrf]').attr("content"); csrf = $('meta[name=_csrf]').attr("content");
suburl = $('meta[name=_suburl]').attr("content"); suburl = $('meta[name=_suburl]').attr("content");
@ -1359,7 +1355,6 @@ $(document).ready(function () {
initEditForm(); initEditForm();
initEditor(); initEditor();
initOrganization(); initOrganization();
initWebhook();
initAdmin(); initAdmin();
initCodeView(); initCodeView();
@ -1379,7 +1374,8 @@ $(document).ready(function () {
var routes = { var routes = {
'div.user.settings': initUserSettings, 'div.user.settings': initUserSettings,
'div.repository.settings.collaboration': initRepositoryCollaboration 'div.repository.settings.collaboration': initRepositoryCollaboration,
'div.webhook.settings': initWebhookSettings
}; };
var selector; var selector;

15
public/less/_repository.less

@ -1411,6 +1411,21 @@
} }
// End of .repository // End of .repository
// Should apply organization webhooks page
.webhook .hook.history.list {
.right.menu {
.redelivery.button {
font-size: 12px;
margin-top: 6px;
height: 30px;
.octicon {
font: normal normal normal 13px/1 Octicons;
width: 12px;
}
}
}
}
&.user-cards { &.user-cards {
.list { .list {
padding: 0; padding: 0;

3
routers/api/v1/repo/hook.go

@ -12,6 +12,7 @@ import (
api "github.com/gogits/go-gogs-client" api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/models/errors"
"github.com/gogits/gogs/modules/context" "github.com/gogits/gogs/modules/context"
"github.com/gogits/gogs/routers/api/v1/convert" "github.com/gogits/gogs/routers/api/v1/convert"
) )
@ -106,7 +107,7 @@ func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
func EditHook(ctx *context.APIContext, form api.EditHookOption) { func EditHook(ctx *context.APIContext, form api.EditHookOption) {
w, err := models.GetWebhookOfRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) w, err := models.GetWebhookOfRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrWebhookNotExist(err) { if errors.IsWebhookNotExist(err) {
ctx.Status(404) ctx.Status(404)
} else { } else {
ctx.Error(500, "GetWebhookOfRepoByID", err) ctx.Error(500, "GetWebhookOfRepoByID", err)

45
routers/repo/webhook.go

@ -55,6 +55,7 @@ type OrgRepoCtx struct {
// getOrgRepoCtx determines whether this is a repo context or organization context. // getOrgRepoCtx determines whether this is a repo context or organization context.
func getOrgRepoCtx(ctx *context.Context) (*OrgRepoCtx, error) { func getOrgRepoCtx(ctx *context.Context) (*OrgRepoCtx, error) {
if len(ctx.Repo.RepoLink) > 0 { if len(ctx.Repo.RepoLink) > 0 {
ctx.Data["PageIsRepositoryContext"] = true
return &OrgRepoCtx{ return &OrgRepoCtx{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Link: ctx.Repo.RepoLink, Link: ctx.Repo.RepoLink,
@ -63,6 +64,7 @@ func getOrgRepoCtx(ctx *context.Context) (*OrgRepoCtx, error) {
} }
if len(ctx.Org.OrgLink) > 0 { if len(ctx.Org.OrgLink) > 0 {
ctx.Data["PageIsOrganizationContext"] = true
return &OrgRepoCtx{ return &OrgRepoCtx{
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
Link: ctx.Org.OrgLink, Link: ctx.Org.OrgLink,
@ -284,11 +286,7 @@ func checkWebhook(ctx *context.Context) (*OrgRepoCtx, *models.Webhook) {
w, err = models.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) w, err = models.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
} }
if err != nil { if err != nil {
if models.IsErrWebhookNotExist(err) { ctx.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err)
ctx.Handle(404, "GetWebhookByID", nil)
} else {
ctx.Handle(500, "GetWebhookByID", err)
}
return nil, nil return nil, nil
} }
@ -469,8 +467,7 @@ func TestWebhook(ctx *context.Context) {
if err == nil { if err == nil {
authorUsername = author.Name authorUsername = author.Name
} else if !errors.IsUserNotExist(err) { } else if !errors.IsUserNotExist(err) {
ctx.Flash.Error(fmt.Sprintf("GetUserByEmail.(author) [%s]: %v", commit.Author.Email, err)) ctx.Handle(500, "GetUserByEmail.(author)", err)
ctx.Status(500)
return return
} }
@ -478,16 +475,14 @@ func TestWebhook(ctx *context.Context) {
if err == nil { if err == nil {
committerUsername = committer.Name committerUsername = committer.Name
} else if !errors.IsUserNotExist(err) { } else if !errors.IsUserNotExist(err) {
ctx.Flash.Error(fmt.Sprintf("GetUserByEmail.(committer) [%s]: %v", commit.Committer.Email, err)) ctx.Handle(500, "GetUserByEmail.(committer)", err)
ctx.Status(500)
return return
} }
} }
fileStatus, err := commit.FileStatus() fileStatus, err := commit.FileStatus()
if err != nil { if err != nil {
ctx.Flash.Error("FileStatus: " + err.Error()) ctx.Handle(500, "FileStatus", err)
ctx.Status(500)
return return
} }
@ -520,15 +515,37 @@ func TestWebhook(ctx *context.Context) {
Pusher: apiUser, Pusher: apiUser,
Sender: apiUser, Sender: apiUser,
} }
if err := models.TestWebhook(ctx.Repo.Repository, models.HOOK_EVENT_PUSH, p, ctx.QueryInt64("id")); err != nil { if err := models.TestWebhook(ctx.Repo.Repository, models.HOOK_EVENT_PUSH, p, ctx.ParamsInt64("id")); err != nil {
ctx.Flash.Error("TestWebhook: " + err.Error()) ctx.Handle(500, "TestWebhook", err)
ctx.Status(500)
} else { } else {
ctx.Flash.Info(ctx.Tr("repo.settings.webhook.test_delivery_success")) ctx.Flash.Info(ctx.Tr("repo.settings.webhook.test_delivery_success"))
ctx.Status(200) ctx.Status(200)
} }
} }
func RedeliveryWebhook(ctx *context.Context) {
webhook, err := models.GetWebhookOfRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
ctx.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err)
return
}
hookTask, err := models.GetHookTaskOfWebhookByUUID(webhook.ID, ctx.Query("uuid"))
if err != nil {
ctx.NotFoundOrServerError("GetHookTaskOfWebhookByUUID/GetWebhookByOrgID", errors.IsHookTaskNotExist, err)
return
}
hookTask.IsDelivered = false
if err = models.UpdateHookTask(hookTask); err != nil {
ctx.Handle(500, "UpdateHookTask", err)
} else {
go models.HookQueue.Add(ctx.Repo.Repository.ID)
ctx.Flash.Info(ctx.Tr("repo.settings.webhook.redelivery_success", hookTask.UUID))
ctx.Status(200)
}
}
func DeleteWebhook(ctx *context.Context) { func DeleteWebhook(ctx *context.Context) {
if err := models.DeleteWebhookOfRepoByID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil { if err := models.DeleteWebhookOfRepoByID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error())

2
templates/.VERSION

@ -1 +1 @@
0.10.23.0318 0.10.24.0319

4
templates/org/settings/webhook_new.tmpl

@ -11,8 +11,8 @@
<div class="ui right"> <div class="ui right">
{{if eq .HookType "gogs"}} {{if eq .HookType "gogs"}}
<img class="img-13" src="{{AppSubUrl}}/img/favicon.png"> <img class="img-13" src="{{AppSubUrl}}/img/favicon.png">
{{else if eq .HookType "slack"}} {{else}}
<img class="img-13" src="{{AppSubUrl}}/img/slack.png"> <img class="img-13" src="{{AppSubUrl}}/img/{{.HookType}}.png">
{{end}} {{end}}
</div> </div>
</h4> </h4>

11
templates/repo/settings/webhook/history.tmpl

@ -1,10 +1,10 @@
{{if .PageIsSettingsHooksEdit}} {{if .PageIsSettingsHooksEdit}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "repo.settings.recent_deliveries"}} {{.i18n.Tr "repo.settings.recent_deliveries"}}
{{if .IsRepositoryAdmin}} {{if .PageIsRepositoryContext}}
<div class="ui right"> <div class="ui right">
<button class="ui teal tiny button poping up" id="test-delivery" data-content= <button class="ui teal tiny delivery button poping up" data-content=
"{{.i18n.Tr "repo.settings.webhook.test_delivery_desc"}}" data-variation="inverted tiny" data-link="{{.Link}}/test?id={{.Webhook.ID}}" data-redirect="{{.Link}}">{{.i18n.Tr "repo.settings.webhook.test_delivery"}}</button> "{{.i18n.Tr "repo.settings.webhook.test_delivery_desc"}}" data-variation="inverted tiny" data-link="{{.Link}}/test" data-redirect="{{.Link}}">{{.i18n.Tr "repo.settings.webhook.test_delivery"}}</button>
</div> </div>
{{end}} {{end}}
</h4> </h4>
@ -40,6 +40,11 @@
<span class="ui label">N/A</span> <span class="ui label">N/A</span>
{{end}} {{end}}
</a> </a>
{{if $.PageIsRepositoryContext}}
<div class="right menu">
<div class="ui basic redelivery button" data-link="{{$.Link}}/redelivery?uuid={{.UUID}}" data-redirect="{{$.Link}}"><i class="octicon octicon-sync"></i> <span>{{$.i18n.Tr "repo.settings.webhook.redelivery"}}</span></div>
</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