Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

appview/notify: notify on issue/PR assign

currently hardcoded to fire on the tangled assignee label def.

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
committer
Tangled
date (May 29, 2026, 5:18 PM +0300) commit 094f1b38 parent fb87b0b3 change-id tpxoxnmo
+117 -34
+2 -2
appview/indexer/notifier.go
··· 63 63 ix.getAndReindexRepo(ctx, issue.RepoDid) 64 64 } 65 65 66 - func (ix *Indexer) NewIssueLabelOp(ctx context.Context, issue *models.Issue) { 66 + func (ix *Indexer) NewIssueLabelOp(ctx context.Context, _ syntax.DID, issue *models.Issue, _ []models.LabelOp) { 67 67 l := log.FromContext(ctx).With("notifier", "indexer", "issue", issue) 68 68 l.Debug("reindexing issue after label change") 69 69 err := ix.Issues.Index(ctx, *issue) ··· 72 72 } 73 73 } 74 74 75 - func (ix *Indexer) NewPullLabelOp(ctx context.Context, pull *models.Pull) { 75 + func (ix *Indexer) NewPullLabelOp(ctx context.Context, _ syntax.DID, pull *models.Pull, _ []models.LabelOp) { 76 76 l := log.FromContext(ctx).With("notifier", "indexer", "pull", pull) 77 77 l.Debug("reindexing pull after label change") 78 78 err := ix.Pulls.Index(ctx, pull)
+2 -2
appview/labels/labels.go
··· 253 253 if subject.Collection() == tangled.RepoIssueNSID { 254 254 issues, err := db.GetIssues(l.db, orm.FilterEq("at_uri", subjectUri)) 255 255 if err == nil && len(issues) == 1 { 256 - l.notifier.NewIssueLabelOp(r.Context(), &issues[0]) 256 + l.notifier.NewIssueLabelOp(r.Context(), syntax.DID(did), &issues[0], validLabelOps) 257 257 } 258 258 } 259 259 if subject.Collection() == tangled.RepoPullNSID { 260 260 pulls, err := db.GetPulls(l.db, orm.FilterEq("at_uri", subjectUri)) 261 261 if err == nil && len(pulls) == 1 { 262 - l.notifier.NewPullLabelOp(r.Context(), pulls[0]) 262 + l.notifier.NewPullLabelOp(r.Context(), syntax.DID(did), pulls[0], validLabelOps) 263 263 } 264 264 } 265 265
+29 -13
appview/models/notifications.go
··· 9 9 type NotificationType string 10 10 11 11 const ( 12 - NotificationTypeRepoStarred NotificationType = "repo_starred" 13 - NotificationTypeIssueCreated NotificationType = "issue_created" 14 - NotificationTypeIssueCommented NotificationType = "issue_commented" 15 - NotificationTypePullCreated NotificationType = "pull_created" 16 - NotificationTypePullCommented NotificationType = "pull_commented" 17 - NotificationTypeFollowed NotificationType = "followed" 18 - NotificationTypePullMerged NotificationType = "pull_merged" 19 - NotificationTypeIssueClosed NotificationType = "issue_closed" 20 - NotificationTypeIssueReopen NotificationType = "issue_reopen" 21 - NotificationTypePullClosed NotificationType = "pull_closed" 22 - NotificationTypePullReopen NotificationType = "pull_reopen" 23 - NotificationTypeUserMentioned NotificationType = "user_mentioned" 12 + NotificationTypeRepoStarred NotificationType = "repo_starred" 13 + NotificationTypeIssueCreated NotificationType = "issue_created" 14 + NotificationTypeIssueCommented NotificationType = "issue_commented" 15 + NotificationTypePullCreated NotificationType = "pull_created" 16 + NotificationTypePullCommented NotificationType = "pull_commented" 17 + NotificationTypeFollowed NotificationType = "followed" 18 + NotificationTypePullMerged NotificationType = "pull_merged" 19 + NotificationTypeIssueClosed NotificationType = "issue_closed" 20 + NotificationTypeIssueReopen NotificationType = "issue_reopen" 21 + NotificationTypePullClosed NotificationType = "pull_closed" 22 + NotificationTypePullReopen NotificationType = "pull_reopen" 23 + NotificationTypeUserMentioned NotificationType = "user_mentioned" 24 + NotificationTypeIssueAssigned NotificationType = "issue_assigned" 25 + NotificationTypeIssueUnassigned NotificationType = "issue_unassigned" 26 + NotificationTypePullAssigned NotificationType = "pull_assigned" 27 + NotificationTypePullUnassigned NotificationType = "pull_unassigned" 24 28 ) 25 29 26 30 var SocialNotificationTypes = []NotificationType{ ··· 39 43 NotificationTypePullClosed, 40 44 NotificationTypePullReopen, 41 45 NotificationTypeUserMentioned, 46 + NotificationTypeIssueAssigned, 47 + NotificationTypeIssueUnassigned, 48 + NotificationTypePullAssigned, 49 + NotificationTypePullUnassigned, 42 50 } 43 51 44 52 type Notification struct { ··· 84 92 return "user-plus" 85 93 case NotificationTypeUserMentioned: 86 94 return "at-sign" 95 + case NotificationTypeIssueAssigned, NotificationTypePullAssigned: 96 + return "user-round-check" 97 + case NotificationTypeIssueUnassigned, NotificationTypePullUnassigned: 98 + return "user-round-x" 87 99 default: 88 100 return "" 89 101 } ··· 135 147 return prefs.PullCreated // same pref for now 136 148 case NotificationTypeFollowed: 137 149 return prefs.Followed 138 - case NotificationTypeUserMentioned: 150 + case NotificationTypeUserMentioned, 151 + NotificationTypeIssueAssigned, 152 + NotificationTypeIssueUnassigned, 153 + NotificationTypePullAssigned, 154 + NotificationTypePullUnassigned: 139 155 return prefs.UserMentioned 140 156 default: 141 157 return false
+58 -3
appview/notify/db/db.go
··· 16 16 ) 17 17 18 18 const ( 19 - maxMentions = 8 19 + maxMentions = 8 20 + assigneeLabelAt = "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/assignee" 20 21 ) 21 22 22 23 type databaseNotifier struct { ··· 276 277 // no-op for now 277 278 } 278 279 279 - func (n *databaseNotifier) NewIssueLabelOp(ctx context.Context, issue *models.Issue) {} 280 - func (n *databaseNotifier) NewPullLabelOp(ctx context.Context, pull *models.Pull) {} 280 + func (n *databaseNotifier) NewIssueLabelOp(ctx context.Context, actor syntax.DID, issue *models.Issue, ops []models.LabelOp) { 281 + entityType := "issue" 282 + entityId := issue.AtUri().String() 283 + repoId := &issue.Repo.Id 284 + issueId := &issue.Id 285 + var pullId *int64 286 + 287 + assigned := sets.New[syntax.DID]() 288 + unassigned := sets.New[syntax.DID]() 289 + for _, op := range ops { 290 + if op.OperandKey != assigneeLabelAt { 291 + continue 292 + } 293 + assignee := syntax.DID(op.OperandValue) 294 + switch op.Operation { 295 + case models.LabelOperationAdd: 296 + assigned.Insert(assignee) 297 + case models.LabelOperationDel: 298 + unassigned.Insert(assignee) 299 + default: 300 + continue 301 + } 302 + } 303 + 304 + n.notifyEvent(ctx, actor, assigned, models.NotificationTypeIssueAssigned, entityType, entityId, repoId, issueId, pullId) 305 + n.notifyEvent(ctx, actor, unassigned, models.NotificationTypeIssueUnassigned, entityType, entityId, repoId, issueId, pullId) 306 + } 307 + 308 + func (n *databaseNotifier) NewPullLabelOp(ctx context.Context, actor syntax.DID, pull *models.Pull, ops []models.LabelOp) { 309 + entityType := "pull" 310 + entityId := pull.AtUri().String() 311 + repoId := &pull.Repo.Id 312 + var issueId *int64 313 + p := int64(pull.ID) 314 + pullId := &p 315 + 316 + assigned := sets.New[syntax.DID]() 317 + unassigned := sets.New[syntax.DID]() 318 + for _, op := range ops { 319 + if op.OperandKey != assigneeLabelAt { 320 + continue 321 + } 322 + assignee := syntax.DID(op.OperandValue) 323 + switch op.Operation { 324 + case models.LabelOperationAdd: 325 + assigned.Insert(assignee) 326 + case models.LabelOperationDel: 327 + unassigned.Insert(assignee) 328 + default: 329 + continue 330 + } 331 + } 332 + 333 + n.notifyEvent(ctx, actor, assigned, models.NotificationTypePullAssigned, entityType, entityId, repoId, issueId, pullId) 334 + n.notifyEvent(ctx, actor, unassigned, models.NotificationTypePullUnassigned, entityType, entityId, repoId, issueId, pullId) 335 + } 281 336 282 337 func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 283 338 actorDid := syntax.DID(follow.UserDid)
+4 -4
appview/notify/logging/notifier.go
··· 70 70 l.inner.DeleteIssue(ctx, issue) 71 71 } 72 72 73 - func (l *loggingNotifier) NewIssueLabelOp(ctx context.Context, issue *models.Issue) { 73 + func (l *loggingNotifier) NewIssueLabelOp(ctx context.Context, actor syntax.DID, issue *models.Issue, ops []models.LabelOp) { 74 74 ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewIssueLabelOp")) 75 - l.inner.NewIssueLabelOp(ctx, issue) 75 + l.inner.NewIssueLabelOp(ctx, actor, issue, ops) 76 76 } 77 77 78 - func (l *loggingNotifier) NewPullLabelOp(ctx context.Context, pull *models.Pull) { 78 + func (l *loggingNotifier) NewPullLabelOp(ctx context.Context, actor syntax.DID, pull *models.Pull, ops []models.LabelOp) { 79 79 ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewPullLabelOp")) 80 - l.inner.NewPullLabelOp(ctx, pull) 80 + l.inner.NewPullLabelOp(ctx, actor, pull, ops) 81 81 } 82 82 83 83 func (l *loggingNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
+4 -4
appview/notify/merged_notifier.go
··· 70 70 m.fanout(func(n Notifier) { n.DeleteIssue(ctx, issue) }) 71 71 } 72 72 73 - func (m *mergedNotifier) NewIssueLabelOp(ctx context.Context, issue *models.Issue) { 74 - m.fanout(func(n Notifier) { n.NewIssueLabelOp(ctx, issue) }) 73 + func (m *mergedNotifier) NewIssueLabelOp(ctx context.Context, actor syntax.DID, issue *models.Issue, ops []models.LabelOp) { 74 + m.fanout(func(n Notifier) { n.NewIssueLabelOp(ctx, actor, issue, ops) }) 75 75 } 76 76 77 - func (m *mergedNotifier) NewPullLabelOp(ctx context.Context, pull *models.Pull) { 78 - m.fanout(func(n Notifier) { n.NewPullLabelOp(ctx, pull) }) 77 + func (m *mergedNotifier) NewPullLabelOp(ctx context.Context, actor syntax.DID, pull *models.Pull, ops []models.LabelOp) { 78 + m.fanout(func(n Notifier) { n.NewPullLabelOp(ctx, actor, pull, ops) }) 79 79 } 80 80 81 81 func (m *mergedNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
+6 -4
appview/notify/notifier.go
··· 28 28 NewPull(ctx context.Context, pull *models.Pull) 29 29 NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) 30 30 31 - NewIssueLabelOp(ctx context.Context, issue *models.Issue) 32 - NewPullLabelOp(ctx context.Context, pull *models.Pull) 31 + NewIssueLabelOp(ctx context.Context, actor syntax.DID, issue *models.Issue, ops []models.LabelOp) 32 + NewPullLabelOp(ctx context.Context, actor syntax.DID, pull *models.Pull, ops []models.LabelOp) 33 33 34 34 UpdateProfile(ctx context.Context, profile *models.Profile) 35 35 ··· 63 63 func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {} 64 64 func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {} 65 65 66 - func (m *BaseNotifier) NewIssueLabelOp(ctx context.Context, issue *models.Issue) {} 67 - func (m *BaseNotifier) NewPullLabelOp(ctx context.Context, pull *models.Pull) {} 66 + func (m *BaseNotifier) NewIssueLabelOp(ctx context.Context, actor syntax.DID, issue *models.Issue, ops []models.LabelOp) { 67 + } 68 + func (m *BaseNotifier) NewPullLabelOp(ctx context.Context, actor syntax.DID, pull *models.Pull, ops []models.LabelOp) { 69 + } 68 70 69 71 func (m *BaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {} 70 72 func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {}
+11 -1
appview/pages/templates/notifications/fragments/item.html
··· 57 57 {{ else if or (eq .Type "pull_created") (eq .Type "pull_reopen") }}text-green-600 dark:text-green-500 58 58 {{ else if eq .Type "pull_merged" }}text-purple-600 dark:text-purple-500 59 59 {{ else if eq .Type "pull_closed" }}text-red-600 dark:text-red-500 60 - {{ else if eq .Type "user_mentioned" }}text-blue-600 dark:text-blue-500 60 + {{ else if eq .Type "user_mentioned" }}text-blue-500 dark:text-blue-400 61 + {{ else if eq .Type "repo_starred" }}text-yellow-500 dark:text-yellow-600 62 + {{ else if or (eq .Type "issue_assigned") (eq .Type "pull_assigned") }} text-blue-500 dark:text-blue-400 61 63 {{ else }}text-gray-500 dark:text-gray-400 62 64 {{ end }} 63 65 {{ end }} ··· 113 115 {{ else }} 114 116 <span class="{{ $textStyle }}">mentioned you in</span> <span class="{{ $subjectStyle }}">{{ $repo }}</span> 115 117 {{ end }} 118 + {{ else if eq .Type "issue_assigned" }} 119 + <span class="{{ $textStyle }}">assigned you to an issue on</span> <span class="{{ $subjectStyle }}">{{ $repo }}</span> 120 + {{ else if eq .Type "issue_unassigned" }} 121 + <span class="{{ $textStyle }}">unassigned you from an issue on</span> <span class="{{ $subjectStyle }}">{{ $repo }}</span> 122 + {{ else if eq .Type "pull_assigned" }} 123 + <span class="{{ $textStyle }}">assigned you to a PR on</span> <span class="{{ $subjectStyle }}">{{ $repo }}</span> 124 + {{ else if eq .Type "pull_unassigned" }} 125 + <span class="{{ $textStyle }}">unassigned you from a PR on</span> <span class="{{ $subjectStyle }}">{{ $repo }}</span> 116 126 {{ end }} 117 127 </span> 118 128 {{ end }}
+1 -1
appview/pulls/labels.go
··· 173 173 continue 174 174 } 175 175 176 - s.notifier.NewPullLabelOp(ctx, pull) 176 + s.notifier.NewPullLabelOp(ctx, userDid, pull, valid) 177 177 } 178 178 } 179 179