Webhook for Pull Request approval/rejection (#5027)

This commit is contained in:
Lanre Adelowo 2018-12-27 19:04:30 +01:00 committed by techknowlogick
parent 8bb0a6f425
commit 945804f800
6 changed files with 168 additions and 20 deletions

View file

@ -9,10 +9,11 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/go-xorm/core" api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/xorm"
"github.com/go-xorm/builder" "github.com/go-xorm/builder"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
) )
// ReviewType defines the sort of feedback a review gives // 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 { if _, err := e.Insert(review); err != nil {
return nil, err 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 return review, nil
} }

View file

@ -19,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
"github.com/Unknwon/com" "github.com/Unknwon/com"
gouuid "github.com/satori/go.uuid" gouuid "github.com/satori/go.uuid"
) )
@ -425,15 +424,18 @@ type HookEventType string
// Types of hook events // Types of hook events
const ( const (
HookEventCreate HookEventType = "create" HookEventCreate HookEventType = "create"
HookEventDelete HookEventType = "delete" HookEventDelete HookEventType = "delete"
HookEventFork HookEventType = "fork" HookEventFork HookEventType = "fork"
HookEventPush HookEventType = "push" HookEventPush HookEventType = "push"
HookEventIssues HookEventType = "issues" HookEventIssues HookEventType = "issues"
HookEventIssueComment HookEventType = "issue_comment" HookEventIssueComment HookEventType = "issue_comment"
HookEventPullRequest HookEventType = "pull_request" HookEventPullRequest HookEventType = "pull_request"
HookEventRepository HookEventType = "repository" HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release" HookEventRelease HookEventType = "release"
HookEventPullRequestApproved HookEventType = "pull_request_approved"
HookEventPullRequestRejected HookEventType = "pull_request_rejected"
HookEventPullRequestComment HookEventType = "pull_request_comment"
) )
// HookRequest represents hook task request information. // HookRequest represents hook task request information.

View file

@ -11,7 +11,6 @@ import (
"code.gitea.io/git" "code.gitea.io/git"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
dingtalk "github.com/lunny/dingtalk_webhook" dingtalk "github.com/lunny/dingtalk_webhook"
) )
@ -271,6 +270,32 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload,
}, nil }, 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) { func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, error) {
var title, url string var title, url string
switch p.Action { switch p.Action {
@ -369,6 +394,8 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*Din
return getDingtalkPushPayload(p.(*api.PushPayload)) return getDingtalkPushPayload(p.(*api.PushPayload))
case HookEventPullRequest: case HookEventPullRequest:
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
case HookEventPullRequestApproved, HookEventPullRequestRejected, HookEventPullRequestComment:
return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case HookEventRepository: case HookEventRepository:
return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))
case HookEventRelease: case HookEventRelease:

View file

@ -400,6 +400,40 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta)
}, nil }, 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) { func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*DiscordPayload, error) {
var title, url string var title, url string
var color int var color int
@ -492,6 +526,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
return getDiscordPushPayload(p.(*api.PushPayload), discord) return getDiscordPushPayload(p.(*api.PushPayload), discord)
case HookEventPullRequest: case HookEventPullRequest:
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event)
case HookEventRepository: case HookEventRepository:
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
case HookEventRelease: case HookEventRelease:
@ -500,3 +536,19 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
return s, nil 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")
}
}

View file

@ -11,9 +11,8 @@ import (
"strings" "strings"
"code.gitea.io/git" "code.gitea.io/git"
api "code.gitea.io/sdk/gitea"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/sdk/gitea"
) )
// SlackMeta contains the slack metadata // SlackMeta contains the slack metadata
@ -328,6 +327,34 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
}, nil }, 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) { func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
var text, title, attachmentText string 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) return getSlackPushPayload(p.(*api.PushPayload), slack)
case HookEventPullRequest: case HookEventPullRequest:
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event)
case HookEventRepository: case HookEventRepository:
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)
case HookEventRelease: case HookEventRelease:

View file

@ -1101,7 +1101,7 @@ settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
settings.event_release = Release settings.event_release = Release
settings.event_release_desc = Release published, updated or deleted in a repository. settings.event_release_desc = Release published, updated or deleted in a repository.
settings.event_pull_request = Pull Request settings.event_pull_request = Pull Request
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized. settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized.
settings.event_push = Push settings.event_push = Push
settings.event_push_desc = Git push to a repository. settings.event_push_desc = Git push to a repository.
settings.event_repository = Repository settings.event_repository = Repository