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
+67 -11
appview
db
issues
models
pages
templates
repo
fragments
issues
pulls
fragments
pulls
+41
appview/db/reaction.go
··· 74 74 return countMap, nil 75 75 } 76 76 77 + func GetReactionMap(e Execer, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) { 78 + const maxUsersPerKind = 20 79 + 80 + query := ` 81 + select kind, reacted_by_did, 82 + row_number() over (partition by kind order by created asc) as rn, 83 + count(*) over (partition by kind) as total 84 + from reactions 85 + where thread_at = ? 86 + order by kind, created asc` 87 + 88 + rows, err := e.Query(query, threadAt) 89 + if err != nil { 90 + return nil, err 91 + } 92 + defer rows.Close() 93 + 94 + reactionMap := map[models.ReactionKind]models.ReactionDisplayData{} 95 + for _, kind := range models.OrderedReactionKinds { 96 + reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}} 97 + } 98 + 99 + for rows.Next() { 100 + var kind models.ReactionKind 101 + var did string 102 + var rn, total int 103 + if err := rows.Scan(&kind, &did, &rn, &total); err != nil { 104 + return nil, err 105 + } 106 + 107 + data := reactionMap[kind] 108 + data.Count = total 109 + if rn <= maxUsersPerKind { 110 + data.Users = append(data.Users, did) 111 + } 112 + reactionMap[kind] = data 113 + } 114 + 115 + return reactionMap, rows.Err() 116 + } 117 + 77 118 func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool { 78 119 if _, err := GetReaction(e, userDid, threadAt, kind); err != nil { 79 120 return false
+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,