* 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 doctags/v1.5.0-dev
@@ -537,7 +537,7 @@ _Symbols used in table:_ | |||
</tr> | |||
<tr> | |||
<td>Webhook support</td> | |||
<td>⁄</td> | |||
<td>✓</td> | |||
<td>✓</td> | |||
<td>✓</td> | |||
<td>✓</td> |
@@ -618,6 +618,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { | |||
case ActionDeleteBranch: // Delete Branch | |||
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 | |||
isHookEventPush = true | |||
@@ -640,6 +650,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { | |||
} | |||
case ActionDeleteTag: // Delete Tag | |||
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 { |
@@ -83,9 +83,10 @@ const ( | |||
type Comment struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
Type CommentType | |||
PosterID int64 `xorm:"INDEX"` | |||
Poster *User `xorm:"-"` | |||
IssueID int64 `xorm:"INDEX"` | |||
PosterID int64 `xorm:"INDEX"` | |||
Poster *User `xorm:"-"` | |||
IssueID int64 `xorm:"INDEX"` | |||
Issue *Issue `xorm:"-"` | |||
LabelID int64 | |||
Label *Label `xorm:"-"` | |||
OldMilestoneID int64 | |||
@@ -116,6 +117,15 @@ type Comment struct { | |||
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. | |||
func (c *Comment) AfterLoad(session *xorm.Session) { | |||
var err error | |||
@@ -146,40 +156,40 @@ func (c *Comment) AfterDelete() { | |||
// HTMLURL formats a URL-string to the issue-comment | |||
func (c *Comment) HTMLURL() string { | |||
issue, err := GetIssueByID(c.IssueID) | |||
err := c.LoadIssue() | |||
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 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 | |||
func (c *Comment) IssueURL() string { | |||
issue, err := GetIssueByID(c.IssueID) | |||
err := c.LoadIssue() | |||
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 "" | |||
} | |||
if issue.IsPull { | |||
if c.Issue.IsPull { | |||
return "" | |||
} | |||
return issue.HTMLURL() | |||
return c.Issue.HTMLURL() | |||
} | |||
// PRURL formats a URL-string to the pull-request | |||
func (c *Comment) PRURL() string { | |||
issue, err := GetIssueByID(c.IssueID) | |||
err := c.LoadIssue() | |||
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 "" | |||
} | |||
if !issue.IsPull { | |||
if !c.Issue.IsPull { | |||
return "" | |||
} | |||
return issue.HTMLURL() | |||
return c.Issue.HTMLURL() | |||
} | |||
// 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. | |||
func (c *Comment) HashTag() string { | |||
return "issuecomment-" + com.ToStr(c.ID) | |||
return CommentHashTag(c.ID) | |||
} | |||
// 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. | |||
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { | |||
return CreateComment(&CreateCommentOptions{ | |||
comment, err := CreateComment(&CreateCommentOptions{ | |||
Type: CommentTypeComment, | |||
Doer: doer, | |||
Repo: repo, | |||
@@ -584,6 +599,21 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri | |||
Content: content, | |||
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. | |||
@@ -696,17 +726,41 @@ func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) { | |||
} | |||
// 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 { | |||
return err | |||
} else if c.Type == CommentTypeComment { | |||
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 | |||
} | |||
// DeleteComment deletes the comment | |||
func DeleteComment(comment *Comment) error { | |||
func DeleteComment(doer *User, comment *Comment) error { | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
if err := sess.Begin(); err != nil { | |||
@@ -733,5 +787,25 @@ func DeleteComment(comment *Comment) error { | |||
} else if comment.Type == CommentTypeComment { | |||
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 | |||
} |
@@ -5,6 +5,9 @@ | |||
package models | |||
import ( | |||
"fmt" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
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 { | |||
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. |
@@ -232,6 +232,8 @@ func TestChangeMilestoneAssign(t *testing.T) { | |||
assert.NoError(t, PrepareTestDatabase()) | |||
issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) | |||
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | |||
assert.NotNil(t, issue) | |||
assert.NotNil(t, doer) | |||
oldMilestoneID := issue.MilestoneID | |||
issue.MilestoneID = 2 |
@@ -10,6 +10,7 @@ import ( | |||
"strings" | |||
"code.gitea.io/git" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/process" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
@@ -190,8 +191,27 @@ func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []stri | |||
} | |||
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. |
@@ -2456,6 +2456,17 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R | |||
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 { | |||
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 | |||
type HookEvents struct { | |||
Create bool `json:"create"` | |||
Push bool `json:"push"` | |||
PullRequest bool `json:"pull_request"` | |||
Repository bool `json:"repository"` | |||
Create bool `json:"create"` | |||
Delete bool `json:"delete"` | |||
Fork bool `json:"fork"` | |||
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. | |||
@@ -155,6 +160,30 @@ func (w *Webhook) HasCreateEvent() bool { | |||
(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. | |||
func (w *Webhook) HasPushEvent() bool { | |||
return w.PushOnly || w.SendEverything || | |||
@@ -167,23 +196,46 @@ func (w *Webhook) HasPullRequestEvent() bool { | |||
(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. | |||
func (w *Webhook) HasRepositoryEvent() bool { | |||
return w.SendEverything || | |||
(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 | |||
func (w *Webhook) EventsArray() []string { | |||
events := make([]string, 0, 3) | |||
if w.HasCreateEvent() { | |||
events = append(events, "create") | |||
} | |||
if w.HasPushEvent() { | |||
events = append(events, "push") | |||
} | |||
if w.HasPullRequestEvent() { | |||
events = append(events, "pull_request") | |||
events := make([]string, 0, 7) | |||
for _, c := range w.eventCheckers() { | |||
if c.has() { | |||
events = append(events, string(c.typ)) | |||
} | |||
} | |||
return events | |||
} | |||
@@ -373,10 +425,15 @@ type HookEventType string | |||
// Types of hook events | |||
const ( | |||
HookEventCreate HookEventType = "create" | |||
HookEventPush HookEventType = "push" | |||
HookEventPullRequest HookEventType = "pull_request" | |||
HookEventRepository HookEventType = "repository" | |||
HookEventCreate HookEventType = "create" | |||
HookEventDelete HookEventType = "delete" | |||
HookEventFork HookEventType = "fork" | |||
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. | |||
@@ -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 { | |||
switch event { | |||
case HookEventCreate: | |||
if !w.HasCreateEvent() { | |||
return nil | |||
} | |||
case HookEventPush: | |||
if !w.HasPushEvent() { | |||
return nil | |||
} | |||
case HookEventPullRequest: | |||
if !w.HasPullRequestEvent() { | |||
return nil | |||
} | |||
case HookEventRepository: | |||
if !w.HasRepositoryEvent() { | |||
return nil | |||
for _, e := range w.eventCheckers() { | |||
if event == e.typ { | |||
if !e.has() { | |||
return nil | |||
} | |||
} | |||
} | |||
@@ -49,6 +49,38 @@ func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) { | |||
}, 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) { | |||
var ( | |||
branchName = git.RefEndName(p.Ref) | |||
@@ -98,6 +130,80 @@ func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { | |||
}, 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) { | |||
var text, title string | |||
switch p.Action { | |||
@@ -182,6 +288,27 @@ func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, e | |||
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 | |||
func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) { | |||
s := new(DingtalkPayload) | |||
@@ -189,12 +316,22 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*Din | |||
switch event { | |||
case HookEventCreate: | |||
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: | |||
return getDingtalkPushPayload(p.(*api.PushPayload)) | |||
case HookEventPullRequest: | |||
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) | |||
case HookEventRepository: | |||
return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) | |||
case HookEventRelease: | |||
return getDingtalkReleasePayload(p.(*api.ReleasePayload)) | |||
} | |||
return s, nil |
@@ -115,6 +115,51 @@ func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordP | |||
}, 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) { | |||
var ( | |||
branchName = git.RefEndName(p.Ref) | |||
@@ -165,6 +210,108 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo | |||
}, 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) { | |||
var text, title string | |||
var color int | |||
@@ -267,6 +414,35 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (* | |||
}, 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 | |||
func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) { | |||
s := new(DiscordPayload) | |||
@@ -279,12 +455,22 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc | |||
switch event { | |||
case HookEventCreate: | |||
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: | |||
return getDiscordPushPayload(p.(*api.PushPayload), discord) | |||
case HookEventPullRequest: | |||
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) | |||
case HookEventRepository: | |||
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) | |||
case HookEventRelease: | |||
return getDiscordReleasePayload(p.(*api.ReleasePayload), discord) | |||
} | |||
return s, nil |
@@ -106,6 +106,122 @@ func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayloa | |||
}, 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) { | |||
// n new commits | |||
var ( | |||
@@ -238,12 +354,22 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP | |||
switch event { | |||
case HookEventCreate: | |||
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: | |||
return getSlackPushPayload(p.(*api.PushPayload), slack) | |||
case HookEventPullRequest: | |||
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) | |||
case HookEventRepository: | |||
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) | |||
case HookEventRelease: | |||
return getSlackReleasePayload(p.(*api.ReleasePayload), slack) | |||
} | |||
return s, nil |
@@ -73,7 +73,7 @@ func TestWebhook_UpdateEvent(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{ | |||
HookEvent: &HookEvent{SendEverything: true}, | |||
}).EventsArray(), |
@@ -155,12 +155,17 @@ func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) | |||
// WebhookForm form for changing web hook | |||
type WebhookForm struct { | |||
Events string | |||
Create bool | |||
Push bool | |||
PullRequest bool | |||
Repository bool | |||
Active bool | |||
Events string | |||
Create bool | |||
Delete bool | |||
Fork bool | |||
Issues bool | |||
IssueComment bool | |||
Release bool | |||
Push bool | |||
PullRequest bool | |||
Repository bool | |||
Active bool | |||
} | |||
// 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_create = Create | |||
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_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized. | |||
settings.event_push = Push |
@@ -261,8 +261,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 | |||
} | |||
@@ -348,7 +349,7 @@ func deleteIssueComment(ctx *context.APIContext) { | |||
return | |||
} | |||
if err = models.DeleteComment(comment); err != nil { | |||
if err = models.DeleteComment(ctx.User, comment); err != nil { | |||
ctx.Error(500, "DeleteCommentByID", err) | |||
return | |||
} |
@@ -7,12 +7,13 @@ package utils | |||
import ( | |||
api "code.gitea.io/sdk/gitea" | |||
"encoding/json" | |||
"net/http" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/routers/api/v1/convert" | |||
"encoding/json" | |||
"github.com/Unknwon/com" | |||
"net/http" | |||
) | |||
// 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{ | |||
ChooseEvents: true, | |||
HookEvents: models.HookEvents{ | |||
Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)), | |||
Push: com.IsSliceContainsStr(form.Events, string(models.HookEventPush)), | |||
PullRequest: com.IsSliceContainsStr(form.Events, string(models.HookEventPullRequest)), | |||
Create: com.IsSliceContainsStr(form.Events, string(models.HookEventCreate)), | |||
Delete: com.IsSliceContainsStr(form.Events, string(models.HookEventDelete)), | |||
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, | |||
@@ -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.Push = com.IsSliceContainsStr(form.Events, string(models.HookEventPush)) | |||
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 { | |||
ctx.Error(500, "UpdateEvent", err) | |||
return false |
@@ -1086,6 +1086,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{}{ | |||
@@ -1093,7 +1094,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.ServerError("UpdateComment", err) | |||
return | |||
} | |||
@@ -1119,7 +1120,7 @@ func DeleteComment(ctx *context.Context) { | |||
return | |||
} | |||
if err = models.DeleteComment(comment); err != nil { | |||
if err = models.DeleteComment(ctx.User, comment); err != nil { | |||
ctx.ServerError("DeleteCommentByID", err) | |||
return | |||
} |
@@ -23,9 +23,9 @@ import ( | |||
) | |||
const ( | |||
tplHooks base.TplName = "repo/settings/hooks" | |||
tplHookNew base.TplName = "repo/settings/hook_new" | |||
tplOrgHookNew base.TplName = "org/settings/hook_new" | |||
tplHooks base.TplName = "repo/settings/webhook/base" | |||
tplHookNew base.TplName = "repo/settings/webhook/new" | |||
tplOrgHookNew base.TplName = "org/settings/webhook/new" | |||
) | |||
// Webhooks render web hooks list page | |||
@@ -118,10 +118,15 @@ func ParseHookEvent(form auth.WebhookForm) *models.HookEvent { | |||
SendEverything: form.SendEverything(), | |||
ChooseEvents: form.ChooseEvents(), | |||
HookEvents: models.HookEvents{ | |||
Create: form.Create, | |||
Push: form.Push, | |||
PullRequest: form.PullRequest, | |||
Repository: form.Repository, | |||
Create: form.Create, | |||
Delete: form.Delete, | |||
Fork: form.Fork, | |||
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/settings/navbar" .}} | |||
<div class="ui container"> | |||
{{template "repo/settings/hook_list" .}} | |||
{{template "repo/settings/webhook/list" .}} | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -6,6 +6,6 @@ | |||
<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> | |||
</div> | |||
{{template "repo/settings/hook_settings" .}} | |||
{{template "repo/settings/webhook/settings" .}} | |||
</form> | |||
{{end}} |
@@ -14,6 +14,6 @@ | |||
<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"> | |||
</div> | |||
{{template "repo/settings/hook_settings" .}} | |||
{{template "repo/settings/webhook/settings" .}} | |||
</form> | |||
{{end}} |
@@ -23,6 +23,6 @@ | |||
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | |||
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> | |||
</div> | |||
{{template "repo/settings/hook_settings" .}} | |||
{{template "repo/settings/webhook/settings" .}} | |||
</form> | |||
{{end}} |
@@ -23,6 +23,6 @@ | |||
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | |||
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> | |||
</div> | |||
{{template "repo/settings/hook_settings" .}} | |||
{{template "repo/settings/webhook/settings" .}} | |||
</form> | |||
{{end}} |
@@ -48,4 +48,4 @@ | |||
</div> | |||
</div> | |||
{{template "repo/settings/hook_delete_modal" .}} | |||
{{template "repo/settings/webhook/delete_modal" .}} |
@@ -21,14 +21,14 @@ | |||
</div> | |||
</h4> | |||
<div class="ui attached segment"> | |||
{{template "repo/settings/hook_gitea" .}} | |||
{{template "repo/settings/hook_gogs" .}} | |||
{{template "repo/settings/hook_slack" .}} | |||
{{template "repo/settings/hook_discord" .}} | |||
{{template "repo/settings/hook_dingtalk" .}} | |||
{{template "repo/settings/webhook/gitea" .}} | |||
{{template "repo/settings/webhook/gogs" .}} | |||
{{template "repo/settings/webhook/slack" .}} | |||
{{template "repo/settings/webhook/discord" .}} | |||
{{template "repo/settings/webhook/dingtalk" .}} | |||
</div> | |||
{{template "repo/settings/hook_history" .}} | |||
{{template "repo/settings/webhook/history" .}} | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -32,6 +32,26 @@ | |||
</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 --> | |||
<div class="seven wide column"> | |||
<div class="field"> | |||
@@ -42,6 +62,26 @@ | |||
</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 --> | |||
<div class="seven wide column"> | |||
<div class="field"> | |||
@@ -62,6 +102,16 @@ | |||
</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> | |||
@@ -83,4 +133,4 @@ | |||
{{end}} | |||
</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> | |||
<input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="e.g. #dd4b39, good, warning, danger"> | |||
</div> | |||
{{template "repo/settings/hook_settings" .}} | |||
{{template "repo/settings/webhook/settings" .}} | |||
</form> | |||
{{end}} |
@@ -172,9 +172,14 @@ type PayloadCommitVerification struct { | |||
var ( | |||
_ Payloader = &CreatePayload{} | |||
_ Payloader = &DeletePayload{} | |||
_ Payloader = &ForkPayload{} | |||
_ Payloader = &PushPayload{} | |||
_ Payloader = &IssuePayload{} | |||
_ Payloader = &IssueCommentPayload{} | |||
_ Payloader = &PullRequestPayload{} | |||
_ Payloader = &RepositoryPayload{} | |||
_ Payloader = &ReleasePayload{} | |||
) | |||
// _________ __ | |||
@@ -224,6 +229,123 @@ func ParseCreateHook(raw []byte) (*CreatePayload, error) { | |||
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 | |||
type EditIssueOption struct { | |||
Title string `json:"title"` | |||
Body *string `json:"body"` | |||
Assignee *string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone *int64 `json:"milestone"` | |||
State *string `json:"state"` | |||
Title string `json:"title"` | |||
Body *string `json:"body"` | |||
Assignee *string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone *int64 `json:"milestone"` | |||
State *string `json:"state"` | |||
// 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 | |||
@@ -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), | |||
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 | |||
type CreatePullRequestOption struct { | |||
Head string `json:"head" binding:"Required"` | |||
Base string `json:"base" binding:"Required"` | |||
Title string `json:"title" binding:"Required"` | |||
Body string `json:"body"` | |||
Assignee string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone int64 `json:"milestone"` | |||
Labels []int64 `json:"labels"` | |||
Head string `json:"head" binding:"Required"` | |||
Base string `json:"base" binding:"Required"` | |||
Title string `json:"title" binding:"Required"` | |||
Body string `json:"body"` | |||
Assignee string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone int64 `json:"milestone"` | |||
Labels []int64 `json:"labels"` | |||
// swagger:strfmt date-time | |||
Deadline *time.Time `json:"due_date"` | |||
Deadline *time.Time `json:"due_date"` | |||
} | |||
// 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 | |||
type EditPullRequestOption struct { | |||
Title string `json:"title"` | |||
Body string `json:"body"` | |||
Assignee string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone int64 `json:"milestone"` | |||
Labels []int64 `json:"labels"` | |||
State *string `json:"state"` | |||
Title string `json:"title"` | |||
Body string `json:"body"` | |||
Assignee string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone int64 `json:"milestone"` | |||
Labels []int64 `json:"labels"` | |||
State *string `json:"state"` | |||
// 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 |
@@ -9,10 +9,10 @@ | |||
"revisionTime": "2018-04-21T01:08:19Z" | |||
}, | |||
{ | |||
"checksumSHA1": "WMD6+Qh2+5hd9uiq910pF/Ihylw=", | |||
"checksumSHA1": "LnxY/6xD4h9dCCJ5nxKEfZZs1Vk=", | |||
"path": "code.gitea.io/sdk/gitea", | |||
"revision": "1c8d12f79a51605ed91587aa6b86cf38fc0f987f", | |||
"revisionTime": "2018-05-01T11:15:19Z" | |||
"revision": "7fa627fa5d67d18c39d6dd3c6c4db836916bf234", | |||
"revisionTime": "2018-05-10T12:54:05Z" | |||
}, | |||
{ | |||
"checksumSHA1": "bOODD4Gbw3GfcuQPU2dI40crxxk=", |