mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Webhook for Pull Request approval/rejection (#5027)
This commit is contained in:
		
				
					committed by
					
						 techknowlogick
						techknowlogick
					
				
			
			
				
	
			
			
			
						parent
						
							8bb0a6f425
						
					
				
				
					commit
					945804f800
				
			| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
| ) | ||||
| @@ -434,6 +433,9 @@ const ( | ||||
| 	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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user