Monorepo for Tangled tangled.org

group timeline events in profile

timeline events in profiles are grouped by month (each month is a card) and by type (repos, issues and pulls).

authored by oppi.li and committed by Tangled d50fdb31 d8a12dae

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