From 24941a10464dc27eaebafda2a208fa827b74ff8d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 16 May 2018 22:01:55 +0800 Subject: [PATCH] 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 * load attributes when created release * update comparsion doc --- docs/content/doc/features/comparison.en-us.md | 2 +- models/action.go | 20 ++ models/issue_comment.go | 110 +++++++++-- models/issue_milestone.go | 47 ++++- models/issue_milestone_test.go | 2 + models/release.go | 22 ++- models/repo.go | 11 ++ models/webhook.go | 112 +++++++---- models/webhook_dingtalk.go | 137 +++++++++++++ models/webhook_discord.go | 186 ++++++++++++++++++ models/webhook_slack.go | 126 ++++++++++++ models/webhook_test.go | 2 +- modules/auth/repo_form.go | 17 +- options/locale/locale_en-US.ini | 10 + routers/api/v1/repo/issue_comment.go | 5 +- routers/api/v1/utils/hook.go | 27 ++- routers/repo/issue.go | 5 +- routers/repo/webhook.go | 19 +- .../{hooks.tmpl => webhook/base.tmpl} | 2 +- .../delete_modal.tmpl} | 0 .../dingtalk.tmpl} | 2 +- .../discord.tmpl} | 2 +- .../{hook_gitea.tmpl => webhook/gitea.tmpl} | 2 +- .../{hook_gogs.tmpl => webhook/gogs.tmpl} | 2 +- .../history.tmpl} | 0 .../{hook_list.tmpl => webhook/list.tmpl} | 2 +- .../{hook_new.tmpl => webhook/new.tmpl} | 12 +- .../settings.tmpl} | 52 ++++- .../{hook_slack.tmpl => webhook/slack.tmpl} | 2 +- vendor/code.gitea.io/sdk/gitea/hook.go | 122 ++++++++++++ vendor/code.gitea.io/sdk/gitea/issue.go | 28 ++- vendor/code.gitea.io/sdk/gitea/pull.go | 34 ++-- vendor/vendor.json | 6 +- 33 files changed, 1010 insertions(+), 118 deletions(-) rename templates/repo/settings/{hooks.tmpl => webhook/base.tmpl} (82%) rename templates/repo/settings/{hook_delete_modal.tmpl => webhook/delete_modal.tmpl} (100%) rename templates/repo/settings/{hook_dingtalk.tmpl => webhook/dingtalk.tmpl} (91%) rename templates/repo/settings/{hook_discord.tmpl => webhook/discord.tmpl} (95%) rename templates/repo/settings/{hook_gitea.tmpl => webhook/gitea.tmpl} (94%) rename templates/repo/settings/{hook_gogs.tmpl => webhook/gogs.tmpl} (96%) rename templates/repo/settings/{hook_history.tmpl => webhook/history.tmpl} (100%) rename templates/repo/settings/{hook_list.tmpl => webhook/list.tmpl} (97%) rename templates/repo/settings/{hook_new.tmpl => webhook/new.tmpl} (77%) rename templates/repo/settings/{hook_settings.tmpl => webhook/settings.tmpl} (63%) rename templates/repo/settings/{hook_slack.tmpl => webhook/slack.tmpl} (96%) diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index c2b7b037c3e..db0d3b55532 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -537,7 +537,7 @@ _Symbols used in table:_ Webhook support - ⁄ + ✓ ✓ ✓ ✓ diff --git a/models/action.go b/models/action.go index 4f357cb2c59..c3ed9c7c024 100644 --- a/models/action.go +++ b/models/action.go @@ -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 { diff --git a/models/issue_comment.go b/models/issue_comment.go index 2c5875c29ca..ad200934bc6 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -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 } diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 8de1f975713..be55dc4f5be 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -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. diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index c9b53f4f4a3..3ea63d2d6b0 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -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 diff --git a/models/release.go b/models/release.go index 586f494e7dc..bc0260c71db 100644 --- a/models/release.go +++ b/models/release.go @@ -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. diff --git a/models/repo.go b/models/repo.go index 4a7eb859c4d..f5ec1a9fddc 100644 --- a/models/repo.go +++ b/models/repo.go @@ -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) } diff --git a/models/webhook.go b/models/webhook.go index 62db84f86a6..c44ca2960d7 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -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 + } } } diff --git a/models/webhook_dingtalk.go b/models/webhook_dingtalk.go index 719ffcae736..7eb189f9bb2 100644 --- a/models/webhook_dingtalk.go +++ b/models/webhook_dingtalk.go @@ -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 diff --git a/models/webhook_discord.go b/models/webhook_discord.go index 40d9d58992f..04ebbc293f9 100644 --- a/models/webhook_discord.go +++ b/models/webhook_discord.go @@ -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 diff --git a/models/webhook_slack.go b/models/webhook_slack.go index 256819adc56..7b18fe32782 100644 --- a/models/webhook_slack.go +++ b/models/webhook_slack.go @@ -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 diff --git a/models/webhook_test.go b/models/webhook_test.go index eeae7efbcbc..50106a37923 100644 --- a/models/webhook_test.go +++ b/models/webhook_test.go @@ -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(), diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 431ab7f13e7..e3471fff5d4 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -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 diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8ef00566081..30f07dc1315 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index a9258849ead..2865ea91658 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -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 } diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index e1533da94ca..d0538ec54f6 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -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 diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 33ba3e0d64d..18ab1691cd3 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -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 } diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go index 35fdf796b5e..6994aa33442 100644 --- a/routers/repo/webhook.go +++ b/routers/repo/webhook.go @@ -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, }, } } diff --git a/templates/repo/settings/hooks.tmpl b/templates/repo/settings/webhook/base.tmpl similarity index 82% rename from templates/repo/settings/hooks.tmpl rename to templates/repo/settings/webhook/base.tmpl index 34c5cfbb6c1..6f486131f72 100644 --- a/templates/repo/settings/hooks.tmpl +++ b/templates/repo/settings/webhook/base.tmpl @@ -3,7 +3,7 @@ {{template "repo/header" .}} {{template "repo/settings/navbar" .}}
- {{template "repo/settings/hook_list" .}} + {{template "repo/settings/webhook/list" .}}
{{template "base/footer" .}} diff --git a/templates/repo/settings/hook_delete_modal.tmpl b/templates/repo/settings/webhook/delete_modal.tmpl similarity index 100% rename from templates/repo/settings/hook_delete_modal.tmpl rename to templates/repo/settings/webhook/delete_modal.tmpl diff --git a/templates/repo/settings/hook_dingtalk.tmpl b/templates/repo/settings/webhook/dingtalk.tmpl similarity index 91% rename from templates/repo/settings/hook_dingtalk.tmpl rename to templates/repo/settings/webhook/dingtalk.tmpl index 37271a7db54..3e6504f6513 100644 --- a/templates/repo/settings/hook_dingtalk.tmpl +++ b/templates/repo/settings/webhook/dingtalk.tmpl @@ -6,6 +6,6 @@ - {{template "repo/settings/hook_settings" .}} + {{template "repo/settings/webhook/settings" .}} {{end}} diff --git a/templates/repo/settings/hook_discord.tmpl b/templates/repo/settings/webhook/discord.tmpl similarity index 95% rename from templates/repo/settings/hook_discord.tmpl rename to templates/repo/settings/webhook/discord.tmpl index 901e7e6311f..75c31efb51b 100644 --- a/templates/repo/settings/hook_discord.tmpl +++ b/templates/repo/settings/webhook/discord.tmpl @@ -14,6 +14,6 @@ - {{template "repo/settings/hook_settings" .}} + {{template "repo/settings/webhook/settings" .}} {{end}} diff --git a/templates/repo/settings/hook_gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl similarity index 94% rename from templates/repo/settings/hook_gitea.tmpl rename to templates/repo/settings/webhook/gitea.tmpl index fc5e35e068d..87a8813d0ea 100644 --- a/templates/repo/settings/hook_gitea.tmpl +++ b/templates/repo/settings/webhook/gitea.tmpl @@ -23,6 +23,6 @@ - {{template "repo/settings/hook_settings" .}} + {{template "repo/settings/webhook/settings" .}} {{end}} diff --git a/templates/repo/settings/hook_gogs.tmpl b/templates/repo/settings/webhook/gogs.tmpl similarity index 96% rename from templates/repo/settings/hook_gogs.tmpl rename to templates/repo/settings/webhook/gogs.tmpl index 28098d14ec6..649fb54aeae 100644 --- a/templates/repo/settings/hook_gogs.tmpl +++ b/templates/repo/settings/webhook/gogs.tmpl @@ -23,6 +23,6 @@ - {{template "repo/settings/hook_settings" .}} + {{template "repo/settings/webhook/settings" .}} {{end}} diff --git a/templates/repo/settings/hook_history.tmpl b/templates/repo/settings/webhook/history.tmpl similarity index 100% rename from templates/repo/settings/hook_history.tmpl rename to templates/repo/settings/webhook/history.tmpl diff --git a/templates/repo/settings/hook_list.tmpl b/templates/repo/settings/webhook/list.tmpl similarity index 97% rename from templates/repo/settings/hook_list.tmpl rename to templates/repo/settings/webhook/list.tmpl index 4e61ba7a079..de6bd2c5f2a 100644 --- a/templates/repo/settings/hook_list.tmpl +++ b/templates/repo/settings/webhook/list.tmpl @@ -48,4 +48,4 @@ -{{template "repo/settings/hook_delete_modal" .}} +{{template "repo/settings/webhook/delete_modal" .}} diff --git a/templates/repo/settings/hook_new.tmpl b/templates/repo/settings/webhook/new.tmpl similarity index 77% rename from templates/repo/settings/hook_new.tmpl rename to templates/repo/settings/webhook/new.tmpl index 7e3cf3c8cf0..1b3d114577a 100644 --- a/templates/repo/settings/hook_new.tmpl +++ b/templates/repo/settings/webhook/new.tmpl @@ -21,14 +21,14 @@
- {{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" .}}
- {{template "repo/settings/hook_history" .}} + {{template "repo/settings/webhook/history" .}} {{template "base/footer" .}} diff --git a/templates/repo/settings/hook_settings.tmpl b/templates/repo/settings/webhook/settings.tmpl similarity index 63% rename from templates/repo/settings/hook_settings.tmpl rename to templates/repo/settings/webhook/settings.tmpl index 7f3406588fa..f04c25a0a32 100644 --- a/templates/repo/settings/hook_settings.tmpl +++ b/templates/repo/settings/webhook/settings.tmpl @@ -32,6 +32,26 @@ + +
+
+
+ + + {{.i18n.Tr "repo.settings.event_delete_desc"}} +
+
+
+ +
+
+
+ + + {{.i18n.Tr "repo.settings.event_fork_desc"}} +
+
+
@@ -42,6 +62,26 @@
+ +
+
+
+ + + {{.i18n.Tr "repo.settings.event_issues_desc"}} +
+
+
+ +
+
+
+ + + {{.i18n.Tr "repo.settings.event_issue_comment_desc"}} +
+
+
@@ -62,6 +102,16 @@
+ +
+
+
+ + + {{.i18n.Tr "repo.settings.event_release_desc"}} +
+
+
@@ -83,4 +133,4 @@ {{end}} -{{template "repo/settings/hook_delete_modal" .}} +{{template "repo/settings/webhook/delete_modal" .}} diff --git a/templates/repo/settings/hook_slack.tmpl b/templates/repo/settings/webhook/slack.tmpl similarity index 96% rename from templates/repo/settings/hook_slack.tmpl rename to templates/repo/settings/webhook/slack.tmpl index 16e18594700..c35a679da79 100644 --- a/templates/repo/settings/hook_slack.tmpl +++ b/templates/repo/settings/webhook/slack.tmpl @@ -23,6 +23,6 @@ - {{template "repo/settings/hook_settings" .}} + {{template "repo/settings/webhook/settings" .}} {{end}} diff --git a/vendor/code.gitea.io/sdk/gitea/hook.go b/vendor/code.gitea.io/sdk/gitea/hook.go index a9b0bdbd066..85d99652dc2 100644 --- a/vendor/code.gitea.io/sdk/gitea/hook.go +++ b/vendor/code.gitea.io/sdk/gitea/hook.go @@ -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, "", " ") +} + // __________ .__ // \______ \__ __ _____| |__ // | ___/ | \/ ___/ | \ diff --git a/vendor/code.gitea.io/sdk/gitea/issue.go b/vendor/code.gitea.io/sdk/gitea/issue.go index 27809ca3b4c..fee7cd6f9fc 100644 --- a/vendor/code.gitea.io/sdk/gitea/issue.go +++ b/vendor/code.gitea.io/sdk/gitea/issue.go @@ -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"` +} diff --git a/vendor/code.gitea.io/sdk/gitea/pull.go b/vendor/code.gitea.io/sdk/gitea/pull.go index 7d38b14d7d2..6fcdd1d41b7 100644 --- a/vendor/code.gitea.io/sdk/gitea/pull.go +++ b/vendor/code.gitea.io/sdk/gitea/pull.go @@ -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 diff --git a/vendor/vendor.json b/vendor/vendor.json index 8b8972b16f1..712706b96e8 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -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=",