1
1
mirror of https://github.com/go-gitea/gitea synced 2025-08-29 12:58:29 +00:00

Prevent duplicate actions email (#35215)

Trying to prevent duplicate action emails by adding an extra check on job status.

---------

Signed-off-by: NorthRealm <155140859+NorthRealm@users.noreply.github.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
This commit is contained in:
NorthRealm
2025-08-25 00:30:56 +08:00
committed by GitHub
parent ed8d4dc37a
commit c7b99c8cc7
5 changed files with 503 additions and 55 deletions

View File

@@ -42,10 +42,8 @@ func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.Ac
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
if len(jobs) > 0 {
job := jobs[0]
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}
job := jobs[0]
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}
}
@@ -101,7 +99,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
return nil
}
// CancelAbandonedJobs cancels the jobs which have waiting status, but haven't been picked by a runner for a long time
// CancelAbandonedJobs cancels jobs that have not been picked by any runner for a long time
func CancelAbandonedJobs(ctx context.Context) error {
jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{
Statuses: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusBlocked},
@@ -113,24 +111,40 @@ func CancelAbandonedJobs(ctx context.Context) error {
}
now := timeutil.TimeStampNow()
// Collect one job per run to send workflow run status update
updatedRuns := map[int64]*actions_model.ActionRunJob{}
for _, job := range jobs {
job.Status = actions_model.StatusCancelled
job.Stopped = now
updated := false
if err := db.WithTx(ctx, func(ctx context.Context) error {
n, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped")
updated = err == nil && n > 0
return err
if err != nil {
return err
}
if err := job.LoadAttributes(ctx); err != nil {
return err
}
updated = n > 0
if updated && job.Run.Status.IsDone() {
updatedRuns[job.RunID] = job
}
return nil
}); err != nil {
log.Warn("cancel abandoned job %v: %v", job.ID, err)
// go on
}
CreateCommitStatus(ctx, job)
if updated {
NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
}
for _, job := range updatedRuns {
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}
return nil
}

View File

@@ -33,7 +33,18 @@ func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Reposito
return fmt.Sprintf("<%s/actions/runs/%d@%s>", repo.FullName(), run.Index, setting.Domain)
}
func composeAndSendActionsWorkflowRunStatusEmail(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) error {
jobs, err := actions_model.GetRunJobsByRunID(ctx, 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
}
}
subject := "Run"
switch run.Status {
case actions_model.StatusFailure:
@@ -48,11 +59,6 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run)
metadataHeaders := generateMetadataHeaders(repo)
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
log.Error("GetRunJobsByRunID: %v", err)
return
}
sort.SliceStable(jobs, func(i, j int) bool {
si, sj := jobs[i].Status, jobs[j].Status
/*
@@ -111,11 +117,11 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
"Jobs": convertedJobs,
"locale": locale,
}); err != nil {
log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err)
return
return err
}
msgs := make([]*sender_service.Message, 0, len(tos))
for _, rec := range tos {
log.Trace("Sending actions email to %s (UID: %d)", rec.Name, rec.ID)
msg := sender_service.NewMessageFrom(
rec.Email,
displayName,
@@ -135,14 +141,16 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
}
SendAsync(msgs...)
}
return nil
}
func MailActionsTrigger(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) error {
if setting.MailService == nil {
return
return nil
}
if run.Status.IsSkipped() {
return
if !run.Status.IsDone() || run.Status.IsSkipped() {
return nil
}
recipients := make([]*user_model.User, 0)
@@ -151,8 +159,7 @@ func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo
notifyPref, err := user_model.GetUserSetting(ctx, sender.ID,
user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly)
if err != nil {
log.Error("GetUserSetting: %v", err)
return
return err
}
if notifyPref == user_model.SettingEmailNotificationGiteaActionsAll || !run.Status.IsSuccess() && notifyPref != user_model.SettingEmailNotificationGiteaActionsDisabled {
recipients = append(recipients, sender)
@@ -160,6 +167,8 @@ func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo
}
if len(recipients) > 0 {
composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
log.Debug("MailActionsTrigger: Initiate email composition")
return composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
}
return nil
}

View File

@@ -208,8 +208,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
}
func (m *mailNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
if !run.Status.IsDone() {
return
if err := MailActionsTrigger(ctx, sender, repo, run); err != nil {
log.Error("MailActionsTrigger: %v", err)
}
MailActionsTrigger(ctx, sender, repo, run)
}