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
···
return count, nil
}
-
func GetReactionCountMap(e Execer, threadAt syntax.ATURI) (map[models.ReactionKind]int, error) {
-
countMap := map[models.ReactionKind]int{}
+
func GetReactionMap(e Execer, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) {
+
const maxUsersPerKind = 20
+
+
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 rn <= maxUsersPerKind {
+
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, 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
+
}
+2 -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
}
···
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
+7 -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 }}
···
hx-disabled-elt="this"
>
<span>{{ .Kind }}</span> <span>{{ .Count }}</span>
+
{{ if gt (length .Users) 0 }}
+
<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">
+
{{ range $i, $did := .Users }}{{ if $i }}, {{ end }}{{ resolve $did }}{{ end }}{{ if gt .Count (length .Users) }}, and {{ sub .Count (length .Users) }} more{{ end }}
+
<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>
+
</div>
+
{{ end }}
</button>
{{ end }}
+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, 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,