Compare commits

...

2 Commits

Author SHA1 Message Date
Jiayuan Zhang
1d631260e1 fix(notifications): parent subscriber inbox items now point to sub-issue
Split notifyIssueSubscribers into subscriberIssueID (which issue's
subscribers to query) and targetIssueID (which issue the inbox item
links to). When notifying parent subscribers, the inbox item correctly
points to the sub-issue where the change occurred, so clicking the
notification navigates to the right place.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:17:21 +08:00
Jiayuan Zhang
f73a99770b feat(notifications): notify parent issue subscribers on sub-issue changes
When a sub-issue receives a change (status, assignee, priority, comment, etc.),
parent issue subscribers are now also notified. Deduplicates against direct
subscribers to avoid double notifications. The inbox item still points to the
sub-issue so clicking the notification navigates to the actual change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 04:23:01 +08:00

View File

@@ -69,6 +69,8 @@ func parseMentions(content string) []mention {
// notifySubscribers queries the subscriber table for an issue, excludes the
// actor and any extra IDs, and creates inbox items for each remaining member
// subscriber. Publishes an inbox:new event for each notification.
// If the issue has a parent, parent issue subscribers are also notified
// (deduplicated against direct subscribers).
func notifySubscribers(
ctx context.Context,
queries *db.Queries,
@@ -84,12 +86,67 @@ func notifySubscribers(
body string,
details []byte,
) {
subs, err := queries.ListIssueSubscribers(ctx, parseUUID(issueID))
notified := notifyIssueSubscribers(ctx, queries, bus,
issueID, issueID, issueStatus, workspaceID, e, exclude,
notifType, severity, title, body, details)
// Also notify parent issue subscribers if this is a sub-issue.
issue, err := queries.GetIssue(ctx, parseUUID(issueID))
if err != nil {
slog.Error("failed to list subscribers for notification",
slog.Error("failed to get issue for parent notification",
"issue_id", issueID, "error", err)
return
}
if !issue.ParentIssueID.Valid {
return
}
// Merge already-notified IDs into exclude set for parent subscribers.
parentExclude := make(map[string]bool, len(exclude)+len(notified))
for id := range exclude {
parentExclude[id] = true
}
for id := range notified {
parentExclude[id] = true
}
// Query subscribers from the parent issue, but the inbox item still
// points to the sub-issue so the user navigates to the actual change.
parentID := util.UUIDToString(issue.ParentIssueID)
notifyIssueSubscribers(ctx, queries, bus,
parentID, issueID, issueStatus, workspaceID, e, parentExclude,
notifType, severity, title, body, details)
}
// notifyIssueSubscribers sends inbox notifications to subscribers of
// subscriberIssueID, but creates inbox items pointing to targetIssueID.
// This allows querying subscribers from a parent issue while the notification
// links to the sub-issue where the change actually occurred.
// Returns the set of member IDs that were notified.
func notifyIssueSubscribers(
ctx context.Context,
queries *db.Queries,
bus *events.Bus,
subscriberIssueID string,
targetIssueID string,
issueStatus string,
workspaceID string,
e events.Event,
exclude map[string]bool,
notifType string,
severity string,
title string,
body string,
details []byte,
) map[string]bool {
notified := map[string]bool{}
subs, err := queries.ListIssueSubscribers(ctx, parseUUID(subscriberIssueID))
if err != nil {
slog.Error("failed to list subscribers for notification",
"issue_id", subscriberIssueID, "error", err)
return notified
}
for _, sub := range subs {
// Only notify member-type subscribers (not agents)
@@ -115,7 +172,7 @@ func notifySubscribers(
RecipientID: sub.UserID,
Type: notifType,
Severity: severity,
IssueID: parseUUID(issueID),
IssueID: parseUUID(targetIssueID),
Title: title,
Body: util.StrToText(body),
ActorType: util.StrToText(e.ActorType),
@@ -128,6 +185,7 @@ func notifySubscribers(
continue
}
notified[subID] = true
resp := inboxItemToResponse(item)
resp["issue_status"] = issueStatus
bus.Publish(events.Event{
@@ -138,6 +196,8 @@ func notifySubscribers(
Payload: map[string]any{"item": resp},
})
}
return notified
}
// notifyDirect creates an inbox item for a specific recipient. Skips if the