mirror of
https://github.com/harness/drone.git
synced 2025-05-19 02:20:03 +08:00
feat: Add notifications support and templates for PR reviewer decision (#881)
This commit is contained in:
parent
9a4e4fa74d
commit
880f62a857
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/harness/gitness/app/api/usererror"
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/app/auth"
|
"github.com/harness/gitness/app/auth"
|
||||||
|
events "github.com/harness/gitness/app/events/pullreq"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
"github.com/harness/gitness/store"
|
"github.com/harness/gitness/store"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
@ -107,6 +108,11 @@ func (c *Controller) ReviewSubmit(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.eventReporter.ReviewSubmitted(ctx, &events.ReviewSubmittedPayload{
|
||||||
|
Base: eventBase(pr, &session.Principal),
|
||||||
|
Decision: review.Decision,
|
||||||
|
ReviewerID: review.CreatedBy,
|
||||||
|
})
|
||||||
|
|
||||||
_, err = c.updateReviewer(ctx, session, pr, review, commitSHA)
|
_, err = c.updateReviewer(ctx, session, pr, review, commitSHA)
|
||||||
return err
|
return err
|
||||||
|
56
app/events/pullreq/events_review_submitted.go
Normal file
56
app/events/pullreq/events_review_submitted.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/events"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ReviewSubmittedEvent events.EventType = "review-submitted"
|
||||||
|
|
||||||
|
type ReviewSubmittedPayload struct {
|
||||||
|
Base
|
||||||
|
ReviewerID int64
|
||||||
|
Decision enum.PullReqReviewDecision
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reporter) ReviewSubmitted(
|
||||||
|
ctx context.Context,
|
||||||
|
payload *ReviewSubmittedPayload,
|
||||||
|
) {
|
||||||
|
if payload == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, ReviewSubmittedEvent, payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Err(err).Msgf("failed to send pull request review submitted event")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Ctx(ctx).Debug().Msgf("reported pull request review submitted event with id '%s'", eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) RegisterReviewSubmitted(
|
||||||
|
fn events.HandlerFunc[*ReviewSubmittedPayload],
|
||||||
|
opts ...events.HandlerOption,
|
||||||
|
) error {
|
||||||
|
return events.ReaderRegisterEvent(r.innerReader, ReviewSubmittedEvent, fn, opts...)
|
||||||
|
}
|
@ -30,4 +30,5 @@ type Client interface {
|
|||||||
recipients []*types.PrincipalInfo,
|
recipients []*types.PrincipalInfo,
|
||||||
payload *PullReqBranchUpdatedPayload,
|
payload *PullReqBranchUpdatedPayload,
|
||||||
) error
|
) error
|
||||||
|
SendReviewSubmitted(ctx context.Context, recipients []*types.PrincipalInfo, payload *ReviewSubmittedPayload) error
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ const (
|
|||||||
TemplateReviewerAdded = "reviewer_added.html"
|
TemplateReviewerAdded = "reviewer_added.html"
|
||||||
TemplateCommentCreated = "comment_created.html"
|
TemplateCommentCreated = "comment_created.html"
|
||||||
TemplatePullReqBranchUpdated = "pullreq_branch_updated.html"
|
TemplatePullReqBranchUpdated = "pullreq_branch_updated.html"
|
||||||
|
TemplateNameReviewSubmitted = "review_submitted.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MailClient struct {
|
type MailClient struct {
|
||||||
@ -45,7 +46,7 @@ func (m MailClient) SendCommentCreated(
|
|||||||
recipients []*types.PrincipalInfo,
|
recipients []*types.PrincipalInfo,
|
||||||
payload *CommentCreatedPayload,
|
payload *CommentCreatedPayload,
|
||||||
) error {
|
) error {
|
||||||
mailPayload, err := GenerateEmailFromPayload(
|
email, err := GenerateEmailFromPayload(
|
||||||
TemplateCommentCreated,
|
TemplateCommentCreated,
|
||||||
recipients,
|
recipients,
|
||||||
payload.Base,
|
payload.Base,
|
||||||
@ -56,7 +57,7 @@ func (m MailClient) SendCommentCreated(
|
|||||||
pullreqevents.CommentCreatedEvent, err)
|
pullreqevents.CommentCreatedEvent, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.Mailer.Send(ctx, *mailPayload)
|
return m.Mailer.Send(ctx, *email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MailClient) SendReviewerAdded(
|
func (m MailClient) SendReviewerAdded(
|
||||||
@ -64,7 +65,7 @@ func (m MailClient) SendReviewerAdded(
|
|||||||
recipients []*types.PrincipalInfo,
|
recipients []*types.PrincipalInfo,
|
||||||
payload *ReviewerAddedPayload,
|
payload *ReviewerAddedPayload,
|
||||||
) error {
|
) error {
|
||||||
reviewerAddedMail, err := GenerateEmailFromPayload(
|
email, err := GenerateEmailFromPayload(
|
||||||
TemplateReviewerAdded,
|
TemplateReviewerAdded,
|
||||||
recipients,
|
recipients,
|
||||||
payload.Base,
|
payload.Base,
|
||||||
@ -75,7 +76,7 @@ func (m MailClient) SendReviewerAdded(
|
|||||||
pullreqevents.ReviewerAddedEvent, err)
|
pullreqevents.ReviewerAddedEvent, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.Mailer.Send(ctx, *reviewerAddedMail)
|
return m.Mailer.Send(ctx, *email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MailClient) SendPullReqBranchUpdated(
|
func (m MailClient) SendPullReqBranchUpdated(
|
||||||
@ -83,7 +84,7 @@ func (m MailClient) SendPullReqBranchUpdated(
|
|||||||
recipients []*types.PrincipalInfo,
|
recipients []*types.PrincipalInfo,
|
||||||
payload *PullReqBranchUpdatedPayload,
|
payload *PullReqBranchUpdatedPayload,
|
||||||
) error {
|
) error {
|
||||||
mailPayload, err := GenerateEmailFromPayload(
|
email, err := GenerateEmailFromPayload(
|
||||||
TemplatePullReqBranchUpdated,
|
TemplatePullReqBranchUpdated,
|
||||||
recipients,
|
recipients,
|
||||||
payload.Base,
|
payload.Base,
|
||||||
@ -94,7 +95,23 @@ func (m MailClient) SendPullReqBranchUpdated(
|
|||||||
pullreqevents.BranchUpdatedEvent, err)
|
pullreqevents.BranchUpdatedEvent, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.Mailer.Send(ctx, *mailPayload)
|
return m.Mailer.Send(ctx, *email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MailClient) SendReviewSubmitted(
|
||||||
|
ctx context.Context,
|
||||||
|
recipients []*types.PrincipalInfo,
|
||||||
|
payload *ReviewSubmittedPayload,
|
||||||
|
) error {
|
||||||
|
email, err := GenerateEmailFromPayload(TemplateNameReviewSubmitted, recipients, payload.Base, payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to generate mail requests after processing %s event: %w",
|
||||||
|
pullreqevents.ReviewSubmittedEvent,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return m.Mailer.Send(ctx, *email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSubjectPullRequest(
|
func GetSubjectPullRequest(
|
||||||
@ -130,14 +147,14 @@ func GenerateEmailFromPayload(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var mail mailer.Payload
|
var email mailer.Payload
|
||||||
mail.Body = string(body)
|
email.Body = string(body)
|
||||||
mail.Subject = subject
|
email.Subject = subject
|
||||||
mail.RepoRef = base.Repo.Path
|
email.RepoRef = base.Repo.Path
|
||||||
|
|
||||||
recipientEmails := RetrieveEmailsFromPrincipals(recipients)
|
recipientEmails := RetrieveEmailsFromPrincipals(recipients)
|
||||||
mail.ToRecipients = recipientEmails
|
email.ToRecipients = recipientEmails
|
||||||
return &mail, nil
|
return &email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RetrieveEmailsFromPrincipals(principals []*types.PrincipalInfo) []string {
|
func RetrieveEmailsFromPrincipals(principals []*types.PrincipalInfo) []string {
|
||||||
|
100
app/services/notification/review_submitted.go
Normal file
100
app/services/notification/review_submitted.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2023 Harness, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||||
|
"github.com/harness/gitness/events"
|
||||||
|
"github.com/harness/gitness/types"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReviewSubmittedPayload struct {
|
||||||
|
Base *BasePullReqPayload
|
||||||
|
Author *types.PrincipalInfo
|
||||||
|
Reviewer *types.PrincipalInfo
|
||||||
|
Decision enum.PullReqReviewDecision
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) notifyReviewSubmitted(
|
||||||
|
ctx context.Context,
|
||||||
|
event *events.Event[*pullreqevents.ReviewSubmittedPayload],
|
||||||
|
) error {
|
||||||
|
notificationPayload, recipients, err := s.processReviewSubmittedEvent(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to process %s event for pullReqID %d: %w",
|
||||||
|
pullreqevents.ReviewSubmittedEvent,
|
||||||
|
event.Payload.PullReqID,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.notificationClient.SendReviewSubmitted(
|
||||||
|
ctx,
|
||||||
|
recipients,
|
||||||
|
notificationPayload,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to send notification for event %s for pullReqID %d: %w",
|
||||||
|
pullreqevents.ReviewSubmittedEvent,
|
||||||
|
event.Payload.PullReqID,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) processReviewSubmittedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
event *events.Event[*pullreqevents.ReviewSubmittedPayload],
|
||||||
|
) (*ReviewSubmittedPayload, []*types.PrincipalInfo, error) {
|
||||||
|
base, err := s.getBasePayload(ctx, event.Payload.Base)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to get base payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authorPrincipal, err := s.principalInfoCache.Get(ctx, base.PullReq.CreatedBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf(
|
||||||
|
"failed to get author from principalInfoCache on %s event for pullReqID %d: %w",
|
||||||
|
pullreqevents.ReviewSubmittedEvent,
|
||||||
|
event.Payload.PullReqID,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewerPrincipal, err := s.principalInfoCache.Get(ctx, event.Payload.ReviewerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf(
|
||||||
|
"failed to get reviewer from principalInfoCache on event %s for pullReqID %d: %w",
|
||||||
|
pullreqevents.ReviewSubmittedEvent,
|
||||||
|
event.Payload.PullReqID,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ReviewSubmittedPayload{
|
||||||
|
Base: base,
|
||||||
|
Author: authorPrincipal,
|
||||||
|
Decision: event.Payload.Decision,
|
||||||
|
Reviewer: reviewerPrincipal,
|
||||||
|
}, []*types.PrincipalInfo{authorPrincipal}, nil
|
||||||
|
}
|
@ -32,7 +32,7 @@ func (s *Service) notifyReviewerAdded(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
event *events.Event[*pullreqevents.ReviewerAddedPayload],
|
event *events.Event[*pullreqevents.ReviewerAddedPayload],
|
||||||
) error {
|
) error {
|
||||||
payload, err := s.processReviewerAddedEvent(ctx, event)
|
payload, recipients, err := s.processReviewerAddedEvent(ctx, event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"failed to process %s event for pullReqID %d: %w",
|
"failed to process %s event for pullReqID %d: %w",
|
||||||
@ -42,11 +42,6 @@ func (s *Service) notifyReviewerAdded(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notification to author and reviewer
|
|
||||||
recipients := []*types.PrincipalInfo{
|
|
||||||
payload.Base.Author,
|
|
||||||
payload.Reviewer,
|
|
||||||
}
|
|
||||||
err = s.notificationClient.SendReviewerAdded(ctx, recipients, payload)
|
err = s.notificationClient.SendReviewerAdded(ctx, recipients, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
@ -63,19 +58,24 @@ func (s *Service) notifyReviewerAdded(
|
|||||||
func (s *Service) processReviewerAddedEvent(
|
func (s *Service) processReviewerAddedEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
event *events.Event[*pullreqevents.ReviewerAddedPayload],
|
event *events.Event[*pullreqevents.ReviewerAddedPayload],
|
||||||
) (*ReviewerAddedPayload, error) {
|
) (*ReviewerAddedPayload, []*types.PrincipalInfo, error) {
|
||||||
base, err := s.getBasePayload(ctx, event.Payload.Base)
|
base, err := s.getBasePayload(ctx, event.Payload.Base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get base payload: %w", err)
|
return nil, nil, fmt.Errorf("failed to get base payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewerPrincipal, err := s.principalInfoCache.Get(ctx, event.Payload.ReviewerID)
|
reviewerPrincipal, err := s.principalInfoCache.Get(ctx, event.Payload.ReviewerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get reviewer from principalInfoCache: %w", err)
|
return nil, nil, fmt.Errorf("failed to get reviewer from principalInfoCache: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipients := []*types.PrincipalInfo{
|
||||||
|
base.Author,
|
||||||
|
reviewerPrincipal,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ReviewerAddedPayload{
|
return &ReviewerAddedPayload{
|
||||||
Base: base,
|
Base: base,
|
||||||
Reviewer: reviewerPrincipal,
|
Reviewer: reviewerPrincipal,
|
||||||
}, nil
|
}, recipients, nil
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,7 @@ func NewService(
|
|||||||
_ = r.RegisterReviewerAdded(service.notifyReviewerAdded)
|
_ = r.RegisterReviewerAdded(service.notifyReviewerAdded)
|
||||||
_ = r.RegisterCommentCreated(service.notifyCommentCreated)
|
_ = r.RegisterCommentCreated(service.notifyCommentCreated)
|
||||||
_ = r.RegisterBranchUpdated(service.notifyPullReqBranchUpdated)
|
_ = r.RegisterBranchUpdated(service.notifyPullReqBranchUpdated)
|
||||||
|
_ = r.RegisterReviewSubmitted(service.notifyReviewSubmitted)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
20
app/services/notification/templates/review_submitted.html
Normal file
20
app/services/notification/templates/review_submitted.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
<b>@{{.Reviewer.DisplayName}}</b>
|
||||||
|
{{if eq .Decision "approved"}}
|
||||||
|
approved
|
||||||
|
{{else if eq .Decision "changereq"}}
|
||||||
|
requested changes to
|
||||||
|
{{end}}
|
||||||
|
pull request #{{.Base.PullReq.Number}} {{.Base.PullReq.Title}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="{{.Base.PullReqURL}}">View pull request #{{.Base.PullReq.Number}}</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user