Skip to content

Commit 945804f

Browse files
adelowotechknowlogick
authored andcommitted
Webhook for Pull Request approval/rejection (#5027)
1 parent 8bb0a6f commit 945804f

File tree

6 files changed

+168
-20
lines changed

6 files changed

+168
-20
lines changed

models/review.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99

1010
"code.gitea.io/gitea/modules/log"
1111
"code.gitea.io/gitea/modules/util"
12-
"github.com/go-xorm/core"
13-
"github.com/go-xorm/xorm"
12+
api "code.gitea.io/sdk/gitea"
1413

1514
"github.com/go-xorm/builder"
15+
"github.com/go-xorm/core"
16+
"github.com/go-xorm/xorm"
1617
)
1718

1819
// ReviewType defines the sort of feedback a review gives
@@ -233,6 +234,43 @@ func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
233234
if _, err := e.Insert(review); err != nil {
234235
return nil, err
235236
}
237+
238+
var reviewHookType HookEventType
239+
240+
switch opts.Type {
241+
case ReviewTypeApprove:
242+
reviewHookType = HookEventPullRequestApproved
243+
case ReviewTypeComment:
244+
reviewHookType = HookEventPullRequestComment
245+
case ReviewTypeReject:
246+
reviewHookType = HookEventPullRequestRejected
247+
default:
248+
// unsupported review webhook type here
249+
return review, nil
250+
}
251+
252+
pr := opts.Issue.PullRequest
253+
254+
if err := pr.LoadIssue(); err != nil {
255+
return nil, err
256+
}
257+
258+
mode, err := AccessLevel(opts.Issue.Poster, opts.Issue.Repo)
259+
if err != nil {
260+
return nil, err
261+
}
262+
263+
if err := PrepareWebhooks(opts.Issue.Repo, reviewHookType, &api.PullRequestPayload{
264+
Action: api.HookIssueSynchronized,
265+
Index: opts.Issue.Index,
266+
PullRequest: pr.APIFormat(),
267+
Repository: opts.Issue.Repo.APIFormat(mode),
268+
Sender: opts.Reviewer.APIFormat(),
269+
}); err != nil {
270+
return nil, err
271+
}
272+
go HookQueue.Add(opts.Issue.Repo.ID)
273+
236274
return review, nil
237275
}
238276

@@ -285,10 +323,10 @@ type PullReviewersWithType struct {
285323
func GetReviewersByPullID(pullID int64) (issueReviewers []*PullReviewersWithType, err error) {
286324
irs := []*PullReviewersWithType{}
287325
if x.Dialect().DBType() == core.MSSQL {
288-
err = x.SQL(`SELECT [user].*, review.type, review.review_updated_unix FROM
289-
(SELECT review.id, review.type, review.reviewer_id, max(review.updated_unix) as review_updated_unix
290-
FROM review WHERE review.issue_id=? AND (review.type = ? OR review.type = ?)
291-
GROUP BY review.id, review.type, review.reviewer_id) as review
326+
err = x.SQL(`SELECT [user].*, review.type, review.review_updated_unix FROM
327+
(SELECT review.id, review.type, review.reviewer_id, max(review.updated_unix) as review_updated_unix
328+
FROM review WHERE review.issue_id=? AND (review.type = ? OR review.type = ?)
329+
GROUP BY review.id, review.type, review.reviewer_id) as review
292330
INNER JOIN [user] ON review.reviewer_id = [user].id ORDER BY review_updated_unix DESC`,
293331
pullID, ReviewTypeApprove, ReviewTypeReject).
294332
Find(&irs)

models/webhook.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"code.gitea.io/gitea/modules/sync"
2020
"code.gitea.io/gitea/modules/util"
2121
api "code.gitea.io/sdk/gitea"
22-
2322
"github.com/Unknwon/com"
2423
gouuid "github.com/satori/go.uuid"
2524
)
@@ -425,15 +424,18 @@ type HookEventType string
425424

426425
// Types of hook events
427426
const (
428-
HookEventCreate HookEventType = "create"
429-
HookEventDelete HookEventType = "delete"
430-
HookEventFork HookEventType = "fork"
431-
HookEventPush HookEventType = "push"
432-
HookEventIssues HookEventType = "issues"
433-
HookEventIssueComment HookEventType = "issue_comment"
434-
HookEventPullRequest HookEventType = "pull_request"
435-
HookEventRepository HookEventType = "repository"
436-
HookEventRelease HookEventType = "release"
427+
HookEventCreate HookEventType = "create"
428+
HookEventDelete HookEventType = "delete"
429+
HookEventFork HookEventType = "fork"
430+
HookEventPush HookEventType = "push"
431+
HookEventIssues HookEventType = "issues"
432+
HookEventIssueComment HookEventType = "issue_comment"
433+
HookEventPullRequest HookEventType = "pull_request"
434+
HookEventRepository HookEventType = "repository"
435+
HookEventRelease HookEventType = "release"
436+
HookEventPullRequestApproved HookEventType = "pull_request_approved"
437+
HookEventPullRequestRejected HookEventType = "pull_request_rejected"
438+
HookEventPullRequestComment HookEventType = "pull_request_comment"
437439
)
438440

439441
// HookRequest represents hook task request information.

models/webhook_dingtalk.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111

1212
"code.gitea.io/git"
1313
api "code.gitea.io/sdk/gitea"
14-
1514
dingtalk "github.com/lunny/dingtalk_webhook"
1615
)
1716

@@ -271,6 +270,32 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload,
271270
}, nil
272271
}
273272

273+
func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event HookEventType) (*DingtalkPayload, error) {
274+
var text, title string
275+
switch p.Action {
276+
case api.HookIssueSynchronized:
277+
action, err := parseHookPullRequestEventType(event)
278+
if err != nil {
279+
return nil, err
280+
}
281+
282+
title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
283+
text = p.PullRequest.Body
284+
285+
}
286+
287+
return &DingtalkPayload{
288+
MsgType: "actionCard",
289+
ActionCard: dingtalk.ActionCard{
290+
Text: title + "\r\n\r\n" + text,
291+
Title: title,
292+
HideAvatar: "0",
293+
SingleTitle: "view pull request",
294+
SingleURL: p.PullRequest.HTMLURL,
295+
},
296+
}, nil
297+
}
298+
274299
func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, error) {
275300
var title, url string
276301
switch p.Action {
@@ -369,6 +394,8 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*Din
369394
return getDingtalkPushPayload(p.(*api.PushPayload))
370395
case HookEventPullRequest:
371396
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
397+
case HookEventPullRequestApproved, HookEventPullRequestRejected, HookEventPullRequestComment:
398+
return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
372399
case HookEventRepository:
373400
return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))
374401
case HookEventRelease:

models/webhook_discord.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,40 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta)
400400
}, nil
401401
}
402402

403+
func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event HookEventType) (*DiscordPayload, error) {
404+
var text, title string
405+
var color int
406+
switch p.Action {
407+
case api.HookIssueSynchronized:
408+
action, err := parseHookPullRequestEventType(event)
409+
if err != nil {
410+
return nil, err
411+
}
412+
413+
title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
414+
text = p.PullRequest.Body
415+
color = warnColor
416+
}
417+
418+
return &DiscordPayload{
419+
Username: meta.Username,
420+
AvatarURL: meta.IconURL,
421+
Embeds: []DiscordEmbed{
422+
{
423+
Title: title,
424+
Description: text,
425+
URL: p.PullRequest.HTMLURL,
426+
Color: color,
427+
Author: DiscordEmbedAuthor{
428+
Name: p.Sender.UserName,
429+
URL: setting.AppURL + p.Sender.UserName,
430+
IconURL: p.Sender.AvatarURL,
431+
},
432+
},
433+
},
434+
}, nil
435+
}
436+
403437
func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*DiscordPayload, error) {
404438
var title, url string
405439
var color int
@@ -492,6 +526,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
492526
return getDiscordPushPayload(p.(*api.PushPayload), discord)
493527
case HookEventPullRequest:
494528
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
529+
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
530+
return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event)
495531
case HookEventRepository:
496532
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
497533
case HookEventRelease:
@@ -500,3 +536,19 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
500536

501537
return s, nil
502538
}
539+
540+
func parseHookPullRequestEventType(event HookEventType) (string, error) {
541+
542+
switch event {
543+
544+
case HookEventPullRequestApproved:
545+
return "approved", nil
546+
case HookEventPullRequestRejected:
547+
return "rejected", nil
548+
case HookEventPullRequestComment:
549+
return "comment", nil
550+
551+
default:
552+
return "", errors.New("unknown event type")
553+
}
554+
}

models/webhook_slack.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import (
1111
"strings"
1212

1313
"code.gitea.io/git"
14-
api "code.gitea.io/sdk/gitea"
15-
1614
"code.gitea.io/gitea/modules/setting"
15+
api "code.gitea.io/sdk/gitea"
1716
)
1817

1918
// SlackMeta contains the slack metadata
@@ -328,6 +327,34 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
328327
}, nil
329328
}
330329

330+
func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*SlackPayload, error) {
331+
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
332+
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
333+
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
334+
var text, title, attachmentText string
335+
switch p.Action {
336+
case api.HookIssueSynchronized:
337+
action, err := parseHookPullRequestEventType(event)
338+
if err != nil {
339+
return nil, err
340+
}
341+
342+
text = fmt.Sprintf("[%s] Pull request review %s : %s by %s", p.Repository.FullName, action, titleLink, senderLink)
343+
}
344+
345+
return &SlackPayload{
346+
Channel: slack.Channel,
347+
Text: text,
348+
Username: slack.Username,
349+
IconURL: slack.IconURL,
350+
Attachments: []SlackAttachment{{
351+
Color: slack.Color,
352+
Title: title,
353+
Text: attachmentText,
354+
}},
355+
}, nil
356+
}
357+
331358
func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) {
332359
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
333360
var text, title, attachmentText string
@@ -376,6 +403,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP
376403
return getSlackPushPayload(p.(*api.PushPayload), slack)
377404
case HookEventPullRequest:
378405
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
406+
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
407+
return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event)
379408
case HookEventRepository:
380409
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)
381410
case HookEventRelease:

options/locale/locale_en-US.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,7 @@ settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
11011101
settings.event_release = Release
11021102
settings.event_release_desc = Release published, updated or deleted in a repository.
11031103
settings.event_pull_request = Pull Request
1104-
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared or synchronized.
1104+
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized.
11051105
settings.event_push = Push
11061106
settings.event_push_desc = Git push to a repository.
11071107
settings.event_repository = Repository

0 commit comments

Comments
 (0)