Monorepo for Tangled tangled.org

appview/pages: add labels to pulls

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

oppi.li b1729c7b 494ace72

verified
Changed files
+171 -43
appview
db
issues
models
pages
pulls
+19 -1
appview/db/pulls.go
··· 1 package db 2 3 import ( 4 "database/sql" 5 "fmt" 6 "maps" ··· 229 p.Submissions = submissions 230 } 231 } 232 233 orderedByPullId := []*models.Pull{} 234 for _, p := range pulls { ··· 339 } 340 } 341 342 - // order the submissions by pull_at 343 m := make(map[syntax.ATURI][]*models.PullSubmission) 344 for _, s := range submissionMap { 345 m[s.PullAt] = append(m[s.PullAt], s) 346 } 347 348 return m, nil
··· 1 package db 2 3 import ( 4 + "cmp" 5 "database/sql" 6 "fmt" 7 "maps" ··· 230 p.Submissions = submissions 231 } 232 } 233 + // collect allLabels for each issue 234 + allLabels, err := GetLabels(e, FilterIn("subject", pullAts)) 235 + if err != nil { 236 + return nil, fmt.Errorf("failed to query labels: %w", err) 237 + } 238 + for pullAt, labels := range allLabels { 239 + if p, ok := pulls[pullAt]; ok { 240 + p.Labels = labels 241 + } 242 + } 243 244 orderedByPullId := []*models.Pull{} 245 for _, p := range pulls { ··· 350 } 351 } 352 353 + // group the submissions by pull_at 354 m := make(map[syntax.ATURI][]*models.PullSubmission) 355 for _, s := range submissionMap { 356 m[s.PullAt] = append(m[s.PullAt], s) 357 + } 358 + 359 + // sort each one by round number 360 + for _, s := range m { 361 + slices.SortFunc(s, func(a, b *models.PullSubmission) int { 362 + return cmp.Compare(a.RoundNumber, b.RoundNumber) 363 + }) 364 } 365 366 return m, nil
+5 -1
appview/issues/issues.go
··· 798 return 799 } 800 801 - labelDefs, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels)) 802 if err != nil { 803 log.Println("failed to fetch labels", err) 804 rp.pages.Error503(w)
··· 798 return 799 } 800 801 + labelDefs, err := db.GetLabelDefinitions( 802 + rp.db, 803 + db.FilterIn("at_uri", f.Repo.Labels), 804 + db.FilterContains("scope", tangled.RepoIssueNSID), 805 + ) 806 if err != nil { 807 log.Println("failed to fetch labels", err) 808 rp.pages.Error503(w)
+44 -1
appview/models/pull.go
··· 77 PullSource *PullSource 78 79 // optionally, populate this when querying for reverse mappings 80 - Repo *Repo 81 } 82 83 func (p Pull) AsRecord() tangled.RepoPull { ··· 206 return p.StackId != "" 207 } 208 209 func (s PullSubmission) IsFormatPatch() bool { 210 return patchutil.IsFormatPatch(s.Patch) 211 } ··· 218 } 219 220 return patches 221 } 222 223 type Stack []*Pull
··· 77 PullSource *PullSource 78 79 // optionally, populate this when querying for reverse mappings 80 + Labels LabelState 81 + Repo *Repo 82 } 83 84 func (p Pull) AsRecord() tangled.RepoPull { ··· 207 return p.StackId != "" 208 } 209 210 + func (p *Pull) Participants() []string { 211 + participantSet := make(map[string]struct{}) 212 + participants := []string{} 213 + 214 + addParticipant := func(did string) { 215 + if _, exists := participantSet[did]; !exists { 216 + participantSet[did] = struct{}{} 217 + participants = append(participants, did) 218 + } 219 + } 220 + 221 + addParticipant(p.OwnerDid) 222 + 223 + for _, s := range p.Submissions { 224 + for _, sp := range s.Participants() { 225 + addParticipant(sp) 226 + } 227 + } 228 + 229 + return participants 230 + } 231 + 232 func (s PullSubmission) IsFormatPatch() bool { 233 return patchutil.IsFormatPatch(s.Patch) 234 } ··· 241 } 242 243 return patches 244 + } 245 + 246 + func (s *PullSubmission) Participants() []string { 247 + participantSet := make(map[string]struct{}) 248 + participants := []string{} 249 + 250 + addParticipant := func(did string) { 251 + if _, exists := participantSet[did]; !exists { 252 + participantSet[did] = struct{}{} 253 + participants = append(participants, did) 254 + } 255 + } 256 + 257 + addParticipant(s.PullAt.Authority().String()) 258 + 259 + for _, c := range s.Comments { 260 + addParticipant(c.OwnerDid) 261 + } 262 + 263 + return participants 264 } 265 266 type Stack []*Pull
+3
appview/pages/pages.go
··· 1083 FilteringBy models.PullState 1084 Stacks map[string]models.Stack 1085 Pipelines map[string]models.Pipeline 1086 } 1087 1088 func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { ··· 1122 OrderedReactionKinds []models.ReactionKind 1123 Reactions map[models.ReactionKind]int 1124 UserReacted map[models.ReactionKind]bool 1125 } 1126 1127 func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
··· 1083 FilteringBy models.PullState 1084 Stacks map[string]models.Stack 1085 Pipelines map[string]models.Pipeline 1086 + LabelDefs map[string]*models.LabelDefinition 1087 } 1088 1089 func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { ··· 1123 OrderedReactionKinds []models.ReactionKind 1124 Reactions map[models.ReactionKind]int 1125 UserReacted map[models.ReactionKind]bool 1126 + 1127 + LabelDefs map[string]*models.LabelDefinition 1128 } 1129 1130 func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
+1 -1
appview/pages/templates/repo/fragments/labelPanel.html
··· 1 {{ define "repo/fragments/labelPanel" }} 2 - <div id="label-panel" class="flex flex-col gap-6"> 3 {{ template "basicLabels" . }} 4 {{ template "kvLabels" . }} 5 </div>
··· 1 {{ define "repo/fragments/labelPanel" }} 2 + <div id="label-panel" class="flex flex-col gap-6 px-6 md:px-0"> 3 {{ template "basicLabels" . }} 4 {{ template "kvLabels" . }} 5 </div>
+26
appview/pages/templates/repo/fragments/participants.html
···
··· 1 + {{ define "repo/fragments/participants" }} 2 + {{ $all := . }} 3 + {{ $ps := take $all 5 }} 4 + <div class="px-6 md:px-0"> 5 + <div class="py-1 flex items-center text-sm"> 6 + <span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span> 7 + <span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span> 8 + </div> 9 + <div class="flex items-center -space-x-3 mt-2"> 10 + {{ $c := "z-50 z-40 z-30 z-20 z-10" }} 11 + {{ range $i, $p := $ps }} 12 + <img 13 + src="{{ tinyAvatar . }}" 14 + alt="" 15 + class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0" 16 + /> 17 + {{ end }} 18 + 19 + {{ if gt (len $all) 5 }} 20 + <span class="pl-4 text-gray-500 dark:text-gray-400 text-sm"> 21 + +{{ sub (len $all) 5 }} 22 + </span> 23 + {{ end }} 24 + </div> 25 + </div> 26 + {{ end }}
+1 -27
appview/pages/templates/repo/issues/issue.html
··· 22 "Defs" $.LabelDefs 23 "Subject" $.Issue.AtUri 24 "State" $.Issue.Labels) }} 25 - {{ template "issueParticipants" . }} 26 </div> 27 </div> 28 {{ end }} ··· 122 </div> 123 {{ end }} 124 125 - {{ define "issueParticipants" }} 126 - {{ $all := .Issue.Participants }} 127 - {{ $ps := take $all 5 }} 128 - <div> 129 - <div class="py-1 flex items-center text-sm"> 130 - <span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span> 131 - <span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span> 132 - </div> 133 - <div class="flex items-center -space-x-3 mt-2"> 134 - {{ $c := "z-50 z-40 z-30 z-20 z-10" }} 135 - {{ range $i, $p := $ps }} 136 - <img 137 - src="{{ tinyAvatar . }}" 138 - alt="" 139 - class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0" 140 - /> 141 - {{ end }} 142 - 143 - {{ if gt (len $all) 5 }} 144 - <span class="pl-4 text-gray-500 dark:text-gray-400 text-sm"> 145 - +{{ sub (len $all) 5 }} 146 - </span> 147 - {{ end }} 148 - </div> 149 - </div> 150 - {{ end }} 151 152 {{ define "repoAfter" }} 153 <div class="flex flex-col gap-4 mt-4">
··· 22 "Defs" $.LabelDefs 23 "Subject" $.Issue.AtUri 24 "State" $.Issue.Labels) }} 25 + {{ template "repo/fragments/participants" $.Issue.Participants }} 26 </div> 27 </div> 28 {{ end }} ··· 122 </div> 123 {{ end }} 124 125 126 {{ define "repoAfter" }} 127 <div class="flex flex-col gap-4 mt-4">
+30 -12
appview/pages/templates/repo/pulls/pull.html
··· 9 {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} 10 {{ end }} 11 12 13 {{ define "repoContent" }} 14 {{ template "repo/pulls/fragments/pullHeader" . }} ··· 39 {{ with $item }} 40 <details {{ if eq $idx $lastIdx }}open{{ end }}> 41 <summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer"> 42 - <div class="flex flex-wrap gap-2 items-center"> 43 <!-- round number --> 44 <div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white"> 45 <span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span> 46 </div> 47 <!-- round summary --> 48 - <div class="rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400"> 49 <span class="gap-1 flex items-center"> 50 {{ $owner := resolve $.Pull.OwnerDid }} 51 {{ $re := "re" }} ··· 72 <span class="hidden md:inline">diff</span> 73 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 74 </a> 75 - {{ if not (eq .RoundNumber 0) }} 76 - <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 77 - hx-boost="true" 78 - href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}/interdiff"> 79 - {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 80 - <span class="hidden md:inline">interdiff</span> 81 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 82 - </a> 83 - <span id="interdiff-error-{{.RoundNumber}}"></span> 84 {{ end }} 85 </div> 86 </summary> 87 ··· 146 147 <div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative"> 148 {{ range $cidx, $c := .Comments }} 149 - <div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full md:max-w-3/5 md:w-fit"> 150 {{ if gt $cidx 0 }} 151 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> 152 {{ end }}
··· 9 {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} 10 {{ end }} 11 12 + {{ define "repoContentLayout" }} 13 + <div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full"> 14 + <div class="col-span-1 md:col-span-8"> 15 + <section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white"> 16 + {{ block "repoContent" . }}{{ end }} 17 + </section> 18 + {{ block "repoAfter" . }}{{ end }} 19 + </div> 20 + <div class="col-span-1 md:col-span-2 flex flex-col gap-6"> 21 + {{ template "repo/fragments/labelPanel" 22 + (dict "RepoInfo" $.RepoInfo 23 + "Defs" $.LabelDefs 24 + "Subject" $.Pull.PullAt 25 + "State" $.Pull.Labels) }} 26 + {{ template "repo/fragments/participants" $.Pull.Participants }} 27 + </div> 28 + </div> 29 + {{ end }} 30 31 {{ define "repoContent" }} 32 {{ template "repo/pulls/fragments/pullHeader" . }} ··· 57 {{ with $item }} 58 <details {{ if eq $idx $lastIdx }}open{{ end }}> 59 <summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer"> 60 + <div class="flex flex-wrap gap-2 items-stretch"> 61 <!-- round number --> 62 <div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white"> 63 <span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span> 64 </div> 65 <!-- round summary --> 66 + <div class="flex-1 rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400"> 67 <span class="gap-1 flex items-center"> 68 {{ $owner := resolve $.Pull.OwnerDid }} 69 {{ $re := "re" }} ··· 90 <span class="hidden md:inline">diff</span> 91 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 92 </a> 93 + {{ if ne $idx 0 }} 94 + <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 95 + hx-boost="true" 96 + href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}/interdiff"> 97 + {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 98 + <span class="hidden md:inline">interdiff</span> 99 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 100 + </a> 101 {{ end }} 102 + <span id="interdiff-error-{{.RoundNumber}}"></span> 103 </div> 104 </summary> 105 ··· 164 165 <div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative"> 166 {{ range $cidx, $c := .Comments }} 167 + <div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full"> 168 {{ if gt $cidx 0 }} 169 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> 170 {{ end }}
+7
appview/pages/templates/repo/pulls/pulls.html
··· 108 <span class="before:content-['·']"></span> 109 {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 110 {{ end }} 111 </div> 112 </div> 113 {{ if .StackId }}
··· 108 <span class="before:content-['·']"></span> 109 {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 110 {{ end }} 111 + 112 + {{ $state := .Labels }} 113 + {{ range $k, $d := $.LabelDefs }} 114 + {{ range $v, $s := $state.GetValSet $d.AtUri.String }} 115 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 116 + {{ end }} 117 + {{ end }} 118 </div> 119 </div> 120 {{ if .StackId }}
+35
appview/pulls/pulls.go
··· 200 userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt()) 201 } 202 203 s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 204 LoggedInUser: user, 205 RepoInfo: repoInfo, ··· 213 OrderedReactionKinds: models.OrderedReactionKinds, 214 Reactions: reactionCountMap, 215 UserReacted: userReactions, 216 }) 217 } 218 ··· 557 m[p.Sha] = p 558 } 559 560 s.pages.RepoPulls(w, pages.RepoPullsParams{ 561 LoggedInUser: s.oauth.GetUser(r), 562 RepoInfo: f.RepoInfo(user), 563 Pulls: pulls, 564 FilteringBy: state, 565 Stacks: stacks, 566 Pipelines: m,
··· 200 userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt()) 201 } 202 203 + labelDefs, err := db.GetLabelDefinitions( 204 + s.db, 205 + db.FilterIn("at_uri", f.Repo.Labels), 206 + db.FilterContains("scope", tangled.RepoPullNSID), 207 + ) 208 + if err != nil { 209 + log.Println("failed to fetch labels", err) 210 + s.pages.Error503(w) 211 + return 212 + } 213 + 214 + defs := make(map[string]*models.LabelDefinition) 215 + for _, l := range labelDefs { 216 + defs[l.AtUri().String()] = &l 217 + } 218 + 219 s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 220 LoggedInUser: user, 221 RepoInfo: repoInfo, ··· 229 OrderedReactionKinds: models.OrderedReactionKinds, 230 Reactions: reactionCountMap, 231 UserReacted: userReactions, 232 + 233 + LabelDefs: defs, 234 }) 235 } 236 ··· 575 m[p.Sha] = p 576 } 577 578 + labelDefs, err := db.GetLabelDefinitions( 579 + s.db, 580 + db.FilterIn("at_uri", f.Repo.Labels), 581 + db.FilterContains("scope", tangled.RepoPullNSID), 582 + ) 583 + if err != nil { 584 + log.Println("failed to fetch labels", err) 585 + s.pages.Error503(w) 586 + return 587 + } 588 + 589 + defs := make(map[string]*models.LabelDefinition) 590 + for _, l := range labelDefs { 591 + defs[l.AtUri().String()] = &l 592 + } 593 + 594 s.pages.RepoPulls(w, pages.RepoPullsParams{ 595 LoggedInUser: s.oauth.GetUser(r), 596 RepoInfo: f.RepoInfo(user), 597 Pulls: pulls, 598 + LabelDefs: defs, 599 FilteringBy: state, 600 Stacks: stacks, 601 Pipelines: m,