From 68305f78d928bdf7a2a83e149a5b9bea6e048aaa Mon Sep 17 00:00:00 2001 From: huanghongbo-hhb Date: Thu, 4 Jun 2026 11:46:50 +0800 Subject: [PATCH 1/5] fix: use orange header for manual execution notifications Signed-off-by: huanghongbo-hhb --- .../aslan/core/common/service/instantmessage/lark.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go index cf90e9592f..0d39b9a447 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go @@ -27,6 +27,7 @@ const ( feishuCardType = "interactive" feishuHeaderTemplateTurquoise = "turquoise" feishuHeaderTemplateGreen = "green" + feishuHeaderTemplateOrange = "orange" feishuHeaderTemplateRed = "red" feiShuTagText = "plain_text" feishuTagMd = "lark_md" @@ -232,5 +233,8 @@ func getColorTemplateWithStatus(status config.Status) string { if status == config.StatusPassed || status == config.StatusCreated { return feishuHeaderTemplateGreen } + if status == config.StatusPause { + return feishuHeaderTemplateOrange + } return feishuHeaderTemplateRed } From 7ddc83ff1dfa4d066b682c9cea48d6c81904800c Mon Sep 17 00:00:00 2001 From: huanghongbo-hhb Date: Thu, 4 Jun 2026 13:14:32 +0800 Subject: [PATCH 2/5] fix: limit orange header to manual execution notifications Signed-off-by: huanghongbo-hhb --- .../aslan/core/common/service/instantmessage/lark.go | 3 --- .../core/common/service/instantmessage/workflow_task.go | 9 ++++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go index 0d39b9a447..42497e5a11 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go @@ -233,8 +233,5 @@ func getColorTemplateWithStatus(status config.Status) string { if status == config.StatusPassed || status == config.StatusCreated { return feishuHeaderTemplateGreen } - if status == config.StatusPause { - return feishuHeaderTemplateOrange - } return feishuHeaderTemplateRed } diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go index b1d5b7b1c9..06810e75ea 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go @@ -1181,7 +1181,7 @@ func (w *Service) getNotificationContentWithOptions(notify *models.NotifyCtl, ta lc := NewLarkCard() lc.SetConfig(true) - lc.SetHeader(getColorTemplateWithStatus(task.Status), title, feiShuTagText) + lc.SetHeader(getWorkflowNotificationHeaderTemplate(task.Status, workflowNotification), title, feiShuTagText) for idx, feildContent := range tplBaseInfo { feildExecContent, _ := getWorkflowTaskTplExec(feildContent, workflowNotification) lc.AddI18NElementsZhcnFeild(feildExecContent, idx == 0) @@ -1195,6 +1195,13 @@ func (w *Service) getNotificationContentWithOptions(notify *models.NotifyCtl, ta return "", "", lc, nil, nil } +func getWorkflowNotificationHeaderTemplate(status config.Status, args *workflowTaskNotification) string { + if args != nil && args.StatusTextKeyOverride == "taskStatusWaitingManualExec" { + return feishuHeaderTemplateOrange + } + return getColorTemplateWithStatus(status) +} + type workflowTaskNotification struct { Task *models.WorkflowTask `json:"task"` ProjectDisplayName string `json:"project_display_name"` From 9153767260f3c20ecca09de22dc30ed16cc88598 Mon Sep 17 00:00:00 2001 From: huanghongbo-hhb Date: Fri, 5 Jun 2026 11:27:43 +0800 Subject: [PATCH 3/5] fix: skip generic pause notification for manual execution Signed-off-by: huanghongbo-hhb --- .../service/instantmessage/workflow_task.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go index 06810e75ea..4f921bf285 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go @@ -339,6 +339,9 @@ func (w *Service) SendWorkflowTaskNotifications(task *models.WorkflowTask) error if task.TaskID <= 0 { return nil } + if shouldSkipGenericPauseNotification(task) { + return nil + } statusChanged := false preTask, err := w.workflowTaskV4Coll.Find(task.WorkflowName, task.TaskID-1) if err != nil { @@ -440,6 +443,27 @@ func (w *Service) SendWorkflowTaskNotifications(task *models.WorkflowTask) error return nil } +func shouldSkipGenericPauseNotification(task *models.WorkflowTask) bool { + if task == nil || task.Status != config.StatusPause { + return false + } + if len(getManualExecStageNotifyCtls(task)) == 0 { + return false + } + + for _, stage := range task.Stages { + if stage == nil || stage.Status != config.StatusPause { + continue + } + if stage.ManualExec == nil || !stage.ManualExec.Enabled || stage.ManualExec.Excuted { + continue + } + return true + } + + return false +} + func (w *Service) SendManualExecStageNotifications(workflowCtx *models.WorkflowTaskCtx, stage *models.StageTask) error { if workflowCtx == nil || stage == nil || stage.ManualExec == nil { return nil From 0c8b6e9c4d84fc472d6ecdf00050a2fe3e1921e8 Mon Sep 17 00:00:00 2001 From: huanghongbo-hhb Date: Fri, 5 Jun 2026 14:02:53 +0800 Subject: [PATCH 4/5] fix: unify manual execution pause notifications Signed-off-by: huanghongbo-hhb --- .../common/repository/models/workflow_v4.go | 1 - .../service/instantmessage/workflow_task.go | 388 ++++++------------ .../service/workflowcontroller/stage.go | 10 +- .../service/workflow/controller/workflow.go | 5 - 4 files changed, 124 insertions(+), 280 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 1e55fb9d9c..a92b89e98d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -141,7 +141,6 @@ type ManualExec struct { Excuted bool `bson:"excuted,omitempty" yaml:"excuted,omitempty" json:"excuted,omitempty"` ManualExecUsers []*User `bson:"manual_exec_users" yaml:"manual_exec_users" json:"manual_exec_users"` LarkPersonNotificationConfig *LarkPersonNotificationConfig `bson:"lark_person_notification_config,omitempty" yaml:"lark_person_notification_config,omitempty" json:"lark_person_notification_config,omitempty"` - NotificationSent bool `bson:"notification_sent,omitempty" yaml:"notification_sent,omitempty" json:"notification_sent,omitempty"` ManualExectorID string `bson:"manual_exector_id,omitempty" yaml:"manual_exector_id,omitempty" json:"manual_exector_id,omitempty"` ManualExectorName string `bson:"manual_exector_name,omitempty" yaml:"manual_exector_name,omitempty" json:"manual_exector_name,omitempty"` } diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go index 4f921bf285..5b4fcf9e73 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/workflow_task.go @@ -339,9 +339,6 @@ func (w *Service) SendWorkflowTaskNotifications(task *models.WorkflowTask) error if task.TaskID <= 0 { return nil } - if shouldSkipGenericPauseNotification(task) { - return nil - } statusChanged := false preTask, err := w.workflowTaskV4Coll.Find(task.WorkflowName, task.TaskID-1) if err != nil { @@ -374,68 +371,14 @@ func (w *Service) SendWorkflowTaskNotifications(task *models.WorkflowTask) error return errors.New(errMsg) } - if notify.WebHookType == setting.NotifyWebHookTypeMail { - if task.TaskCreatorID != "" { - for _, user := range notify.MailNotificationConfig.TargetUsers { - if user.Type == setting.UserTypeTaskCreator { - userInfo, err := userclient.New().GetUserByID(task.TaskCreatorID) - if err != nil { - log.Errorf("failed to find user %s, error: %s", task.TaskCreatorID, err) - break - } - user.Type = setting.UserTypeUser - user.UserID = userInfo.Uid - user.UserName = userInfo.Name - break - } - } - } + notifyToSend, err := w.prepareWorkflowTaskNotifyTargets(task, notify) + if err != nil && notifyToSend == nil { + return err } - - if notify.WebHookType == setting.NotifyWebHookTypeFeishuPerson { - - for _, target := range notify.LarkPersonNotificationConfig.TargetUsers { - if target.IsExecutor { - if task.TaskCreatorID == "" { - errMsg := fmt.Errorf("executor id is empty, cannot send message") - log.Error(errMsg) - continue - } - - userInfo, err := userclient.New().GetUserByID(task.TaskCreatorID) - if err != nil { - log.Errorf("failed to find user %s, error: %s", task.TaskCreatorID, err) - return fmt.Errorf("failed to find user %s, error: %s", task.TaskCreatorID, err) - } - - if len(userInfo.Phone) == 0 { - return fmt.Errorf("executor phone not configured") - } - - client, err := larkservice.GetLarkClientByIMAppID(notify.LarkPersonNotificationConfig.AppID) - if err != nil { - return fmt.Errorf("failed to get notify target info: create feishu client error: %s", err) - } - - larkUser, err := client.GetUserIDByEmailOrMobile(lark.QueryTypeMobile, userInfo.Phone, setting.LarkUserID) - if err != nil { - return fmt.Errorf("find lark user with phone %s error: %v", userInfo.Phone, err) - } - - userDetailedInfo, err := client.GetUserInfoByID(util.GetStringFromPointer(larkUser.UserId), setting.LarkUserID) - if err != nil { - return fmt.Errorf("find lark user info for userID %s error: %v", util.GetStringFromPointer(larkUser.UserId), err) - } - - target.ID = util.GetStringFromPointer(larkUser.UserId) - target.Name = userDetailedInfo.Name - target.Avatar = userDetailedInfo.Avatar - target.IDType = setting.LarkUserID - } - } + if err != nil { + log.Errorf("failed to resolve notification targets, err: %s", err) } - - if err := w.sendNotification(title, content, notify, larkCard, webhookNotify, task.Status); err != nil { + if err := w.sendNotification(title, content, notifyToSend, larkCard, webhookNotify, task.Status); err != nil { log.Errorf("failed to send notification, err: %s", err) } } @@ -443,170 +386,95 @@ func (w *Service) SendWorkflowTaskNotifications(task *models.WorkflowTask) error return nil } -func shouldSkipGenericPauseNotification(task *models.WorkflowTask) bool { - if task == nil || task.Status != config.StatusPause { - return false - } - if len(getManualExecStageNotifyCtls(task)) == 0 { - return false - } - - for _, stage := range task.Stages { - if stage == nil || stage.Status != config.StatusPause { - continue - } - if stage.ManualExec == nil || !stage.ManualExec.Enabled || stage.ManualExec.Excuted { - continue - } - return true - } +type workflowTaskUsersFlattener func(users []*models.User, taskCreatorID string) ([]*models.User, map[string]*types.UserInfo) - return false -} +type workflowTaskLarkTargetResolver func(userID, userName string) (*lark.UserInfo, error) -func (w *Service) SendManualExecStageNotifications(workflowCtx *models.WorkflowTaskCtx, stage *models.StageTask) error { - if workflowCtx == nil || stage == nil || stage.ManualExec == nil { - return nil +func (w *Service) prepareWorkflowTaskNotifyTargets(task *models.WorkflowTask, notify *models.NotifyCtl) (*models.NotifyCtl, error) { + if notify == nil { + return nil, nil } - taskForNotification := &models.WorkflowTask{ - WorkflowName: workflowCtx.WorkflowName, - WorkflowDisplayName: workflowCtx.WorkflowDisplayName, - ProjectName: workflowCtx.ProjectName, - ProjectDisplayName: workflowCtx.ProjectDisplayName, - TaskID: workflowCtx.TaskID, - Remark: workflowCtx.Remark, - TaskCreator: workflowCtx.WorkflowTaskCreatorUsername, - TaskCreatorID: workflowCtx.WorkflowTaskCreatorUserID, - TaskCreatorPhone: workflowCtx.WorkflowTaskCreatorMobile, - TaskCreatorEmail: workflowCtx.WorkflowTaskCreatorEmail, - StartTime: workflowCtx.StartTime.Unix(), - Status: config.StatusPause, - Stages: []*models.StageTask{stage}, - Type: config.WorkflowTaskTypeWorkflow, - } - stageForNotification := stage - if taskInColl, findErr := w.workflowTaskV4Coll.Find(workflowCtx.WorkflowName, workflowCtx.TaskID); findErr == nil && taskInColl != nil { - taskForNotification = taskInColl - if matchedStage := getStageTaskByName(taskInColl.Stages, stage.Name); matchedStage != nil { - stageForNotification = matchedStage + pauseStage := getPauseStageTask(task) + switch notify.WebHookType { + case setting.NotifyWebHookTypeMail: + if notify.MailNotificationConfig == nil { + return notify, nil } - } - taskForNotification.Status = config.StatusPause - notifyCtls := getManualExecStageNotifyCtls(taskForNotification) - if len(notifyCtls) == 0 { - return nil - } - - respErr := new(multierror.Error) - for _, notify := range notifyCtls { - switch notify.WebHookType { - case setting.NotifyWebHookTypeFeishuPerson: - resolvedTargets, err := w.resolveManualExecStageLarkTargets(taskForNotification, stageForNotification, notify) - if err != nil { - respErr = multierror.Append(respErr, err) - continue - } - if len(resolvedTargets) == 0 { - continue - } + targetUsers, err := resolveWorkflowTaskMailUsers(task, pauseStage, notify, flattenWorkflowTaskUsers) + if err != nil { + return nil, err + } - notifyToSend := *notify - notifyToSend.LarkPersonNotificationConfig = &models.LarkPersonNotificationConfig{ - AppID: notify.LarkPersonNotificationConfig.AppID, - TargetUsers: resolvedTargets, - } + notifyToSend := *notify + notifyToSend.MailNotificationConfig = &models.MailNotificationConfig{TargetUsers: targetUsers} + return ¬ifyToSend, nil + case setting.NotifyWebHookTypeFeishuPerson: + if notify.LarkPersonNotificationConfig == nil { + return notify, nil + } - title, content, card, webhookNotify, err := w.getNotificationContentWithOptions(¬ifyToSend, taskForNotification, &workflowNotificationOptions{ - StatusTextKeyOverride: "taskStatusWaitingManualExec", - PendingStageName: stageForNotification.Name, - }) - if err != nil { - respErr = multierror.Append(respErr, err) - continue - } - if err := w.sendNotification(title, content, ¬ifyToSend, card, webhookNotify, config.StatusPause); err != nil { - respErr = multierror.Append(respErr, err) - } + client, err := larkservice.GetLarkClientByIMAppID(notify.LarkPersonNotificationConfig.AppID) + if err != nil { + return nil, fmt.Errorf("failed to get notify target info: create feishu client error: %s", err) + } - case setting.NotifyWebHookTypeMail: - resolvedUsers, err := w.resolveManualExecStageMailUsers(taskForNotification, stageForNotification, notify) - if err != nil { - respErr = multierror.Append(respErr, err) - continue - } - if len(resolvedUsers) == 0 { - continue - } + targetUsers, err := resolveWorkflowTaskLarkTargets(task, pauseStage, notify, func(userID, userName string) (*lark.UserInfo, error) { + return w.resolveWorkflowTaskLarkTargetFromUser(client, userID, userName) + }, flattenWorkflowTaskUsers) - notifyToSend := *notify - notifyToSend.MailNotificationConfig = &models.MailNotificationConfig{TargetUsers: resolvedUsers} - title, content, card, webhookNotify, err := w.getNotificationContentWithOptions(¬ifyToSend, taskForNotification, &workflowNotificationOptions{ - StatusTextKeyOverride: "taskStatusWaitingManualExec", - PendingStageName: stageForNotification.Name, - }) - if err != nil { - respErr = multierror.Append(respErr, err) - continue - } - if err := w.sendNotification(title, content, ¬ifyToSend, card, webhookNotify, config.StatusPause); err != nil { - respErr = multierror.Append(respErr, err) - } + notifyToSend := *notify + notifyToSend.LarkPersonNotificationConfig = &models.LarkPersonNotificationConfig{ + AppID: notify.LarkPersonNotificationConfig.AppID, + TargetUsers: targetUsers, } + return ¬ifyToSend, err + default: + return notify, nil } - - return respErr.ErrorOrNil() } -func getManualExecStageNotifyCtls(task *models.WorkflowTask) []*models.NotifyCtl { - if task == nil { - return nil +func flattenWorkflowTaskUsers(users []*models.User, taskCreatorID string) ([]*models.User, map[string]*types.UserInfo) { + if taskCreatorID != "" { + return commonutil.GeneFlatUsersWithCaller(users, taskCreatorID) } + return commonutil.GeneFlatUsers(users) +} - var notifyCtls []*models.NotifyCtl - switch { - case task.OriginWorkflowArgs != nil: - notifyCtls = task.OriginWorkflowArgs.NotifyCtls - case task.WorkflowArgs != nil: - notifyCtls = task.WorkflowArgs.NotifyCtls +func resolveWorkflowTaskMailUsers(task *models.WorkflowTask, stage *models.StageTask, notify *models.NotifyCtl, flattener workflowTaskUsersFlattener) ([]*models.User, error) { + if notify == nil || notify.MailNotificationConfig == nil { + return nil, nil } - ret := make([]*models.NotifyCtl, 0, len(notifyCtls)) - for _, notify := range notifyCtls { - if notify == nil || !notify.Enabled { - continue - } - if err := notify.GenerateNewNotifyConfigWithOldData(); err != nil { - log.Errorf("failed to parse notification config for workflow %s task %d: %v", task.WorkflowName, task.TaskID, err) - continue - } - if !sets.NewString(notify.NotifyTypes...).Has(string(config.StatusPause)) { + usersToExpand := make([]*models.User, 0, len(notify.MailNotificationConfig.TargetUsers)) + for _, user := range notify.MailNotificationConfig.TargetUsers { + if user == nil { continue } - if notify.WebHookType != setting.NotifyWebHookTypeFeishuPerson && notify.WebHookType != setting.NotifyWebHookTypeMail { + if user.Type == setting.UserTypeStageExecutor { + if stage == nil || stage.ManualExec == nil { + continue + } + usersToExpand = append(usersToExpand, stage.ManualExec.ManualExecUsers...) continue } - ret = append(ret, notify) + usersToExpand = append(usersToExpand, user) } - return ret + users, _ := flattener(usersToExpand, getTaskCreatorID(task)) + return users, nil } -func (w *Service) resolveManualExecStageLarkTargets(task *models.WorkflowTask, stage *models.StageTask, notify *models.NotifyCtl) ([]*lark.UserInfo, error) { - if notify == nil || notify.LarkPersonNotificationConfig == nil || notify.LarkPersonNotificationConfig.AppID == "" { +func resolveWorkflowTaskLarkTargets(task *models.WorkflowTask, stage *models.StageTask, notify *models.NotifyCtl, resolver workflowTaskLarkTargetResolver, flattener workflowTaskUsersFlattener) ([]*lark.UserInfo, error) { + if notify == nil || notify.LarkPersonNotificationConfig == nil { return nil, nil } - client, err := larkservice.GetLarkClientByIMAppID(notify.LarkPersonNotificationConfig.AppID) - if err != nil { - return nil, fmt.Errorf("create feishu client error: %w", err) - } - respErr := new(multierror.Error) targets := make([]*lark.UserInfo, 0, len(notify.LarkPersonNotificationConfig.TargetUsers)) targetSet := sets.NewString() - stageUsers, stageUserInfoMap := resolveManualExecStageUsers(stage, task.TaskCreatorID) + stageUsers, stageUserInfoMap := resolveWorkflowTaskStageUsers(stage, getTaskCreatorID(task), flattener) for _, target := range notify.LarkPersonNotificationConfig.TargetUsers { if target == nil { @@ -614,21 +482,16 @@ func (w *Service) resolveManualExecStageLarkTargets(task *models.WorkflowTask, s } if target.IsExecutor { - if task.TaskCreatorID == "" { + if task == nil || task.TaskCreatorID == "" { respErr = multierror.Append(respErr, fmt.Errorf("executor id is empty, cannot send message")) continue } - userInfo, err := userclient.New().GetUserByID(task.TaskCreatorID) + resolvedTarget, err := resolver(task.TaskCreatorID, "") if err != nil { - respErr = multierror.Append(respErr, fmt.Errorf("failed to find user %s, error: %w", task.TaskCreatorID, err)) - continue - } - resolvedTarget, _, resolveErr := w.resolveManualExecStageLarkTargetFromUser(client, userInfo.Uid, userInfo.Name) - if resolveErr != nil { - respErr = multierror.Append(respErr, resolveErr) + respErr = multierror.Append(respErr, fmt.Errorf("executor %s: %w", task.TaskCreatorID, err)) continue } - targets = appendManualExecStageLarkTarget(targets, targetSet, resolvedTarget) + targets = appendWorkflowTaskLarkTarget(targets, targetSet, resolvedTarget) continue } @@ -637,16 +500,18 @@ func (w *Service) resolveManualExecStageLarkTargets(task *models.WorkflowTask, s if stageUser == nil || stageUser.UserID == "" { continue } + displayName := stageUser.UserName if info, ok := stageUserInfoMap[stageUser.UserID]; ok && info != nil && info.Name != "" { displayName = info.Name } - resolvedTarget, _, resolveErr := w.resolveManualExecStageLarkTargetFromUser(client, stageUser.UserID, displayName) - if resolveErr != nil { - respErr = multierror.Append(respErr, fmt.Errorf("stage executor %s: %w", stageUser.UserID, resolveErr)) + + resolvedTarget, err := resolver(stageUser.UserID, displayName) + if err != nil { + respErr = multierror.Append(respErr, fmt.Errorf("stage executor %s: %w", stageUser.UserID, err)) continue } - targets = appendManualExecStageLarkTarget(targets, targetSet, resolvedTarget) + targets = appendWorkflowTaskLarkTarget(targets, targetSet, resolvedTarget) } continue } @@ -659,76 +524,37 @@ func (w *Service) resolveManualExecStageLarkTargets(task *models.WorkflowTask, s IsExecutor: target.IsExecutor, IsStageExecutor: target.IsStageExecutor, } - if resolvedTarget.ID == "" { - continue - } - if resolvedTarget.IDType == "" { - resolvedTarget.IDType = setting.LarkUserID - } - targets = appendManualExecStageLarkTarget(targets, targetSet, resolvedTarget) + targets = appendWorkflowTaskLarkTarget(targets, targetSet, resolvedTarget) } return targets, respErr.ErrorOrNil() } -func (w *Service) resolveManualExecStageMailUsers(task *models.WorkflowTask, stage *models.StageTask, notify *models.NotifyCtl) ([]*models.User, error) { - if notify == nil || notify.MailNotificationConfig == nil { - return nil, nil - } - - usersToExpand := make([]*models.User, 0, len(notify.MailNotificationConfig.TargetUsers)) - for _, user := range notify.MailNotificationConfig.TargetUsers { - if user == nil { - continue - } - if user.Type == setting.UserTypeStageExecutor { - if stage == nil || stage.ManualExec == nil { - continue - } - usersToExpand = append(usersToExpand, stage.ManualExec.ManualExecUsers...) - continue - } - usersToExpand = append(usersToExpand, user) - } - - if task.TaskCreatorID != "" { - users, _ := commonutil.GeneFlatUsersWithCaller(usersToExpand, task.TaskCreatorID) - return users, nil - } - - users, _ := commonutil.GeneFlatUsers(usersToExpand) - return users, nil -} - -func resolveManualExecStageUsers(stage *models.StageTask, taskCreatorID string) ([]*models.User, map[string]*types.UserInfo) { +func resolveWorkflowTaskStageUsers(stage *models.StageTask, taskCreatorID string, flattener workflowTaskUsersFlattener) ([]*models.User, map[string]*types.UserInfo) { if stage == nil || stage.ManualExec == nil || len(stage.ManualExec.ManualExecUsers) == 0 { return nil, map[string]*types.UserInfo{} } - if taskCreatorID != "" { - return commonutil.GeneFlatUsersWithCaller(stage.ManualExec.ManualExecUsers, taskCreatorID) - } - - return commonutil.GeneFlatUsers(stage.ManualExec.ManualExecUsers) + return flattener(stage.ManualExec.ManualExecUsers, taskCreatorID) } -func (w *Service) resolveManualExecStageLarkTargetFromUser(client *lark.Client, userID, userName string) (*lark.UserInfo, string, error) { +func (w *Service) resolveWorkflowTaskLarkTargetFromUser(client *lark.Client, userID, userName string) (*lark.UserInfo, error) { userInfo, err := userclient.New().GetUserByID(userID) if err != nil { - return nil, "", fmt.Errorf("failed to find user %s, error: %w", userID, err) + return nil, fmt.Errorf("failed to find user %s, error: %w", userID, err) } if len(userInfo.Phone) == 0 { - return nil, "", fmt.Errorf("phone not configured") + return nil, fmt.Errorf("phone not configured") } larkUser, err := client.GetUserIDByEmailOrMobile(lark.QueryTypeMobile, userInfo.Phone, setting.LarkUserID) if err != nil { - return nil, "", fmt.Errorf("find lark user with phone %s error: %w", userInfo.Phone, err) + return nil, fmt.Errorf("find lark user with phone %s error: %w", userInfo.Phone, err) } userDetailedInfo, err := client.GetUserInfoByID(util.GetStringFromPointer(larkUser.UserId), setting.LarkUserID) if err != nil { - return nil, "", fmt.Errorf("find lark user info for userID %s error: %w", util.GetStringFromPointer(larkUser.UserId), err) + return nil, fmt.Errorf("find lark user info for userID %s error: %w", util.GetStringFromPointer(larkUser.UserId), err) } displayName := userDetailedInfo.Name @@ -739,12 +565,12 @@ func (w *Service) resolveManualExecStageLarkTargetFromUser(client *lark.Client, return &lark.UserInfo{ ID: util.GetStringFromPointer(larkUser.UserId), IDType: setting.LarkUserID, - Name: userDetailedInfo.Name, + Name: displayName, Avatar: userDetailedInfo.Avatar, - }, displayName, nil + }, nil } -func appendManualExecStageLarkTarget(targets []*lark.UserInfo, targetSet sets.String, target *lark.UserInfo) []*lark.UserInfo { +func appendWorkflowTaskLarkTarget(targets []*lark.UserInfo, targetSet sets.String, target *lark.UserInfo) []*lark.UserInfo { if target == nil || target.ID == "" { return targets } @@ -761,13 +587,11 @@ func appendManualExecStageLarkTarget(targets []*lark.UserInfo, targetSet sets.St return targets } -func getStageTaskByName(stages []*models.StageTask, stageName string) *models.StageTask { - for _, stage := range stages { - if stage != nil && stage.Name == stageName { - return stage - } +func getTaskCreatorID(task *models.WorkflowTask) string { + if task == nil { + return "" } - return nil + return task.TaskCreatorID } func buildWorkflowNotifyReleasePlan(releasePlan *models.ReleasePlanRef) *webhooknotify.WorkflowNotifyReleasePlan { @@ -964,7 +788,7 @@ func (w *Service) getApproveNotificationContent(notify *models.NotifyCtl, task * // @note custom workflow task v4 notification func (w *Service) getNotificationContent(notify *models.NotifyCtl, task *models.WorkflowTask) (string, string, *LarkCard, *webhooknotify.WorkflowNotify, error) { - return w.getNotificationContentWithOptions(notify, task, nil) + return w.getNotificationContentWithOptions(notify, task, getWorkflowNotificationOptions(task)) } func (w *Service) BuildWorkflowWebhookNotify(task *models.WorkflowTask) (*webhooknotify.WorkflowNotify, error) { @@ -1022,6 +846,40 @@ type workflowNotificationOptions struct { PendingStageName string } +func getWorkflowNotificationOptions(task *models.WorkflowTask) *workflowNotificationOptions { + if task == nil || task.Status != config.StatusPause { + return nil + } + + return &workflowNotificationOptions{ + StatusTextKeyOverride: "taskStatusWaitingManualExec", + PendingStageName: getPauseStageName(task.Stages), + } +} + +func getPauseStageName(stages []*models.StageTask) string { + if stage := getPauseStage(stages); stage != nil { + return stage.Name + } + return "" +} + +func getPauseStageTask(task *models.WorkflowTask) *models.StageTask { + if task == nil { + return nil + } + return getPauseStage(task.Stages) +} + +func getPauseStage(stages []*models.StageTask) *models.StageTask { + for _, stage := range stages { + if stage != nil && stage.Status == config.StatusPause { + return stage + } + } + return nil +} + func (w *Service) getNotificationContentWithOptions(notify *models.NotifyCtl, task *models.WorkflowTask, opts *workflowNotificationOptions) (string, string, *LarkCard, *webhooknotify.WorkflowNotify, error) { project, err := templaterepo.NewProductColl().Find(task.ProjectName) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go index 33c68544b3..ff7bfbcb1d 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go @@ -26,7 +26,6 @@ import ( "github.com/koderover/zadig/v2/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models" approvalservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/approval" - "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/instantmessage" ) type StageCtl interface { @@ -83,7 +82,7 @@ func ApproveStage(workflowName, jobName, userName, userID, comment string, taskI return err } -func waitForManualExec(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger, ack func()) (wait bool, err error) { +func waitForManualExec(ctx context.Context, stage *commonmodels.StageTask, _ *commonmodels.WorkflowTaskCtx, _ *zap.SugaredLogger, ack func()) (wait bool, err error) { if stage.ManualExec == nil { return false, nil } @@ -95,13 +94,6 @@ func waitForManualExec(ctx context.Context, stage *commonmodels.StageTask, workf } stage.Status = config.StatusPause - if !stage.ManualExec.NotificationSent { - if err := instantmessage.NewWeChatClient().SendManualExecStageNotifications(workflowCtx, stage); err != nil { - logger.Errorf("failed to send manual execution stage notification for stage %s: %v", stage.Name, err) - } else { - stage.ManualExec.NotificationSent = true - } - } return true, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/controller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/controller/workflow.go index d72ff230c1..0beb6cfd59 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/controller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/controller/workflow.go @@ -455,11 +455,6 @@ func (w *Workflow) Validate(isExecution bool) error { return e.ErrLicenseInvalid.AddDesc("基础版不支持工作流手动执行") } } - if stage.ManualExec != nil && stage.ManualExec.LarkPersonNotificationConfig != nil { - if stage.ManualExec.LarkPersonNotificationConfig.AppID == "" { - return e.ErrLintWorkflow.AddDesc(fmt.Sprintf("manual execution notification app id cannot be empty for stage %s", stage.Name)) - } - } if _, ok := stageNameMap[stage.Name]; !ok { stageNameMap[stage.Name] = true } else { From 3053499d4f98a5c317e0cfe170d903b3254818fa Mon Sep 17 00:00:00 2001 From: huanghongbo-hhb Date: Fri, 5 Jun 2026 14:21:17 +0800 Subject: [PATCH 5/5] fix: avoid duplicate workflow start notifications Signed-off-by: huanghongbo-hhb --- .../aslan/core/workflow/service/workflow/workflow_task_v4.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 19b3ae813d..5888622b25 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -1293,9 +1293,6 @@ func ManualExecWorkflowTaskV4(workflowName string, taskID int64, stageName strin task.Status = config.StatusCreated task.EndTime = 0 task.Error = "" - if err := instantmessage.NewWeChatClient().SendWorkflowTaskNotifications(task); err != nil { - log.Errorf("send workflow task notification failed, error: %v", err) - } if err := runtimeWorkflowController.UpdateTask(task); err != nil { log.Errorf("manual execute workflow task error: %v", err)