snapshot of https://github.com/go-gitea/gitea.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
cache-seeder / prune (push) Has been skipped
cache-seeder / frontend (push) Failing after 3s
cache-seeder / python (push) Failing after 3s
cache-seeder / gobuild (push) Failing after 4s
cache-seeder / lint (bindata, lint-backend) (push) Failing after 3s
giteabot backport / giteabot (push) Has been skipped
giteabot / giteabot (push) Has been skipped
cache-prune / prune (push) Has been skipped
release-nightly-snapcraft / build-and-publish (ubuntu-24.04) (push) Has been cancelled
release-nightly-snapcraft / build-and-publish (ubuntu-24.04-arm) (push) Has been cancelled
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled
cron-renovate / cron-renovate (push) Has been skipped
Some checks failed
cache-seeder / prune (push) Has been skipped
cache-seeder / frontend (push) Failing after 3s
cache-seeder / python (push) Failing after 3s
cache-seeder / gobuild (push) Failing after 4s
cache-seeder / lint (bindata, lint-backend) (push) Failing after 3s
giteabot backport / giteabot (push) Has been skipped
giteabot / giteabot (push) Has been skipped
cache-prune / prune (push) Has been skipped
release-nightly-snapcraft / build-and-publish (ubuntu-24.04) (push) Has been cancelled
release-nightly-snapcraft / build-and-publish (ubuntu-24.04-arm) (push) Has been cancelled
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled
cron-renovate / cron-renovate (push) Has been skipped
This commit is contained in:
179
services/mailer/mail_workflow_run.go
Normal file
179
services/mailer/mail_workflow_run.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/base"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/templates"
|
||||
"gitea.dev/modules/translation"
|
||||
"gitea.dev/modules/util"
|
||||
sender_service "gitea.dev/services/mailer/sender"
|
||||
)
|
||||
|
||||
const tplWorkflowRun templates.TplName = "mail/repo/actions/workflow_run"
|
||||
|
||||
//go:embed icons/*.png
|
||||
var iconsFS embed.FS
|
||||
|
||||
// LoadMailIcon returns an embedded mail icon.
|
||||
func LoadMailIcon(name string) ([]byte, error) {
|
||||
return iconsFS.ReadFile("icons/" + name)
|
||||
}
|
||||
|
||||
type workflowRunMailJob struct {
|
||||
HTMLURL string
|
||||
Name string
|
||||
Status actions_model.Status
|
||||
StatusIconCID string
|
||||
StatusIconAlt string
|
||||
StatusClass string
|
||||
Attempt int64
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
func workflowRunJobStatusPresentation(status actions_model.Status) (icon, class string) {
|
||||
switch {
|
||||
case status.IsSuccess():
|
||||
return "status-success.png", "status-success"
|
||||
case status.IsCancelled():
|
||||
return "status-cancelled.png", "status-neutral"
|
||||
case status.IsSkipped():
|
||||
return "status-skipped.png", "status-neutral"
|
||||
default:
|
||||
return "status-failure.png", "status-failure"
|
||||
}
|
||||
}
|
||||
|
||||
func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, recipient *user_model.User) error {
|
||||
jobs, err := actions_model.GetLatestAttemptJobsByRepoAndRunID(ctx, repo.ID, run.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, job := range jobs {
|
||||
if !job.Status.IsDone() {
|
||||
log.Debug("composeAndSendActionsWorkflowRunStatusEmail: A job is not done. Will not compose and send actions email.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
locale := translation.NewLocale(recipient.Language)
|
||||
|
||||
slices.SortStableFunc(jobs, func(a, b *actions_model.ActionRunJob) int {
|
||||
if a.Status.IsSuccess() != b.Status.IsSuccess() {
|
||||
return util.Iif(a.Status.IsSuccess(), 1, -1)
|
||||
}
|
||||
return cmp.Compare(a.Status, b.Status)
|
||||
})
|
||||
|
||||
mailJobs := make([]workflowRunMailJob, 0, len(jobs))
|
||||
var embeds []sender_service.EmbeddedFile
|
||||
embedded := make(container.Set[string])
|
||||
for _, job := range jobs {
|
||||
icon, class := workflowRunJobStatusPresentation(job.Status)
|
||||
contentID := fmt.Sprintf("%s.actions-run-%d@%s", icon, run.ID, setting.Domain)
|
||||
mailJobs = append(mailJobs, workflowRunMailJob{
|
||||
HTMLURL: fmt.Sprintf("%s/actions/runs/%d/jobs/%d", repo.HTMLURL(ctx), run.ID, job.ID),
|
||||
Name: job.Name,
|
||||
Status: job.Status,
|
||||
StatusIconCID: contentID,
|
||||
StatusIconAlt: job.Status.LocaleString(locale),
|
||||
StatusClass: class,
|
||||
Attempt: job.Attempt,
|
||||
Duration: job.Duration(),
|
||||
})
|
||||
if !embedded.Add(icon) {
|
||||
continue
|
||||
}
|
||||
content, err := LoadMailIcon(icon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
embeds = append(embeds, sender_service.EmbeddedFile{Name: icon, ContentID: contentID, Content: content})
|
||||
}
|
||||
|
||||
var runStatusTrString string
|
||||
switch run.Status {
|
||||
case actions_model.StatusSuccess:
|
||||
runStatusTrString = "mail.repo.actions.jobs.all_succeeded"
|
||||
case actions_model.StatusFailure:
|
||||
runStatusTrString = "mail.repo.actions.jobs.all_failed"
|
||||
for _, job := range jobs {
|
||||
if !job.Status.IsFailure() {
|
||||
runStatusTrString = "mail.repo.actions.jobs.some_not_successful"
|
||||
break
|
||||
}
|
||||
}
|
||||
case actions_model.StatusCancelled:
|
||||
runStatusTrString = "mail.repo.actions.jobs.all_cancelled"
|
||||
}
|
||||
subject := fmt.Sprintf("[%s] %s: %s (%s - %s)", repo.FullName(), run.Status.LocaleString(locale), run.WorkflowID, run.PrettyRef(), base.ShortSha(run.CommitSHA))
|
||||
var mailBody bytes.Buffer
|
||||
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, string(tplWorkflowRun), map[string]any{
|
||||
"Subject": subject,
|
||||
"Repo": repo,
|
||||
"Run": run,
|
||||
"RunStatusText": locale.TrString(runStatusTrString),
|
||||
"Jobs": mailJobs,
|
||||
"locale": locale,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace("Sending actions email to %s (UID: %d)", recipient.Name, recipient.ID)
|
||||
msg := sender_service.NewMessageFrom(recipient.Email, fromDisplayName(recipient), setting.MailService.FromEmail, subject, mailBody.String())
|
||||
msg.Info = subject
|
||||
msg.Embeds = embeds
|
||||
for key, value := range generateSenderRecipientHeaders(recipient, recipient) {
|
||||
msg.SetHeader(key, value)
|
||||
}
|
||||
for key, value := range generateMetadataHeaders(repo) {
|
||||
msg.SetHeader(key, value)
|
||||
}
|
||||
msg.SetHeader("Message-ID", fmt.Sprintf("<%s/actions/runs/%d@%s>", repo.FullName(), run.Index, setting.Domain))
|
||||
SendAsync(msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MailActionsTrigger(ctx context.Context, recipient *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) error {
|
||||
if setting.MailService == nil {
|
||||
return nil
|
||||
}
|
||||
if !run.Status.IsDone() || run.Status.IsSkipped() {
|
||||
return nil
|
||||
}
|
||||
if !recipient.IsMailable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
notifyPref, err := user_model.GetUserSetting(ctx, recipient.ID,
|
||||
user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// "disabled" never sends
|
||||
if notifyPref == user_model.SettingEmailNotificationGiteaActionsDisabled {
|
||||
return nil
|
||||
}
|
||||
// "failure-only" skips non-failure runs
|
||||
if notifyPref != user_model.SettingEmailNotificationGiteaActionsAll && !run.Status.IsFailure() {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("MailActionsTrigger: Initiate email composition")
|
||||
return composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, recipient)
|
||||
}
|
||||
Reference in New Issue
Block a user