webhook: add issue comment event

pull/1983/merge
Unknwon 2017-03-09 04:11:23 -05:00
parent c93731339f
commit 89cc6aa430
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
19 changed files with 256 additions and 78 deletions

View File

@ -762,8 +762,10 @@ settings.event_fork = Fork
settings.event_fork_desc = Repository forked
settings.event_push = Push
settings.event_push_desc = Git push to a repository
settings.event_issue = Issues
settings.event_issue_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned.
settings.event_issues = Issues
settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned.
settings.event_issue_comment = Issue Comment
settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
settings.event_pull_request = Pull Request
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, demilestoned, or synchronized.
settings.active = Active

View File

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

View File

@ -150,9 +150,13 @@ func (c *Comment) APIFormat() *api.Comment {
}
}
func CommentHashTag(id int64) string {
return "issuecomment-" + com.ToStr(id)
}
// HashTag returns unique hash tag for comment.
func (c *Comment) HashTag() string {
return "issuecomment-" + com.ToStr(c.ID)
return CommentHashTag(c.ID)
}
// EventTag returns unique event hash tag for comment.
@ -330,7 +334,7 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
return CreateComment(&CreateCommentOptions{
comment, err := CreateComment(&CreateCommentOptions{
Type: COMMENT_TYPE_COMMENT,
Doer: doer,
Repo: repo,
@ -338,6 +342,22 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
Content: content,
Attachments: attachments,
})
if err != nil {
return nil, fmt.Errorf("CreateComment: %v", err)
}
comment.Issue = issue
if err = PrepareWebhooks(repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{
Action: api.HOOK_ISSUE_COMMENT_CREATED,
Issue: issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: repo.APIFormat(nil),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
}
return comment, nil
}
// CreateRefComment creates a commit reference comment to issue.
@ -437,13 +457,33 @@ func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
}
// UpdateComment updates information of comment.
func UpdateComment(c *Comment) error {
_, err := x.Id(c.ID).AllCols().Update(c)
return err
func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
if _, err = x.Id(c.ID).AllCols().Update(c); err != nil {
return err
}
if err = c.Issue.LoadAttributes(); err != nil {
log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", c.IssueID, err)
} else if err = PrepareWebhooks(c.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{
Action: api.HOOK_ISSUE_COMMENT_EDITED,
Issue: c.Issue.APIFormat(),
Comment: c.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
From: oldContent,
},
},
Repository: c.Issue.Repo.APIFormat(nil),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
}
return nil
}
// DeleteCommentByID deletes the comment by given ID.
func DeleteCommentByID(id int64) error {
func DeleteCommentByID(doer *User, id int64) error {
comment, err := GetCommentByID(id)
if err != nil {
if IsErrCommentNotExist(err) {
@ -468,5 +508,20 @@ func DeleteCommentByID(id int64) error {
}
}
return sess.Commit()
if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
if err = comment.Issue.LoadAttributes(); err != nil {
log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", comment.IssueID, err)
} else if err = PrepareWebhooks(comment.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{
Action: api.HOOK_ISSUE_COMMENT_DELETED,
Issue: comment.Issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(nil),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
}
return nil
}

View File

@ -251,8 +251,6 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} else {
go HookQueue.Add(issue.RepoID)
}
}
@ -363,8 +361,6 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} else {
go HookQueue.Add(issue.RepoID)
}
return nil
@ -502,8 +498,6 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
} else {
go HookQueue.Add(repo.ID)
}
return nil
@ -546,8 +540,6 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} else {
go HookQueue.Add(issue.RepoID)
}
return nil
@ -590,8 +582,6 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} else {
go HookQueue.Add(issue.RepoID)
}
return nil
@ -641,8 +631,6 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
}
if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, isRemoveAssignee, err)
} else {
go HookQueue.Add(issue.RepoID)
}
return nil

View File

@ -348,8 +348,6 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} else {
go HookQueue.Add(issue.RepoID)
}
return nil

View File

@ -454,7 +454,6 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
}); err != nil {
log.Error(2, "PrepareWebhooks: %v", err)
}
go HookQueue.Add(repo.ID)
return nil
}

View File

@ -62,12 +62,13 @@ func IsValidHookContentType(name string) bool {
}
type HookEvents struct {
Create bool `json:"create"`
Delete bool `json:"delete"`
Fork bool `json:"fork"`
Push bool `json:"push"`
Issues bool `json:"issues"`
PullRequest bool `json:"pull_request"`
Create bool `json:"create"`
Delete bool `json:"delete"`
Fork bool `json:"fork"`
Push bool `json:"push"`
Issues bool `json:"issues"`
IssueComment bool `json:"issue_comment"`
PullRequest bool `json:"pull_request"`
}
// HookEvent represents events that will delivery hook.
@ -183,28 +184,38 @@ func (w *Webhook) HasIssuesEvent() bool {
(w.ChooseEvents && w.HookEvents.Issues)
}
// HasIssueCommentEvent returns true if hook enabled issue comment event.
func (w *Webhook) HasIssueCommentEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.IssueComment)
}
// HasPullRequestEvent returns true if hook enabled pull request event.
func (w *Webhook) HasPullRequestEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.PullRequest)
}
type eventChecker struct {
checker func() bool
typ HookEventType
}
func (w *Webhook) EventsArray() []string {
events := make([]string, 0, 5)
if w.HasCreateEvent() {
events = append(events, string(HOOK_EVENT_CREATE))
events := make([]string, 0, 7)
eventCheckers := []eventChecker{
{w.HasCreateEvent, HOOK_EVENT_CREATE},
{w.HasDeleteEvent, HOOK_EVENT_DELETE},
{w.HasForkEvent, HOOK_EVENT_FORK},
{w.HasPushEvent, HOOK_EVENT_PUSH},
{w.HasIssuesEvent, HOOK_EVENT_ISSUES},
{w.HasIssueCommentEvent, HOOK_EVENT_ISSUE_COMMENT},
{w.HasPullRequestEvent, HOOK_EVENT_PULL_REQUEST},
}
if w.HasDeleteEvent() {
events = append(events, string(HOOK_EVENT_DELETE))
}
if w.HasForkEvent() {
events = append(events, string(HOOK_EVENT_FORK))
}
if w.HasPushEvent() {
events = append(events, string(HOOK_EVENT_PUSH))
}
if w.HasPullRequestEvent() {
events = append(events, string(HOOK_EVENT_PULL_REQUEST))
for _, c := range eventCheckers {
if c.checker() {
events = append(events, string(c.typ))
}
}
return events
}
@ -363,12 +374,13 @@ func IsValidHookTaskType(name string) bool {
type HookEventType string
const (
HOOK_EVENT_CREATE HookEventType = "create"
HOOK_EVENT_DELETE HookEventType = "delete"
HOOK_EVENT_FORK HookEventType = "fork"
HOOK_EVENT_PUSH HookEventType = "push"
HOOK_EVENT_ISSUES HookEventType = "issues"
HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request"
HOOK_EVENT_CREATE HookEventType = "create"
HOOK_EVENT_DELETE HookEventType = "delete"
HOOK_EVENT_FORK HookEventType = "fork"
HOOK_EVENT_PUSH HookEventType = "push"
HOOK_EVENT_ISSUES HookEventType = "issues"
HOOK_EVENT_ISSUE_COMMENT HookEventType = "issue_comment"
HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request"
)
// HookRequest represents hook task request information.
@ -496,6 +508,10 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
if !w.HasDeleteEvent() {
continue
}
case HOOK_EVENT_FORK:
if !w.HasForkEvent() {
continue
}
case HOOK_EVENT_PUSH:
if !w.HasPushEvent() {
continue
@ -504,6 +520,10 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
if !w.HasIssuesEvent() {
continue
}
case HOOK_EVENT_ISSUE_COMMENT:
if !w.HasIssueCommentEvent() {
continue
}
case HOOK_EVENT_PULL_REQUEST:
if !w.HasPullRequestEvent() {
continue
@ -554,6 +574,7 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
// It's safe to fail when the whole function is called during hook execution
// because resource released after exit.
// FIXME: need a more safe way to not call this function if it's during hook execution.
go HookQueue.Add(repo.ID)
return nil
}

View File

@ -241,6 +241,45 @@ func getDiscordIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*DiscordPa
}, nil
}
func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*DiscordPayload, error) {
title := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)
url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
content := ""
fields := make([]*DiscordEmbedFieldObject, 0, 1)
switch p.Action {
case api.HOOK_ISSUE_COMMENT_CREATED:
title = "New comment: " + title
content = p.Comment.Body
case api.HOOK_ISSUE_COMMENT_EDITED:
title = "Comment edited: " + title
content = p.Comment.Body
case api.HOOK_ISSUE_COMMENT_DELETED:
title = "Comment deleted: " + title
url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
content = p.Comment.Body
}
color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32)
return &DiscordPayload{
Username: slack.Username,
AvatarURL: slack.IconURL,
Embeds: []*DiscordEmbedObject{{
Title: title,
Description: content,
URL: url,
Color: int(color),
Footer: &DiscordEmbedFooterObject{
Text: p.Repository.FullName,
},
Author: &DiscordEmbedAuthorObject{
Name: p.Sender.UserName,
IconURL: p.Sender.AvatarUrl,
},
Fields: fields,
}},
}, nil
}
func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*DiscordPayload, error) {
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
url := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
@ -331,6 +370,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (paylo
payload, err = getDiscordPushPayload(p.(*api.PushPayload), slack)
case HOOK_EVENT_ISSUES:
payload, err = getDiscordIssuesPayload(p.(*api.IssuesPayload), slack)
case HOOK_EVENT_ISSUE_COMMENT:
payload, err = getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
case HOOK_EVENT_PULL_REQUEST:
payload, err = getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack)
}

View File

@ -191,6 +191,40 @@ func getSlackIssuesPayload(p *api.IssuesPayload, slack *SlackMeta) (*SlackPayloa
}, nil
}
func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppUrl+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)),
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
var text, title, attachmentText string
switch p.Action {
case api.HOOK_ISSUE_COMMENT_CREATED:
text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink)
title = titleLink
attachmentText = SlackTextFormatter(p.Comment.Body)
case api.HOOK_ISSUE_COMMENT_EDITED:
text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink)
title = titleLink
attachmentText = SlackTextFormatter(p.Comment.Body)
case api.HOOK_ISSUE_COMMENT_DELETED:
text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink)
title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index),
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
attachmentText = SlackTextFormatter(p.Comment.Body)
}
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []*SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, nil
}
func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppUrl+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
@ -260,6 +294,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload
payload, err = getSlackPushPayload(p.(*api.PushPayload), slack)
case HOOK_EVENT_ISSUES:
payload, err = getSlackIssuesPayload(p.(*api.IssuesPayload), slack)
case HOOK_EVENT_ISSUE_COMMENT:
payload, err = getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
case HOOK_EVENT_PULL_REQUEST:
payload, err = getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
}

File diff suppressed because one or more lines are too long

View File

@ -133,14 +133,15 @@ func (f *ProtectBranch) Validate(ctx *macaron.Context, errs binding.Errors) bind
// \/ \/ \/ \/ \/ \/
type Webhook struct {
Events string
Create bool
Delete bool
Fork bool
Push bool
Issues bool
PullRequest bool
Active bool
Events string
Create bool
Delete bool
Fork bool
Push bool
Issues bool
IssueComment bool
PullRequest bool
Active bool
}
func (f Webhook) PushOnly() bool {

View File

@ -92,8 +92,9 @@ func EditIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return
}
oldContent := comment.Content
comment.Content = form.Body
if err := models.UpdateComment(comment); err != nil {
if err := models.UpdateComment(ctx.User, comment, oldContent); err != nil {
ctx.Error(500, "UpdateComment", err)
return
}
@ -119,7 +120,7 @@ func DeleteIssueComment(ctx *context.APIContext) {
return
}
if err = models.DeleteCommentByID(comment.ID); err != nil {
if err = models.DeleteCommentByID(ctx.User, comment.ID); err != nil {
ctx.Error(500, "DeleteCommentByID", err)
return
}

View File

@ -906,6 +906,7 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
oldContent := comment.Content
comment.Content = ctx.Query("content")
if len(comment.Content) == 0 {
ctx.JSON(200, map[string]interface{}{
@ -913,7 +914,7 @@ func UpdateCommentContent(ctx *context.Context) {
})
return
}
if err = models.UpdateComment(comment); err != nil {
if err = models.UpdateComment(ctx.User, comment, oldContent); err != nil {
ctx.Handle(500, "UpdateComment", err)
return
}
@ -938,7 +939,7 @@ func DeleteComment(ctx *context.Context) {
return
}
if err = models.DeleteCommentByID(comment.ID); err != nil {
if err = models.DeleteCommentByID(ctx.User, comment.ID); err != nil {
ctx.Handle(500, "DeleteCommentByID", err)
return
}

View File

@ -109,12 +109,13 @@ func ParseHookEvent(f form.Webhook) *models.HookEvent {
SendEverything: f.SendEverything(),
ChooseEvents: f.ChooseEvents(),
HookEvents: models.HookEvents{
Create: f.Create,
Delete: f.Delete,
Fork: f.Fork,
Push: f.Push,
Issues: f.Issues,
PullRequest: f.PullRequest,
Create: f.Create,
Delete: f.Delete,
Fork: f.Fork,
Push: f.Push,
Issues: f.Issues,
IssueComment: f.IssueComment,
PullRequest: f.PullRequest,
},
}
}

View File

@ -1 +1 @@
0.10.11.0308
0.10.12.0309

View File

@ -67,8 +67,18 @@
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issues" type="checkbox" tabindex="0" {{if .Webhook.Issues}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_desc"}}</span>
<label>{{.i18n.Tr "repo.settings.event_issues"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issues_desc"}}</span>
</div>
</div>
</div>
<!-- Issue Comment -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="issue_comment" type="checkbox" tabindex="0" {{if .Webhook.IssueComment}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_issue_comment"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_issue_comment_desc"}}</span>
</div>
</div>
</div>

View File

@ -14,7 +14,7 @@ import (
)
func Version() string {
return "0.12.8"
return "0.12.9"
}
// Client represents a Gogs API client.

View File

@ -92,7 +92,10 @@ type PayloadCommit struct {
var (
_ Payloader = &CreatePayload{}
_ Payloader = &DeletePayload{}
_ Payloader = &ForkPayload{}
_ Payloader = &PushPayload{}
_ Payloader = &IssuesPayload{}
_ Payloader = &IssueCommentPayload{}
_ Payloader = &PullRequestPayload{}
)
@ -266,6 +269,27 @@ func (p *IssuesPayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
type HookIssueCommentAction string
const (
HOOK_ISSUE_COMMENT_CREATED HookIssueCommentAction = "created"
HOOK_ISSUE_COMMENT_EDITED HookIssueCommentAction = "edited"
HOOK_ISSUE_COMMENT_DELETED HookIssueCommentAction = "deleted"
)
type IssueCommentPayload struct {
Action HookIssueCommentAction `json:"action"`
Issue *Issue `json:"issue"`
Comment *Comment `json:"comment"`
Changes *ChangesPayload `json:"changes,omitempty"`
Repository *Repository `json:"repository"`
Sender *User `json:"sender"`
}
func (p *IssueCommentPayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
// __________ .__ .__ __________ __
// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\

6
vendor/vendor.json vendored
View File

@ -165,10 +165,10 @@
"revisionTime": "2017-02-19T18:16:29Z"
},
{
"checksumSHA1": "Ut3Nu1GaTwTt+Q4k+t9tV3/3nF8=",
"checksumSHA1": "Rvj0LCHGhFQyIM7MzBPt1iRP89c=",
"path": "github.com/gogits/go-gogs-client",
"revision": "559fec59f2c88391ba624da5c40f77c49d8c84eb",
"revisionTime": "2017-03-09T05:00:34Z"
"revision": "8e438478f71b840fcd0b3e810684ea4f6bf476bb",
"revisionTime": "2017-03-09T09:10:09Z"
},
{
"checksumSHA1": "p4yoFWgDiTfpu1JYgh26t6+VDTk=",