+19
-1
appview/db/pulls.go
+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
+5
-1
appview/issues/issues.go
+44
-1
appview/models/pull.go
+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
+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
-1
appview/pages/templates/repo/fragments/labelPanel.html
+26
appview/pages/templates/repo/fragments/participants.html
+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
+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">
+30
-12
appview/pages/templates/repo/pulls/pull.html
+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
+7
appview/pages/templates/repo/pulls/pulls.html
···
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
+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,