show handles of users that reacted to issues and pulls #636

merged
opened by camsmith.dev targeting master from camsmith.dev/core: feat/user-reaction-handles

Displays the handles of users that reacted on issues and pulls, in addition to the counts. Closes https://tangled.org/@tangled.org/core/issues/246

There's a couple ways to do it

  1. Doing the db query in 1 roundtrip -- that's what I did here
  2. Doing 2 db queries, 1 for the count and 1 for the usernames

If we prefer (2), or another route, that's fine, I can change it.

Changed files
+62 -18
appview
db
issues
models
pages
templates
repo
fragments
issues
pulls
fragments
pulls
+36 -7
appview/db/reaction.go
··· 62 62 return count, nil 63 63 } 64 64 65 - func GetReactionCountMap(e Execer, threadAt syntax.ATURI) (map[models.ReactionKind]int, error) { 66 - countMap := map[models.ReactionKind]int{} 65 + func GetReactionMap(e Execer, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) { 66 + const maxUsersPerKind = 20 67 + 68 + query := ` 69 + select kind, reacted_by_did, 70 + row_number() over (partition by kind order by created asc) as rn, 71 + count(*) over (partition by kind) as total 72 + from reactions 73 + where thread_at = ? 74 + order by kind, created asc` 75 + 76 + rows, err := e.Query(query, threadAt) 77 + if err != nil { 78 + return nil, err 79 + } 80 + defer rows.Close() 81 + 82 + reactionMap := map[models.ReactionKind]models.ReactionDisplayData{} 67 83 for _, kind := range models.OrderedReactionKinds { 68 - count, err := GetReactionCount(e, threadAt, kind) 69 - if err != nil { 70 - return map[models.ReactionKind]int{}, nil 84 + reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}} 85 + } 86 + 87 + for rows.Next() { 88 + var kind models.ReactionKind 89 + var did string 90 + var rn, total int 91 + if err := rows.Scan(&kind, &did, &rn, &total); err != nil { 92 + return nil, err 71 93 } 72 - countMap[kind] = count 94 + 95 + data := reactionMap[kind] 96 + data.Count = total 97 + if rn <= maxUsersPerKind { 98 + data.Users = append(data.Users, did) 99 + } 100 + reactionMap[kind] = data 73 101 } 74 - return countMap, nil 102 + 103 + return reactionMap, rows.Err() 75 104 } 76 105 77 106 func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool {
+2 -2
appview/issues/issues.go
··· 83 83 return 84 84 } 85 85 86 - reactionCountMap, err := db.GetReactionCountMap(rp.db, issue.AtUri()) 86 + reactionMap, err := db.GetReactionMap(rp.db, issue.AtUri()) 87 87 if err != nil { 88 88 l.Error("failed to get issue reactions", "err", err) 89 89 } ··· 115 115 Issue: issue, 116 116 CommentList: issue.CommentList(), 117 117 OrderedReactionKinds: models.OrderedReactionKinds, 118 - Reactions: reactionCountMap, 118 + Reactions: reactionMap, 119 119 UserReacted: userReactions, 120 120 LabelDefs: defs, 121 121 })
+5
appview/models/reaction.go
··· 55 55 Rkey string 56 56 Kind ReactionKind 57 57 } 58 + 59 + type ReactionDisplayData struct { 60 + Count int 61 + Users []string 62 + }
+2 -2
appview/pages/pages.go
··· 985 985 LabelDefs map[string]*models.LabelDefinition 986 986 987 987 OrderedReactionKinds []models.ReactionKind 988 - Reactions map[models.ReactionKind]int 988 + Reactions map[models.ReactionKind]models.ReactionDisplayData 989 989 UserReacted map[models.ReactionKind]bool 990 990 } 991 991 ··· 1138 1138 Pipelines map[string]models.Pipeline 1139 1139 1140 1140 OrderedReactionKinds []models.ReactionKind 1141 - Reactions map[models.ReactionKind]int 1141 + Reactions map[models.ReactionKind]models.ReactionDisplayData 1142 1142 UserReacted map[models.ReactionKind]bool 1143 1143 1144 1144 LabelDefs map[string]*models.LabelDefinition
+7 -1
appview/pages/templates/repo/fragments/reaction.html
··· 2 2 <button 3 3 id="reactIndi-{{ .Kind }}" 4 4 class="flex justify-center items-center min-w-8 min-h-8 rounded border 5 - leading-4 px-3 gap-1 5 + leading-4 px-3 gap-1 relative group 6 6 {{ if eq .Count 0 }} 7 7 hidden 8 8 {{ end }} ··· 30 30 hx-disabled-elt="this" 31 31 > 32 32 <span>{{ .Kind }}</span> <span>{{ .Count }}</span> 33 + {{ if gt (length .Users) 0 }} 34 + <div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-gray-900 dark:bg-gray-700 text-white text-sm rounded shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-opacity pointer-events-none w-96 break-words z-10"> 35 + {{ range $i, $did := .Users }}{{ if $i }}, {{ end }}{{ resolve $did }}{{ end }}{{ if gt .Count (length .Users) }}, and {{ sub .Count (length .Users) }} more{{ end }} 36 + <div class="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div> 37 + </div> 38 + {{ end }} 33 39 </button> 34 40 {{ end }}
+4 -2
appview/pages/templates/repo/issues/issue.html
··· 110 110 <div class="flex items-center gap-2"> 111 111 {{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }} 112 112 {{ range $kind := .OrderedReactionKinds }} 113 + {{ $reactionData := index $.Reactions $kind }} 113 114 {{ 114 115 template "repo/fragments/reaction" 115 116 (dict 116 117 "Kind" $kind 117 - "Count" (index $.Reactions $kind) 118 + "Count" $reactionData.Count 118 119 "IsReacted" (index $.UserReacted $kind) 119 - "ThreadAt" $.Issue.AtUri) 120 + "ThreadAt" $.Issue.AtUri 121 + "Users" $reactionData.Users) 120 122 }} 121 123 {{ end }} 122 124 </div>
+4 -2
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 66 66 <div class="flex items-center gap-2 mt-2"> 67 67 {{ template "repo/fragments/reactionsPopUp" . }} 68 68 {{ range $kind := . }} 69 + {{ $reactionData := index $.Reactions $kind }} 69 70 {{ 70 71 template "repo/fragments/reaction" 71 72 (dict 72 73 "Kind" $kind 73 - "Count" (index $.Reactions $kind) 74 + "Count" $reactionData.Count 74 75 "IsReacted" (index $.UserReacted $kind) 75 - "ThreadAt" $.Pull.PullAt) 76 + "ThreadAt" $.Pull.PullAt 77 + "Users" $reactionData.Users) 76 78 }} 77 79 {{ end }} 78 80 </div>
+2 -2
appview/pulls/pulls.go
··· 189 189 m[p.Sha] = p 190 190 } 191 191 192 - reactionCountMap, err := db.GetReactionCountMap(s.db, pull.PullAt()) 192 + reactionMap, err := db.GetReactionMap(s.db, pull.PullAt()) 193 193 if err != nil { 194 194 log.Println("failed to get pull reactions") 195 195 s.pages.Notice(w, "pulls", "Failed to load pull. Try again later.") ··· 227 227 Pipelines: m, 228 228 229 229 OrderedReactionKinds: models.OrderedReactionKinds, 230 - Reactions: reactionCountMap, 230 + Reactions: reactionMap, 231 231 UserReacted: userReactions, 232 232 233 233 LabelDefs: defs,