| @@ -9,10 +9,11 @@ import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| api "code.gitea.io/sdk/gitea" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| ) | |||
| // ReviewType defines the sort of feedback a review gives | |||
| @@ -233,6 +234,43 @@ func createReview(e Engine, opts CreateReviewOptions) (*Review, error) { | |||
| if _, err := e.Insert(review); err != nil { | |||
| return nil, err | |||
| } | |||
| var reviewHookType HookEventType | |||
| switch opts.Type { | |||
| case ReviewTypeApprove: | |||
| reviewHookType = HookEventPullRequestApproved | |||
| case ReviewTypeComment: | |||
| reviewHookType = HookEventPullRequestComment | |||
| case ReviewTypeReject: | |||
| reviewHookType = HookEventPullRequestRejected | |||
| default: | |||
| // unsupported review webhook type here | |||
| return review, nil | |||
| } | |||
| pr := opts.Issue.PullRequest | |||
| if err := pr.LoadIssue(); err != nil { | |||
| return nil, err | |||
| } | |||
| mode, err := AccessLevel(opts.Issue.Poster, opts.Issue.Repo) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if err := PrepareWebhooks(opts.Issue.Repo, reviewHookType, &api.PullRequestPayload{ | |||
| Action: api.HookIssueSynchronized, | |||
| Index: opts.Issue.Index, | |||
| PullRequest: pr.APIFormat(), | |||
| Repository: opts.Issue.Repo.APIFormat(mode), | |||
| Sender: opts.Reviewer.APIFormat(), | |||
| }); err != nil { | |||
| return nil, err | |||
| } | |||
| go HookQueue.Add(opts.Issue.Repo.ID) | |||
| return review, nil | |||
| } | |||
| @@ -285,10 +323,10 @@ type PullReviewersWithType struct { | |||
| func GetReviewersByPullID(pullID int64) (issueReviewers []*PullReviewersWithType, err error) { | |||
| irs := []*PullReviewersWithType{} | |||
| if x.Dialect().DBType() == core.MSSQL { | |||
| err = x.SQL(`SELECT [user].*, review.type, review.review_updated_unix FROM | |||
| (SELECT review.id, review.type, review.reviewer_id, max(review.updated_unix) as review_updated_unix | |||
| FROM review WHERE review.issue_id=? AND (review.type = ? OR review.type = ?) | |||
| GROUP BY review.id, review.type, review.reviewer_id) as review | |||
| err = x.SQL(`SELECT [user].*, review.type, review.review_updated_unix FROM | |||
| (SELECT review.id, review.type, review.reviewer_id, max(review.updated_unix) as review_updated_unix | |||
| FROM review WHERE review.issue_id=? AND (review.type = ? OR review.type = ?) | |||
| GROUP BY review.id, review.type, review.reviewer_id) as review | |||
| INNER JOIN [user] ON review.reviewer_id = [user].id ORDER BY review_updated_unix DESC`, | |||
| pullID, ReviewTypeApprove, ReviewTypeReject). | |||
| Find(&irs) | |||
| @@ -19,7 +19,6 @@ import ( | |||
| "code.gitea.io/gitea/modules/sync" | |||
| "code.gitea.io/gitea/modules/util" | |||
| api "code.gitea.io/sdk/gitea" | |||
| "github.com/Unknwon/com" | |||
| gouuid "github.com/satori/go.uuid" | |||
| ) | |||
| @@ -425,15 +424,18 @@ type HookEventType string | |||
| // Types of hook events | |||
| const ( | |||
| 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" | |||
| 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" | |||
| HookEventPullRequestApproved HookEventType = "pull_request_approved" | |||
| HookEventPullRequestRejected HookEventType = "pull_request_rejected" | |||
| HookEventPullRequestComment HookEventType = "pull_request_comment" | |||
| ) | |||
| // HookRequest represents hook task request information. | |||
| @@ -11,7 +11,6 @@ import ( | |||
| "code.gitea.io/git" | |||
| api "code.gitea.io/sdk/gitea" | |||
| dingtalk "github.com/lunny/dingtalk_webhook" | |||
| ) | |||
| @@ -271,6 +270,32 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, | |||
| }, nil | |||
| } | |||
| func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event HookEventType) (*DingtalkPayload, error) { | |||
| var text, title string | |||
| switch p.Action { | |||
| case api.HookIssueSynchronized: | |||
| action, err := parseHookPullRequestEventType(event) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) | |||
| text = p.PullRequest.Body | |||
| } | |||
| return &DingtalkPayload{ | |||
| MsgType: "actionCard", | |||
| ActionCard: dingtalk.ActionCard{ | |||
| Text: title + "\r\n\r\n" + text, | |||
| Title: title, | |||
| HideAvatar: "0", | |||
| SingleTitle: "view pull request", | |||
| SingleURL: p.PullRequest.HTMLURL, | |||
| }, | |||
| }, nil | |||
| } | |||
| func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, error) { | |||
| var title, url string | |||
| switch p.Action { | |||
| @@ -369,6 +394,8 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*Din | |||
| return getDingtalkPushPayload(p.(*api.PushPayload)) | |||
| case HookEventPullRequest: | |||
| return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) | |||
| case HookEventPullRequestApproved, HookEventPullRequestRejected, HookEventPullRequestComment: | |||
| return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) | |||
| case HookEventRepository: | |||
| return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) | |||
| case HookEventRelease: | |||
| @@ -400,6 +400,40 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) | |||
| }, nil | |||
| } | |||
| func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event HookEventType) (*DiscordPayload, error) { | |||
| var text, title string | |||
| var color int | |||
| switch p.Action { | |||
| case api.HookIssueSynchronized: | |||
| action, err := parseHookPullRequestEventType(event) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) | |||
| text = p.PullRequest.Body | |||
| color = warnColor | |||
| } | |||
| return &DiscordPayload{ | |||
| Username: meta.Username, | |||
| AvatarURL: meta.IconURL, | |||
| Embeds: []DiscordEmbed{ | |||
| { | |||
| Title: title, | |||
| Description: text, | |||
| URL: p.PullRequest.HTMLURL, | |||
| Color: color, | |||
| Author: DiscordEmbedAuthor{ | |||
| Name: p.Sender.UserName, | |||
| URL: setting.AppURL + p.Sender.UserName, | |||
| IconURL: p.Sender.AvatarURL, | |||
| }, | |||
| }, | |||
| }, | |||
| }, nil | |||
| } | |||
| func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*DiscordPayload, error) { | |||
| var title, url string | |||
| var color int | |||
| @@ -492,6 +526,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc | |||
| return getDiscordPushPayload(p.(*api.PushPayload), discord) | |||
| case HookEventPullRequest: | |||
| return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) | |||
| case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment: | |||
| return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event) | |||
| case HookEventRepository: | |||
| return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) | |||
| case HookEventRelease: | |||
| @@ -500,3 +536,19 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc | |||
| return s, nil | |||
| } | |||
| func parseHookPullRequestEventType(event HookEventType) (string, error) { | |||
| switch event { | |||
| case HookEventPullRequestApproved: | |||
| return "approved", nil | |||
| case HookEventPullRequestRejected: | |||
| return "rejected", nil | |||
| case HookEventPullRequestComment: | |||
| return "comment", nil | |||
| default: | |||
| return "", errors.New("unknown event type") | |||
| } | |||
| } | |||
| @@ -11,9 +11,8 @@ import ( | |||
| "strings" | |||
| "code.gitea.io/git" | |||
| api "code.gitea.io/sdk/gitea" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| api "code.gitea.io/sdk/gitea" | |||
| ) | |||
| // SlackMeta contains the slack metadata | |||
| @@ -328,6 +327,34 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S | |||
| }, nil | |||
| } | |||
| func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*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.PullRequest.Title)) | |||
| var text, title, attachmentText string | |||
| switch p.Action { | |||
| case api.HookIssueSynchronized: | |||
| action, err := parseHookPullRequestEventType(event) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| text = fmt.Sprintf("[%s] Pull request review %s : %s by %s", p.Repository.FullName, action, 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 getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) { | |||
| senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) | |||
| var text, title, attachmentText string | |||
| @@ -376,6 +403,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP | |||
| return getSlackPushPayload(p.(*api.PushPayload), slack) | |||
| case HookEventPullRequest: | |||
| return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) | |||
| case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment: | |||
| return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event) | |||
| case HookEventRepository: | |||
| return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) | |||
| case HookEventRelease: | |||
| @@ -1101,7 +1101,7 @@ settings.event_issue_comment_desc = Issue comment created, edited, or deleted. | |||
| settings.event_release = Release | |||
| settings.event_release_desc = Release published, updated or deleted 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_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized. | |||
| settings.event_push = Push | |||
| settings.event_push_desc = Git push to a repository. | |||
| settings.event_repository = Repository | |||