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

master
Unknwon 2017-03-19 17:44:46 -04:00
parent 55a5ad5cdc
commit 2807274e2d
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
15 changed files with 199 additions and 120 deletions

View File

@ -460,12 +460,16 @@ func runWeb(ctx *cli.Context) error {
m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebHooksNewPost)
m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksNewPost)
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("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksEditPost)
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.Get("", repo.SettingsGitHooks)
m.Combo("/:name").Get(repo.SettingsGitHooksEdit).

View File

@ -742,6 +742,8 @@ settings.webhook_deletion_success = Webhook has been deleted successfully!
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_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.response = Response
settings.webhook.headers = Headers

View File

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

View File

@ -436,26 +436,6 @@ func (err ErrBranchNotExist) Error() string {
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 Normal file
View File

@ -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)
}

View File

@ -21,6 +21,7 @@ import (
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/models/errors"
"github.com/gogits/gogs/modules/httplib"
"github.com/gogits/gogs/modules/setting"
"github.com/gogits/gogs/modules/sync"
@ -241,7 +242,7 @@ func getWebhook(bean *Webhook) (*Webhook, error) {
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{bean.ID}
return nil, errors.WebhookNotExist{bean.ID}
}
return bean, nil
}
@ -494,6 +495,21 @@ func createHookTask(e Engine, t *HookTask) error {
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.
func UpdateHookTask(t *HookTask) error {
_, err := x.Id(t.ID).AllCols().Update(t)
@ -704,7 +720,7 @@ func (t *HookTask) deliver() {
// TODO: shoot more hooks at same time.
func DeliverHooks() {
tasks := make([]*HookTask, 0, 10)
x.Where("is_delivered=?", false).Iterate(new(HookTask),
x.Where("is_delivered = ?", false).Iterate(new(HookTask),
func(idx int, bean interface{}) error {
t := bean.(*HookTask)
t.deliver()
@ -725,7 +741,7 @@ func DeliverHooks() {
HookQueue.Remove(repoID)
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)
continue
}

File diff suppressed because one or more lines are too long

View File

@ -2375,6 +2375,15 @@ footer .ui.language .menu {
margin-left: 26px;
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 {
padding: 0;
}

View File

@ -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() {
var $editArea = $('.repository.wiki textarea#edit_area');
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() {
if ($('.admin').length == 0) {
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 () {
csrf = $('meta[name=_csrf]').attr("content");
suburl = $('meta[name=_suburl]').attr("content");
@ -1359,7 +1355,6 @@ $(document).ready(function () {
initEditForm();
initEditor();
initOrganization();
initWebhook();
initAdmin();
initCodeView();
@ -1379,7 +1374,8 @@ $(document).ready(function () {
var routes = {
'div.user.settings': initUserSettings,
'div.repository.settings.collaboration': initRepositoryCollaboration
'div.repository.settings.collaboration': initRepositoryCollaboration,
'div.webhook.settings': initWebhookSettings
};
var selector;

View File

@ -1411,6 +1411,21 @@
}
// 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 {
.list {
padding: 0;

View File

@ -12,6 +12,7 @@ import (
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/models/errors"
"github.com/gogits/gogs/modules/context"
"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) {
w, err := models.GetWebhookOfRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrWebhookNotExist(err) {
if errors.IsWebhookNotExist(err) {
ctx.Status(404)
} else {
ctx.Error(500, "GetWebhookOfRepoByID", err)

View File

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

View File

@ -1 +1 @@
0.10.23.0318
0.10.24.0319

View File

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

View File

@ -1,10 +1,10 @@
{{if .PageIsSettingsHooksEdit}}
<h4 class="ui top attached header">
{{.i18n.Tr "repo.settings.recent_deliveries"}}
{{if .IsRepositoryAdmin}}
{{if .PageIsRepositoryContext}}
<div class="ui right">
<button class="ui teal tiny button poping up" id="test-delivery" 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>
<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" data-redirect="{{.Link}}">{{.i18n.Tr "repo.settings.webhook.test_delivery"}}</button>
</div>
{{end}}
</h4>
@ -40,6 +40,11 @@
<span class="ui label">N/A</span>
{{end}}
</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 class="ui bottom attached tab segment active" data-tab="request-{{.ID}}">
{{if .RequestInfo}}