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
+68 -24
appview
db
issues
models
pages
templates
repo
fragments
issues
pulls
fragments
pulls
state
+34 -7
appview/db/reaction.go
···
return count, nil
}
-
func GetReactionCountMap(e Execer, threadAt syntax.ATURI) (map[models.ReactionKind]int, error) {
-
countMap := map[models.ReactionKind]int{}
+
func GetReactionMap(e Execer, userLimit int, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) {
+
query := `
+
select kind, reacted_by_did,
+
row_number() over (partition by kind order by created asc) as rn,
+
count(*) over (partition by kind) as total
+
from reactions
+
where thread_at = ?
+
order by kind, created asc`
+
+
rows, err := e.Query(query, threadAt)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
reactionMap := map[models.ReactionKind]models.ReactionDisplayData{}
for _, kind := range models.OrderedReactionKinds {
-
count, err := GetReactionCount(e, threadAt, kind)
-
if err != nil {
-
return map[models.ReactionKind]int{}, nil
+
reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}}
+
}
+
+
for rows.Next() {
+
var kind models.ReactionKind
+
var did string
+
var rn, total int
+
if err := rows.Scan(&kind, &did, &rn, &total); err != nil {
+
return nil, err
}
-
countMap[kind] = count
+
+
data := reactionMap[kind]
+
data.Count = total
+
if userLimit > 0 && rn <= userLimit {
+
data.Users = append(data.Users, did)
+
}
+
reactionMap[kind] = data
}
-
return countMap, nil
+
+
return reactionMap, rows.Err()
}
func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool {
+2 -2
appview/issues/issues.go
···
return
}
-
reactionCountMap, err := db.GetReactionCountMap(rp.db, issue.AtUri())
+
reactionMap, err := db.GetReactionMap(rp.db, 20, issue.AtUri())
if err != nil {
l.Error("failed to get issue reactions", "err", err)
}
···
Issue: issue,
CommentList: issue.CommentList(),
OrderedReactionKinds: models.OrderedReactionKinds,
-
Reactions: reactionCountMap,
+
Reactions: reactionMap,
UserReacted: userReactions,
LabelDefs: defs,
})
+5
appview/models/reaction.go
···
Rkey string
Kind ReactionKind
}
+
+
type ReactionDisplayData struct {
+
Count int
+
Users []string
+
}
+3 -2
appview/pages/pages.go
···
LabelDefs map[string]*models.LabelDefinition
OrderedReactionKinds []models.ReactionKind
-
Reactions map[models.ReactionKind]int
+
Reactions map[models.ReactionKind]models.ReactionDisplayData
UserReacted map[models.ReactionKind]bool
}
···
ThreadAt syntax.ATURI
Kind models.ReactionKind
Count int
+
Users []string
IsReacted bool
···
Pipelines map[string]models.Pipeline
OrderedReactionKinds []models.ReactionKind
-
Reactions map[models.ReactionKind]int
+
Reactions map[models.ReactionKind]models.ReactionDisplayData
UserReacted map[models.ReactionKind]bool
LabelDefs map[string]*models.LabelDefinition
+6 -1
appview/pages/templates/repo/fragments/reaction.html
···
<button
id="reactIndi-{{ .Kind }}"
class="flex justify-center items-center min-w-8 min-h-8 rounded border
-
leading-4 px-3 gap-1
+
leading-4 px-3 gap-1 relative group
{{ if eq .Count 0 }}
hidden
{{ end }}
···
dark:hover:border-gray-600
{{ end }}
"
+
{{ if gt (length .Users) 0 }}
+
title="{{ range $i, $did := .Users }}{{ if ne $i 0 }}, {{ end }}{{ resolve $did }}{{ end }}{{ if gt .Count (length .Users) }}, and {{ sub .Count (length .Users) }} more{{ end }}"
+
{{ else }}
+
title="{{ .Kind }}"
+
{{ end }}
{{ if .IsReacted }}
hx-delete="/react?subject={{ .ThreadAt }}&kind={{ .Kind }}"
{{ else }}
+4 -2
appview/pages/templates/repo/issues/issue.html
···
<div class="flex items-center gap-2">
{{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }}
{{ range $kind := .OrderedReactionKinds }}
+
{{ $reactionData := index $.Reactions $kind }}
{{
template "repo/fragments/reaction"
(dict
"Kind" $kind
-
"Count" (index $.Reactions $kind)
+
"Count" $reactionData.Count
"IsReacted" (index $.UserReacted $kind)
-
"ThreadAt" $.Issue.AtUri)
+
"ThreadAt" $.Issue.AtUri
+
"Users" $reactionData.Users)
}}
{{ end }}
</div>
+4 -2
appview/pages/templates/repo/pulls/fragments/pullHeader.html
···
<div class="flex items-center gap-2 mt-2">
{{ template "repo/fragments/reactionsPopUp" . }}
{{ range $kind := . }}
+
{{ $reactionData := index $.Reactions $kind }}
{{
template "repo/fragments/reaction"
(dict
"Kind" $kind
-
"Count" (index $.Reactions $kind)
+
"Count" $reactionData.Count
"IsReacted" (index $.UserReacted $kind)
-
"ThreadAt" $.Pull.PullAt)
+
"ThreadAt" $.Pull.PullAt
+
"Users" $reactionData.Users)
}}
{{ end }}
</div>
+2 -2
appview/pulls/pulls.go
···
m[p.Sha] = p
}
-
reactionCountMap, err := db.GetReactionCountMap(s.db, pull.PullAt())
+
reactionMap, err := db.GetReactionMap(s.db, 20, pull.PullAt())
if err != nil {
log.Println("failed to get pull reactions")
s.pages.Notice(w, "pulls", "Failed to load pull. Try again later.")
···
Pipelines: m,
OrderedReactionKinds: models.OrderedReactionKinds,
-
Reactions: reactionCountMap,
+
Reactions: reactionMap,
UserReacted: userReactions,
LabelDefs: defs,
+8 -6
appview/state/reaction.go
···
return
}
-
count, err := db.GetReactionCount(s.db, subjectUri, reactionKind)
+
reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
if err != nil {
-
log.Println("failed to get reaction count for ", subjectUri)
+
log.Println("failed to get reactions for ", subjectUri)
}
log.Println("created atproto record: ", resp.Uri)
···
s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
ThreadAt: subjectUri,
Kind: reactionKind,
-
Count: count,
+
Count: reactionMap[reactionKind].Count,
+
Users: reactionMap[reactionKind].Users,
IsReacted: true,
})
···
// this is not an issue, the firehose event might have already done this
}
-
count, err := db.GetReactionCount(s.db, subjectUri, reactionKind)
+
reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
if err != nil {
-
log.Println("failed to get reaction count for ", subjectUri)
+
log.Println("failed to get reactions for ", subjectUri)
return
}
s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
ThreadAt: subjectUri,
Kind: reactionKind,
-
Count: count,
+
Count: reactionMap[reactionKind].Count,
+
Users: reactionMap[reactionKind].Users,
IsReacted: false,
})