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/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 | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user