From c8b64c768685e3fe5f9232074baca3ee3838b89b Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Sun, 6 Jul 2025 21:59:04 +0800 Subject: [PATCH 01/33] Refactor --- services/mailer/mail.go | 36 ++++++++++++++++++++ services/mailer/mail_issue_common.go | 51 +++++++++++----------------- services/mailer/mail_test.go | 4 +-- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index aa51cbdbcfb79..a0ead7c9eb4bf 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -171,3 +171,39 @@ func fromDisplayName(u *user_model.User) string { } return u.GetCompleteName() } + +func generateMetadataHeaders(repo *repo_model.Repository) map[string]string { + return map[string]string{ + // https://datatracker.ietf.org/doc/html/rfc2919 + "List-ID": fmt.Sprintf("%s <%s.%s.%s>", repo.FullName(), repo.Name, repo.OwnerName, setting.Domain), + + // https://datatracker.ietf.org/doc/html/rfc2369 + "List-Archive": fmt.Sprintf("<%s>", repo.HTMLURL()), + + "X-Mailer": "Gitea", + + "X-Gitea-Repository": repo.Name, + "X-Gitea-Repository-Path": repo.FullName(), + "X-Gitea-Repository-Link": repo.HTMLURL(), + + "X-GitLab-Project": repo.Name, + "X-GitLab-Project-Path": repo.FullName(), + } +} + +func generateSenderRecipientHeaders(doer, recipient *user_model.User) map[string]string { + return map[string]string{ + "X-Gitea-Sender": doer.Name, + "X-Gitea-Recipient": recipient.Name, + "X-GitHub-Sender": doer.Name, + "X-GitHub-Recipient": recipient.Name, + } +} + +func generateReasonHeaders(reason string) map[string]string { + return map[string]string{ + "X-Gitea-Reason": reason, + "X-GitHub-Reason": reason, + "X-GitLab-NotificationReason": reason, + } +} diff --git a/services/mailer/mail_issue_common.go b/services/mailer/mail_issue_common.go index ebfd52162cb19..b921475518f08 100644 --- a/services/mailer/mail_issue_common.go +++ b/services/mailer/mail_issue_common.go @@ -29,7 +29,7 @@ import ( // Many e-mail service providers have limitations on the size of the email body, it's usually from 10MB to 25MB const maxEmailBodySize = 9_000_000 -func fallbackMailSubject(issue *issues_model.Issue) string { +func fallbackIssueMailSubject(issue *issues_model.Issue) string { return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index) } @@ -86,7 +86,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang if actName != "new" { prefix = "Re: " } - fallback = prefix + fallbackMailSubject(comment.Issue) + fallback = prefix + fallbackIssueMailSubject(comment.Issue) if comment.Comment != nil && comment.Comment.Review != nil { reviewComments = make([]*issues_model.Comment, 0, 10) @@ -202,7 +202,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang msg.SetHeader("References", references...) msg.SetHeader("List-Unsubscribe", listUnsubscribe...) - for key, value := range generateAdditionalHeaders(comment, actType, recipient) { + for key, value := range generateAdditionalHeadersForIssue(comment, actType, recipient) { msg.SetHeader(key, value) } @@ -302,35 +302,24 @@ func generateMessageIDForIssue(issue *issues_model.Issue, comment *issues_model. return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain) } -func generateAdditionalHeaders(ctx *mailComment, reason string, recipient *user_model.User) map[string]string { +func generateAdditionalHeadersForIssue(ctx *mailComment, reason string, recipient *user_model.User) map[string]string { repo := ctx.Issue.Repo - return map[string]string{ - // https://datatracker.ietf.org/doc/html/rfc2919 - "List-ID": fmt.Sprintf("%s <%s.%s.%s>", repo.FullName(), repo.Name, repo.OwnerName, setting.Domain), - - // https://datatracker.ietf.org/doc/html/rfc2369 - "List-Archive": fmt.Sprintf("<%s>", repo.HTMLURL()), - - "X-Mailer": "Gitea", - "X-Gitea-Reason": reason, - "X-Gitea-Sender": ctx.Doer.Name, - "X-Gitea-Recipient": recipient.Name, - "X-Gitea-Recipient-Address": recipient.Email, - "X-Gitea-Repository": repo.Name, - "X-Gitea-Repository-Path": repo.FullName(), - "X-Gitea-Repository-Link": repo.HTMLURL(), - "X-Gitea-Issue-ID": strconv.FormatInt(ctx.Issue.Index, 10), - "X-Gitea-Issue-Link": ctx.Issue.HTMLURL(), - - "X-GitHub-Reason": reason, - "X-GitHub-Sender": ctx.Doer.Name, - "X-GitHub-Recipient": recipient.Name, - "X-GitHub-Recipient-Address": recipient.Email, - - "X-GitLab-NotificationReason": reason, - "X-GitLab-Project": repo.Name, - "X-GitLab-Project-Path": repo.FullName(), - "X-GitLab-Issue-IID": strconv.FormatInt(ctx.Issue.Index, 10), + issueID := strconv.FormatInt(ctx.Issue.Index, 10) + headers := generateMetadataHeaders(repo) + + for k, v := range generateSenderRecipientHeaders(ctx.Doer, recipient) { + headers[k] = v + } + for k, v := range generateReasonHeaders(reason) { + headers[k] = v } + + headers["X-Gitea-Recipient-Address"] = recipient.Email + headers["X-Gitea-Issue-ID"] = issueID + headers["X-Gitea-Issue-Link"] = ctx.Issue.HTMLURL() + headers["X-GitHub-Recipient-Address"] = recipient.Email + headers["X-GitLab-Issue-IID"] = issueID + + return headers } diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index b15949f3524fe..c5f1da303c8fc 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -297,13 +297,13 @@ func testComposeIssueCommentMessage(t *testing.T, ctx *mailComment, recipients [ return msgs[0] } -func TestGenerateAdditionalHeaders(t *testing.T) { +func TestGenerateAdditionalHeadersForIssue(t *testing.T) { doer, _, issue, _ := prepareMailerTest(t) comment := &mailComment{Issue: issue, Doer: doer} recipient := &user_model.User{Name: "test", Email: "test@gitea.com"} - headers := generateAdditionalHeaders(comment, "dummy-reason", recipient) + headers := generateAdditionalHeadersForIssue(comment, "dummy-reason", recipient) expected := map[string]string{ "List-ID": "user2/repo1 ", 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 02/33] 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 c5f1da303c8fc..bb20a3d7f5f44 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 0000000000000..0167c5e71a5f4 --- /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 77c366fe3195d..847fdfe94e240 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 0000000000000..e4e899db482bb --- /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}}. +

+ + From a63093528a4525731d242d23e969059e8c8524f1 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 13:01:21 +0800 Subject: [PATCH 03/33] MAILER --- templates/mail/notify/workflow_run.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index e4e899db482bb..11faf80ae8f44 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -11,7 +11,7 @@ {{range $index, $job := .Jobs}}
  • - {{$job.Status}}: {{$job.Name}}{{if $job.Attempt gt 1}}, Attempt #{{$job.Attempt}}{{end}} + {{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}}
  • {{end}} From 1f6c68d57242b7eb33be302901ba38a3b4c08cbd Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:32:27 +0800 Subject: [PATCH 04/33] MAILER --- services/mailer/mail_workflow_run.go | 65 +++++++++++++++---------- templates/mail/notify/workflow_run.tmpl | 17 ++++--- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 0167c5e71a5f4..5dd858edb1cb8 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -9,6 +9,8 @@ import ( "fmt" "sort" + "code.gitea.io/gitea/modules/translation" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -26,8 +28,6 @@ func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Reposito } 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) @@ -62,35 +62,46 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep }) } - 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) - } + displayName := fromDisplayName(sender) - 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) + langMap := make(map[string][]*user_model.User) + for _, user := range recipients { + langMap[user.Language] = append(langMap[user.Language], user) + } + for lang, tos := range langMap { + locale := translation.NewLocale(lang) + var mailBody bytes.Buffer + if err := bodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{ + "Subject": subject, + "Repo": repo, + "Run": run, + "Jobs": jobs, + "locale": locale, + "Language": locale.Language(), + }); err != nil { + log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err) } - for k, v := range headers { - msg.SetHeader(k, v) + msgs := make([]*sender_service.Message, 0, len(tos)) + for _, rec := range tos { + msg := sender_service.NewMessageFrom( + rec.Email, + displayName, + setting.MailService.FromEmail, + subject, + mailBody.String(), + ) + msg.Info = subject + for k, v := range generateSenderRecipientHeaders(sender, rec) { + msg.SetHeader(k, v) + } + for k, v := range headers { + msg.SetHeader(k, v) + } + msg.SetHeader("Message-ID", messageID) + msgs = append(msgs, msg) } - msg.SetHeader("Message-ID", messageID) - msgs = append(msgs, msg) + SendAsync(msgs...) } - SendAsync(msgs...) } func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) { diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index 11faf80ae8f44..9d1d72135f8a6 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -4,22 +4,27 @@ {{.Subject}} + -

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

    +

    + {{.Repo.FullName}} {{.Run.WorkflowID}}: {{if .Run.Status.IsSuccess}}{{.locale.Tr "actions.status.success"}}{{else if .Run.Status.IsFailure}}{{.locale.Tr "actions.status.failure"}}{{else if .Run.Status.IsCancelled}}{{.locale.Tr "actions.status.cancelled"}}{{else}}{{.locale.Tr "actions.status.skipped"}}{{end}} +

      - {{range $index, $job := .Jobs}} + {{range $job := .Jobs}}
    • - - {{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}} - + {{if $job.Status.IsSuccess}}{{.locale.Tr "actions.status.success"}}{{else if $job.Status.IsFailure}}{{.locale.Tr "actions.status.failure"}}{{else if $job.Status.IsCancelled}}{{.locale.Tr "actions.status.cancelled"}}{{else}}{{.locale.Tr "actions.status.skipped"}}{{end}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}}
    • {{end}}
    + From bf468fee217485e732abdee17c50600b8776983b Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:46:43 +0800 Subject: [PATCH 05/33] MAILER --- services/mailer/mail_workflow_run.go | 41 +++++++++++++++++++------ templates/mail/notify/workflow_run.tmpl | 6 ++-- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 5dd858edb1cb8..74b68e0e89d10 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -5,6 +5,8 @@ package mailer import ( "bytes" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/convert" "context" "fmt" "sort" @@ -45,13 +47,13 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep } subject = fmt.Sprintf("%s: %s (%s)", subject, run.WorkflowID, base.ShortSha(run.CommitSHA)) - jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + jobs0, 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 + sort.SliceStable(jobs0, func(i, j int) bool { + si := jobs0[i].Status + sj := jobs0[j].Status if si.IsSuccess() { si = 99 } @@ -61,6 +63,15 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep return si < sj }) } + convertedJobs0 := make([]*api.ActionWorkflowJob, 0, len(jobs0)) + for _, job := range jobs0 { + c, err := convert.ToActionWorkflowJob(ctx, repo, nil, job) + if err != nil { + log.Error("convert.ToActionWorkflowJob: %v", err) + continue + } + convertedJobs0 = append(convertedJobs0, c) + } displayName := fromDisplayName(sender) @@ -70,14 +81,24 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep } for lang, tos := range langMap { locale := translation.NewLocale(lang) + var runStatusText string + switch run.Status { + case actions_model.StatusSuccess: + runStatusText = locale.TrString("actions.status.success") + case actions_model.StatusFailure: + runStatusText = locale.TrString("actions.status.failure") + case actions_model.StatusCancelled: + runStatusText = locale.TrString("actions.status.cancelled") + } var mailBody bytes.Buffer if err := bodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{ - "Subject": subject, - "Repo": repo, - "Run": run, - "Jobs": jobs, - "locale": locale, - "Language": locale.Language(), + "Subject": subject, + "Repo": repo, + "Run": run, + "RunStatusText": runStatusText, + "Jobs": convertedJobs0, + "locale": locale, + "Language": locale.Language(), }); err != nil { log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err) } diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index 9d1d72135f8a6..0d9bde128b8b5 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -10,12 +10,14 @@

    - {{.Repo.FullName}} {{.Run.WorkflowID}}: {{if .Run.Status.IsSuccess}}{{.locale.Tr "actions.status.success"}}{{else if .Run.Status.IsFailure}}{{.locale.Tr "actions.status.failure"}}{{else if .Run.Status.IsCancelled}}{{.locale.Tr "actions.status.cancelled"}}{{else}}{{.locale.Tr "actions.status.skipped"}}{{end}} + {{.Repo.FullName}} {{.Run.WorkflowID}}: {{.RunStatusText}}

    From bf3559f0b59546ccc378d80f3621ba2a4f33b292 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:22:57 +0800 Subject: [PATCH 06/33] MAILER --- services/mailer/mail_workflow_run.go | 31 ++++++++++++++++--------- templates/mail/notify/workflow_run.tmpl | 8 +------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 74b68e0e89d10..1cfda240a3c1c 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -5,12 +5,12 @@ package mailer import ( "bytes" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/services/convert" "context" "fmt" "sort" + "code.gitea.io/gitea/services/convert" + "code.gitea.io/gitea/modules/translation" actions_model "code.gitea.io/gitea/models/actions" @@ -25,6 +25,13 @@ import ( const tplWorkflowRun = "notify/workflow_run" +type convertedWorkflowJob struct { + HTMLURL string + Status actions_model.Status + Name string + Attempt int64 +} + 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) } @@ -34,16 +41,13 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep headers := generateMetadataHeaders(repo) subject := "Run" - if run.IsForkPullRequest { - subject = "PR run" - } switch run.Status { case actions_model.StatusFailure: - subject = subject + " failed" + subject += " failed" case actions_model.StatusCancelled: - subject = subject + " cancelled" + subject += " cancelled" case actions_model.StatusSuccess: - subject = subject + " is successful" + subject += " is successful" } subject = fmt.Sprintf("%s: %s (%s)", subject, run.WorkflowID, base.ShortSha(run.CommitSHA)) @@ -63,14 +67,19 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep return si < sj }) } - convertedJobs0 := make([]*api.ActionWorkflowJob, 0, len(jobs0)) + convertedJobs := make([]convertedWorkflowJob, 0, len(jobs0)) for _, job := range jobs0 { c, err := convert.ToActionWorkflowJob(ctx, repo, nil, job) if err != nil { log.Error("convert.ToActionWorkflowJob: %v", err) continue } - convertedJobs0 = append(convertedJobs0, c) + convertedJobs = append(convertedJobs, convertedWorkflowJob{ + HTMLURL: c.HTMLURL, + Name: c.Name, + Status: job.Status, + Attempt: c.RunAttempt, + }) } displayName := fromDisplayName(sender) @@ -96,7 +105,7 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep "Repo": repo, "Run": run, "RunStatusText": runStatusText, - "Jobs": convertedJobs0, + "Jobs": convertedJobs, "locale": locale, "Language": locale.Language(), }); err != nil { diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index 0d9bde128b8b5..a1c1c245466ac 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -4,9 +4,6 @@ {{.Subject}} -

    @@ -16,17 +13,14 @@ {{range $job := .Jobs}}
  • - {{$job.Status}}: {{$job.Name}}{{if gt $job.RunAttempt 1}}, Attempt #{{$job.RunAttempt}}{{end}} + {{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}}
  • {{end}} - From bb5e10b519df42d50552c8136fe09a54c5fa2e87 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:31:20 +0800 Subject: [PATCH 07/33] fmt --- services/mailer/mail_test.go | 1 - services/mailer/mail_workflow_run.go | 6 ++---- services/mailer/notify.go | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index bb20a3d7f5f44..34cd6dd93103a 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -17,7 +17,6 @@ import ( 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" diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 1cfda240a3c1c..c337263b415c4 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -9,10 +9,6 @@ import ( "fmt" "sort" - "code.gitea.io/gitea/services/convert" - - "code.gitea.io/gitea/modules/translation" - actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -20,6 +16,8 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/services/convert" sender_service "code.gitea.io/gitea/services/mailer/sender" ) diff --git a/services/mailer/notify.go b/services/mailer/notify.go index 847fdfe94e240..7533b1822ab58 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -8,7 +8,6 @@ import ( "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" From ed7b2bc24224252814da915f0b12d3f350c0cb9c Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:41:26 +0800 Subject: [PATCH 08/33] Refactor --- services/mailer/mail_issue_common.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/services/mailer/mail_issue_common.go b/services/mailer/mail_issue_common.go index b921475518f08..45ec7609b756e 100644 --- a/services/mailer/mail_issue_common.go +++ b/services/mailer/mail_issue_common.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "fmt" + "maps" "strconv" "strings" "time" @@ -308,12 +309,8 @@ func generateAdditionalHeadersForIssue(ctx *mailComment, reason string, recipien issueID := strconv.FormatInt(ctx.Issue.Index, 10) headers := generateMetadataHeaders(repo) - for k, v := range generateSenderRecipientHeaders(ctx.Doer, recipient) { - headers[k] = v - } - for k, v := range generateReasonHeaders(reason) { - headers[k] = v - } + maps.Copy(headers, generateSenderRecipientHeaders(ctx.Doer, recipient)) + maps.Copy(headers, generateReasonHeaders(reason)) headers["X-Gitea-Recipient-Address"] = recipient.Email headers["X-Gitea-Issue-ID"] = issueID From 4f3f1b9c8aa25420616d6512506225642e9a3ff5 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:52:03 +0800 Subject: [PATCH 09/33] Decoration --- templates/mail/notify/workflow_run.tmpl | 76 +++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index a1c1c245466ac..70ba74937a399 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -4,11 +4,73 @@ {{.Subject}} + -

    +

    {{.Repo.FullName}} {{.Run.WorkflowID}}: {{.RunStatusText}} -

    +
      {{range $job := .Jobs}}
    • @@ -18,9 +80,11 @@
    • {{end}}
    -

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

    +
    + From a0737b1ff45ae4352bf4e16a436c832c55966d2d Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 00:33:34 +0800 Subject: [PATCH 10/33] Decoration --- services/mailer/mail_workflow_run.go | 44 +++++++++++++++------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index c337263b415c4..1267a380d2407 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -35,9 +35,6 @@ func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Reposito } func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, sender *user_model.User, recipients []*user_model.User) { - messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run) - headers := generateMetadataHeaders(repo) - subject := "Run" switch run.Status { case actions_model.StatusFailure: @@ -45,17 +42,20 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep case actions_model.StatusCancelled: subject += " cancelled" case actions_model.StatusSuccess: - subject += " is successful" + subject += " succeeded" } subject = fmt.Sprintf("%s: %s (%s)", subject, run.WorkflowID, base.ShortSha(run.CommitSHA)) + displayName := fromDisplayName(sender) + messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run) + metadataHeaders := generateMetadataHeaders(repo) - jobs0, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { log.Error("GetRunJobsByRunID: %v", err) } else { - sort.SliceStable(jobs0, func(i, j int) bool { - si := jobs0[i].Status - sj := jobs0[j].Status + sort.SliceStable(jobs, func(i, j int) bool { + si := jobs[i].Status + sj := jobs[j].Status if si.IsSuccess() { si = 99 } @@ -65,23 +65,21 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep return si < sj }) } - convertedJobs := make([]convertedWorkflowJob, 0, len(jobs0)) - for _, job := range jobs0 { - c, err := convert.ToActionWorkflowJob(ctx, repo, nil, job) + convertedJobs := make([]convertedWorkflowJob, 0, len(jobs)) + for _, job := range jobs { + converted0, err := convert.ToActionWorkflowJob(ctx, repo, nil, job) if err != nil { log.Error("convert.ToActionWorkflowJob: %v", err) continue } convertedJobs = append(convertedJobs, convertedWorkflowJob{ - HTMLURL: c.HTMLURL, - Name: c.Name, + HTMLURL: converted0.HTMLURL, + Name: converted0.Name, Status: job.Status, - Attempt: c.RunAttempt, + Attempt: converted0.RunAttempt, }) } - displayName := fromDisplayName(sender) - langMap := make(map[string][]*user_model.User) for _, user := range recipients { langMap[user.Language] = append(langMap[user.Language], user) @@ -91,11 +89,17 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep var runStatusText string switch run.Status { case actions_model.StatusSuccess: - runStatusText = locale.TrString("actions.status.success") + runStatusText = "All jobs have succeeded" case actions_model.StatusFailure: - runStatusText = locale.TrString("actions.status.failure") + runStatusText = "All jobs have failed" + for _, job := range jobs { + if !job.Status.IsFailure() { + runStatusText = "Some jobs were not successful" + break + } + } case actions_model.StatusCancelled: - runStatusText = locale.TrString("actions.status.cancelled") + runStatusText = "All jobs have been cancelled" } var mailBody bytes.Buffer if err := bodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{ @@ -122,7 +126,7 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep for k, v := range generateSenderRecipientHeaders(sender, rec) { msg.SetHeader(k, v) } - for k, v := range headers { + for k, v := range metadataHeaders { msg.SetHeader(k, v) } msg.SetHeader("Message-ID", messageID) From 53970ec4eda50970507d570a52d5edd014243300 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:09:38 +0800 Subject: [PATCH 11/33] Refactor --- services/mailer/mail.go | 10 ++++++---- services/mailer/mail_issue_common.go | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index a0ead7c9eb4bf..d673fad80fe63 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -193,10 +193,12 @@ func generateMetadataHeaders(repo *repo_model.Repository) map[string]string { func generateSenderRecipientHeaders(doer, recipient *user_model.User) map[string]string { return map[string]string{ - "X-Gitea-Sender": doer.Name, - "X-Gitea-Recipient": recipient.Name, - "X-GitHub-Sender": doer.Name, - "X-GitHub-Recipient": recipient.Name, + "X-Gitea-Sender": doer.Name, + "X-Gitea-Recipient": recipient.Name, + "X-Gitea-Recipient-Address": recipient.Email, + "X-GitHub-Sender": doer.Name, + "X-GitHub-Recipient": recipient.Name, + "X-GitHub-Recipient-Address": recipient.Email, } } diff --git a/services/mailer/mail_issue_common.go b/services/mailer/mail_issue_common.go index 45ec7609b756e..32a85b690e594 100644 --- a/services/mailer/mail_issue_common.go +++ b/services/mailer/mail_issue_common.go @@ -312,10 +312,8 @@ func generateAdditionalHeadersForIssue(ctx *mailComment, reason string, recipien maps.Copy(headers, generateSenderRecipientHeaders(ctx.Doer, recipient)) maps.Copy(headers, generateReasonHeaders(reason)) - headers["X-Gitea-Recipient-Address"] = recipient.Email headers["X-Gitea-Issue-ID"] = issueID headers["X-Gitea-Issue-Link"] = ctx.Issue.HTMLURL() - headers["X-GitHub-Recipient-Address"] = recipient.Email headers["X-GitLab-Issue-IID"] = issueID return headers From 8100f63457bfe04b985ca1f4ccd1257209437b64 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:21:57 +0800 Subject: [PATCH 12/33] silly --- services/mailer/mail_workflow_run.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 1267a380d2407..a7a2285ba7473 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -52,6 +52,7 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { log.Error("GetRunJobsByRunID: %v", err) + return } else { sort.SliceStable(jobs, func(i, j int) bool { si := jobs[i].Status From 204f7ca8867096236eb98ff201b446fcbd8caf34 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:25:03 +0800 Subject: [PATCH 13/33] stop if cannot render template --- services/mailer/mail_workflow_run.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index a7a2285ba7473..9de9d60d82c53 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -113,6 +113,7 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep "Language": locale.Language(), }); err != nil { log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err) + return } msgs := make([]*sender_service.Message, 0, len(tos)) for _, rec := range tos { From d41aee8fa3594a546dc6500cad0281c1a15bc57e Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:27:25 +0800 Subject: [PATCH 14/33] 34982#discussion_r2191579495 --- services/mailer/mail_workflow_run.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 9de9d60d82c53..561e9f2e36a22 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -57,11 +57,8 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep 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 + if si == sj || si.IsSuccess() { + return false } return si < sj }) From 2bac8d86d72f12a9846fbfdabfbedb1ec9e134ac Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:59:30 +0800 Subject: [PATCH 15/33] terrible --- services/mailer/mail_workflow_run.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 561e9f2e36a22..f6eade8110d70 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -53,16 +53,13 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep if err != nil { log.Error("GetRunJobsByRunID: %v", err) return - } else { - sort.SliceStable(jobs, func(i, j int) bool { - si := jobs[i].Status - sj := jobs[j].Status - if si == sj || si.IsSuccess() { - return false - } - return si < sj - }) } + sort.SliceStable(jobs, func(i, j int) bool { + si := jobs[i].Status + sj := jobs[j].Status + return !(si == sj || si.IsSuccess()) && si < sj + }) + convertedJobs := make([]convertedWorkflowJob, 0, len(jobs)) for _, job := range jobs { converted0, err := convert.ToActionWorkflowJob(ctx, repo, nil, job) From cc6c8d1b0d00b690fd123bac8a381d52b1ebb98d Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:28:45 +0800 Subject: [PATCH 16/33] Any other recipient? --- services/mailer/mail_workflow_run.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index f6eade8110d70..8f642302aa20a 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -10,7 +10,6 @@ import ( "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" @@ -155,18 +154,6 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U } } - 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) - } - } + // TODO: Any other recipient? sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) } From 6a28be304eb309bcf300b42d50a23eb86ee3c141 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:58:47 +0800 Subject: [PATCH 17/33] style --- templates/mail/notify/workflow_run.tmpl | 83 ++++--------------------- 1 file changed, 13 insertions(+), 70 deletions(-) diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index 70ba74937a399..3b76d6110757a 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -4,87 +4,30 @@ {{.Subject}} - - -

    - {{.Repo.FullName}} {{.Run.WorkflowID}}: {{.RunStatusText}} + + +

    + {{.Repo.FullName}} {{.Run.WorkflowID}}: {{.RunStatusText}}

    -
      + + +
      -
      - + + + From 2fa624a32e7df81fe58d11b33d9c9695cf9b9133 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 9 Jul 2025 10:39:50 +0800 Subject: [PATCH 18/33] fix merge --- routers/web/devtest/mail_preview.go | 2 +- services/mailer/mail_workflow_run.go | 10 ++++++---- ...{activate.mock.yml => activate.devtest.yml} | 0 templates/mail/notify/workflow_run.devtest.yml | 18 ++++++++++++++++++ templates/mail/notify/workflow_run.tmpl | 4 ++-- 5 files changed, 27 insertions(+), 7 deletions(-) rename templates/mail/auth/{activate.mock.yml => activate.devtest.yml} (100%) create mode 100644 templates/mail/notify/workflow_run.devtest.yml diff --git a/routers/web/devtest/mail_preview.go b/routers/web/devtest/mail_preview.go index 79dd441eab916..d6bade15d7add 100644 --- a/routers/web/devtest/mail_preview.go +++ b/routers/web/devtest/mail_preview.go @@ -16,7 +16,7 @@ import ( func MailPreviewRender(ctx *context.Context) { tmplName := ctx.PathParam("*") - mockDataContent, err := templates.AssetFS().ReadFile("mail/" + tmplName + ".mock.yml") + mockDataContent, err := templates.AssetFS().ReadFile("mail/" + tmplName + ".devtest.yml") mockData := map[string]any{} if err == nil { err = yaml.Unmarshal(mockDataContent, &mockData) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 8f642302aa20a..d57784cfcaf87 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -54,9 +54,11 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep return } sort.SliceStable(jobs, func(i, j int) bool { - si := jobs[i].Status - sj := jobs[j].Status - return !(si == sj || si.IsSuccess()) && si < sj + si, sj := jobs[i].Status, jobs[j].Status + if si != sj || sj.IsSuccess() /* if not equal, then success is the max */ { + return true + } + return si < sj }) convertedJobs := make([]convertedWorkflowJob, 0, len(jobs)) @@ -96,7 +98,7 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep runStatusText = "All jobs have been cancelled" } var mailBody bytes.Buffer - if err := bodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{ + if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{ "Subject": subject, "Repo": repo, "Run": run, diff --git a/templates/mail/auth/activate.mock.yml b/templates/mail/auth/activate.devtest.yml similarity index 100% rename from templates/mail/auth/activate.mock.yml rename to templates/mail/auth/activate.devtest.yml diff --git a/templates/mail/notify/workflow_run.devtest.yml b/templates/mail/notify/workflow_run.devtest.yml new file mode 100644 index 0000000000000..1e285be328ba2 --- /dev/null +++ b/templates/mail/notify/workflow_run.devtest.yml @@ -0,0 +1,18 @@ +RunStatusText: run status text .... + +Repo: + FullName: RepoName + +Run: + WorkflowID: WorkflowID + HTMLURL: http://localhost/run/1 + +Jobs: + - Name: Job-Name-1 + Status: success + Attempt: 1 + HTMLURL: http://localhost/job/1 + - Name: Job-Name-2 + Status: failed + Attempt: 2 + HTMLURL: http://localhost/job/2 diff --git a/templates/mail/notify/workflow_run.tmpl b/templates/mail/notify/workflow_run.tmpl index 3b76d6110757a..f6dd8ad510e33 100644 --- a/templates/mail/notify/workflow_run.tmpl +++ b/templates/mail/notify/workflow_run.tmpl @@ -14,7 +14,7 @@
        {{range $job := .Jobs}}
      • - + {{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}}
      • @@ -24,7 +24,7 @@
        From 0a92da5719b212921ee1a7b3bf432bdfed8ddfa5 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Thu, 10 Jul 2025 02:12:07 +0800 Subject: [PATCH 19/33] sort --- services/mailer/mail_workflow_run.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index d57784cfcaf87..9cb1be4626d99 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -55,8 +55,13 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep } sort.SliceStable(jobs, func(i, j int) bool { si, sj := jobs[i].Status, jobs[j].Status - if si != sj || sj.IsSuccess() /* if not equal, then success is the max */ { - return true + /* + If both i and j are/are not success, leave it to si < sj. + If i is success and j is not, since the desired is j goes "smaller" and i goes "bigger", this func should return false. + If j is success and i is not, since the desired is i goes "smaller" and j goes "bigger", this func should return true. + */ + if si.IsSuccess() != sj.IsSuccess() { + return !si.IsSuccess() } return si < sj }) From 540c3f00eb59a5c7a3c327da6807fce0ece1bc9d Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:09:54 +0800 Subject: [PATCH 20/33] DATABASE --- .../fixtures/user_notification_settings.yml | 84 +++++++++++++++++++ models/migrations/migrations.go | 1 + models/migrations/v1_24/v321.go | 59 +++++++++++++ models/user/user.go | 6 ++ models/user/user_notification.go | 54 ++++++++++++ models/user/user_notification_test.go | 37 ++++++++ services/user/delete.go | 1 + services/user/update.go | 12 +++ services/user/update_test.go | 22 +++++ 9 files changed, 276 insertions(+) create mode 100644 models/fixtures/user_notification_settings.yml create mode 100644 models/migrations/v1_24/v321.go create mode 100644 models/user/user_notification.go create mode 100644 models/user/user_notification_test.go diff --git a/models/fixtures/user_notification_settings.yml b/models/fixtures/user_notification_settings.yml new file mode 100644 index 0000000000000..57df18f513b77 --- /dev/null +++ b/models/fixtures/user_notification_settings.yml @@ -0,0 +1,84 @@ +- user_id: 1 + actions: failureonly +- user_id: 2 + actions: failureonly +- user_id: 3 + actions: failureonly +- user_id: 4 + actions: failureonly +- user_id: 5 + actions: failureonly +- user_id: 6 + actions: failureonly +- user_id: 7 + actions: failureonly +- user_id: 8 + actions: failureonly +- user_id: 9 + actions: failureonly +- user_id: 10 + actions: failureonly +- user_id: 11 + actions: failureonly +- user_id: 12 + actions: failureonly +- user_id: 13 + actions: failureonly +- user_id: 14 + actions: failureonly +- user_id: 15 + actions: failureonly +- user_id: 16 + actions: failureonly +- user_id: 17 + actions: failureonly +- user_id: 18 + actions: failureonly +- user_id: 19 + actions: failureonly +- user_id: 20 + actions: failureonly +- user_id: 21 + actions: failureonly +- user_id: 22 + actions: failureonly +- user_id: 23 + actions: failureonly +- user_id: 24 + actions: failureonly +- user_id: 25 + actions: failureonly +- user_id: 26 + actions: failureonly +- user_id: 27 + actions: failureonly +- user_id: 28 + actions: failureonly +- user_id: 29 + actions: failureonly +- user_id: 30 + actions: failureonly +- user_id: 31 + actions: failureonly +- user_id: 32 + actions: failureonly +- user_id: 33 + actions: failureonly +- user_id: 34 + actions: failureonly +- user_id: 35 + actions: failureonly +- user_id: 36 + actions: failureonly +- user_id: 37 + actions: failureonly +- user_id: 38 + actions: failureonly +- user_id: 39 + actions: failureonly +- user_id: 40 + actions: failureonly +- user_id: 41 + actions: failureonly +- user_id: 42 + actions: failureonly diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 176372486e8f6..46659bf247df8 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -382,6 +382,7 @@ func prepareMigrationTasks() []*migration { newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode), newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable), newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor), + newMigration(321, "Add new table for fine-grained notification settings", v1_24.AddFineGrainedActionsNotificationSettings), } return preparedMigrations } diff --git a/models/migrations/v1_24/v321.go b/models/migrations/v1_24/v321.go new file mode 100644 index 0000000000000..3e26f0f961f68 --- /dev/null +++ b/models/migrations/v1_24/v321.go @@ -0,0 +1,59 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_24 + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + + "xorm.io/xorm" +) + +type NotificationSettings struct { + UserID int64 `xorm:"pk"` + Actions string `xorm:"NOT NULL DEFAULT 'failureonly'"` +} + +func (*NotificationSettings) TableName() string { + return "user_notification_settings" +} + +func AddFineGrainedActionsNotificationSettings(x *xorm.Engine) error { + if err := x.Sync(&NotificationSettings{}); err != nil { + return err + } + + settings := make([]NotificationSettings, 0, 100) + + type User struct { + ID int64 `xorm:"pk autoincr"` + } + + if err := db.Iterate(context.Background(), nil, func(ctx context.Context, user *User) error { + settings = append(settings, NotificationSettings{ + UserID: user.ID, + Actions: user_model.NotificationActionsFailureOnly, + }) + if len(settings) >= 100 { + _, err := x.Insert(&settings) + if err != nil { + return err + } + settings = settings[:0] + } + return nil + }); err != nil { + return err + } + + if len(settings) > 0 { + if _, err := x.Insert(&settings); err != nil { + return err + } + } + + return nil +} diff --git a/models/user/user.go b/models/user/user.go index c362cbc6d2b55..7074cdba6d0d6 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -798,6 +798,12 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o return err } + if err := db.Insert(ctx, &NotificationSettings{ + UserID: u.ID, + }); err != nil { + return err + } + return committer.Commit() } diff --git a/models/user/user_notification.go b/models/user/user_notification.go new file mode 100644 index 0000000000000..37f30f9c6c5dc --- /dev/null +++ b/models/user/user_notification.go @@ -0,0 +1,54 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "context" + + "code.gitea.io/gitea/models/db" +) + +const ( + NotificationActionsAll = "all" + NotificationActionsFailureOnly = "failureonly" + NotificationActionsDisabled = "disabled" +) + +type NotificationSettings struct { + UserID int64 `xorm:"pk"` + User *User `xorm:"-"` + Actions string `xorm:"NOT NULL DEFAULT 'failureonly'"` +} + +func (NotificationSettings) TableName() string { + return "user_notification_settings" +} + +func init() { + db.RegisterModel(new(NotificationSettings)) +} + +// GetUserNotificationSettings returns a user's fine-grained notification preference +func GetUserNotificationSettings(ctx context.Context, userID int64) (*NotificationSettings, error) { + settings := &NotificationSettings{} + if has, err := db.GetEngine(ctx).Where("user_id=?", userID).Get(settings); err != nil { + return nil, err + } else if !has { + return nil, nil + } + user, err := GetUserByID(ctx, userID) + if err != nil { + return nil, err + } + settings.User = user + return settings, nil +} + +func UpdateUserNotificationSettings(ctx context.Context, settings *NotificationSettings) error { + _, err := db.GetEngine(ctx).Where("user_id = ?", settings.UserID). + Update(&NotificationSettings{ + Actions: settings.Actions, + }) + return err +} diff --git a/models/user/user_notification_test.go b/models/user/user_notification_test.go new file mode 100644 index 0000000000000..396b03fd4dae0 --- /dev/null +++ b/models/user/user_notification_test.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestUserNotificationSettings(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + settings, err := GetUserNotificationSettings(db.DefaultContext, 1) + assert.NoError(t, err) + assert.Equal(t, NotificationActionsFailureOnly, settings.Actions) + + assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ + UserID: 1, + Actions: NotificationActionsAll, + })) + settings, err = GetUserNotificationSettings(db.DefaultContext, 1) + assert.NoError(t, err) + assert.Equal(t, NotificationActionsAll, settings.Actions) + + assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ + UserID: 1, + Actions: NotificationActionsDisabled, + })) + settings, err = GetUserNotificationSettings(db.DefaultContext, 1) + assert.NoError(t, err) + assert.Equal(t, NotificationActionsDisabled, settings.Actions) +} diff --git a/services/user/delete.go b/services/user/delete.go index 39c6ef052dca7..49f8403c7e0c7 100644 --- a/services/user/delete.go +++ b/services/user/delete.go @@ -95,6 +95,7 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) &user_model.Blocking{BlockerID: u.ID}, &user_model.Blocking{BlockeeID: u.ID}, &actions_model.ActionRunnerToken{OwnerID: u.ID}, + &user_model.NotificationSettings{UserID: u.ID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/services/user/update.go b/services/user/update.go index d7354542bf680..2aef5c1daab7a 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -244,3 +244,15 @@ func UpdateAuth(ctx context.Context, u *user_model.User, opts *UpdateAuthOptions } return nil } + +type UpdateNotificationSettingsOptions struct { + Actions optional.Option[string] +} + +func UpdateNotificationSettings(ctx context.Context, settings *user_model.NotificationSettings, opts *UpdateNotificationSettingsOptions) error { + if opts.Actions.Has() { + settings.Actions = opts.Actions.Value() + } + + return user_model.UpdateUserNotificationSettings(ctx, settings) +} diff --git a/services/user/update_test.go b/services/user/update_test.go index 27513e80409bb..599c3fd2b6476 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -122,3 +122,25 @@ func TestUpdateAuth(t *testing.T) { Password: optional.Some("aaaa"), }), password_module.ErrMinLength) } + +func TestUpdateNotificationSettings(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + settings := &user_model.NotificationSettings{UserID: 2} + exists, err := db.GetEngine(db.DefaultContext).Get(settings) + assert.NoError(t, err) + assert.True(t, exists) + settingsCopy := *settings + + assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings, &UpdateNotificationSettingsOptions{ + Actions: optional.Some(user_model.NotificationActionsAll), + })) + assert.Equal(t, user_model.NotificationActionsAll, settings.Actions) + assert.NotEqual(t, settingsCopy.Actions, settings.Actions) + + assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings, &UpdateNotificationSettingsOptions{ + Actions: optional.Some(user_model.NotificationActionsDisabled), + })) + assert.Equal(t, user_model.NotificationActionsDisabled, settings.Actions) + assert.NotEqual(t, settingsCopy.Actions, settings.Actions) +} From 4f31cd88f74593b01c44d995bfff1c412599ae84 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:01:11 +0800 Subject: [PATCH 21/33] UI --- models/migrations/v1_24/v321.go | 2 +- models/user/user_notification.go | 7 ++-- models/user/user_notification_test.go | 10 +++--- routers/web/user/setting/notifications.go | 37 +++++++++++++++++++++- routers/web/web.go | 1 + services/user/update_test.go | 8 ++--- templates/user/settings/notifications.tmpl | 31 ++++++++++++++++++ 7 files changed, 82 insertions(+), 14 deletions(-) diff --git a/models/migrations/v1_24/v321.go b/models/migrations/v1_24/v321.go index 3e26f0f961f68..430b6c1adce2f 100644 --- a/models/migrations/v1_24/v321.go +++ b/models/migrations/v1_24/v321.go @@ -35,7 +35,7 @@ func AddFineGrainedActionsNotificationSettings(x *xorm.Engine) error { if err := db.Iterate(context.Background(), nil, func(ctx context.Context, user *User) error { settings = append(settings, NotificationSettings{ UserID: user.ID, - Actions: user_model.NotificationActionsFailureOnly, + Actions: user_model.NotificationGiteaActionsFailureOnly, }) if len(settings) >= 100 { _, err := x.Insert(&settings) diff --git a/models/user/user_notification.go b/models/user/user_notification.go index 37f30f9c6c5dc..2a13c6cc9c6be 100644 --- a/models/user/user_notification.go +++ b/models/user/user_notification.go @@ -9,10 +9,11 @@ import ( "code.gitea.io/gitea/models/db" ) +// Actions email preference const ( - NotificationActionsAll = "all" - NotificationActionsFailureOnly = "failureonly" - NotificationActionsDisabled = "disabled" + NotificationGiteaActionsAll = "all" + NotificationGiteaActionsFailureOnly = "failureonly" + NotificationGiteaActionsDisabled = "disabled" ) type NotificationSettings struct { diff --git a/models/user/user_notification_test.go b/models/user/user_notification_test.go index 396b03fd4dae0..2dbd0a01b33b5 100644 --- a/models/user/user_notification_test.go +++ b/models/user/user_notification_test.go @@ -17,21 +17,21 @@ func TestUserNotificationSettings(t *testing.T) { settings, err := GetUserNotificationSettings(db.DefaultContext, 1) assert.NoError(t, err) - assert.Equal(t, NotificationActionsFailureOnly, settings.Actions) + assert.Equal(t, NotificationGiteaActionsFailureOnly, settings.Actions) assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ UserID: 1, - Actions: NotificationActionsAll, + Actions: NotificationGiteaActionsAll, })) settings, err = GetUserNotificationSettings(db.DefaultContext, 1) assert.NoError(t, err) - assert.Equal(t, NotificationActionsAll, settings.Actions) + assert.Equal(t, NotificationGiteaActionsAll, settings.Actions) assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ UserID: 1, - Actions: NotificationActionsDisabled, + Actions: NotificationGiteaActionsDisabled, })) settings, err = GetUserNotificationSettings(db.DefaultContext, 1) assert.NoError(t, err) - assert.Equal(t, NotificationActionsDisabled, settings.Actions) + assert.Equal(t, NotificationGiteaActionsDisabled, settings.Actions) } diff --git a/routers/web/user/setting/notifications.go b/routers/web/user/setting/notifications.go index 16e58a0481a64..27fbebf721b89 100644 --- a/routers/web/user/setting/notifications.go +++ b/routers/web/user/setting/notifications.go @@ -29,6 +29,13 @@ func Notifications(ctx *context.Context) { ctx.Data["PageIsSettingsNotifications"] = true ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference + fineGrainedPreference, err := user_model.GetUserNotificationSettings(ctx, ctx.Doer.ID) + if err != nil { + ctx.ServerError("GetUserNotificationSettings", err) + return + } + ctx.Data["ActionsEmailNotificationsPreference"] = fineGrainedPreference.Actions + ctx.HTML(http.StatusOK, tplSettingsNotifications) } @@ -45,7 +52,7 @@ func NotificationsEmailPost(ctx *context.Context) { preference == user_model.EmailNotificationsDisabled || preference == user_model.EmailNotificationsAndYourOwn) { log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name) - ctx.ServerError("SetEmailPreference", errors.New("option unrecognized")) + ctx.ServerError("NotificationsEmailPost", errors.New("option unrecognized")) return } opts := &user.UpdateOptions{ @@ -60,3 +67,31 @@ func NotificationsEmailPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success")) ctx.Redirect(setting.AppSubURL + "/user/settings/notifications") } + +// NotificationsActionsEmailPost set user's email notification preference on Gitea Actions +func NotificationsActionsEmailPost(ctx *context.Context) { + if !ctx.GetContextValue("EnableActions").(bool) { + ctx.NotFound(nil) + return + } + + preference := ctx.FormString("preference") + if !(preference == user_model.NotificationGiteaActionsAll || + preference == user_model.NotificationGiteaActionsDisabled || + preference == user_model.NotificationGiteaActionsFailureOnly) { + log.Error("Actions Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name) + ctx.ServerError("NotificationsActionsEmailPost", errors.New("option unrecognized")) + return + } + opts := &user.UpdateNotificationSettingsOptions{ + Actions: optional.Some(preference), + } + if err := user.UpdateNotificationSettings(ctx, new(user_model.NotificationSettings), opts); err != nil { + log.Error("Cannot set actions email notifications preference: %v", err) + ctx.ServerError("UpdateNotificationSettings", err) + return + } + log.Trace("Actions email notifications preference made %s: %s", preference, ctx.Doer.Name) + ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success")) + ctx.Redirect(setting.AppSubURL + "/user/settings/notifications") +} diff --git a/routers/web/web.go b/routers/web/web.go index b9c7013f6395f..f8612db504dc5 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -598,6 +598,7 @@ func registerWebRoutes(m *web.Router) { m.Group("/notifications", func() { m.Get("", user_setting.Notifications) m.Post("/email", user_setting.NotificationsEmailPost) + m.Post("/actions", user_setting.NotificationsActionsEmailPost) }) m.Group("/security", func() { m.Get("", security.Security) diff --git a/services/user/update_test.go b/services/user/update_test.go index 599c3fd2b6476..e8a7ad8c046ad 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -133,14 +133,14 @@ func TestUpdateNotificationSettings(t *testing.T) { settingsCopy := *settings assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings, &UpdateNotificationSettingsOptions{ - Actions: optional.Some(user_model.NotificationActionsAll), + Actions: optional.Some(user_model.NotificationGiteaActionsAll), })) - assert.Equal(t, user_model.NotificationActionsAll, settings.Actions) + assert.Equal(t, user_model.NotificationGiteaActionsAll, settings.Actions) assert.NotEqual(t, settingsCopy.Actions, settings.Actions) assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings, &UpdateNotificationSettingsOptions{ - Actions: optional.Some(user_model.NotificationActionsDisabled), + Actions: optional.Some(user_model.NotificationGiteaActionsDisabled), })) - assert.Equal(t, user_model.NotificationActionsDisabled, settings.Actions) + assert.Equal(t, user_model.NotificationGiteaActionsDisabled, settings.Actions) assert.NotEqual(t, settingsCopy.Actions, settings.Actions) } diff --git a/templates/user/settings/notifications.tmpl b/templates/user/settings/notifications.tmpl index 4694bbb30a7ae..db00fe59a4b40 100644 --- a/templates/user/settings/notifications.tmpl +++ b/templates/user/settings/notifications.tmpl @@ -29,6 +29,37 @@
      + + {{if .EnableActions}} +

      + Actions +

      +
      +
      +
      +
      + {{$.CsrfTokenHtml}} +
      + + +
      +
      + +
      +
      +
      +
      +
      + {{end}} {{template "user/settings/layout_footer" .}} From 32ad9ba89c223419203ba3ceb22af49644aebf88 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:13:20 +0800 Subject: [PATCH 22/33] update --- services/mailer/mail_workflow_run.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 9cb1be4626d99..0350498fa9a10 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -149,14 +149,18 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U recipients := make([]*user_model.User, 0) if !sender.IsGiteaActions() && !sender.IsGhost() && sender.IsMailable() { + notifyPref, err := user_model.GetUserNotificationSettings(ctx, sender.ID) + if err != nil { + log.Error("GetUserNotificationSettings: %v", err) + return + } if run.Status.IsSuccess() { - if sender.EmailNotificationsPreference == user_model.EmailNotificationsAndYourOwn { + if notifyPref.Actions == user_model.NotificationGiteaActionsAll { recipients = append(recipients, sender) } sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) return - } else if sender.EmailNotificationsPreference != user_model.EmailNotificationsOnMention && - sender.EmailNotificationsPreference != user_model.EmailNotificationsDisabled { + } else if notifyPref.Actions != user_model.EmailNotificationsDisabled { recipients = append(recipients, sender) } } From 07602a092152f69221ff36ee78b2664760f46ce3 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Fri, 11 Jul 2025 18:02:07 +0800 Subject: [PATCH 23/33] fix --- models/user/user.go | 3 ++- models/user/user_notification_test.go | 6 ++++++ routers/web/user/setting/notifications.go | 5 +++-- services/mailer/mail_workflow_run.go | 3 +-- services/user/update.go | 5 ++++- services/user/update_test.go | 15 ++++++++++----- templates/user/settings/notifications.tmpl | 4 ++-- 7 files changed, 28 insertions(+), 13 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index 7074cdba6d0d6..c70fe258e4eff 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -799,7 +799,8 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o } if err := db.Insert(ctx, &NotificationSettings{ - UserID: u.ID, + UserID: u.ID, + Actions: NotificationGiteaActionsFailureOnly, }); err != nil { return err } diff --git a/models/user/user_notification_test.go b/models/user/user_notification_test.go index 2dbd0a01b33b5..b81119287e9ea 100644 --- a/models/user/user_notification_test.go +++ b/models/user/user_notification_test.go @@ -17,6 +17,8 @@ func TestUserNotificationSettings(t *testing.T) { settings, err := GetUserNotificationSettings(db.DefaultContext, 1) assert.NoError(t, err) + assert.NotNil(t, settings.User) + assert.Equal(t, settings.User.ID, settings.UserID) assert.Equal(t, NotificationGiteaActionsFailureOnly, settings.Actions) assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ @@ -25,6 +27,8 @@ func TestUserNotificationSettings(t *testing.T) { })) settings, err = GetUserNotificationSettings(db.DefaultContext, 1) assert.NoError(t, err) + assert.NotNil(t, settings.User) + assert.Equal(t, settings.User.ID, settings.UserID) assert.Equal(t, NotificationGiteaActionsAll, settings.Actions) assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ @@ -33,5 +37,7 @@ func TestUserNotificationSettings(t *testing.T) { })) settings, err = GetUserNotificationSettings(db.DefaultContext, 1) assert.NoError(t, err) + assert.NotNil(t, settings.User) + assert.Equal(t, settings.User.ID, settings.UserID) assert.Equal(t, NotificationGiteaActionsDisabled, settings.Actions) } diff --git a/routers/web/user/setting/notifications.go b/routers/web/user/setting/notifications.go index 27fbebf721b89..3f42b2d4146cb 100644 --- a/routers/web/user/setting/notifications.go +++ b/routers/web/user/setting/notifications.go @@ -7,6 +7,7 @@ import ( "errors" "net/http" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -70,7 +71,7 @@ func NotificationsEmailPost(ctx *context.Context) { // NotificationsActionsEmailPost set user's email notification preference on Gitea Actions func NotificationsActionsEmailPost(ctx *context.Context) { - if !ctx.GetContextValue("EnableActions").(bool) { + if !setting.Actions.Enabled || unit.TypeActions.UnitGlobalDisabled() { ctx.NotFound(nil) return } @@ -86,7 +87,7 @@ func NotificationsActionsEmailPost(ctx *context.Context) { opts := &user.UpdateNotificationSettingsOptions{ Actions: optional.Some(preference), } - if err := user.UpdateNotificationSettings(ctx, new(user_model.NotificationSettings), opts); err != nil { + if err := user.UpdateNotificationSettings(ctx, ctx.Doer.ID, opts); err != nil { log.Error("Cannot set actions email notifications preference: %v", err) ctx.ServerError("UpdateNotificationSettings", err) return diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 0350498fa9a10..c91292841a50c 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -160,11 +160,10 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U } sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) return - } else if notifyPref.Actions != user_model.EmailNotificationsDisabled { + } else if notifyPref.Actions != user_model.NotificationGiteaActionsDisabled { recipients = append(recipients, sender) } } - // TODO: Any other recipient? sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) } diff --git a/services/user/update.go b/services/user/update.go index 2aef5c1daab7a..4fcebbc44c0a4 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -249,7 +249,10 @@ type UpdateNotificationSettingsOptions struct { Actions optional.Option[string] } -func UpdateNotificationSettings(ctx context.Context, settings *user_model.NotificationSettings, opts *UpdateNotificationSettingsOptions) error { +func UpdateNotificationSettings(ctx context.Context, userID int64, opts *UpdateNotificationSettingsOptions) error { + settings := &user_model.NotificationSettings{ + UserID: userID, + } if opts.Actions.Has() { settings.Actions = opts.Actions.Value() } diff --git a/services/user/update_test.go b/services/user/update_test.go index e8a7ad8c046ad..e7fca20920a9e 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -130,17 +130,22 @@ func TestUpdateNotificationSettings(t *testing.T) { exists, err := db.GetEngine(db.DefaultContext).Get(settings) assert.NoError(t, err) assert.True(t, exists) - settingsCopy := *settings - assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings, &UpdateNotificationSettingsOptions{ + assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings.UserID, &UpdateNotificationSettingsOptions{ Actions: optional.Some(user_model.NotificationGiteaActionsAll), })) + settings, err = user_model.GetUserNotificationSettings(db.DefaultContext, settings.UserID) + assert.NoError(t, err) + assert.NotNil(t, settings) assert.Equal(t, user_model.NotificationGiteaActionsAll, settings.Actions) - assert.NotEqual(t, settingsCopy.Actions, settings.Actions) + assert.NotEqual(t, user_model.NotificationGiteaActionsFailureOnly, settings.Actions) - assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings, &UpdateNotificationSettingsOptions{ + assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings.UserID, &UpdateNotificationSettingsOptions{ Actions: optional.Some(user_model.NotificationGiteaActionsDisabled), })) + settings, err = user_model.GetUserNotificationSettings(db.DefaultContext, settings.UserID) + assert.NoError(t, err) + assert.NotNil(t, settings) assert.Equal(t, user_model.NotificationGiteaActionsDisabled, settings.Actions) - assert.NotEqual(t, settingsCopy.Actions, settings.Actions) + assert.NotEqual(t, user_model.NotificationGiteaActionsFailureOnly, settings.Actions) } diff --git a/templates/user/settings/notifications.tmpl b/templates/user/settings/notifications.tmpl index db00fe59a4b40..117149380bc85 100644 --- a/templates/user/settings/notifications.tmpl +++ b/templates/user/settings/notifications.tmpl @@ -46,8 +46,8 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
      From d92ec015c495551bd20c1ab3df85456547604a53 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Sat, 12 Jul 2025 00:40:45 +0800 Subject: [PATCH 24/33] locale --- options/locale/locale_en-US.ini | 2 ++ templates/user/settings/notifications.tmpl | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ff32c94ff93e9..937eec14561c9 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1021,6 +1021,8 @@ email_notifications.onmention = Only Email on Mention email_notifications.disable = Disable Email Notifications email_notifications.submit = Set Email Preference email_notifications.andyourown = And Your Own Notifications +email_notifications.actions.desc = Notifications for workflow runs on repositories set up with Gitea Actions. +email_notifications.actions.failureonly = Only notify for failed workflow runs visibility = User visibility visibility.public = Public diff --git a/templates/user/settings/notifications.tmpl b/templates/user/settings/notifications.tmpl index 117149380bc85..a37747f391e07 100644 --- a/templates/user/settings/notifications.tmpl +++ b/templates/user/settings/notifications.tmpl @@ -32,7 +32,7 @@ {{if .EnableActions}}

      - Actions + {{ctx.Locale.Tr "actions.actions"}}

      @@ -40,14 +40,14 @@
      {{$.CsrfTokenHtml}}
      - + From fed3ade25446d6a0938e92f7d1fb18d70255631a Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:04:39 +0800 Subject: [PATCH 25/33] rewrite --- .../fixtures/user_notification_settings.yml | 84 ------------------- models/migrations/migrations.go | 1 - models/migrations/v1_24/v321.go | 59 ------------- models/user/email_notification.go | 11 +++ models/user/email_notification_test.go | 34 ++++++++ models/user/setting_keys.go | 2 + models/user/user.go | 5 +- models/user/user_notification.go | 55 ------------ models/user/user_notification_test.go | 43 ---------- routers/web/user/setting/notifications.go | 23 ++--- services/mailer/mail_workflow_run.go | 9 +- services/user/delete.go | 1 - services/user/update.go | 15 ---- services/user/update_test.go | 27 ------ 14 files changed, 66 insertions(+), 303 deletions(-) delete mode 100644 models/fixtures/user_notification_settings.yml delete mode 100644 models/migrations/v1_24/v321.go create mode 100644 models/user/email_notification.go create mode 100644 models/user/email_notification_test.go delete mode 100644 models/user/user_notification.go delete mode 100644 models/user/user_notification_test.go diff --git a/models/fixtures/user_notification_settings.yml b/models/fixtures/user_notification_settings.yml deleted file mode 100644 index 57df18f513b77..0000000000000 --- a/models/fixtures/user_notification_settings.yml +++ /dev/null @@ -1,84 +0,0 @@ -- user_id: 1 - actions: failureonly -- user_id: 2 - actions: failureonly -- user_id: 3 - actions: failureonly -- user_id: 4 - actions: failureonly -- user_id: 5 - actions: failureonly -- user_id: 6 - actions: failureonly -- user_id: 7 - actions: failureonly -- user_id: 8 - actions: failureonly -- user_id: 9 - actions: failureonly -- user_id: 10 - actions: failureonly -- user_id: 11 - actions: failureonly -- user_id: 12 - actions: failureonly -- user_id: 13 - actions: failureonly -- user_id: 14 - actions: failureonly -- user_id: 15 - actions: failureonly -- user_id: 16 - actions: failureonly -- user_id: 17 - actions: failureonly -- user_id: 18 - actions: failureonly -- user_id: 19 - actions: failureonly -- user_id: 20 - actions: failureonly -- user_id: 21 - actions: failureonly -- user_id: 22 - actions: failureonly -- user_id: 23 - actions: failureonly -- user_id: 24 - actions: failureonly -- user_id: 25 - actions: failureonly -- user_id: 26 - actions: failureonly -- user_id: 27 - actions: failureonly -- user_id: 28 - actions: failureonly -- user_id: 29 - actions: failureonly -- user_id: 30 - actions: failureonly -- user_id: 31 - actions: failureonly -- user_id: 32 - actions: failureonly -- user_id: 33 - actions: failureonly -- user_id: 34 - actions: failureonly -- user_id: 35 - actions: failureonly -- user_id: 36 - actions: failureonly -- user_id: 37 - actions: failureonly -- user_id: 38 - actions: failureonly -- user_id: 39 - actions: failureonly -- user_id: 40 - actions: failureonly -- user_id: 41 - actions: failureonly -- user_id: 42 - actions: failureonly diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 46659bf247df8..176372486e8f6 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -382,7 +382,6 @@ func prepareMigrationTasks() []*migration { newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode), newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable), newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor), - newMigration(321, "Add new table for fine-grained notification settings", v1_24.AddFineGrainedActionsNotificationSettings), } return preparedMigrations } diff --git a/models/migrations/v1_24/v321.go b/models/migrations/v1_24/v321.go deleted file mode 100644 index 430b6c1adce2f..0000000000000 --- a/models/migrations/v1_24/v321.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package v1_24 - -import ( - "context" - - "code.gitea.io/gitea/models/db" - user_model "code.gitea.io/gitea/models/user" - - "xorm.io/xorm" -) - -type NotificationSettings struct { - UserID int64 `xorm:"pk"` - Actions string `xorm:"NOT NULL DEFAULT 'failureonly'"` -} - -func (*NotificationSettings) TableName() string { - return "user_notification_settings" -} - -func AddFineGrainedActionsNotificationSettings(x *xorm.Engine) error { - if err := x.Sync(&NotificationSettings{}); err != nil { - return err - } - - settings := make([]NotificationSettings, 0, 100) - - type User struct { - ID int64 `xorm:"pk autoincr"` - } - - if err := db.Iterate(context.Background(), nil, func(ctx context.Context, user *User) error { - settings = append(settings, NotificationSettings{ - UserID: user.ID, - Actions: user_model.NotificationGiteaActionsFailureOnly, - }) - if len(settings) >= 100 { - _, err := x.Insert(&settings) - if err != nil { - return err - } - settings = settings[:0] - } - return nil - }); err != nil { - return err - } - - if len(settings) > 0 { - if _, err := x.Insert(&settings); err != nil { - return err - } - } - - return nil -} diff --git a/models/user/email_notification.go b/models/user/email_notification.go new file mode 100644 index 0000000000000..c6faa2690960d --- /dev/null +++ b/models/user/email_notification.go @@ -0,0 +1,11 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +// setting values +const ( + EmailNotificationGiteaActionsAll = "all" + EmailNotificationGiteaActionsFailureOnly = "failureonly" // Default for actions email preference + EmailNotificationGiteaActionsDisabled = "disabled" +) diff --git a/models/user/email_notification_test.go b/models/user/email_notification_test.go new file mode 100644 index 0000000000000..a099e10b246df --- /dev/null +++ b/models/user/email_notification_test.go @@ -0,0 +1,34 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestNotificationSettings(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + u := unittest.AssertExistsAndLoadBean(t, &User{ID: 1}) + + assert.NoError(t, SetUserSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsAll)) + settings, err := GetSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions) + assert.NoError(t, err) + assert.Equal(t, EmailNotificationGiteaActionsAll, settings) + + assert.NoError(t, SetUserSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsDisabled)) + settings, err = GetSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions) + assert.NoError(t, err) + assert.Equal(t, EmailNotificationGiteaActionsDisabled, settings) + + assert.NoError(t, SetUserSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsFailureOnly)) + settings, err = GetSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions) + assert.NoError(t, err) + assert.Equal(t, EmailNotificationGiteaActionsFailureOnly, settings) +} diff --git a/models/user/setting_keys.go b/models/user/setting_keys.go index 2c2ed078beabb..1505c8aa3538d 100644 --- a/models/user/setting_keys.go +++ b/models/user/setting_keys.go @@ -21,4 +21,6 @@ const ( SignupUserAgent = "signup.user_agent" SettingsKeyCodeViewShowFileTree = "code_view.show_file_tree" + + SettingsEmailNotificationGiteaActions = "email_notifications.actions" ) diff --git a/models/user/user.go b/models/user/user.go index c70fe258e4eff..f8d74924b5db5 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -798,10 +798,7 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o return err } - if err := db.Insert(ctx, &NotificationSettings{ - UserID: u.ID, - Actions: NotificationGiteaActionsFailureOnly, - }); err != nil { + if err := SetUserSetting(ctx, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsFailureOnly); err != nil { return err } diff --git a/models/user/user_notification.go b/models/user/user_notification.go deleted file mode 100644 index 2a13c6cc9c6be..0000000000000 --- a/models/user/user_notification.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user - -import ( - "context" - - "code.gitea.io/gitea/models/db" -) - -// Actions email preference -const ( - NotificationGiteaActionsAll = "all" - NotificationGiteaActionsFailureOnly = "failureonly" - NotificationGiteaActionsDisabled = "disabled" -) - -type NotificationSettings struct { - UserID int64 `xorm:"pk"` - User *User `xorm:"-"` - Actions string `xorm:"NOT NULL DEFAULT 'failureonly'"` -} - -func (NotificationSettings) TableName() string { - return "user_notification_settings" -} - -func init() { - db.RegisterModel(new(NotificationSettings)) -} - -// GetUserNotificationSettings returns a user's fine-grained notification preference -func GetUserNotificationSettings(ctx context.Context, userID int64) (*NotificationSettings, error) { - settings := &NotificationSettings{} - if has, err := db.GetEngine(ctx).Where("user_id=?", userID).Get(settings); err != nil { - return nil, err - } else if !has { - return nil, nil - } - user, err := GetUserByID(ctx, userID) - if err != nil { - return nil, err - } - settings.User = user - return settings, nil -} - -func UpdateUserNotificationSettings(ctx context.Context, settings *NotificationSettings) error { - _, err := db.GetEngine(ctx).Where("user_id = ?", settings.UserID). - Update(&NotificationSettings{ - Actions: settings.Actions, - }) - return err -} diff --git a/models/user/user_notification_test.go b/models/user/user_notification_test.go deleted file mode 100644 index b81119287e9ea..0000000000000 --- a/models/user/user_notification_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user - -import ( - "testing" - - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/unittest" - - "github.com/stretchr/testify/assert" -) - -func TestUserNotificationSettings(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - settings, err := GetUserNotificationSettings(db.DefaultContext, 1) - assert.NoError(t, err) - assert.NotNil(t, settings.User) - assert.Equal(t, settings.User.ID, settings.UserID) - assert.Equal(t, NotificationGiteaActionsFailureOnly, settings.Actions) - - assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ - UserID: 1, - Actions: NotificationGiteaActionsAll, - })) - settings, err = GetUserNotificationSettings(db.DefaultContext, 1) - assert.NoError(t, err) - assert.NotNil(t, settings.User) - assert.Equal(t, settings.User.ID, settings.UserID) - assert.Equal(t, NotificationGiteaActionsAll, settings.Actions) - - assert.NoError(t, UpdateUserNotificationSettings(db.DefaultContext, &NotificationSettings{ - UserID: 1, - Actions: NotificationGiteaActionsDisabled, - })) - settings, err = GetUserNotificationSettings(db.DefaultContext, 1) - assert.NoError(t, err) - assert.NotNil(t, settings.User) - assert.Equal(t, settings.User.ID, settings.UserID) - assert.Equal(t, NotificationGiteaActionsDisabled, settings.Actions) -} diff --git a/routers/web/user/setting/notifications.go b/routers/web/user/setting/notifications.go index 3f42b2d4146cb..dd81a862d33d2 100644 --- a/routers/web/user/setting/notifications.go +++ b/routers/web/user/setting/notifications.go @@ -30,12 +30,18 @@ func Notifications(ctx *context.Context) { ctx.Data["PageIsSettingsNotifications"] = true ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference - fineGrainedPreference, err := user_model.GetUserNotificationSettings(ctx, ctx.Doer.ID) + fineGrainedPreference, err := user_model.GetSettings(ctx, ctx.Doer.ID, []string{ + user_model.SettingsEmailNotificationGiteaActions, + }) if err != nil { ctx.ServerError("GetUserNotificationSettings", err) return } - ctx.Data["ActionsEmailNotificationsPreference"] = fineGrainedPreference.Actions + actionsNotify := fineGrainedPreference[user_model.SettingsEmailNotificationGiteaActions].SettingValue + if actionsNotify == "" { + actionsNotify = user_model.EmailNotificationGiteaActionsFailureOnly + } + ctx.Data["ActionsEmailNotificationsPreference"] = actionsNotify ctx.HTML(http.StatusOK, tplSettingsNotifications) } @@ -77,19 +83,16 @@ func NotificationsActionsEmailPost(ctx *context.Context) { } preference := ctx.FormString("preference") - if !(preference == user_model.NotificationGiteaActionsAll || - preference == user_model.NotificationGiteaActionsDisabled || - preference == user_model.NotificationGiteaActionsFailureOnly) { + if !(preference == user_model.EmailNotificationGiteaActionsAll || + preference == user_model.EmailNotificationGiteaActionsDisabled || + preference == user_model.EmailNotificationGiteaActionsFailureOnly) { log.Error("Actions Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name) ctx.ServerError("NotificationsActionsEmailPost", errors.New("option unrecognized")) return } - opts := &user.UpdateNotificationSettingsOptions{ - Actions: optional.Some(preference), - } - if err := user.UpdateNotificationSettings(ctx, ctx.Doer.ID, opts); err != nil { + if err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsEmailNotificationGiteaActions, preference); err != nil { log.Error("Cannot set actions email notifications preference: %v", err) - ctx.ServerError("UpdateNotificationSettings", err) + ctx.ServerError("SetUserSetting", err) return } log.Trace("Actions email notifications preference made %s: %s", preference, ctx.Doer.Name) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index c91292841a50c..169de71a528a3 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -149,18 +149,19 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U recipients := make([]*user_model.User, 0) if !sender.IsGiteaActions() && !sender.IsGhost() && sender.IsMailable() { - notifyPref, err := user_model.GetUserNotificationSettings(ctx, sender.ID) + notifyPref, err := user_model.GetUserSetting(ctx, sender.ID, + user_model.SettingsEmailNotificationGiteaActions, user_model.EmailNotificationGiteaActionsFailureOnly) if err != nil { - log.Error("GetUserNotificationSettings: %v", err) + log.Error("GetUserSetting: %v", err) return } if run.Status.IsSuccess() { - if notifyPref.Actions == user_model.NotificationGiteaActionsAll { + if notifyPref == user_model.EmailNotificationGiteaActionsAll { recipients = append(recipients, sender) } sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) return - } else if notifyPref.Actions != user_model.NotificationGiteaActionsDisabled { + } else if notifyPref != user_model.EmailNotificationGiteaActionsDisabled { recipients = append(recipients, sender) } } diff --git a/services/user/delete.go b/services/user/delete.go index 49f8403c7e0c7..39c6ef052dca7 100644 --- a/services/user/delete.go +++ b/services/user/delete.go @@ -95,7 +95,6 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) &user_model.Blocking{BlockerID: u.ID}, &user_model.Blocking{BlockeeID: u.ID}, &actions_model.ActionRunnerToken{OwnerID: u.ID}, - &user_model.NotificationSettings{UserID: u.ID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/services/user/update.go b/services/user/update.go index 4fcebbc44c0a4..d7354542bf680 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -244,18 +244,3 @@ func UpdateAuth(ctx context.Context, u *user_model.User, opts *UpdateAuthOptions } return nil } - -type UpdateNotificationSettingsOptions struct { - Actions optional.Option[string] -} - -func UpdateNotificationSettings(ctx context.Context, userID int64, opts *UpdateNotificationSettingsOptions) error { - settings := &user_model.NotificationSettings{ - UserID: userID, - } - if opts.Actions.Has() { - settings.Actions = opts.Actions.Value() - } - - return user_model.UpdateUserNotificationSettings(ctx, settings) -} diff --git a/services/user/update_test.go b/services/user/update_test.go index e7fca20920a9e..27513e80409bb 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -122,30 +122,3 @@ func TestUpdateAuth(t *testing.T) { Password: optional.Some("aaaa"), }), password_module.ErrMinLength) } - -func TestUpdateNotificationSettings(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - settings := &user_model.NotificationSettings{UserID: 2} - exists, err := db.GetEngine(db.DefaultContext).Get(settings) - assert.NoError(t, err) - assert.True(t, exists) - - assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings.UserID, &UpdateNotificationSettingsOptions{ - Actions: optional.Some(user_model.NotificationGiteaActionsAll), - })) - settings, err = user_model.GetUserNotificationSettings(db.DefaultContext, settings.UserID) - assert.NoError(t, err) - assert.NotNil(t, settings) - assert.Equal(t, user_model.NotificationGiteaActionsAll, settings.Actions) - assert.NotEqual(t, user_model.NotificationGiteaActionsFailureOnly, settings.Actions) - - assert.NoError(t, UpdateNotificationSettings(db.DefaultContext, settings.UserID, &UpdateNotificationSettingsOptions{ - Actions: optional.Some(user_model.NotificationGiteaActionsDisabled), - })) - settings, err = user_model.GetUserNotificationSettings(db.DefaultContext, settings.UserID) - assert.NoError(t, err) - assert.NotNil(t, settings) - assert.Equal(t, user_model.NotificationGiteaActionsDisabled, settings.Actions) - assert.NotEqual(t, user_model.NotificationGiteaActionsFailureOnly, settings.Actions) -} From 985e641cd57bf9c1a4d7068ae694879100df45a9 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 14 Jul 2025 00:24:16 +0800 Subject: [PATCH 26/33] update --- services/mailer/mail_workflow_run.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 169de71a528a3..39c585d1d67eb 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -166,5 +166,7 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U } } - sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) + if len(recipients) > 0 { + sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) + } } From 628ace2aca45220a419cc10a8939d63d7ad353c4 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 14 Jul 2025 01:06:06 +0800 Subject: [PATCH 27/33] UPDATE Co-authored-by: ChristopherHX Signed-off-by: NorthRealm <155140859+NorthRealm@users.noreply.github.com> --- services/mailer/mail_workflow_run.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 39c585d1d67eb..4c6cc81200767 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -155,13 +155,7 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U log.Error("GetUserSetting: %v", err) return } - if run.Status.IsSuccess() { - if notifyPref == user_model.EmailNotificationGiteaActionsAll { - recipients = append(recipients, sender) - } - sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) - return - } else if notifyPref != user_model.EmailNotificationGiteaActionsDisabled { + if notifyPref == user_model.EmailNotificationGiteaActionsAll || !run.Status.IsSuccess() && notifyPref != user_model.EmailNotificationGiteaActionsDisabled { recipients = append(recipients, sender) } } From 391d6ee70a086d80f61cc8a1a70f10812e88a81a Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:55:20 +0800 Subject: [PATCH 28/33] update --- services/mailer/mail_workflow_run.go | 6 +++--- services/mailer/notify.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 4c6cc81200767..892768c43c872 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -33,7 +33,7 @@ func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Reposito 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) { +func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, sender *user_model.User, recipients []*user_model.User) { subject := "Run" switch run.Status { case actions_model.StatusFailure: @@ -138,7 +138,7 @@ func sendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Rep } } -func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) { +func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) { if setting.MailService == nil { return } @@ -161,6 +161,6 @@ func SendActionsWorkflowRunStatusEmail(ctx context.Context, sender *user_model.U } if len(recipients) > 0 { - sendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) + composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients) } } diff --git a/services/mailer/notify.go b/services/mailer/notify.go index 7533b1822ab58..c008685e131c2 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -211,5 +211,5 @@ func (m *mailNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_m if !run.Status.IsDone() { return } - SendActionsWorkflowRunStatusEmail(ctx, sender, repo, run) + MailActionsTrigger(ctx, sender, repo, run) } From 741263b03ce5265f273b4778a7c8e1a176e1cd9c Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:01:19 +0800 Subject: [PATCH 29/33] workaround --- routers/web/user/setting/notifications.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/routers/web/user/setting/notifications.go b/routers/web/user/setting/notifications.go index dd81a862d33d2..44f56959df0c7 100644 --- a/routers/web/user/setting/notifications.go +++ b/routers/web/user/setting/notifications.go @@ -30,18 +30,12 @@ func Notifications(ctx *context.Context) { ctx.Data["PageIsSettingsNotifications"] = true ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference - fineGrainedPreference, err := user_model.GetSettings(ctx, ctx.Doer.ID, []string{ - user_model.SettingsEmailNotificationGiteaActions, - }) + actionsEmailPref, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsEmailNotificationGiteaActions, user_model.EmailNotificationGiteaActionsFailureOnly) if err != nil { - ctx.ServerError("GetUserNotificationSettings", err) + ctx.ServerError("GetUserSetting", err) return } - actionsNotify := fineGrainedPreference[user_model.SettingsEmailNotificationGiteaActions].SettingValue - if actionsNotify == "" { - actionsNotify = user_model.EmailNotificationGiteaActionsFailureOnly - } - ctx.Data["ActionsEmailNotificationsPreference"] = actionsNotify + ctx.Data["ActionsEmailNotificationsPreference"] = actionsEmailPref ctx.HTML(http.StatusOK, tplSettingsNotifications) } From 33000c71d8f927ae845fbd6f856abc8b8a7f7c58 Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 15 Jul 2025 12:25:44 +0800 Subject: [PATCH 30/33] remove --- models/user/email_notification_test.go | 34 -------------------------- 1 file changed, 34 deletions(-) delete mode 100644 models/user/email_notification_test.go diff --git a/models/user/email_notification_test.go b/models/user/email_notification_test.go deleted file mode 100644 index a099e10b246df..0000000000000 --- a/models/user/email_notification_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user - -import ( - "testing" - - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/unittest" - - "github.com/stretchr/testify/assert" -) - -func TestNotificationSettings(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - u := unittest.AssertExistsAndLoadBean(t, &User{ID: 1}) - - assert.NoError(t, SetUserSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsAll)) - settings, err := GetSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions) - assert.NoError(t, err) - assert.Equal(t, EmailNotificationGiteaActionsAll, settings) - - assert.NoError(t, SetUserSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsDisabled)) - settings, err = GetSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions) - assert.NoError(t, err) - assert.Equal(t, EmailNotificationGiteaActionsDisabled, settings) - - assert.NoError(t, SetUserSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsFailureOnly)) - settings, err = GetSetting(db.DefaultContext, u.ID, SettingsEmailNotificationGiteaActions) - assert.NoError(t, err) - assert.Equal(t, EmailNotificationGiteaActionsFailureOnly, settings) -} From 3ce0d7ed93604f8f8efeb648393345e4b4c6d85b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 15 Jul 2025 12:43:03 +0800 Subject: [PATCH 31/33] fix --- models/user/email_notification.go | 11 ----------- models/user/{setting_keys.go => setting_options.go} | 5 ++++- models/user/user.go | 4 ---- options/locale/locale_en-US.ini | 4 ++-- routers/web/user/setting/notifications.go | 10 +++++----- services/mailer/mail_workflow_run.go | 4 ++-- templates/user/settings/notifications.tmpl | 6 +++--- 7 files changed, 16 insertions(+), 28 deletions(-) delete mode 100644 models/user/email_notification.go rename models/user/{setting_keys.go => setting_options.go} (76%) diff --git a/models/user/email_notification.go b/models/user/email_notification.go deleted file mode 100644 index c6faa2690960d..0000000000000 --- a/models/user/email_notification.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user - -// setting values -const ( - EmailNotificationGiteaActionsAll = "all" - EmailNotificationGiteaActionsFailureOnly = "failureonly" // Default for actions email preference - EmailNotificationGiteaActionsDisabled = "disabled" -) diff --git a/models/user/setting_keys.go b/models/user/setting_options.go similarity index 76% rename from models/user/setting_keys.go rename to models/user/setting_options.go index 1505c8aa3538d..7be503932962c 100644 --- a/models/user/setting_keys.go +++ b/models/user/setting_options.go @@ -22,5 +22,8 @@ const ( SettingsKeyCodeViewShowFileTree = "code_view.show_file_tree" - SettingsEmailNotificationGiteaActions = "email_notifications.actions" + SettingsKeyEmailNotificationGiteaActions = "email_notification.gitea_actions" + SettingEmailNotificationGiteaActionsAll = "all" + SettingEmailNotificationGiteaActionsFailureOnly = "failure-only" // Default for actions email preference + SettingEmailNotificationGiteaActionsDisabled = "disabled" ) diff --git a/models/user/user.go b/models/user/user.go index f8d74924b5db5..c362cbc6d2b55 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -798,10 +798,6 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o return err } - if err := SetUserSetting(ctx, u.ID, SettingsEmailNotificationGiteaActions, EmailNotificationGiteaActionsFailureOnly); err != nil { - return err - } - return committer.Commit() } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 937eec14561c9..188d4acff64a6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1021,8 +1021,8 @@ email_notifications.onmention = Only Email on Mention email_notifications.disable = Disable Email Notifications email_notifications.submit = Set Email Preference email_notifications.andyourown = And Your Own Notifications -email_notifications.actions.desc = Notifications for workflow runs on repositories set up with Gitea Actions. -email_notifications.actions.failureonly = Only notify for failed workflow runs +email_notifications.actions.desc = Notifications for workflow runs on repositories set up with Gitea Actions. +email_notifications.actions.failure_only = Only notify for failed workflow runs visibility = User visibility visibility.public = Public diff --git a/routers/web/user/setting/notifications.go b/routers/web/user/setting/notifications.go index 44f56959df0c7..d5083b4233fcc 100644 --- a/routers/web/user/setting/notifications.go +++ b/routers/web/user/setting/notifications.go @@ -30,7 +30,7 @@ func Notifications(ctx *context.Context) { ctx.Data["PageIsSettingsNotifications"] = true ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference - actionsEmailPref, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsEmailNotificationGiteaActions, user_model.EmailNotificationGiteaActionsFailureOnly) + actionsEmailPref, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly) if err != nil { ctx.ServerError("GetUserSetting", err) return @@ -77,14 +77,14 @@ func NotificationsActionsEmailPost(ctx *context.Context) { } preference := ctx.FormString("preference") - if !(preference == user_model.EmailNotificationGiteaActionsAll || - preference == user_model.EmailNotificationGiteaActionsDisabled || - preference == user_model.EmailNotificationGiteaActionsFailureOnly) { + if !(preference == user_model.SettingEmailNotificationGiteaActionsAll || + preference == user_model.SettingEmailNotificationGiteaActionsDisabled || + preference == user_model.SettingEmailNotificationGiteaActionsFailureOnly) { log.Error("Actions Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name) ctx.ServerError("NotificationsActionsEmailPost", errors.New("option unrecognized")) return } - if err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsEmailNotificationGiteaActions, preference); err != nil { + if err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyEmailNotificationGiteaActions, preference); err != nil { log.Error("Cannot set actions email notifications preference: %v", err) ctx.ServerError("SetUserSetting", err) return diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index 892768c43c872..f181db645265a 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -150,12 +150,12 @@ func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo if !sender.IsGiteaActions() && !sender.IsGhost() && sender.IsMailable() { notifyPref, err := user_model.GetUserSetting(ctx, sender.ID, - user_model.SettingsEmailNotificationGiteaActions, user_model.EmailNotificationGiteaActionsFailureOnly) + user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly) if err != nil { log.Error("GetUserSetting: %v", err) return } - if notifyPref == user_model.EmailNotificationGiteaActionsAll || !run.Status.IsSuccess() && notifyPref != user_model.EmailNotificationGiteaActionsDisabled { + if notifyPref == user_model.SettingEmailNotificationGiteaActionsAll || !run.Status.IsSuccess() && notifyPref != user_model.SettingEmailNotificationGiteaActionsDisabled { recipients = append(recipients, sender) } } diff --git a/templates/user/settings/notifications.tmpl b/templates/user/settings/notifications.tmpl index a37747f391e07..40094aab4ca3d 100644 --- a/templates/user/settings/notifications.tmpl +++ b/templates/user/settings/notifications.tmpl @@ -46,9 +46,9 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
      From 253c18d8892570386ee3142a2284877cc33a6dab Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 15 Jul 2025 12:47:57 +0800 Subject: [PATCH 32/33] fix --- routers/web/user/setting/notifications.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/routers/web/user/setting/notifications.go b/routers/web/user/setting/notifications.go index d5083b4233fcc..8ff6f1d941f86 100644 --- a/routers/web/user/setting/notifications.go +++ b/routers/web/user/setting/notifications.go @@ -4,12 +4,10 @@ package setting import ( - "errors" "net/http" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" @@ -52,19 +50,17 @@ func NotificationsEmailPost(ctx *context.Context) { preference == user_model.EmailNotificationsOnMention || preference == user_model.EmailNotificationsDisabled || preference == user_model.EmailNotificationsAndYourOwn) { - log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name) - ctx.ServerError("NotificationsEmailPost", errors.New("option unrecognized")) + ctx.Flash.Error(ctx.Tr("invalid_data", preference)) + ctx.Redirect(setting.AppSubURL + "/user/settings/notifications") return } opts := &user.UpdateOptions{ EmailNotificationsPreference: optional.Some(preference), } if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil { - log.Error("Set Email Notifications failed: %v", err) ctx.ServerError("UpdateUser", err) return } - log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success")) ctx.Redirect(setting.AppSubURL + "/user/settings/notifications") } @@ -80,16 +76,14 @@ func NotificationsActionsEmailPost(ctx *context.Context) { if !(preference == user_model.SettingEmailNotificationGiteaActionsAll || preference == user_model.SettingEmailNotificationGiteaActionsDisabled || preference == user_model.SettingEmailNotificationGiteaActionsFailureOnly) { - log.Error("Actions Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name) - ctx.ServerError("NotificationsActionsEmailPost", errors.New("option unrecognized")) + ctx.Flash.Error(ctx.Tr("invalid_data", preference)) + ctx.Redirect(setting.AppSubURL + "/user/settings/notifications") return } if err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyEmailNotificationGiteaActions, preference); err != nil { - log.Error("Cannot set actions email notifications preference: %v", err) ctx.ServerError("SetUserSetting", err) return } - log.Trace("Actions email notifications preference made %s: %s", preference, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success")) ctx.Redirect(setting.AppSubURL + "/user/settings/notifications") } From 0ddbbad572dcc5540e01ace747d68e3b3e2f942e Mon Sep 17 00:00:00 2001 From: NorthRealm <155140859+NorthRealm@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:16:15 +0800 Subject: [PATCH 33/33] update --- services/mailer/mail_workflow_run.go | 1 - 1 file changed, 1 deletion(-) diff --git a/services/mailer/mail_workflow_run.go b/services/mailer/mail_workflow_run.go index f181db645265a..29b3abda8ee29 100644 --- a/services/mailer/mail_workflow_run.go +++ b/services/mailer/mail_workflow_run.go @@ -110,7 +110,6 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo "RunStatusText": runStatusText, "Jobs": convertedJobs, "locale": locale, - "Language": locale.Language(), }); err != nil { log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err) return