Add review comments to mail notifications (#8996)
This commit is contained in:
parent
97dc314652
commit
9930d47be2
6 changed files with 73 additions and 35 deletions
|
@ -32,6 +32,8 @@ Currently, the following notification events make use of templates:
|
||||||
| `close` | An issue or pull request was closed. |
|
| `close` | An issue or pull request was closed. |
|
||||||
| `reopen` | An issue or pull request was reopened. |
|
| `reopen` | An issue or pull request was reopened. |
|
||||||
| `review` | The head comment of a review in a pull request. |
|
| `review` | The head comment of a review in a pull request. |
|
||||||
|
| `approve` | The head comment of a approving review for a pull request. |
|
||||||
|
| `reject` | The head comment of a review requesting changes for a pull request. |
|
||||||
| `code` | A single comment on the code of a pull request. |
|
| `code` | A single comment on the code of a pull request. |
|
||||||
| `assigned` | Used was assigned to an issue or pull request. |
|
| `assigned` | Used was assigned to an issue or pull request. |
|
||||||
| `default` | Any action not included in the above categories, or when the corresponding category template is not present. |
|
| `default` | Any action not included in the above categories, or when the corresponding category template is not present. |
|
||||||
|
@ -84,22 +86,23 @@ _subject_ and _mail body_ templates requires at least three dashes; no other cha
|
||||||
_Subject_ and _mail body_ are parsed by [Golang's template engine](https://golang.org/pkg/text/template/) and
|
_Subject_ and _mail body_ are parsed by [Golang's template engine](https://golang.org/pkg/text/template/) and
|
||||||
are provided with a _metadata context_ assembled for each notification. The context contains the following elements:
|
are provided with a _metadata context_ assembled for each notification. The context contains the following elements:
|
||||||
|
|
||||||
| Name | Type | Available | Usage |
|
| Name | Type | Available | Usage |
|
||||||
|--------------------|----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `.FallbackSubject` | string | Always | A default subject line. See Below. |
|
| `.FallbackSubject` | string | Always | A default subject line. See Below. |
|
||||||
| `.Subject` | string | Only in body | The _subject_, once resolved. |
|
| `.Subject` | string | Only in body | The _subject_, once resolved. |
|
||||||
| `.Body` | string | Always | The message of the issue, pull request or comment, parsed from Markdown into HTML and sanitized. Do not confuse with the _mail body_ |
|
| `.Body` | string | Always | The message of the issue, pull request or comment, parsed from Markdown into HTML and sanitized. Do not confuse with the _mail body_. |
|
||||||
| `.Link` | string | Always | The address of the originating issue, pull request or comment. |
|
| `.Link` | string | Always | The address of the originating issue, pull request or comment. |
|
||||||
| `.Issue` | models.Issue | Always | The issue (or pull request) originating the notification. To get data specific to a pull request (e.g. `HasMerged`), `.Issue.PullRequest` can be used, but care should be taken as this field will be `nil` if the issue is *not* a pull request. |
|
| `.Issue` | models.Issue | Always | The issue (or pull request) originating the notification. To get data specific to a pull request (e.g. `HasMerged`), `.Issue.PullRequest` can be used, but care should be taken as this field will be `nil` if the issue is *not* a pull request. |
|
||||||
| `.Comment` | models.Comment | If applicable | If the notification is from a comment added to an issue or pull request, this will contain the information about the comment. |
|
| `.Comment` | models.Comment | If applicable | If the notification is from a comment added to an issue or pull request, this will contain the information about the comment. |
|
||||||
| `.IsPull` | bool | Always | `true` if the mail notification is associated with a pull request (i.e. `.Issue.PullRequest` is not `nil`). |
|
| `.IsPull` | bool | Always | `true` if the mail notification is associated with a pull request (i.e. `.Issue.PullRequest` is not `nil`). |
|
||||||
| `.Repo` | string | Always | Name of the repository, including owner name (e.g. `mike/stuff`) |
|
| `.Repo` | string | Always | Name of the repository, including owner name (e.g. `mike/stuff`) |
|
||||||
| `.User` | models.User | Always | Owner of the repository from which the event originated. To get the user name (e.g. `mike`),`.User.Name` can be used. |
|
| `.User` | models.User | Always | Owner of the repository from which the event originated. To get the user name (e.g. `mike`),`.User.Name` can be used. |
|
||||||
| `.Doer` | models.User | Always | User that executed the action triggering the notification event. To get the user name (e.g. `rhonda`), `.Doer.Name` can be used. |
|
| `.Doer` | models.User | Always | User that executed the action triggering the notification event. To get the user name (e.g. `rhonda`), `.Doer.Name` can be used. |
|
||||||
| `.IsMention` | bool | Always | `true` if this notification was only generated because the user was mentioned in the comment, while not being subscribed to the source. It will be `false` if the recipient was subscribed to the issue or repository. |
|
| `.IsMention` | bool | Always | `true` if this notification was only generated because the user was mentioned in the comment, while not being subscribed to the source. It will be `false` if the recipient was subscribed to the issue or repository. |
|
||||||
| `.SubjectPrefix` | string | Always | `Re: ` if the notification is about other than issue or pull request creation; otherwise an empty string. |
|
| `.SubjectPrefix` | string | Always | `Re: ` if the notification is about other than issue or pull request creation; otherwise an empty string. |
|
||||||
| `.ActionType` | string | Always | `"issue"` or `"pull"`. Will correspond to the actual _action type_ independently of which template was selected. |
|
| `.ActionType` | string | Always | `"issue"` or `"pull"`. Will correspond to the actual _action type_ independently of which template was selected. |
|
||||||
| `.ActionName` | string | Always | It will be one of the action types described above (`new`, `comment`, etc.), and will correspond to the actual _action name_ independently of which template was selected. |
|
| `.ActionName` | string | Always | It will be one of the action types described above (`new`, `comment`, etc.), and will correspond to the actual _action name_ independently of which template was selected. |
|
||||||
|
| `.ReviewComments` | []models.Comment | Always | List of code comments in a review. The comment text will be in `.RenderedContent` and the referenced code will be in `.Patch`. |
|
||||||
|
|
||||||
All names are case sensitive.
|
All names are case sensitive.
|
||||||
|
|
||||||
|
@ -254,13 +257,14 @@ This template produces something along these lines:
|
||||||
The template system contains several functions that can be used to further process and format
|
The template system contains several functions that can be used to further process and format
|
||||||
the messages. Here's a list of some of them:
|
the messages. Here's a list of some of them:
|
||||||
|
|
||||||
| Name | Parameters | Available | Usage |
|
| Name | Parameters | Available | Usage |
|
||||||
|----------------------|-------------|-----------|---------------------------------------------------------------------|
|
|----------------------|-------------|-----------|------------------------------------------------------------------------------|
|
||||||
| `AppUrl` | - | Any | Gitea's URL |
|
| `AppUrl` | - | Any | Gitea's URL |
|
||||||
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
|
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
|
||||||
| `AppDomain` | - | Any | Gitea's host name |
|
| `AppDomain` | - | Any | Gitea's host name |
|
||||||
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
|
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
|
||||||
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
|
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
|
||||||
|
| `Safe` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
|
||||||
|
|
||||||
These are _functions_, not metadata, so they have to be used:
|
These are _functions_, not metadata, so they have to be used:
|
||||||
|
|
||||||
|
|
|
@ -1016,10 +1016,6 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := CommentList(comments).loadPosters(e); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := issue.loadRepo(e); err != nil {
|
if err := issue.loadRepo(e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,9 @@ type Review struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Review) loadCodeComments(e Engine) (err error) {
|
func (r *Review) loadCodeComments(e Engine) (err error) {
|
||||||
r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
|
if r.CodeComments == nil {
|
||||||
|
r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mailer.MailParticipantsComment(comment, act, issue); err != nil {
|
if err := mailer.MailParticipantsComment(comment, act, issue); err != nil {
|
||||||
log.Error("MailParticipants: %v", err)
|
log.Error("MailParticipantsComment: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models
|
||||||
act = models.ActionCommentIssue
|
act = models.ActionCommentIssue
|
||||||
}
|
}
|
||||||
if err := mailer.MailParticipantsComment(comment, act, pr.Issue); err != nil {
|
if err := mailer.MailParticipantsComment(comment, act, pr.Issue); err != nil {
|
||||||
log.Error("MailParticipants: %v", err)
|
log.Error("MailParticipantsComment: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,8 @@ func composeIssueCommentMessage(issue *models.Issue, doer *models.User, actionTy
|
||||||
link string
|
link string
|
||||||
prefix string
|
prefix string
|
||||||
// Fall back subject for bad templates, make sure subject is never empty
|
// Fall back subject for bad templates, make sure subject is never empty
|
||||||
fallback string
|
fallback string
|
||||||
|
reviewComments []*models.Comment
|
||||||
)
|
)
|
||||||
|
|
||||||
commentType := models.CommentTypeComment
|
commentType := models.CommentTypeComment
|
||||||
|
@ -189,12 +190,26 @@ func composeIssueCommentMessage(issue *models.Issue, doer *models.User, actionTy
|
||||||
link = issue.HTMLURL()
|
link = issue.HTMLURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reviewType := models.ReviewTypeComment
|
||||||
|
if comment != nil && comment.Review != nil {
|
||||||
|
reviewType = comment.Review.Type
|
||||||
|
}
|
||||||
|
|
||||||
fallback = prefix + fallbackMailSubject(issue)
|
fallback = prefix + fallbackMailSubject(issue)
|
||||||
|
|
||||||
// This is the body of the new issue or comment, not the mail body
|
// This is the body of the new issue or comment, not the mail body
|
||||||
body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
|
body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
|
||||||
|
|
||||||
actType, actName, tplName := actionToTemplate(issue, actionType, commentType)
|
actType, actName, tplName := actionToTemplate(issue, actionType, commentType, reviewType)
|
||||||
|
|
||||||
|
if comment != nil && comment.Review != nil {
|
||||||
|
reviewComments = make([]*models.Comment, 0, 10)
|
||||||
|
for _, lines := range comment.Review.CodeComments {
|
||||||
|
for _, comments := range lines {
|
||||||
|
reviewComments = append(reviewComments, comments...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mailMeta := map[string]interface{}{
|
mailMeta := map[string]interface{}{
|
||||||
"FallbackSubject": fallback,
|
"FallbackSubject": fallback,
|
||||||
|
@ -210,6 +225,7 @@ func composeIssueCommentMessage(issue *models.Issue, doer *models.User, actionTy
|
||||||
"SubjectPrefix": prefix,
|
"SubjectPrefix": prefix,
|
||||||
"ActionType": actType,
|
"ActionType": actType,
|
||||||
"ActionName": actName,
|
"ActionName": actName,
|
||||||
|
"ReviewComments": reviewComments,
|
||||||
}
|
}
|
||||||
|
|
||||||
var mailSubject bytes.Buffer
|
var mailSubject bytes.Buffer
|
||||||
|
@ -272,7 +288,8 @@ func SendIssueMentionMail(issue *models.Issue, doer *models.User, actionType mod
|
||||||
|
|
||||||
// actionToTemplate returns the type and name of the action facing the user
|
// actionToTemplate returns the type and name of the action facing the user
|
||||||
// (slightly different from models.ActionType) and the name of the template to use (based on availability)
|
// (slightly different from models.ActionType) and the name of the template to use (based on availability)
|
||||||
func actionToTemplate(issue *models.Issue, actionType models.ActionType, commentType models.CommentType) (typeName, name, template string) {
|
func actionToTemplate(issue *models.Issue, actionType models.ActionType,
|
||||||
|
commentType models.CommentType, reviewType models.ReviewType) (typeName, name, template string) {
|
||||||
if issue.IsPull {
|
if issue.IsPull {
|
||||||
typeName = "pull"
|
typeName = "pull"
|
||||||
} else {
|
} else {
|
||||||
|
@ -292,7 +309,14 @@ func actionToTemplate(issue *models.Issue, actionType models.ActionType, comment
|
||||||
default:
|
default:
|
||||||
switch commentType {
|
switch commentType {
|
||||||
case models.CommentTypeReview:
|
case models.CommentTypeReview:
|
||||||
name = "review"
|
switch reviewType {
|
||||||
|
case models.ReviewTypeApprove:
|
||||||
|
name = "approve"
|
||||||
|
case models.ReviewTypeReject:
|
||||||
|
name = "reject"
|
||||||
|
default:
|
||||||
|
name = "review"
|
||||||
|
}
|
||||||
case models.CommentTypeCode:
|
case models.CommentTypeCode:
|
||||||
name = "code"
|
name = "code"
|
||||||
case models.CommentTypeAssignees:
|
case models.CommentTypeAssignees:
|
||||||
|
|
|
@ -3,6 +3,12 @@
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<title>{{.Subject}}</title>
|
<title>{{.Subject}}</title>
|
||||||
|
{{if .ReviewComments}}
|
||||||
|
<style>
|
||||||
|
.review { padding-left: 1em; margin: 1em 0; }
|
||||||
|
.review > pre { padding: 1em; border-left: 1px solid grey; }
|
||||||
|
</style>
|
||||||
|
{{end}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -15,12 +21,18 @@
|
||||||
Closed #{{.Issue.Index}}.
|
Closed #{{.Issue.Index}}.
|
||||||
{{else if eq .ActionName "reopen"}}
|
{{else if eq .ActionName "reopen"}}
|
||||||
Reopened #{{.Issue.Index}}.
|
Reopened #{{.Issue.Index}}.
|
||||||
{{else}}
|
{{else if ne .ReviewComments}}
|
||||||
Empty comment on #{{.Issue.Index}}.
|
Empty comment on #{{.Issue.Index}}.
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{.Body | Str2html}}
|
{{.Body | Str2html}}
|
||||||
{{end -}}
|
{{end -}}
|
||||||
|
{{- range .ReviewComments}}
|
||||||
|
<div class="review">
|
||||||
|
<pre>{{.Patch}}</pre>
|
||||||
|
<div>{{.RenderedContent | Safe}}</div>
|
||||||
|
</div>
|
||||||
|
{{end -}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
---
|
---
|
||||||
|
|
Reference in a new issue