Feat v0.7.10 workflow handoff (#64)
All checks were successful
CI/CD / test-go-pg (push) Successful in 2m54s
CI/CD / test-sqlite (push) Successful in 3m1s
CI/CD / build-and-deploy (push) Successful in 1m47s
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #64.
This commit is contained in:
2026-04-02 23:16:19 +00:00
committed by xcaliber
parent a9cf71b76d
commit f06c6c954b
17 changed files with 818 additions and 36 deletions

View File

@@ -100,6 +100,8 @@ func (e *Engine) Start(ctx context.Context, workflowID string, initialData json.
}
if err := e.stores.Workflows.CreateAssignment(ctx, a); err != nil {
log.Printf("[workflow-engine] create initial assignment: %v", err)
} else {
e.notifyAssignment(ctx, a, wf.Name, stages[0].Name)
}
}
@@ -247,6 +249,12 @@ func (e *Engine) advanceInternal(ctx context.Context, instanceID string, stageDa
"assignment_id": a.ID,
"team_id": a.TeamID,
})
// Notify team members
wfTitle := ""
if wf, _ := e.stores.Workflows.GetByID(ctx, inst.WorkflowID); wf != nil {
wfTitle = wf.Name
}
e.notifyAssignment(ctx, a, wfTitle, nextStage.Name)
}
}
@@ -477,6 +485,45 @@ func (e *Engine) emit(_ context.Context, label, room string, payload map[string]
})
}
// notifyAssignment creates notifications for team members when a workflow
// assignment is created. If the assignment targets a specific user, only
// that user is notified. Otherwise all team members are notified.
func (e *Engine) notifyAssignment(ctx context.Context, a *models.WorkflowAssignment, workflowTitle, stageName string) {
if e.stores.Notifications == nil || e.stores.Teams == nil {
return
}
var recipients []string
if a.AssignedTo != nil && *a.AssignedTo != "" {
recipients = []string{*a.AssignedTo}
} else {
members, err := e.stores.Teams.ListMembers(ctx, a.TeamID)
if err != nil {
log.Printf("[workflow-engine] list team members for notification: %v", err)
return
}
for _, m := range members {
recipients = append(recipients, m.UserID)
}
}
title := "Workflow assignment: " + stageName
body := workflowTitle
for _, uid := range recipients {
n := &models.Notification{
UserID: uid,
Type: models.NotifTypeWorkflowAssign,
Title: title,
Body: body,
ResourceType: "workflow",
ResourceID: a.InstanceID,
}
if err := e.stores.Notifications.Create(ctx, n); err != nil {
log.Printf("[workflow-engine] create notification: %v", err)
}
}
}
// mergeJSON merges b into a (shallow). Returns a if b is empty.
func mergeJSON(a, b json.RawMessage) json.RawMessage {
if len(b) == 0 || string(b) == "{}" || string(b) == "null" {