From 027c9fef2b3c164bfadf8d9ede95bda300128fc3 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 02:49:32 +0800 Subject: [PATCH] MAILER --- services/mailer/mail_test.go | 12 +++ services/mailer/mail_workflow_run.go | 133 ++++++++++++++++++++++++ services/mailer/notify.go | 9 ++ templates/mail/notify/workflow_run.tmpl | 25 +++++ 4 files changed, 179 insertions(+) create mode 100644 services/mailer/mail_workflow_run.go create mode 100644 templates/mail/notify/workflow_run.tmpl diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index c5f1da303c..bb20a3d7f5 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -16,6 +16,8 @@ import ( "testing" texttmpl "text/template" + actions_model "code.gitea.io/gitea/models/actions" + activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" @@ -440,6 +442,16 @@ func TestGenerateMessageIDForRelease(t *testing.T) { assert.Equal(t, "", msgID) } +func TestGenerateMessageIDForActionsWorkflowRunStatusEmail(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 795, RepoID: repo.ID}) + assert.NoError(t, run.LoadAttributes(db.DefaultContext)) + msgID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run) + assert.Equal(t, "", msgID) +} + func TestFromDisplayName(t *testing.T) { tmpl, err := texttmpl.New("mailFrom").Parse("{{ .DisplayName }}") assert.NoError(t, err) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go new file mode 100644 index 0000000000..0167c5e71a --- /dev/null +++ b/services/mailer/mail_workflow_run.go @@ -0,0 +1,133 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package mailer + +import ( + "bytes" + "context" + "fmt" + "sort" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + sender_service "code.gitea.io/gitea/services/mailer/sender" +) + +const tplWorkflowRun = "notify/workflow_run" + +func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Repository, run *actions_model.ActionRun) string { + return fmt.Sprintf("<%s/actions/runs/%d@%s>", repo.FullName(), run.Index, setting.Domain) +} + +func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, sender *user_model.User, recipients []*user_model.User) { + msgs := make([]*sender_service.Message, 0, len(recipients)) + + messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run) + headers := generateMetadataHeaders(repo) + + subject := "Run" + if run.IsForkPullRequest { + subject = "PR run" + } + switch run.Status { + case actions_model.StatusFailure: + subject = subject + " failed" + case actions_model.StatusCancelled: + subject = subject + " cancelled" + case actions_model.StatusSuccess: + subject = subject + " is successful" + } + subject = fmt.Sprintf("%s: %s (%s)", subject, run.WorkflowID, base.ShortSha(run.CommitSHA)) + + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + log.Error("GetRunJobsByRunID: %v", err) + } else { + sort.SliceStable(jobs, func(i, j int) bool { + si := jobs[i].Status + sj := jobs[j].Status + if si.IsSuccess() { + si = 99 + } + if sj.IsSuccess() { + sj = 99 + } + return si < sj + }) + } + + var mailBody bytes.Buffer + if err := bodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{ + "Subject": subject, + "Repo": repo, + "Run": run, + "Jobs": jobs, + }); err != nil { + log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err) + } + + for _, recipient := range recipients { + msg := sender_service.NewMessageFrom( + recipient.Email, + fromDisplayName(sender), + setting.MailService.FromEmail, + subject, + mailBody.String(), + ) + msg.Info = subject + for k, v := range generateSenderRecipientHeaders(sender, recipient) { + msg.SetHeader(k, v) + } + for k, v := range headers { + msg.SetHeader(k, v) + } + msg.SetHeader("Message-ID", messageID) + msgs = append(msgs, msg) + } + SendAsync(msgs...) +} + +func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) { + if setting.MailService == nil { + return + } + if run.Status.IsSkipped() { + return + } + + recipients := make([]*user_model.User, 0) + + if !sender.IsGiteaActions() && !sender.IsGhost() && sender.IsMailable() { + if run.Status.IsSuccess() { + if sender.EmailNotificationsPreference == user_model.EmailNotificationsAndYourOwn { + recipients = append(recipients, sender) + } + sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) + return + } else if sender.EmailNotificationsPreference != user_model.EmailNotificationsOnMention && + sender.EmailNotificationsPreference != user_model.EmailNotificationsDisabled { + recipients = append(recipients, sender) + } + } + + watchers, err := repo_model.GetRepoWatchers(ctx, repo.ID, db.ListOptionsAll) + if err != nil { + log.Error("GetWatchers: %v", err) + } + for _, watcher := range watchers { + if watcher.ID == sender.ID { + continue + } + if watcher.IsMailable() && watcher.EmailNotificationsPreference != user_model.EmailNotificationsOnMention && + watcher.EmailNotificationsPreference != user_model.EmailNotificationsDisabled { + recipients = append(recipients, watcher) + } + } + sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) +} diff --git a/services/mailer/notify.go b/services/mailer/notify.go index 77c366fe31..847fdfe94e 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + actions_model "code.gitea.io/gitea/models/actions" + activities_model "code.gitea.io/gitea/models/activities" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" @@ -205,3 +207,10 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner * log.Error("SendRepoTransferNotifyMail: %v", err) } } + +func (m *mailNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) { + if !run.Status.IsDone() { + return + } + SendActionsWorkflowRunStatusEmail(ctx, sender, repo, run) +} diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl new file mode 100644 index 0000000000..e4e899db48 --- /dev/null +++ b/templates/mail/notify/workflow_run.tmpl @@ -0,0 +1,25 @@ + + + + + + {{.Subject}} + + +

{{.Repo.FullName}} {{.Run.WorkflowID}}: {{.Run.Status}}

+ +

+ --- +
+ {{.locale.Tr "mail.view_it_on" AppName}}. +

+ +