Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webhook for Pull Request approval/rejection #5027

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 44 additions & 6 deletions models/review.go
Expand Up @@ -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"
adelowo marked this conversation as resolved.
Show resolved Hide resolved
)

// ReviewType defines the sort of feedback a review gives
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
22 changes: 12 additions & 10 deletions models/webhook.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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.
Expand Down
29 changes: 28 additions & 1 deletion models/webhook_dingtalk.go
Expand Up @@ -11,7 +11,6 @@ import (

"code.gitea.io/git"
api "code.gitea.io/sdk/gitea"

dingtalk "github.com/lunny/dingtalk_webhook"
)

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down
52 changes: 52 additions & 0 deletions models/webhook_discord.go
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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")
}
}
33 changes: 31 additions & 2 deletions models/webhook_slack.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion options/locale/locale_en-US.ini
Expand Up @@ -1099,7 +1099,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
Expand Down