+48
-18
appview/db/issues.go
+48
-18
appview/db/issues.go
···
12
OwnerDid string
13
IssueId int
14
IssueAt string
15
-
Created *time.Time
16
Title string
17
Body string
18
Open bool
19
Metadata *IssueMetadata
20
}
21
22
type IssueMetadata struct {
23
CommentCount int
24
// labels, assignee etc.
25
}
26
···
143
if err != nil {
144
return nil, err
145
}
146
-
issue.Created = &createdTime
147
issue.Metadata = &metadata
148
149
issues = append(issues, issue)
···
156
return issues, nil
157
}
158
159
-
func GetIssuesByOwnerDid(e Execer, ownerDid string) ([]Issue, error) {
160
var issues []Issue
161
162
rows, err := e.Query(
···
168
i.title,
169
i.body,
170
i.open,
171
-
count(c.id)
172
from
173
issues i
174
-
left join
175
-
comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
176
where
177
-
i.owner_did = ?
178
-
group by
179
-
i.id, i.owner_did, i.repo_at, i.issue_id, i.created, i.title, i.body, i.open
180
order by
181
i.created desc`,
182
-
ownerDid)
183
if err != nil {
184
return nil, err
185
}
···
187
188
for rows.Next() {
189
var issue Issue
190
-
var createdAt string
191
-
var metadata IssueMetadata
192
-
err := rows.Scan(&issue.OwnerDid, &issue.RepoAt, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)
193
if err != nil {
194
return nil, err
195
}
196
197
-
createdTime, err := time.Parse(time.RFC3339, createdAt)
198
if err != nil {
199
return nil, err
200
}
201
-
issue.Created = &createdTime
202
-
issue.Metadata = &metadata
203
204
issues = append(issues, issue)
205
}
···
226
if err != nil {
227
return nil, err
228
}
229
-
issue.Created = &createdTime
230
231
return &issue, nil
232
}
···
246
if err != nil {
247
return nil, nil, err
248
}
249
-
issue.Created = &createdTime
250
251
comments, err := GetComments(e, repoAt, issueId)
252
if err != nil {
···
12
OwnerDid string
13
IssueId int
14
IssueAt string
15
+
Created time.Time
16
Title string
17
Body string
18
Open bool
19
+
20
+
// optionally, populate this when querying for reverse mappings
21
+
// like comment counts, parent repo etc.
22
Metadata *IssueMetadata
23
}
24
25
type IssueMetadata struct {
26
CommentCount int
27
+
Repo *Repo
28
// labels, assignee etc.
29
}
30
···
147
if err != nil {
148
return nil, err
149
}
150
+
issue.Created = createdTime
151
issue.Metadata = &metadata
152
153
issues = append(issues, issue)
···
160
return issues, nil
161
}
162
163
+
// timeframe here is directly passed into the sql query filter, and any
164
+
// timeframe in the past should be negative; e.g.: "-3 months"
165
+
func GetIssuesByOwnerDid(e Execer, ownerDid string, timeframe string) ([]Issue, error) {
166
var issues []Issue
167
168
rows, err := e.Query(
···
174
i.title,
175
i.body,
176
i.open,
177
+
r.did,
178
+
r.name,
179
+
r.knot,
180
+
r.rkey,
181
+
r.created
182
from
183
issues i
184
+
join
185
+
repos r on i.repo_at = r.at_uri
186
where
187
+
i.owner_did = ? and i.created >= date ('now', ?)
188
order by
189
i.created desc`,
190
+
ownerDid, timeframe)
191
if err != nil {
192
return nil, err
193
}
···
195
196
for rows.Next() {
197
var issue Issue
198
+
var issueCreatedAt, repoCreatedAt string
199
+
var repo Repo
200
+
err := rows.Scan(
201
+
&issue.OwnerDid,
202
+
&issue.RepoAt,
203
+
&issue.IssueId,
204
+
&issueCreatedAt,
205
+
&issue.Title,
206
+
&issue.Body,
207
+
&issue.Open,
208
+
&repo.Did,
209
+
&repo.Name,
210
+
&repo.Knot,
211
+
&repo.Rkey,
212
+
&repoCreatedAt,
213
+
)
214
if err != nil {
215
return nil, err
216
}
217
218
+
issueCreatedTime, err := time.Parse(time.RFC3339, issueCreatedAt)
219
+
if err != nil {
220
+
return nil, err
221
+
}
222
+
issue.Created = issueCreatedTime
223
+
224
+
repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt)
225
if err != nil {
226
return nil, err
227
}
228
+
repo.Created = repoCreatedTime
229
+
230
+
issue.Metadata = &IssueMetadata{
231
+
Repo: &repo,
232
+
}
233
234
issues = append(issues, issue)
235
}
···
256
if err != nil {
257
return nil, err
258
}
259
+
issue.Created = createdTime
260
261
return &issue, nil
262
}
···
276
if err != nil {
277
return nil, nil, err
278
}
279
+
issue.Created = createdTime
280
281
comments, err := GetComments(e, repoAt, issueId)
282
if err != nil {
+119
-48
appview/db/profile.go
+119
-48
appview/db/profile.go
···
2
3
import (
4
"fmt"
5
-
"sort"
6
"time"
7
)
8
9
-
type ProfileTimelineEvent struct {
10
-
EventAt time.Time
11
-
Type string
12
-
*Issue
13
-
*Pull
14
-
*Repo
15
16
-
// optional: populate only if Repo is a fork
17
-
Source *Repo
18
}
19
20
-
func MakeProfileTimeline(e Execer, forDid string) ([]ProfileTimelineEvent, error) {
21
-
timeline := []ProfileTimelineEvent{}
22
-
limit := 30
23
24
-
pulls, err := GetPullsByOwnerDid(e, forDid)
25
if err != nil {
26
-
return timeline, fmt.Errorf("error getting pulls by owner did: %w", err)
27
}
28
29
for _, pull := range pulls {
30
-
repo, err := GetRepoByAtUri(e, string(pull.RepoAt))
31
-
if err != nil {
32
-
return timeline, fmt.Errorf("error getting repo by at uri: %w", err)
33
}
34
35
-
timeline = append(timeline, ProfileTimelineEvent{
36
-
EventAt: pull.Created,
37
-
Type: "pull",
38
-
Pull: &pull,
39
-
Repo: repo,
40
-
})
41
}
42
43
-
issues, err := GetIssuesByOwnerDid(e, forDid)
44
if err != nil {
45
-
return timeline, fmt.Errorf("error getting issues by owner did: %w", err)
46
}
47
48
for _, issue := range issues {
49
-
repo, err := GetRepoByAtUri(e, string(issue.RepoAt))
50
-
if err != nil {
51
-
return timeline, fmt.Errorf("error getting repo by at uri: %w", err)
52
}
53
54
-
timeline = append(timeline, ProfileTimelineEvent{
55
-
EventAt: *issue.Created,
56
-
Type: "issue",
57
-
Issue: &issue,
58
-
Repo: repo,
59
-
})
60
}
61
62
repos, err := GetAllReposByDid(e, forDid)
63
if err != nil {
64
-
return timeline, fmt.Errorf("error getting all repos by did: %w", err)
65
}
66
67
for _, repo := range repos {
68
var sourceRepo *Repo
69
if repo.Source != "" {
70
sourceRepo, err = GetRepoByAtUri(e, repo.Source)
···
73
}
74
}
75
76
-
timeline = append(timeline, ProfileTimelineEvent{
77
-
EventAt: repo.Created,
78
-
Type: "repo",
79
-
Repo: &repo,
80
-
Source: sourceRepo,
81
-
})
82
-
}
83
84
-
sort.Slice(timeline, func(i, j int) bool {
85
-
return timeline[i].EventAt.After(timeline[j].EventAt)
86
-
})
87
88
-
if len(timeline) > limit {
89
-
timeline = timeline[:limit]
90
}
91
92
-
return timeline, nil
93
}
···
2
3
import (
4
"fmt"
5
"time"
6
)
7
8
+
type RepoEvent struct {
9
+
Repo *Repo
10
+
Source *Repo
11
+
}
12
+
13
+
type ProfileTimeline struct {
14
+
ByMonth []ByMonth
15
+
}
16
17
+
type ByMonth struct {
18
+
RepoEvents []RepoEvent
19
+
IssueEvents IssueEvents
20
+
PullEvents PullEvents
21
}
22
23
+
func (b ByMonth) IsEmpty() bool {
24
+
return len(b.RepoEvents) == 0 &&
25
+
len(b.IssueEvents.Items) == 0 &&
26
+
len(b.PullEvents.Items) == 0
27
+
}
28
+
29
+
type IssueEvents struct {
30
+
Items []*Issue
31
+
}
32
+
33
+
type IssueEventStats struct {
34
+
Open int
35
+
Closed int
36
+
}
37
+
38
+
func (i IssueEvents) Stats() IssueEventStats {
39
+
var open, closed int
40
+
for _, issue := range i.Items {
41
+
if issue.Open {
42
+
open += 1
43
+
} else {
44
+
closed += 1
45
+
}
46
+
}
47
+
48
+
return IssueEventStats{
49
+
Open: open,
50
+
Closed: closed,
51
+
}
52
+
}
53
+
54
+
type PullEvents struct {
55
+
Items []*Pull
56
+
}
57
+
58
+
func (p PullEvents) Stats() PullEventStats {
59
+
var open, merged, closed int
60
+
for _, pull := range p.Items {
61
+
switch pull.State {
62
+
case PullOpen:
63
+
open += 1
64
+
case PullMerged:
65
+
merged += 1
66
+
case PullClosed:
67
+
closed += 1
68
+
}
69
+
}
70
+
71
+
return PullEventStats{
72
+
Open: open,
73
+
Merged: merged,
74
+
Closed: closed,
75
+
}
76
+
}
77
+
78
+
type PullEventStats struct {
79
+
Closed int
80
+
Open int
81
+
Merged int
82
+
}
83
+
84
+
const TimeframeMonths = 3
85
+
86
+
func MakeProfileTimeline(e Execer, forDid string) (*ProfileTimeline, error) {
87
+
timeline := ProfileTimeline{
88
+
ByMonth: make([]ByMonth, TimeframeMonths),
89
+
}
90
+
currentMonth := time.Now().Month()
91
+
timeframe := fmt.Sprintf("-%d months", TimeframeMonths)
92
93
+
pulls, err := GetPullsByOwnerDid(e, forDid, timeframe)
94
if err != nil {
95
+
return nil, fmt.Errorf("error getting pulls by owner did: %w", err)
96
}
97
98
+
// group pulls by month
99
for _, pull := range pulls {
100
+
pullMonth := pull.Created.Month()
101
+
102
+
if currentMonth-pullMonth > TimeframeMonths {
103
+
// shouldn't happen; but times are weird
104
+
continue
105
}
106
107
+
idx := currentMonth - pullMonth
108
+
items := &timeline.ByMonth[idx].PullEvents.Items
109
+
110
+
*items = append(*items, &pull)
111
}
112
113
+
issues, err := GetIssuesByOwnerDid(e, forDid, timeframe)
114
if err != nil {
115
+
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
116
}
117
118
for _, issue := range issues {
119
+
issueMonth := issue.Created.Month()
120
+
121
+
if currentMonth-issueMonth > TimeframeMonths {
122
+
// shouldn't happen; but times are weird
123
+
continue
124
}
125
126
+
idx := currentMonth - issueMonth
127
+
items := &timeline.ByMonth[idx].IssueEvents.Items
128
+
129
+
*items = append(*items, &issue)
130
}
131
132
repos, err := GetAllReposByDid(e, forDid)
133
if err != nil {
134
+
return nil, fmt.Errorf("error getting all repos by did: %w", err)
135
}
136
137
for _, repo := range repos {
138
+
// TODO: get this in the original query; requires COALESCE because nullable
139
var sourceRepo *Repo
140
if repo.Source != "" {
141
sourceRepo, err = GetRepoByAtUri(e, repo.Source)
···
144
}
145
}
146
147
+
repoMonth := repo.Created.Month()
148
+
149
+
if currentMonth-repoMonth > TimeframeMonths {
150
+
// shouldn't happen; but times are weird
151
+
continue
152
+
}
153
154
+
idx := currentMonth - repoMonth
155
156
+
items := &timeline.ByMonth[idx].RepoEvents
157
+
*items = append(*items, RepoEvent{
158
+
Repo: &repo,
159
+
Source: sourceRepo,
160
+
})
161
}
162
163
+
return &timeline, nil
164
}
+40
-14
appview/db/pulls.go
+40
-14
appview/db/pulls.go
···
64
// meta
65
Created time.Time
66
PullSource *PullSource
67
}
68
69
type PullSource struct {
···
522
return &pull, nil
523
}
524
525
-
func GetPullsByOwnerDid(e Execer, did string) ([]Pull, error) {
526
var pulls []Pull
527
528
rows, err := e.Query(`
529
select
530
-
owner_did,
531
-
repo_at,
532
-
pull_id,
533
-
created,
534
-
title,
535
-
state
536
from
537
-
pulls
538
where
539
-
owner_did = ?
540
order by
541
-
created desc`, did)
542
if err != nil {
543
return nil, err
544
}
···
546
547
for rows.Next() {
548
var pull Pull
549
-
var createdAt string
550
err := rows.Scan(
551
&pull.OwnerDid,
552
&pull.RepoAt,
553
&pull.PullId,
554
-
&createdAt,
555
&pull.Title,
556
&pull.State,
557
)
558
if err != nil {
559
return nil, err
560
}
561
562
-
createdTime, err := time.Parse(time.RFC3339, createdAt)
563
if err != nil {
564
return nil, err
565
}
566
-
pull.Created = createdTime
567
568
pulls = append(pulls, pull)
569
}
···
64
// meta
65
Created time.Time
66
PullSource *PullSource
67
+
68
+
// optionally, populate this when querying for reverse mappings
69
+
Repo *Repo
70
}
71
72
type PullSource struct {
···
525
return &pull, nil
526
}
527
528
+
// timeframe here is directly passed into the sql query filter, and any
529
+
// timeframe in the past should be negative; e.g.: "-3 months"
530
+
func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]Pull, error) {
531
var pulls []Pull
532
533
rows, err := e.Query(`
534
select
535
+
p.owner_did,
536
+
p.repo_at,
537
+
p.pull_id,
538
+
p.created,
539
+
p.title,
540
+
p.state,
541
+
r.did,
542
+
r.name,
543
+
r.knot,
544
+
r.rkey,
545
+
r.created
546
from
547
+
pulls p
548
+
join
549
+
repos r on p.repo_at = r.at_uri
550
where
551
+
p.owner_did = ? and p.created >= date ('now', ?)
552
order by
553
+
p.created desc`, did, timeframe)
554
if err != nil {
555
return nil, err
556
}
···
558
559
for rows.Next() {
560
var pull Pull
561
+
var repo Repo
562
+
var pullCreatedAt, repoCreatedAt string
563
err := rows.Scan(
564
&pull.OwnerDid,
565
&pull.RepoAt,
566
&pull.PullId,
567
+
&pullCreatedAt,
568
&pull.Title,
569
&pull.State,
570
+
&repo.Did,
571
+
&repo.Name,
572
+
&repo.Knot,
573
+
&repo.Rkey,
574
+
&repoCreatedAt,
575
)
576
if err != nil {
577
return nil, err
578
}
579
580
+
pullCreatedTime, err := time.Parse(time.RFC3339, pullCreatedAt)
581
if err != nil {
582
return nil, err
583
}
584
+
pull.Created = pullCreatedTime
585
+
586
+
repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt)
587
+
if err != nil {
588
+
return nil, err
589
+
}
590
+
repo.Created = repoCreatedTime
591
+
592
+
pull.Repo = &repo
593
594
pulls = append(pulls, pull)
595
}
+3
-1
appview/db/repos.go
+3
-1
appview/db/repos.go
+3
-2
appview/pages/pages.go
+3
-2
appview/pages/pages.go
+1
-1
appview/pages/templates/repo/blob.html
+1
-1
appview/pages/templates/repo/blob.html
+3
-3
appview/pages/templates/repo/tree.html
+3
-3
appview/pages/templates/repo/tree.html
···
17
{{ $containerstyle := "py-1" }}
18
{{ $linkstyle := "no-underline hover:underline" }}
19
20
-
<div class="pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-500">
21
<div class="flex flex-col md:flex-row md:justify-between gap-2">
22
-
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap">
23
{{ range .BreadCrumbs }}
24
-
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ index . 0 }}</a> /
25
{{ end }}
26
</div>
27
<div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0">
···
17
{{ $containerstyle := "py-1" }}
18
{{ $linkstyle := "no-underline hover:underline" }}
19
20
+
<div class="pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
21
<div class="flex flex-col md:flex-row md:justify-between gap-2">
22
+
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">
23
{{ range .BreadCrumbs }}
24
+
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ index . 0 }}</a> /
25
{{ end }}
26
</div>
27
<div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0">
+193
-69
appview/pages/templates/user/profile.html
+193
-69
appview/pages/templates/user/profile.html
···
9
{{ block "ownRepos" . }}{{ end }}
10
{{ block "collaboratingRepos" . }}{{ end }}
11
</div>
12
-
13
<div class="md:col-span-2 order-3 md:order-3">
14
{{ block "profileTimeline" . }}{{ end }}
15
</div>
16
</div>
17
{{ end }}
18
19
20
-
{{ define "profileTimeline" }}
21
-
<div class="flex flex-col gap-3 relative">
22
-
<p class="px-6 text-sm font-bold py-2 dark:text-white">ACTIVITY</p>
23
-
{{ range .ProfileTimeline }}
24
-
{{ if eq .Type "issue" }}
25
-
<div class="px-6 py-2 bg-white dark:bg-gray-800 rounded drop-shadow-sm w-fit max-w-full flex items-center gap-2">
26
-
{{ $textColor := "text-gray-800 dark:text-gray-400" }}
27
-
{{ $icon := "ban" }}
28
-
{{ if .Issue.Open }}
29
-
{{ $textColor = "text-green-600 dark:text-green-500" }}
30
-
{{ $icon = "circle-dot" }}
31
-
{{ end }}
32
-
<div class="p-1 {{ $textColor }}">
33
-
{{ i $icon "w-5 h-5" }}
34
</div>
35
-
<div>
36
-
<p class="text-gray-600 dark:text-gray-300">
37
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .Issue.IssueId }}" class="no-underline hover:underline">{{ .Issue.Title }} <span class="text-gray-500 dark:text-gray-400">#{{ .Issue.IssueId }}</span></a>
38
-
on
39
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ index $.DidHandleMap .Repo.Did }}<span class="select-none">/</span>{{ .Repo.Name }}</a>
40
-
<time class="text-gray-700 dark:text-gray-400 text-xs ml-2">{{ .Issue.Created | shortTimeFmt }}</time>
41
-
</p>
42
</div>
43
</div>
44
-
{{ else if eq .Type "pull" }}
45
-
<div class="px-6 py-2 bg-white dark:bg-gray-800 rounded drop-shadow-sm w-fit flex items-center gap-3">
46
-
{{ $textColor := "text-gray-800 dark:text-gray-400" }}
47
-
{{ $icon := "git-pull-request-closed" }}
48
-
{{ if .Pull.State.IsOpen }}
49
-
{{ $textColor = "text-green-600 dark:text-green-500" }}
50
-
{{ $icon = "git-pull-request" }}
51
-
{{ else if .Pull.State.IsMerged }}
52
-
{{ $textColor = "text-purple-600 dark:text-purple-500" }}
53
-
{{ $icon = "git-merge" }}
54
{{ end }}
55
-
<div class="{{ $textColor }} p-1">
56
-
{{ i $icon "w-5 h-5" }}
57
</div>
58
-
<div>
59
-
<p class="text-gray-600 dark:text-gray-300">
60
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}/pulls/{{ .Pull.PullId }}" class="no-underline hover:underline">{{ .Pull.Title }} <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span></a>
61
on
62
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
63
-
{{ index $.DidHandleMap .Repo.Did }}<span class="select-none">/</span>{{ .Repo.Name }}</a>
64
-
<time class="text-gray-700 dark:text-gray-400 text-xs ml-2">{{ .Pull.Created | shortTimeFmt }}</time>
65
-
</p>
66
</div>
67
</div>
68
-
{{ else if eq .Type "repo" }}
69
-
<div class="px-6 py-2 bg-white dark:bg-gray-800 rounded drop-shadow-sm w-fit flex items-center gap-3">
70
-
{{ if .Source }}
71
-
<div class="text-gray-800 dark:text-gray-400 p-1">
72
-
{{ i "git-fork" "w-5 h-5" }}
73
-
</div>
74
{{ else }}
75
-
<div class="text-gray-800 dark:text-gray-400 p-1">
76
-
{{ i "book-plus" "w-5 h-5" }}
77
</div>
78
-
{{ end }}
79
-
<div>
80
-
<p class="text-gray-600 dark:text-gray-300">
81
-
82
-
{{ if .Source }}
83
-
forked
84
-
<a href="/{{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }}" class="no-underline hover:underline">
85
-
{{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }}
86
-
</a>
87
-
to
88
-
<a href="/{{ didOrHandle $.UserHandle $.UserDid }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ .Repo.Name }}</a>
89
-
{{ else }}
90
-
created
91
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ .Repo.Name }}</a>
92
-
{{ end }}
93
-
<time class="text-gray-700 dark:text-gray-400 text-xs ml-2">{{ .Repo.Created | shortTimeFmt }}</time>
94
-
</p>
95
</div>
96
</div>
97
-
{{ end }}
98
-
{{ else }}
99
-
<p class="px-6 dark:text-white">This user does not have any activity yet.</p>
100
{{ end }}
101
</div>
102
{{ end }}
103
104
{{ define "profileCard" }}
···
9
{{ block "ownRepos" . }}{{ end }}
10
{{ block "collaboratingRepos" . }}{{ end }}
11
</div>
12
<div class="md:col-span-2 order-3 md:order-3">
13
{{ block "profileTimeline" . }}{{ end }}
14
</div>
15
</div>
16
{{ end }}
17
18
+
{{ define "profileTimeline" }}
19
+
<p class="text-sm font-bold py-2 dark:text-white px-6">ACTIVITY</p>
20
+
<div class="flex flex-col gap-6 relative">
21
+
{{ with .ProfileTimeline }}
22
+
{{ range $idx, $byMonth := .ByMonth }}
23
+
{{ with $byMonth }}
24
+
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm">
25
+
{{ if eq $idx 0 }}
26
+
27
+
{{ else }}
28
+
{{ $s := "s" }}
29
+
{{ if eq $idx 1 }}
30
+
{{ $s = "" }}
31
+
{{ end }}
32
+
<p class="text-sm font-bold dark:text-white mb-2">{{$idx}} month{{$s}} ago</p>
33
+
{{ end }}
34
35
+
{{ if .IsEmpty }}
36
+
<div class="text-gray-500 dark:text-gray-400">
37
+
No activity for this month
38
</div>
39
+
{{ else }}
40
+
<div class="flex flex-col gap-1">
41
+
{{ block "repoEvents" (list .RepoEvents $.DidHandleMap) }} {{ end }}
42
+
{{ block "issueEvents" (list .IssueEvents $.DidHandleMap) }} {{ end }}
43
+
{{ block "pullEvents" (list .PullEvents $.DidHandleMap) }} {{ end }}
44
</div>
45
+
{{ end }}
46
+
</div>
47
+
48
+
{{ end }}
49
+
{{ else }}
50
+
<p class="dark:text-white">This user does not have any activity yet.</p>
51
+
{{ end }}
52
+
{{ end }}
53
+
</div>
54
+
{{ end }}
55
+
56
+
{{ define "repoEvents" }}
57
+
{{ $items := index . 0 }}
58
+
{{ $handleMap := index . 1 }}
59
+
60
+
{{ if gt (len $items) 0 }}
61
+
<details>
62
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
63
+
<div class="flex flex-wrap items-center gap-2">
64
+
{{ i "book-plus" "w-4 h-4" }}
65
+
created {{ len $items }} {{if eq (len $items) 1 }}repository{{else}}repositories{{end}}
66
+
</div>
67
+
</summary>
68
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
69
+
{{ range $items }}
70
+
<div class="flex flex-wrap items-center gap-2">
71
+
<span class="text-gray-500 dark:text-gray-400">
72
+
{{ if .Source }}
73
+
{{ i "git-fork" "w-4 h-4" }}
74
+
{{ else }}
75
+
{{ i "book-plus" "w-4 h-4" }}
76
+
{{ end }}
77
+
</span>
78
+
<a href="/{{ index $handleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
79
+
{{- .Repo.Name -}}
80
+
</a>
81
</div>
82
+
{{ end }}
83
+
</div>
84
+
</details>
85
+
{{ end }}
86
+
{{ end }}
87
+
88
+
{{ define "issueEvents" }}
89
+
{{ $i := index . 0 }}
90
+
{{ $items := $i.Items }}
91
+
{{ $stats := $i.Stats }}
92
+
{{ $handleMap := index . 1 }}
93
+
94
+
{{ if gt (len $items) 0 }}
95
+
<details>
96
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
97
+
<div class="flex flex-wrap items-center gap-2">
98
+
{{ i "circle-dot" "w-4 h-4" }}
99
+
100
+
<div>
101
+
created {{ len $items }} {{if eq (len $items) 1 }}issue{{else}}issues{{end}}
102
+
</div>
103
+
104
+
{{ if gt $stats.Open 0 }}
105
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
106
+
{{$stats.Open}} open
107
+
</span>
108
+
{{ end }}
109
+
110
+
{{ if gt $stats.Closed 0 }}
111
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-gray-800 dark:bg-gray-700">
112
+
{{$stats.Closed}} closed
113
+
</span>
114
+
{{ end }}
115
+
116
+
</div>
117
+
</summary>
118
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
119
+
{{ range $items }}
120
+
{{ $repoOwner := index $handleMap .Metadata.Repo.Did }}
121
+
{{ $repoName := .Metadata.Repo.Name }}
122
+
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
123
+
124
+
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
125
+
{{ if .Open }}
126
+
<span class="text-green-600 dark:text-green-500">
127
+
{{ i "circle-dot" "w-4 h-4" }}
128
+
</span>
129
+
{{ else }}
130
+
<span class="text-gray-500 dark:text-gray-400">
131
+
{{ i "ban" "w-4 h-4" }}
132
+
</span>
133
{{ end }}
134
+
<div class="flex-none min-w-8 text-right">
135
+
<span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span>
136
</div>
137
+
<div class="break-words max-w-full">
138
+
<a href="/{{$repoUrl}}/issues/{{ .IssueId }}" class="no-underline hover:underline">
139
+
{{ .Title -}}
140
+
</a>
141
on
142
+
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
143
+
{{$repoUrl}}
144
+
</a>
145
</div>
146
</div>
147
+
{{ end }}
148
+
</div>
149
+
</details>
150
+
{{ end }}
151
+
{{ end }}
152
+
153
+
{{ define "pullEvents" }}
154
+
{{ $i := index . 0 }}
155
+
{{ $items := $i.Items }}
156
+
{{ $stats := $i.Stats }}
157
+
{{ $handleMap := index . 1 }}
158
+
{{ if gt (len $items) 0 }}
159
+
<details>
160
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
161
+
<div class="flex flex-wrap items-center gap-2">
162
+
{{ i "git-pull-request" "w-4 h-4" }}
163
+
164
+
<div>
165
+
created {{ len $items }} {{if eq (len $items) 1 }}pull request{{else}}pull requests{{end}}
166
+
</div>
167
+
168
+
{{ if gt $stats.Open 0 }}
169
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
170
+
{{$stats.Open}} open
171
+
</span>
172
+
{{ end }}
173
+
174
+
{{ if gt $stats.Merged 0 }}
175
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-purple-600 dark:bg-purple-700">
176
+
{{$stats.Merged}} merged
177
+
</span>
178
+
{{ end }}
179
+
180
+
181
+
{{ if gt $stats.Closed 0 }}
182
+
<span class="px-2 py-1/2 text-sm rounded text-black dark:text-white bg-gray-50 dark:bg-gray-700 ">
183
+
{{$stats.Closed}} closed
184
+
</span>
185
+
{{ end }}
186
+
187
+
</div>
188
+
</summary>
189
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
190
+
{{ range $items }}
191
+
{{ $repoOwner := index $handleMap .Repo.Did }}
192
+
{{ $repoName := .Repo.Name }}
193
+
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
194
+
195
+
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
196
+
{{ if .State.IsOpen }}
197
+
<span class="text-green-600 dark:text-green-500">
198
+
{{ i "git-pull-request" "w-4 h-4" }}
199
+
</span>
200
+
{{ else if .State.IsMerged }}
201
+
<span class="text-purple-600 dark:text-purple-500">
202
+
{{ i "git-merge" "w-4 h-4" }}
203
+
</span>
204
{{ else }}
205
+
<span class="text-gray-600 dark:text-gray-300">
206
+
{{ i "git-pull-request-closed" "w-4 h-4" }}
207
+
</span>
208
+
{{ end }}
209
+
<div class="flex-none min-w-8 text-right">
210
+
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
211
</div>
212
+
<div class="break-words max-w-full">
213
+
<a href="/{{$repoUrl}}/pulls/{{ .PullId }}" class="no-underline hover:underline">
214
+
{{ .Title -}}
215
+
</a>
216
+
on
217
+
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
218
+
{{$repoUrl}}
219
+
</a>
220
</div>
221
</div>
222
{{ end }}
223
</div>
224
+
</details>
225
+
{{ end }}
226
{{ end }}
227
228
{{ define "profileCard" }}
+11
-5
appview/state/profile.go
+11
-5
appview/state/profile.go
···
43
for _, r := range collaboratingRepos {
44
didsToResolve = append(didsToResolve, r.Did)
45
}
46
-
for _, evt := range timeline {
47
-
if evt.Repo != nil {
48
-
if evt.Repo.Source != "" {
49
-
didsToResolve = append(didsToResolve, evt.Source.Did)
50
}
51
}
52
-
didsToResolve = append(didsToResolve, evt.Repo.Did)
53
}
54
55
resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve)
···
43
for _, r := range collaboratingRepos {
44
didsToResolve = append(didsToResolve, r.Did)
45
}
46
+
for _, byMonth := range timeline.ByMonth {
47
+
for _, pe := range byMonth.PullEvents.Items {
48
+
didsToResolve = append(didsToResolve, pe.Repo.Did)
49
+
}
50
+
for _, ie := range byMonth.IssueEvents.Items {
51
+
didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did)
52
+
}
53
+
for _, re := range byMonth.RepoEvents {
54
+
didsToResolve = append(didsToResolve, re.Repo.Did)
55
+
if re.Source != nil {
56
+
didsToResolve = append(didsToResolve, re.Source.Did)
57
}
58
}
59
}
60
61
resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve)
+1
-1
go.mod
+1
-1
go.mod
···
26
github.com/sethvargo/go-envconfig v1.1.0
27
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
28
github.com/yuin/goldmark v1.4.13
29
-
golang.org/x/crypto v0.36.0
30
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
31
)
32
···
107
go.uber.org/atomic v1.11.0 // indirect
108
go.uber.org/multierr v1.11.0 // indirect
109
go.uber.org/zap v1.26.0 // indirect
110
golang.org/x/net v0.37.0 // indirect
111
golang.org/x/sys v0.31.0 // indirect
112
golang.org/x/time v0.5.0 // indirect
···
26
github.com/sethvargo/go-envconfig v1.1.0
27
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
28
github.com/yuin/goldmark v1.4.13
29
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
30
)
31
···
106
go.uber.org/atomic v1.11.0 // indirect
107
go.uber.org/multierr v1.11.0 // indirect
108
go.uber.org/zap v1.26.0 // indirect
109
+
golang.org/x/crypto v0.36.0 // indirect
110
golang.org/x/net v0.37.0 // indirect
111
golang.org/x/sys v0.31.0 // indirect
112
golang.org/x/time v0.5.0 // indirect