Skip to content

Commit 027c9fe

Browse files
committed
MAILER
1 parent c8b64c7 commit 027c9fe

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed

services/mailer/mail_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"testing"
1717
texttmpl "text/template"
1818

19+
actions_model "code.gitea.io/gitea/models/actions"
20+
1921
activities_model "code.gitea.io/gitea/models/activities"
2022
"code.gitea.io/gitea/models/db"
2123
issues_model "code.gitea.io/gitea/models/issues"
@@ -440,6 +442,16 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
440442
assert.Equal(t, "<owner/repo/releases/1@localhost>", msgID)
441443
}
442444

445+
func TestGenerateMessageIDForActionsWorkflowRunStatusEmail(t *testing.T) {
446+
assert.NoError(t, unittest.PrepareTestDatabase())
447+
448+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
449+
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 795, RepoID: repo.ID})
450+
assert.NoError(t, run.LoadAttributes(db.DefaultContext))
451+
msgID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run)
452+
assert.Equal(t, "<user2/repo2/actions/runs/191@localhost>", msgID)
453+
}
454+
443455
func TestFromDisplayName(t *testing.T) {
444456
tmpl, err := texttmpl.New("mailFrom").Parse("{{ .DisplayName }}")
445457
assert.NoError(t, err)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package mailer
5+
6+
import (
7+
"bytes"
8+
"context"
9+
"fmt"
10+
"sort"
11+
12+
actions_model "code.gitea.io/gitea/models/actions"
13+
"code.gitea.io/gitea/models/db"
14+
repo_model "code.gitea.io/gitea/models/repo"
15+
user_model "code.gitea.io/gitea/models/user"
16+
"code.gitea.io/gitea/modules/base"
17+
"code.gitea.io/gitea/modules/log"
18+
"code.gitea.io/gitea/modules/setting"
19+
sender_service "code.gitea.io/gitea/services/mailer/sender"
20+
)
21+
22+
const tplWorkflowRun = "notify/workflow_run"
23+
24+
func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Repository, run *actions_model.ActionRun) string {
25+
return fmt.Sprintf("<%s/actions/runs/%d@%s>", repo.FullName(), run.Index, setting.Domain)
26+
}
27+
28+
func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, sender *user_model.User, recipients []*user_model.User) {
29+
msgs := make([]*sender_service.Message, 0, len(recipients))
30+
31+
messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run)
32+
headers := generateMetadataHeaders(repo)
33+
34+
subject := "Run"
35+
if run.IsForkPullRequest {
36+
subject = "PR run"
37+
}
38+
switch run.Status {
39+
case actions_model.StatusFailure:
40+
subject = subject + " failed"
41+
case actions_model.StatusCancelled:
42+
subject = subject + " cancelled"
43+
case actions_model.StatusSuccess:
44+
subject = subject + " is successful"
45+
}
46+
subject = fmt.Sprintf("%s: %s (%s)", subject, run.WorkflowID, base.ShortSha(run.CommitSHA))
47+
48+
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
49+
if err != nil {
50+
log.Error("GetRunJobsByRunID: %v", err)
51+
} else {
52+
sort.SliceStable(jobs, func(i, j int) bool {
53+
si := jobs[i].Status
54+
sj := jobs[j].Status
55+
if si.IsSuccess() {
56+
si = 99
57+
}
58+
if sj.IsSuccess() {
59+
sj = 99
60+
}
61+
return si < sj
62+
})
63+
}
64+
65+
var mailBody bytes.Buffer
66+
if err := bodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{
67+
"Subject": subject,
68+
"Repo": repo,
69+
"Run": run,
70+
"Jobs": jobs,
71+
}); err != nil {
72+
log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err)
73+
}
74+
75+
for _, recipient := range recipients {
76+
msg := sender_service.NewMessageFrom(
77+
recipient.Email,
78+
fromDisplayName(sender),
79+
setting.MailService.FromEmail,
80+
subject,
81+
mailBody.String(),
82+
)
83+
msg.Info = subject
84+
for k, v := range generateSenderRecipientHeaders(sender, recipient) {
85+
msg.SetHeader(k, v)
86+
}
87+
for k, v := range headers {
88+
msg.SetHeader(k, v)
89+
}
90+
msg.SetHeader("Message-ID", messageID)
91+
msgs = append(msgs, msg)
92+
}
93+
SendAsync(msgs...)
94+
}
95+
96+
func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) {
97+
if setting.MailService == nil {
98+
return
99+
}
100+
if run.Status.IsSkipped() {
101+
return
102+
}
103+
104+
recipients := make([]*user_model.User, 0)
105+
106+
if !sender.IsGiteaActions() && !sender.IsGhost() && sender.IsMailable() {
107+
if run.Status.IsSuccess() {
108+
if sender.EmailNotificationsPreference == user_model.EmailNotificationsAndYourOwn {
109+
recipients = append(recipients, sender)
110+
}
111+
sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
112+
return
113+
} else if sender.EmailNotificationsPreference != user_model.EmailNotificationsOnMention &&
114+
sender.EmailNotificationsPreference != user_model.EmailNotificationsDisabled {
115+
recipients = append(recipients, sender)
116+
}
117+
}
118+
119+
watchers, err := repo_model.GetRepoWatchers(ctx, repo.ID, db.ListOptionsAll)
120+
if err != nil {
121+
log.Error("GetWatchers: %v", err)
122+
}
123+
for _, watcher := range watchers {
124+
if watcher.ID == sender.ID {
125+
continue
126+
}
127+
if watcher.IsMailable() && watcher.EmailNotificationsPreference != user_model.EmailNotificationsOnMention &&
128+
watcher.EmailNotificationsPreference != user_model.EmailNotificationsDisabled {
129+
recipients = append(recipients, watcher)
130+
}
131+
}
132+
sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
133+
}

services/mailer/notify.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"context"
88
"fmt"
99

10+
actions_model "code.gitea.io/gitea/models/actions"
11+
1012
activities_model "code.gitea.io/gitea/models/activities"
1113
issues_model "code.gitea.io/gitea/models/issues"
1214
repo_model "code.gitea.io/gitea/models/repo"
@@ -205,3 +207,10 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
205207
log.Error("SendRepoTransferNotifyMail: %v", err)
206208
}
207209
}
210+
211+
func (m *mailNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
212+
if !run.Status.IsDone() {
213+
return
214+
}
215+
SendActionsWorkflowRunStatusEmail(ctx, sender, repo, run)
216+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5+
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no">
6+
<title>{{.Subject}}</title>
7+
</head>
8+
<body>
9+
<h1>{{.Repo.FullName}} {{.Run.WorkflowID}}: {{.Run.Status}}</h1>
10+
<ul>
11+
{{range $index, $job := .Jobs}}
12+
<li>
13+
<a href="{{$.Run.Link}}/jobs/{{$index}}">
14+
{{$job.Status}}: {{$job.Name}}{{if $job.Attempt gt 1}}, Attempt #{{$job.Attempt}}{{end}}
15+
</a>
16+
</li>
17+
{{end}}
18+
</ul>
19+
<p>
20+
---
21+
<br>
22+
<a href="{{.Run.Link}}">{{.locale.Tr "mail.view_it_on" AppName}}</a>.
23+
</p>
24+
</body>
25+
</html>

0 commit comments

Comments
 (0)