Add more webhooks support and refactor webhook templates directory (#3929)

* add more webhook support

* move hooks templates to standalone dir and add more webhooks ui

* fix tests

* update vendor checksum

* add more webhook support

* move hooks templates to standalone dir and add more webhooks ui

* fix tests

* update vendor checksum

* update vendor

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* load attributes when created release

* update comparsion doc
pull/3976/head^2
Lunny Xiao 7 years ago committed by GitHub
parent 188fe6c301
commit 24941a1046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docs/content/doc/features/comparison.en-us.md
  2. 20
      models/action.go
  3. 110
      models/issue_comment.go
  4. 47
      models/issue_milestone.go
  5. 2
      models/issue_milestone_test.go
  6. 22
      models/release.go
  7. 11
      models/repo.go
  8. 112
      models/webhook.go
  9. 137
      models/webhook_dingtalk.go
  10. 186
      models/webhook_discord.go
  11. 126
      models/webhook_slack.go
  12. 2
      models/webhook_test.go
  13. 17
      modules/auth/repo_form.go
  14. 10
      options/locale/locale_en-US.ini
  15. 5
      routers/api/v1/repo/issue_comment.go
  16. 27
      routers/api/v1/utils/hook.go
  17. 5
      routers/repo/issue.go
  18. 19
      routers/repo/webhook.go
  19. 2
      templates/repo/settings/webhook/base.tmpl
  20. 0
      templates/repo/settings/webhook/delete_modal.tmpl
  21. 2
      templates/repo/settings/webhook/dingtalk.tmpl
  22. 2
      templates/repo/settings/webhook/discord.tmpl
  23. 2
      templates/repo/settings/webhook/gitea.tmpl
  24. 2
      templates/repo/settings/webhook/gogs.tmpl
  25. 0
      templates/repo/settings/webhook/history.tmpl
  26. 2
      templates/repo/settings/webhook/list.tmpl
  27. 12
      templates/repo/settings/webhook/new.tmpl
  28. 52
      templates/repo/settings/webhook/settings.tmpl
  29. 2
      templates/repo/settings/webhook/slack.tmpl
  30. 122
      vendor/code.gitea.io/sdk/gitea/hook.go
  31. 28
      vendor/code.gitea.io/sdk/gitea/issue.go
  32. 34
      vendor/code.gitea.io/sdk/gitea/pull.go
  33. 6
      vendor/vendor.json

@ -537,7 +537,7 @@ _Symbols used in table:_
</tr> </tr>
<tr> <tr>
<td>Webhook support</td> <td>Webhook support</td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>

@ -618,6 +618,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
case ActionDeleteBranch: // Delete Branch case ActionDeleteBranch: // Delete Branch
isHookEventPush = true isHookEventPush = true
if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{
Ref: refName,
RefType: "branch",
PusherType: api.PusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err)
}
case ActionPushTag: // Create case ActionPushTag: // Create
isHookEventPush = true isHookEventPush = true
@ -640,6 +650,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
} }
case ActionDeleteTag: // Delete Tag case ActionDeleteTag: // Delete Tag
isHookEventPush = true isHookEventPush = true
if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{
Ref: refName,
RefType: "tag",
PusherType: api.PusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err)
}
} }
if isHookEventPush { if isHookEventPush {

@ -83,9 +83,10 @@ const (
type Comment struct { type Comment struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Type CommentType Type CommentType
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"` Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64 LabelID int64
Label *Label `xorm:"-"` Label *Label `xorm:"-"`
OldMilestoneID int64 OldMilestoneID int64
@ -116,6 +117,15 @@ type Comment struct {
ShowTag CommentTag `xorm:"-"` ShowTag CommentTag `xorm:"-"`
} }
// LoadIssue loads issue from database
func (c *Comment) LoadIssue() (err error) {
if c.Issue != nil {
return nil
}
c.Issue, err = GetIssueByID(c.IssueID)
return
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (c *Comment) AfterLoad(session *xorm.Session) { func (c *Comment) AfterLoad(session *xorm.Session) {
var err error var err error
@ -146,40 +156,40 @@ func (c *Comment) AfterDelete() {
// HTMLURL formats a URL-string to the issue-comment // HTMLURL formats a URL-string to the issue-comment
func (c *Comment) HTMLURL() string { func (c *Comment) HTMLURL() string {
issue, err := GetIssueByID(c.IssueID) err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
return fmt.Sprintf("%s#%s", issue.HTMLURL(), c.HashTag()) return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
} }
// IssueURL formats a URL-string to the issue // IssueURL formats a URL-string to the issue
func (c *Comment) IssueURL() string { func (c *Comment) IssueURL() string {
issue, err := GetIssueByID(c.IssueID) err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
if issue.IsPull { if c.Issue.IsPull {
return "" return ""
} }
return issue.HTMLURL() return c.Issue.HTMLURL()
} }
// PRURL formats a URL-string to the pull-request // PRURL formats a URL-string to the pull-request
func (c *Comment) PRURL() string { func (c *Comment) PRURL() string {
issue, err := GetIssueByID(c.IssueID) err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
if !issue.IsPull { if !c.Issue.IsPull {
return "" return ""
} }
return issue.HTMLURL() return c.Issue.HTMLURL()
} }
// APIFormat converts a Comment to the api.Comment format // APIFormat converts a Comment to the api.Comment format
@ -196,9 +206,14 @@ func (c *Comment) APIFormat() *api.Comment {
} }
} }
// CommentHashTag returns unique hash tag for comment id.
func CommentHashTag(id int64) string {
return fmt.Sprintf("issuecomment-%d", id)
}
// HashTag returns unique hash tag for comment. // HashTag returns unique hash tag for comment.
func (c *Comment) HashTag() string { func (c *Comment) HashTag() string {
return "issuecomment-" + com.ToStr(c.ID) return CommentHashTag(c.ID)
} }
// EventTag returns unique event hash tag for comment. // EventTag returns unique event hash tag for comment.
@ -576,7 +591,7 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
// CreateIssueComment creates a plain issue comment. // CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
return CreateComment(&CreateCommentOptions{ comment, err := CreateComment(&CreateCommentOptions{
Type: CommentTypeComment, Type: CommentTypeComment,
Doer: doer, Doer: doer,
Repo: repo, Repo: repo,
@ -584,6 +599,21 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
Content: content, Content: content,
Attachments: attachments, Attachments: attachments,
}) })
if err != nil {
return nil, fmt.Errorf("CreateComment: %v", err)
}
mode, _ := AccessLevel(doer.ID, repo)
if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated,
Issue: issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: repo.APIFormat(mode),
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. // CreateRefComment creates a commit reference comment to issue.
@ -696,17 +726,41 @@ func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
} }
// UpdateComment updates information of comment. // UpdateComment updates information of comment.
func UpdateComment(c *Comment) error { func UpdateComment(doer *User, c *Comment, oldContent string) error {
if _, err := x.ID(c.ID).AllCols().Update(c); err != nil { if _, err := x.ID(c.ID).AllCols().Update(c); err != nil {
return err return err
} else if c.Type == CommentTypeComment { } else if c.Type == CommentTypeComment {
UpdateIssueIndexer(c.IssueID) UpdateIssueIndexer(c.IssueID)
} }
if err := c.LoadIssue(); err != nil {
return err
}
if err := c.Issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, c.Issue.Repo)
if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited,
Issue: c.Issue.APIFormat(),
Comment: c.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
From: oldContent,
},
},
Repository: c.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
}
return nil return nil
} }
// DeleteComment deletes the comment // DeleteComment deletes the comment
func DeleteComment(comment *Comment) error { func DeleteComment(doer *User, comment *Comment) error {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err := sess.Begin(); err != nil { if err := sess.Begin(); err != nil {
@ -733,5 +787,25 @@ func DeleteComment(comment *Comment) error {
} else if comment.Type == CommentTypeComment { } else if comment.Type == CommentTypeComment {
UpdateIssueIndexer(comment.IssueID) UpdateIssueIndexer(comment.IssueID)
} }
if err := comment.LoadIssue(); err != nil {
return err
}
if err := comment.Issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, comment.Issue.Repo)
if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted,
Issue: comment.Issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
}
return nil return nil
} }

@ -5,6 +5,9 @@
package models package models
import ( import (
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
@ -358,7 +361,49 @@ func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err
if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil { if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil {
return err return err
} }
return sess.Commit()
if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
var hookAction api.HookIssueAction
if issue.MilestoneID > 0 {
hookAction = api.HookIssueMilestoned
} else {
hookAction = api.HookIssueDemilestoned
}
if err = issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, issue.Repo)
if issue.IsPull {
err = issue.PullRequest.LoadIssue()
if err != nil {
log.Error(2, "LoadIssue: %v", err)
return
}
err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
Action: hookAction,
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
} else {
err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{
Action: hookAction,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
}
if err != nil {
log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
}
return nil
} }
// DeleteMilestoneByRepoID deletes a milestone from a repository. // DeleteMilestoneByRepoID deletes a milestone from a repository.

@ -232,6 +232,8 @@ func TestChangeMilestoneAssign(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue)
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.NotNil(t, issue)
assert.NotNil(t, doer)
oldMilestoneID := issue.MilestoneID oldMilestoneID := issue.MilestoneID
issue.MilestoneID = 2 issue.MilestoneID = 2

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/git" "code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -190,8 +191,27 @@ func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []stri
} }
err = addReleaseAttachments(rel.ID, attachmentUUIDs) err = addReleaseAttachments(rel.ID, attachmentUUIDs)
if err != nil {
return err
}
return err if !rel.IsDraft {
if err := rel.LoadAttributes(); err != nil {
log.Error(2, "LoadAttributes: %v", err)
} else {
mode, _ := AccessLevel(rel.PublisherID, rel.Repo)
if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{
Action: api.HookReleasePublished,
Release: rel.APIFormat(),
Repository: rel.Repo.APIFormat(mode),
Sender: rel.Publisher.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks: %v", err)
}
}
}
return nil
} }
// GetRelease returns release by given ID. // GetRelease returns release by given ID.

@ -2456,6 +2456,17 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R
return nil, err return nil, err
} }
oldMode, _ := AccessLevel(doer.ID, oldRepo)
mode, _ := AccessLevel(doer.ID, repo)
if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{
Forkee: repo.APIFormat(mode),
Repo: oldRepo.APIFormat(oldMode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err)
}
if err = repo.UpdateSize(); err != nil { if err = repo.UpdateSize(); err != nil {
log.Error(4, "Failed to update size for repository: %v", err) log.Error(4, "Failed to update size for repository: %v", err)
} }

@ -66,10 +66,15 @@ func IsValidHookContentType(name string) bool {
// HookEvents is a set of web hook events // HookEvents is a set of web hook events
type HookEvents struct { type HookEvents struct {
Create bool `json:"create"` Create bool `json:"create"`
Push bool `json:"push"` Delete bool `json:"delete"`
PullRequest bool `json:"pull_request"` Fork bool `json:"fork"`
Repository bool `json:"repository"` Issues bool `json:"issues"`
IssueComment bool `json:"issue_comment"`
Push bool `json:"push"`
PullRequest bool `json:"pull_request"`
Repository bool `json:"repository"`
Release bool `json:"release"`
} }
// HookEvent represents events that will delivery hook. // HookEvent represents events that will delivery hook.
@ -155,6 +160,30 @@ func (w *Webhook) HasCreateEvent() bool {
(w.ChooseEvents && w.HookEvents.Create) (w.ChooseEvents && w.HookEvents.Create)
} }
// HasDeleteEvent returns true if hook enabled delete event.
func (w *Webhook) HasDeleteEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.Delete)
}
// HasForkEvent returns true if hook enabled fork event.
func (w *Webhook) HasForkEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.Fork)
}
// HasIssuesEvent returns true if hook enabled issues event.
func (w *Webhook) HasIssuesEvent() bool {
return w.SendEverything ||
(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)
}
// HasPushEvent returns true if hook enabled push event. // HasPushEvent returns true if hook enabled push event.
func (w *Webhook) HasPushEvent() bool { func (w *Webhook) HasPushEvent() bool {
return w.PushOnly || w.SendEverything || return w.PushOnly || w.SendEverything ||
@ -167,23 +196,46 @@ func (w *Webhook) HasPullRequestEvent() bool {
(w.ChooseEvents && w.HookEvents.PullRequest) (w.ChooseEvents && w.HookEvents.PullRequest)
} }
// HasReleaseEvent returns if hook enabled release event.
func (w *Webhook) HasReleaseEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.Release)
}
// HasRepositoryEvent returns if hook enabled repository event. // HasRepositoryEvent returns if hook enabled repository event.
func (w *Webhook) HasRepositoryEvent() bool { func (w *Webhook) HasRepositoryEvent() bool {
return w.SendEverything || return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.Repository) (w.ChooseEvents && w.HookEvents.Repository)
} }
func (w *Webhook) eventCheckers() []struct {
has func() bool
typ HookEventType
} {
return []struct {
has func() bool
typ HookEventType
}{
{w.HasCreateEvent, HookEventCreate},
{w.HasDeleteEvent, HookEventDelete},
{w.HasForkEvent, HookEventFork},
{w.HasPushEvent, HookEventPush},
{w.HasIssuesEvent, HookEventIssues},
{w.HasIssueCommentEvent, HookEventIssueComment},
{w.HasPullRequestEvent, HookEventPullRequest},
{w.HasRepositoryEvent, HookEventRepository},
{w.HasReleaseEvent, HookEventRelease},
}
}
// EventsArray returns an array of hook events // EventsArray returns an array of hook events
func (w *Webhook) EventsArray() []string { func (w *Webhook) EventsArray() []string {
events := make([]string, 0, 3) events := make([]string, 0, 7)
if w.HasCreateEvent() {
events = append(events, "create") for _, c := range w.eventCheckers() {
} if c.has() {
if w.HasPushEvent() { events = append(events, string(c.typ))
events = append(events, "push") }
}
if w.HasPullRequestEvent() {
events = append(events, "pull_request")
} }
return events return events
} }
@ -373,10 +425,15 @@ type HookEventType string
// Types of hook events // Types of hook events
const ( const (
HookEventCreate HookEventType = "create" HookEventCreate HookEventType = "create"
HookEventPush HookEventType = "push" HookEventDelete HookEventType = "delete"
HookEventPullRequest HookEventType = "pull_request" HookEventFork HookEventType = "fork"
HookEventRepository HookEventType = "repository" HookEventPush HookEventType = "push"
HookEventIssues HookEventType = "issues"
HookEventIssueComment HookEventType = "issue_comment"
HookEventPullRequest HookEventType = "pull_request"
HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release"
) )
// HookRequest represents hook task request information. // HookRequest represents hook task request information.
@ -488,22 +545,11 @@ func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Pay
} }
func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error { func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error {
switch event { for _, e := range w.eventCheckers() {
case HookEventCreate: if event == e.typ {
if !w.HasCreateEvent() { if !e.has() {
return nil return nil
} }
case HookEventPush:
if !w.HasPushEvent() {
return nil
}
case HookEventPullRequest:
if !w.HasPullRequestEvent() {
return nil
}
case HookEventRepository:
if !w.HasRepositoryEvent() {
return nil
} }
} }

@ -49,6 +49,38 @@ func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) {
}, nil }, nil
} }
func getDingtalkDeletePayload(p *api.DeletePayload) (*DingtalkPayload, error) {
// created tag/branch
refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
return &DingtalkPayload{
MsgType: "actionCard",
ActionCard: dingtalk.ActionCard{
Text: title,
Title: title,
HideAvatar: "0",
SingleTitle: fmt.Sprintf("view branch %s", refName),
SingleURL: p.Repo.HTMLURL + "/src/" + refName,
},
}, nil
}
func getDingtalkForkPayload(p *api.ForkPayload) (*DingtalkPayload, error) {
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return &DingtalkPayload{
MsgType: "actionCard",
ActionCard: dingtalk.ActionCard{
Text: title,
Title: title,
HideAvatar: "0",
SingleTitle: fmt.Sprintf("view forked repo %s", p.Repo.FullName),
SingleURL: p.Repo.HTMLURL,
},
}, nil
}
func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) {
var ( var (
branchName = git.RefEndName(p.Ref) branchName = git.RefEndName(p.Ref)
@ -98,6 +130,80 @@ func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) {
}, nil }, nil
} }
func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) {
var text, title string
switch p.Action {
case api.HookIssueOpened:
title = fmt.Sprintf("[%s] Issue opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueClosed:
title = fmt.Sprintf("[%s] Issue closed: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueReOpened:
title = fmt.Sprintf("[%s] Issue re-opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueEdited:
title = fmt.Sprintf("[%s] Issue edited: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueAssigned:
title = fmt.Sprintf("[%s] Issue assigned to %s: #%d %s", p.Repository.FullName,
p.Issue.Assignee.UserName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueUnassigned:
title = fmt.Sprintf("[%s] Issue unassigned: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueLabelUpdated:
title = fmt.Sprintf("[%s] Pull request labels updated: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueLabelCleared:
title = fmt.Sprintf("[%s] Pull request labels cleared: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
case api.HookIssueSynchronized:
title = fmt.Sprintf("[%s] Pull request synchronized: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
}
return &DingtalkPayload{
MsgType: "actionCard",
ActionCard: dingtalk.ActionCard{
Text: text,
Title: title,
HideAvatar: "0",
SingleTitle: "view pull request",
SingleURL: p.Issue.URL,
},
}, nil
}
func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, 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))
var content string
switch p.Action {
case api.HookIssueCommentCreated:
title = "New comment: " + title
content = p.Comment.Body
case api.HookIssueCommentEdited:
title = "Comment edited: " + title
content = p.Comment.Body
case api.HookIssueCommentDeleted:
title = "Comment deleted: " + title
url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
content = p.Comment.Body
}
return &DingtalkPayload{
MsgType: "actionCard",
ActionCard: dingtalk.ActionCard{
Text: content,
Title: title,
HideAvatar: "0",
SingleTitle: "view pull request",
SingleURL: url,
},
}, nil
}
func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) { func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) {
var text, title string var text, title string
switch p.Action { switch p.Action {
@ -182,6 +288,27 @@ func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, e
return nil, nil return nil, nil
} }
func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) {
var title, url string
switch p.Action {
case api.HookReleasePublished:
title = fmt.Sprintf("[%s] Release created", p.Release.TagName)
url = p.Release.URL
return &DingtalkPayload{
MsgType: "actionCard",
ActionCard: dingtalk.ActionCard{
Text: title,
Title: title,
HideAvatar: "0",
SingleTitle: "view repository",
SingleURL: url,
},
}, nil
}
return nil, nil
}
// GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload // GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload
func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) { func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) {
s := new(DingtalkPayload) s := new(DingtalkPayload)
@ -189,12 +316,22 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*Din
switch event { switch event {
case HookEventCreate: case HookEventCreate:
return getDingtalkCreatePayload(p.(*api.CreatePayload)) return getDingtalkCreatePayload(p.(*api.CreatePayload))
case HookEventDelete:
return getDingtalkDeletePayload(p.(*api.DeletePayload))
case HookEventFork:
return getDingtalkForkPayload(p.(*api.ForkPayload))
case HookEventIssues:
return getDingtalkIssuesPayload(p.(*api.IssuePayload))
case HookEventIssueComment:
return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
case HookEventPush: case HookEventPush:
return getDingtalkPushPayload(p.(*api.PushPayload)) return getDingtalkPushPayload(p.(*api.PushPayload))
case HookEventPullRequest: case HookEventPullRequest:
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
case HookEventRepository: case HookEventRepository:
return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))
case HookEventRelease:
return getDingtalkReleasePayload(p.(*api.ReleasePayload))
} }
return s, nil return s, nil

@ -115,6 +115,51 @@ func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordP
}, nil }, nil
} }
func getDiscordDeletePayload(p *api.DeletePayload, meta *DiscordMeta) (*DiscordPayload, error) {
// deleted tag/branch
refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
return &DiscordPayload{
Username: meta.Username,
AvatarURL: meta.IconURL,
Embeds: []DiscordEmbed{
{
Title: title,
URL: p.Repo.HTMLURL + "/src/" + refName,
Color: warnColor,
Author: DiscordEmbedAuthor{
Name: p.Sender.UserName,
URL: setting.AppURL + p.Sender.UserName,
IconURL: p.Sender.AvatarURL,
},
},
},
}, nil
}
func getDiscordForkPayload(p *api.ForkPayload, meta *DiscordMeta) (*DiscordPayload, error) {
// fork
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return &DiscordPayload{
Username: meta.Username,
AvatarURL: meta.IconURL,
Embeds: []DiscordEmbed{
{
Title: title,
URL: p.Repo.HTMLURL,
Color: successColor,
Author: DiscordEmbedAuthor{
Name: p.Sender.UserName,
URL: setting.AppURL + p.Sender.UserName,
IconURL: p.Sender.AvatarURL,
},
},
},
}, nil
}
func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) { func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) {
var ( var (
branchName = git.RefEndName(p.Ref) branchName = git.RefEndName(p.Ref)
@ -165,6 +210,108 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo
}, nil }, nil
} }
func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPayload, error) {
var text, title string
var color int
switch p.Action {
case api.HookIssueOpened:
title = fmt.Sprintf("[%s] Issue opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
case api.HookIssueClosed:
title = fmt.Sprintf("[%s] Issue closed: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
color = failedColor
text = p.Issue.Body
case api.HookIssueReOpened:
title = fmt.Sprintf("[%s] Issue re-opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
case api.HookIssueEdited:
title = fmt.Sprintf("[%s] Issue edited: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
case api.HookIssueAssigned:
title = fmt.Sprintf("[%s] Issue assigned to %s: #%d %s", p.Repository.FullName,
p.Issue.Assignee.UserName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = successColor
case api.HookIssueUnassigned:
title = fmt.Sprintf("[%s] Issue unassigned: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
case api.HookIssueLabelUpdated:
title = fmt.Sprintf("[%s] Issue labels updated: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
case api.HookIssueLabelCleared:
title = fmt.Sprintf("[%s] Issue labels cleared: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
case api.HookIssueSynchronized:
title = fmt.Sprintf("[%s] Issue synchronized: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
text = p.Issue.Body
color = warnColor
}
return &DiscordPayload{
Username: meta.Username,
AvatarURL: meta.IconURL,
Embeds: []DiscordEmbed{
{
Title: title,
Description: text,
URL: p.Issue.URL,
Color: color,
Author: DiscordEmbedAuthor{
Name: p.Sender.UserName,
URL: setting.AppURL + p.Sender.UserName,
IconURL: p.Sender.AvatarURL,
},
},
},
}, nil
}
func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*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 := ""
var color int
switch p.Action {
case api.HookIssueCommentCreated:
title = "New comment: " + title
content = p.Comment.Body
color = successColor
case api.HookIssueCommentEdited:
title = "Comment edited: " + title
content = p.Comment.Body
color = warnColor
case api.HookIssueCommentDeleted:
title = "Comment deleted: " + title
url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
content = p.Comment.Body
color = warnColor
}
return &DiscordPayload{
Username: discord.Username,
AvatarURL: discord.IconURL,
Embeds: []DiscordEmbed{
{
Title: title,
Description: content,
URL: url,
Color: color,
Author: DiscordEmbedAuthor{
Name: p.Sender.UserName,
URL: setting.AppURL + p.Sender.UserName,
IconURL: p.Sender.AvatarURL,
},
},
},
}, nil
}
func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) { func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) {
var text, title string var text, title string
var color int var color int
@ -267,6 +414,35 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*
}, nil }, nil
} }
func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*DiscordPayload, error) {
var title, url string
var color int
switch p.Action {
case api.HookReleasePublished:
title = fmt.Sprintf("[%s] Release created", p.Release.TagName)
url = p.Release.URL
color = successColor
}
return &DiscordPayload{
Username: meta.Username,
AvatarURL: meta.IconURL,
Embeds: []DiscordEmbed{
{
Title: title,
Description: fmt.Sprintf("%s", p.Release.Note),
URL: url,
Color: color,
Author: DiscordEmbedAuthor{
Name: p.Sender.UserName,
URL: setting.AppURL + p.Sender.UserName,
IconURL: p.Sender.AvatarURL,
},
},
},
}, nil
}
// GetDiscordPayload converts a discord webhook into a DiscordPayload // GetDiscordPayload converts a discord webhook into a DiscordPayload
func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) { func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) {
s := new(DiscordPayload) s := new(DiscordPayload)
@ -279,12 +455,22 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
switch event { switch event {
case HookEventCreate: case HookEventCreate:
return getDiscordCreatePayload(p.(*api.CreatePayload), discord) return getDiscordCreatePayload(p.(*api.CreatePayload), discord)
case HookEventDelete:
return getDiscordDeletePayload(p.(*api.DeletePayload), discord)
case HookEventFork:
return getDiscordForkPayload(p.(*api.ForkPayload), discord)
case HookEventIssues:
return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
case HookEventIssueComment:
return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
case HookEventPush: case HookEventPush:
return getDiscordPushPayload(p.(*api.PushPayload), discord) return getDiscordPushPayload(p.(*api.PushPayload), discord)
case HookEventPullRequest: case HookEventPullRequest:
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
case HookEventRepository: case HookEventRepository:
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
case HookEventRelease:
return getDiscordReleasePayload(p.(*api.ReleasePayload), discord)
} }
return s, nil return s, nil

@ -106,6 +106,122 @@ func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayloa
}, nil }, nil
} }
// getSlackDeletePayload composes Slack payload for delete a branch or tag.
func getSlackDeletePayload(p *api.DeletePayload, slack *SlackMeta) (*SlackPayload, error) {
refName := git.RefEndName(p.Ref)
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName)
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
}, nil
}
// getSlackForkPayload composes Slack payload for forked by a repository.
func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, error) {
baseLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
forkLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
}, nil
}
func getSlackIssuesPayload(p *api.IssuePayload, 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),
fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
var text, title, attachmentText string
switch p.Action {
case api.HookIssueOpened:
text = fmt.Sprintf("[%s] Issue submitted by %s", p.Repository.FullName, senderLink)
title = titleLink
attachmentText = SlackTextFormatter(p.Issue.Body)
case api.HookIssueClosed:
text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
case api.HookIssueReOpened:
text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
case api.HookIssueEdited:
text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
attachmentText = SlackTextFormatter(p.Issue.Body)
case api.HookIssueAssigned:
text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName,
SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
titleLink, senderLink)
case api.HookIssueUnassigned:
text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
case api.HookIssueLabelUpdated:
text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
case api.HookIssueLabelCleared:
text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
case api.HookIssueSynchronized:
text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
}
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, 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.HookIssueCommentCreated:
text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink)
title = titleLink
attachmentText = SlackTextFormatter(p.Comment.Body)
case api.HookIssueCommentEdited:
text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink)
title = titleLink
attachmentText = SlackTextFormatter(p.Comment.Body)
case api.HookIssueCommentDeleted:
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 getSlackReleasePayload(p *api.ReleasePayload, slack *SlackMeta) (*SlackPayload, error) {
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.Name)
refLink := SlackLinkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName)
text := fmt.Sprintf("[%s] new release %s published by %s", repoLink, refLink, p.Sender.UserName)
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
}, nil
}
func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) { func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) {
// n new commits // n new commits
var ( var (
@ -238,12 +354,22 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP
switch event { switch event {
case HookEventCreate: case HookEventCreate:
return getSlackCreatePayload(p.(*api.CreatePayload), slack) return getSlackCreatePayload(p.(*api.CreatePayload), slack)
case HookEventDelete:
return getSlackDeletePayload(p.(*api.DeletePayload), slack)
case HookEventFork:
return getSlackForkPayload(p.(*api.ForkPayload), slack)
case HookEventIssues:
return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
case HookEventIssueComment:
return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
case HookEventPush: case HookEventPush:
return getSlackPushPayload(p.(*api.PushPayload), slack) return getSlackPushPayload(p.(*api.PushPayload), slack)
case HookEventPullRequest: case HookEventPullRequest:
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
case HookEventRepository: case HookEventRepository:
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)
case HookEventRelease:
return getSlackReleasePayload(p.(*api.ReleasePayload), slack)
} }
return s, nil return s, nil

@ -73,7 +73,7 @@ func TestWebhook_UpdateEvent(t *testing.T) {
} }
func TestWebhook_EventsArray(t *testing.T) { func TestWebhook_EventsArray(t *testing.T) {
assert.Equal(t, []string{"create", "push", "pull_request"}, assert.Equal(t, []string{"create", "delete", "fork", "push", "issues", "issue_comment", "pull_request", "repository", "release"},
(&Webhook{ (&Webhook{
HookEvent: &HookEvent{SendEverything: true}, HookEvent: &HookEvent{SendEverything: true},
}).EventsArray(), }).EventsArray(),

@ -155,12 +155,17 @@ func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors)
// WebhookForm form for changing web hook // WebhookForm form for changing web hook
type WebhookForm struct { type WebhookForm struct {
Events string Events string
Create bool Create bool
Push bool Delete bool
PullRequest bool Fork bool
Repository bool Issues bool
Active bool IssueComment bool
Release bool
Push bool
PullRequest bool
Repository bool
Active bool
} }
// PushOnly if the hook will be triggered when push // PushOnly if the hook will be triggered when push

@ -1000,6 +1000,16 @@ settings.event_send_everything = All Events
settings.event_choose = Custom Events… settings.event_choose = Custom Events…
settings.event_create = Create settings.event_create = Create
settings.event_create_desc = Branch or tag created. settings.event_create_desc = Branch or tag created.
settings.event_delete = Delete
settings.event_delete_desc = Branch or tag deleted
settings.event_fork = Fork
settings.event_fork_desc = Repository forked
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_release = Release
settings.event_release_desc = Release published in a repository.
settings.event_pull_request = Pull Request settings.event_pull_request = Pull Request
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized. settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized.
settings.event_push = Push settings.event_push = Push

@ -261,8 +261,9 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return return
} }
oldContent := comment.Content
comment.Content = form.Body 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) ctx.Error(500, "UpdateComment", err)
return return
} }
@ -348,7 +349,7 @@ func deleteIssueComment(ctx *context.APIContext) {
return return
} }
if err = models.DeleteComment(comment); err != nil { if err = models.DeleteComment(ctx.User, comment); err != nil {
ctx.Error(500, "DeleteCommentByID", err) ctx.Error(500, "DeleteCommentByID", err)
return return
} }

@ -7,12 +7,13 @@ package utils
import ( import (
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
"encoding/json"
"net/http"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/convert" "code.gitea.io/gitea/routers/api/v1/convert"
"encoding/json"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"net/http"
) )
// GetOrgHook get an organization's webhook. If there is an error, write to // GetOrgHook get an organization's webhook. If there is an error, write to
@ -98,9 +99,15 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
HookEvent: &models.HookEvent{ HookEvent: &models.HookEvent{
ChooseEvents: true, ChooseEvents: true,
HookEvents: models.HookEvents{ HookEvents: models.HookEvents{
Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)), Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)),
Push: com.IsSliceContainsStr(form.Events, string(models.HookEventPush)), Delete: com.IsSliceContainsStr(form.Events, string(models.HookEventDelete)),
PullRequest: com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)), Fork: com.IsSliceContainsStr(form.Events, string(models.HookEventFork)),
Issues: com.IsSliceContainsStr(form.Events, string(models.HookEventIssues)),
IssueComment: com.IsSliceContainsStr(form.Events, string(models.HookEventIssueComment)),
Push: com.IsSliceContainsStr(form.Events, string(models.HookEventPush)),
PullRequest: com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)),
Repository: com.IsSliceContainsStr(form.Events, string(models.HookEventRepository)),
Release: com.IsSliceContainsStr(form.Events, string(models.HookEventRelease)),
}, },
}, },
IsActive: form.Active, IsActive: form.Active,
@ -211,6 +218,16 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *models.Webho
w.Create = com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)) w.Create = com.IsSliceContainsStr(form.Events, string(models.HookEventCreate))
w.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush)) w.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush))
w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)) w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest))
w.Create = com.IsSliceContainsStr(form.Events, string(models.HookEventCreate))
w.Delete = com.IsSliceContainsStr(form.Events, string(models.HookEventDelete))
w.Fork = com.IsSliceContainsStr(form.Events, string(models.HookEventFork))
w.Issues = com.IsSliceContainsStr(form.Events, string(models.HookEventIssues))
w.IssueComment = com.IsSliceContainsStr(form.Events, string(models.HookEventIssueComment))
w.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush))
w.PullRequest = com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest))
w.Repository = com.IsSliceContainsStr(form.Events, string(models.HookEventRepository))
w.Release = com.IsSliceContainsStr(form.Events, string(models.HookEventRelease))
if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
ctx.Error(500, "UpdateEvent", err) ctx.Error(500, "UpdateEvent", err)
return false return false

@ -1086,6 +1086,7 @@ func UpdateCommentContent(ctx *context.Context) {
return return
} }
oldContent := comment.Content
comment.Content = ctx.Query("content") comment.Content = ctx.Query("content")
if len(comment.Content) == 0 { if len(comment.Content) == 0 {
ctx.JSON(200, map[string]interface{}{ ctx.JSON(200, map[string]interface{}{
@ -1093,7 +1094,7 @@ func UpdateCommentContent(ctx *context.Context) {
}) })
return return
} }
if err = models.UpdateComment(comment); err != nil { if err = models.UpdateComment(ctx.User, comment, oldContent); err != nil {
ctx.ServerError("UpdateComment", err) ctx.ServerError("UpdateComment", err)
return return
} }
@ -1119,7 +1120,7 @@ func DeleteComment(ctx *context.Context) {
return return
} }
if err = models.DeleteComment(comment); err != nil { if err = models.DeleteComment(ctx.User, comment); err != nil {
ctx.ServerError("DeleteCommentByID", err) ctx.ServerError("DeleteCommentByID", err)
return return
} }

@ -23,9 +23,9 @@ import (
) )
const ( const (
tplHooks base.TplName = "repo/settings/hooks" tplHooks base.TplName = "repo/settings/webhook/base"
tplHookNew base.TplName = "repo/settings/hook_new" tplHookNew base.TplName = "repo/settings/webhook/new"
tplOrgHookNew base.TplName = "org/settings/hook_new" tplOrgHookNew base.TplName = "org/settings/webhook/new"
) )
// Webhooks render web hooks list page // Webhooks render web hooks list page
@ -118,10 +118,15 @@ func ParseHookEvent(form auth.WebhookForm) *models.HookEvent {
SendEverything: form.SendEverything(), SendEverything: form.SendEverything(),
ChooseEvents: form.ChooseEvents(), ChooseEvents: form.ChooseEvents(),
HookEvents: models.HookEvents{ HookEvents: models.HookEvents{
Create: form.Create, Create: form.Create,
Push: form.Push, Delete: form.Delete,
PullRequest: form.PullRequest, Fork: form.Fork,
Repository: form.Repository, Issues: form.Issues,
IssueComment: form.IssueComment,
Release: form.Release,
Push: form.Push,
PullRequest: form.PullRequest,
Repository: form.Repository,
}, },
} }
} }

@ -3,7 +3,7 @@
{{template "repo/header" .}} {{template "repo/header" .}}
{{template "repo/settings/navbar" .}} {{template "repo/settings/navbar" .}}
<div class="ui container"> <div class="ui container">
{{template "repo/settings/hook_list" .}} {{template "repo/settings/webhook/list" .}}
</div> </div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

@ -6,6 +6,6 @@
<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
<input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required>
</div> </div>
{{template "repo/settings/hook_settings" .}} {{template "repo/settings/webhook/settings" .}}
</form> </form>
{{end}} {{end}}

@ -14,6 +14,6 @@
<label for="icon_url">{{.i18n.Tr "repo.settings.discord_icon_url"}}</label> <label for="icon_url">{{.i18n.Tr "repo.settings.discord_icon_url"}}</label>
<input id="icon_url" name="icon_url" value="{{.DiscordHook.IconURL}}" placeholder="e.g. https://example.com/img/favicon.png"> <input id="icon_url" name="icon_url" value="{{.DiscordHook.IconURL}}" placeholder="e.g. https://example.com/img/favicon.png">
</div> </div>
{{template "repo/settings/hook_settings" .}} {{template "repo/settings/webhook/settings" .}}
</form> </form>
{{end}} {{end}}

@ -23,6 +23,6 @@
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off">
</div> </div>
{{template "repo/settings/hook_settings" .}} {{template "repo/settings/webhook/settings" .}}
</form> </form>
{{end}} {{end}}

@ -23,6 +23,6 @@
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off">
</div> </div>
{{template "repo/settings/hook_settings" .}} {{template "repo/settings/webhook/settings" .}}
</form> </form>
{{end}} {{end}}

@ -48,4 +48,4 @@
</div> </div>
</div> </div>
{{template "repo/settings/hook_delete_modal" .}} {{template "repo/settings/webhook/delete_modal" .}}

@ -21,14 +21,14 @@
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "repo/settings/hook_gitea" .}} {{template "repo/settings/webhook/gitea" .}}
{{template "repo/settings/hook_gogs" .}} {{template "repo/settings/webhook/gogs" .}}
{{template "repo/settings/hook_slack" .}} {{template "repo/settings/webhook/slack" .}}
{{template "repo/settings/hook_discord" .}} {{template "repo/settings/webhook/discord" .}}
{{template "repo/settings/hook_dingtalk" .}} {{template "repo/settings/webhook/dingtalk" .}}
</div> </div>
{{template "repo/settings/hook_history" .}} {{template "repo/settings/webhook/history" .}}
</div> </div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

@ -32,6 +32,26 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Delete -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="delete" type="checkbox" tabindex="0" {{if .Webhook.Delete}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_delete"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_delete_desc"}}</span>
</div>
</div>
</div>
<!-- Fork -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="fork" type="checkbox" tabindex="0" {{if .Webhook.Fork}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_fork"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_fork_desc"}}</span>
</div>
</div>
</div>
<!-- Push --> <!-- Push -->
<div class="seven wide column"> <div class="seven wide column">
<div class="field"> <div class="field">
@ -42,6 +62,26 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Issues -->
<div class="seven wide column">
<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_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>
<!-- Pull Request --> <!-- Pull Request -->
<div class="seven wide column"> <div class="seven wide column">
<div class="field"> <div class="field">
@ -62,6 +102,16 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Release -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="release" type="checkbox" tabindex="0" {{if .Webhook.Release}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_release"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_release_desc"}}</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
@ -83,4 +133,4 @@
{{end}} {{end}}
</div> </div>
{{template "repo/settings/hook_delete_modal" .}} {{template "repo/settings/webhook/delete_modal" .}}

@ -23,6 +23,6 @@
<label for="color">{{.i18n.Tr "repo.settings.slack_color"}}</label> <label for="color">{{.i18n.Tr "repo.settings.slack_color"}}</label>
<input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="e.g. #dd4b39, good, warning, danger"> <input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="e.g. #dd4b39, good, warning, danger">
</div> </div>
{{template "repo/settings/hook_settings" .}} {{template "repo/settings/webhook/settings" .}}
</form> </form>
{{end}} {{end}}

@ -172,9 +172,14 @@ type PayloadCommitVerification struct {
var ( var (
_ Payloader = &CreatePayload{} _ Payloader = &CreatePayload{}
_ Payloader = &DeletePayload{}
_ Payloader = &ForkPayload{}
_ Payloader = &PushPayload{} _ Payloader = &PushPayload{}
_ Payloader = &IssuePayload{} _ Payloader = &IssuePayload{}
_ Payloader = &IssueCommentPayload{}
_ Payloader = &PullRequestPayload{} _ Payloader = &PullRequestPayload{}
_ Payloader = &RepositoryPayload{}
_ Payloader = &ReleasePayload{}
) )
// _________ __ // _________ __
@ -224,6 +229,123 @@ func ParseCreateHook(raw []byte) (*CreatePayload, error) {
return hook, nil return hook, nil
} }
// ________ .__ __
// \______ \ ____ | | _____/ |_ ____
// | | \_/ __ \| | _/ __ \ __\/ __ \
// | ` \ ___/| |_\ ___/| | \ ___/
// /_______ /\___ >____/\___ >__| \___ >
// \/ \/ \/ \/
// PusherType define the type to push
type PusherType string
// describe all the PusherTypes
const (
PusherTypeUser PusherType = "user"
)
// DeletePayload represents delete payload
type DeletePayload struct {
Ref string `json:"ref"`
RefType string `json:"ref_type"`
PusherType PusherType `json:"pusher_type"`
Repo *Repository `json:"repository"`
Sender *User `json:"sender"`
}
// SetSecret implements Payload
func (p *DeletePayload) SetSecret(secret string) {
}
// JSONPayload implements Payload
func (p *DeletePayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
// ___________ __
// \_ _____/__________| | __
// | __)/ _ \_ __ \ |/ /
// | \( <_> ) | \/ <
// \___ / \____/|__| |__|_ \
// \/ \/
// ForkPayload represents fork payload
type ForkPayload struct {
Forkee *Repository `json:"forkee"`
Repo *Repository `json:"repository"`
Sender *User `json:"sender"`
}
// SetSecret implements Payload
func (p *ForkPayload) SetSecret(secret string) {
}
// JSONPayload implements Payload
func (p *ForkPayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
// HookIssueCommentAction defines hook issue comment action
type HookIssueCommentAction string
// all issue comment actions
const (
HookIssueCommentCreated HookIssueCommentAction = "created"
HookIssueCommentEdited HookIssueCommentAction = "edited"
HookIssueCommentDeleted HookIssueCommentAction = "deleted"
)
// IssueCommentPayload represents a payload information of issue comment event.
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"`
}
// SetSecret implements Payload
func (p *IssueCommentPayload) SetSecret(secret string) {
}
// JSONPayload implements Payload
func (p *IssueCommentPayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
// __________ .__
// \______ \ ____ | | ____ _____ ______ ____
// | _// __ \| | _/ __ \\__ \ / ___// __ \
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
// |____|_ /\___ >____/\___ >____ /____ >\___ >
// \/ \/ \/ \/ \/ \/
// HookReleaseAction defines hook release action type
type HookReleaseAction string
// all release actions
const (
HookReleasePublished HookReleaseAction = "published"
)
// ReleasePayload represents a payload information of release event.
type ReleasePayload struct {
Action HookReleaseAction `json:"action"`
Release *Release `json:"release"`
Repository *Repository `json:"repository"`
Sender *User `json:"sender"`
}
// SetSecret implements Payload
func (p *ReleasePayload) SetSecret(secret string) {
}
// JSONPayload implements Payload
func (p *ReleasePayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
// __________ .__ // __________ .__
// \______ \__ __ _____| |__ // \______ \__ __ _____| |__
// | ___/ | \/ ___/ | \ // | ___/ | \/ ___/ | \

@ -118,14 +118,14 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue,
// EditIssueOption options for editing an issue // EditIssueOption options for editing an issue
type EditIssueOption struct { type EditIssueOption struct {
Title string `json:"title"` Title string `json:"title"`
Body *string `json:"body"` Body *string `json:"body"`
Assignee *string `json:"assignee"` Assignee *string `json:"assignee"`
Assignees []string `json:"assignees"` Assignees []string `json:"assignees"`
Milestone *int64 `json:"milestone"` Milestone *int64 `json:"milestone"`
State *string `json:"state"` State *string `json:"state"`
// swagger:strfmt date-time // swagger:strfmt date-time
Deadline *time.Time `json:"due_date"` Deadline *time.Time `json:"due_date"`
} }
// EditIssue modify an existing issue for a given repository // EditIssue modify an existing issue for a given repository
@ -138,3 +138,17 @@ func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption)
return issue, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), return issue, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
jsonHeader, bytes.NewReader(body), issue) jsonHeader, bytes.NewReader(body), issue)
} }
// EditDeadlineOption options for creating a deadline
type EditDeadlineOption struct {
// required:true
// swagger:strfmt date-time
Deadline *time.Time `json:"due_date"`
}
// IssueDeadline represents an issue deadline
// swagger:model
type IssueDeadline struct {
// swagger:strfmt date-time
Deadline *time.Time `json:"due_date"`
}

@ -85,16 +85,16 @@ func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest,
// CreatePullRequestOption options when creating a pull request // CreatePullRequestOption options when creating a pull request
type CreatePullRequestOption struct { type CreatePullRequestOption struct {
Head string `json:"head" binding:"Required"` Head string `json:"head" binding:"Required"`
Base string `json:"base" binding:"Required"` Base string `json:"base" binding:"Required"`
Title string `json:"title" binding:"Required"` Title string `json:"title" binding:"Required"`
Body string `json:"body"` Body string `json:"body"`
Assignee string `json:"assignee"` Assignee string `json:"assignee"`
Assignees []string `json:"assignees"` Assignees []string `json:"assignees"`
Milestone int64 `json:"milestone"` Milestone int64 `json:"milestone"`
Labels []int64 `json:"labels"` Labels []int64 `json:"labels"`
// swagger:strfmt date-time // swagger:strfmt date-time
Deadline *time.Time `json:"due_date"` Deadline *time.Time `json:"due_date"`
} }
// CreatePullRequest create pull request with options // CreatePullRequest create pull request with options
@ -110,15 +110,15 @@ func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOpti
// EditPullRequestOption options when modify pull request // EditPullRequestOption options when modify pull request
type EditPullRequestOption struct { type EditPullRequestOption struct {
Title string `json:"title"` Title string `json:"title"`
Body string `json:"body"` Body string `json:"body"`
Assignee string `json:"assignee"` Assignee string `json:"assignee"`
Assignees []string `json:"assignees"` Assignees []string `json:"assignees"`
Milestone int64 `json:"milestone"` Milestone int64 `json:"milestone"`
Labels []int64 `json:"labels"` Labels []int64 `json:"labels"`
State *string `json:"state"` State *string `json:"state"`
// swagger:strfmt date-time // swagger:strfmt date-time
Deadline *time.Time `json:"due_date"` Deadline *time.Time `json:"due_date"`
} }
// EditPullRequest modify pull request with PR id and options // EditPullRequest modify pull request with PR id and options

@ -9,10 +9,10 @@
"revisionTime": "2018-04-21T01:08:19Z" "revisionTime": "2018-04-21T01:08:19Z"
}, },
{ {
"checksumSHA1": "WMD6+Qh2+5hd9uiq910pF/Ihylw=", "checksumSHA1": "LnxY/6xD4h9dCCJ5nxKEfZZs1Vk=",
"path": "code.gitea.io/sdk/gitea", "path": "code.gitea.io/sdk/gitea",
"revision": "1c8d12f79a51605ed91587aa6b86cf38fc0f987f", "revision": "7fa627fa5d67d18c39d6dd3c6c4db836916bf234",
"revisionTime": "2018-05-01T11:15:19Z" "revisionTime": "2018-05-10T12:54:05Z"
}, },
{ {
"checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", "checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=",

Loading…
Cancel
Save