forked from tangled.org/core
this repo has no description

appview: add language, open issues and open PR stats to repo cards

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

oppi.li 6ea25d1b 5d8c5d4b

verified
Changed files
+153 -116
appview
+68 -44
appview/db/repos.go
··· 3 import ( 4 "database/sql" 5 "fmt" 6 "strings" 7 "time" 8 ··· 72 } 73 74 func GetRepos(e Execer, filters ...filter) ([]Repo, error) { 75 - repoMap := make(map[syntax.ATURI]Repo) 76 77 var conditions []string 78 var args []any ··· 139 repo.Spindle = spindle.String 140 } 141 142 - repoMap[repo.RepoAt()] = repo 143 } 144 145 if err = rows.Err(); err != nil { ··· 148 149 inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ") 150 args = make([]any, len(repoMap)) 151 for _, r := range repoMap { 152 - args = append(args, r.RepoAt()) 153 } 154 155 starCountQuery := fmt.Sprintf( ··· 168 var repoat string 169 var count int 170 if err := rows.Scan(&repoat, &count); err != nil { 171 continue 172 } 173 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 196 var repoat string 197 var open, closed int 198 if err := rows.Scan(&repoat, &open, &closed); err != nil { 199 continue 200 } 201 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 236 var repoat string 237 var open, merged, closed, deleted int 238 if err := rows.Scan(&repoat, &open, &merged, &closed, &deleted); err != nil { 239 continue 240 } 241 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 251 252 var repos []Repo 253 for _, r := range repoMap { 254 - repos = append(repos, r) 255 } 256 257 return repos, nil 258 } 259 ··· 509 } 510 511 func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) { 512 - var repos []Repo 513 - 514 - rows, err := e.Query( 515 - `select 516 - r.did, r.name, r.knot, r.rkey, r.description, r.created, count(s.id) as star_count 517 - from 518 - repos r 519 - join 520 - collaborators c on r.id = c.repo 521 - left join 522 - stars s on r.at_uri = s.repo_at 523 - where 524 - c.did = ? 525 - group by 526 - r.id;`, collaborator) 527 if err != nil { 528 return nil, err 529 } 530 defer rows.Close() 531 532 for rows.Next() { 533 - var repo Repo 534 - var repoStats RepoStats 535 - var createdAt string 536 - var nullableDescription sql.NullString 537 - 538 - err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount) 539 if err != nil { 540 return nil, err 541 } 542 - 543 - if nullableDescription.Valid { 544 - repo.Description = nullableDescription.String 545 - } else { 546 - repo.Description = "" 547 - } 548 - 549 - createdAtTime, err := time.Parse(time.RFC3339, createdAt) 550 - if err != nil { 551 - repo.Created = time.Now() 552 - } else { 553 - repo.Created = createdAtTime 554 - } 555 - 556 - repo.RepoStats = &repoStats 557 - 558 - repos = append(repos, repo) 559 } 560 - 561 if err := rows.Err(); err != nil { 562 return nil, err 563 } 564 565 - return repos, nil 566 } 567 568 type RepoStats struct { 569 StarCount int 570 IssueCount IssueCount 571 PullCount PullCount
··· 3 import ( 4 "database/sql" 5 "fmt" 6 + "log" 7 + "slices" 8 "strings" 9 "time" 10 ··· 74 } 75 76 func GetRepos(e Execer, filters ...filter) ([]Repo, error) { 77 + repoMap := make(map[syntax.ATURI]*Repo) 78 79 var conditions []string 80 var args []any ··· 141 repo.Spindle = spindle.String 142 } 143 144 + repo.RepoStats = &RepoStats{} 145 + repoMap[repo.RepoAt()] = &repo 146 } 147 148 if err = rows.Err(); err != nil { ··· 151 152 inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ") 153 args = make([]any, len(repoMap)) 154 + 155 + i := 0 156 for _, r := range repoMap { 157 + args[i] = r.RepoAt() 158 + i++ 159 + } 160 + 161 + languageQuery := fmt.Sprintf( 162 + ` 163 + select 164 + repo_at, language 165 + from 166 + repo_languages r1 167 + where 168 + repo_at IN (%s) 169 + and is_default_ref = 1 170 + and id = ( 171 + select id 172 + from repo_languages r2 173 + where r2.repo_at = r1.repo_at 174 + and r2.is_default_ref = 1 175 + order by bytes desc 176 + limit 1 177 + ); 178 + `, 179 + inClause, 180 + ) 181 + rows, err = e.Query(languageQuery, args...) 182 + if err != nil { 183 + return nil, fmt.Errorf("failed to execute lang query: %w ", err) 184 + } 185 + for rows.Next() { 186 + var repoat, lang string 187 + if err := rows.Scan(&repoat, &lang); err != nil { 188 + log.Println("err", "err", err) 189 + continue 190 + } 191 + if r, ok := repoMap[syntax.ATURI(repoat)]; ok { 192 + r.RepoStats.Language = lang 193 + } 194 + } 195 + if err = rows.Err(); err != nil { 196 + return nil, fmt.Errorf("failed to execute lang query: %w ", err) 197 } 198 199 starCountQuery := fmt.Sprintf( ··· 212 var repoat string 213 var count int 214 if err := rows.Scan(&repoat, &count); err != nil { 215 + log.Println("err", "err", err) 216 continue 217 } 218 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 241 var repoat string 242 var open, closed int 243 if err := rows.Scan(&repoat, &open, &closed); err != nil { 244 + log.Println("err", "err", err) 245 continue 246 } 247 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 282 var repoat string 283 var open, merged, closed, deleted int 284 if err := rows.Scan(&repoat, &open, &merged, &closed, &deleted); err != nil { 285 + log.Println("err", "err", err) 286 continue 287 } 288 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 298 299 var repos []Repo 300 for _, r := range repoMap { 301 + repos = append(repos, *r) 302 } 303 304 + slices.SortFunc(repos, func(a, b Repo) int { 305 + if a.Created.After(b.Created) { 306 + return 1 307 + } 308 + return -1 309 + }) 310 + 311 return repos, nil 312 } 313 ··· 563 } 564 565 func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) { 566 + rows, err := e.Query(`select repo from collaborators where did = ?`, collaborator) 567 if err != nil { 568 return nil, err 569 } 570 defer rows.Close() 571 572 + var repoIds []int 573 for rows.Next() { 574 + var id int 575 + err := rows.Scan(&id) 576 if err != nil { 577 return nil, err 578 } 579 + repoIds = append(repoIds, id) 580 } 581 if err := rows.Err(); err != nil { 582 return nil, err 583 } 584 + if repoIds == nil { 585 + return nil, nil 586 + } 587 588 + return GetRepos(e, FilterIn("id", repoIds)) 589 } 590 591 type RepoStats struct { 592 + Language string 593 StarCount int 594 IssueCount IssueCount 595 PullCount PullCount
+2
appview/pages/funcmap.go
··· 17 "time" 18 19 "github.com/dustin/go-humanize" 20 "github.com/microcosm-cc/bluemonday" 21 "tangled.sh/tangled.sh/core/appview/filetree" 22 "tangled.sh/tangled.sh/core/appview/pages/markup" ··· 241 }, 242 243 "tinyAvatar": p.tinyAvatar, 244 } 245 } 246
··· 17 "time" 18 19 "github.com/dustin/go-humanize" 20 + "github.com/go-enry/go-enry/v2" 21 "github.com/microcosm-cc/bluemonday" 22 "tangled.sh/tangled.sh/core/appview/filetree" 23 "tangled.sh/tangled.sh/core/appview/pages/markup" ··· 242 }, 243 244 "tinyAvatar": p.tinyAvatar, 245 + "langColor": enry.GetColor, 246 } 247 } 248
+57
appview/pages/templates/user/fragments/repoCard.html
···
··· 1 + {{ define "user/fragments/repoCard" }} 2 + {{ $root := index . 0 }} 3 + {{ $repo := index . 1 }} 4 + {{ $fullName := index . 2 }} 5 + 6 + {{ with $repo }} 7 + <div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800"> 8 + <div class="font-medium dark:text-white"> 9 + {{- if $fullName -}} 10 + <a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ index $root.DidHandleMap .Did }}/{{ .Name }}</a> 11 + {{- else -}} 12 + <a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ .Name }}</a> 13 + {{- end -}} 14 + </div> 15 + {{ with .Description }} 16 + <div class="text-gray-600 dark:text-gray-300 text-sm"> 17 + {{ . }} 18 + </div> 19 + {{ end }} 20 + 21 + {{ if .RepoStats }} 22 + {{ block "repoStats" .RepoStats }} {{ end }} 23 + {{ end }} 24 + </div> 25 + {{ end }} 26 + {{ end }} 27 + 28 + {{ define "repoStats" }} 29 + <div class="text-gray-400 pt-4 text-sm font-mono inline-flex gap-4 mt-auto"> 30 + {{ with .Language }} 31 + <div class="flex gap-2 items-center text-sm"> 32 + <div class="size-2 rounded-full" style="background-color: {{ langColor . }};"></div> 33 + <span>{{ . }}</span> 34 + </div> 35 + {{ end }} 36 + {{ with .StarCount }} 37 + <div class="flex gap-1 items-center text-sm"> 38 + {{ i "star" "w-3 h-3 fill-current" }} 39 + <span>{{ . }}</span> 40 + </div> 41 + {{ end }} 42 + {{ with .IssueCount.Open }} 43 + <div class="flex gap-1 items-center text-sm"> 44 + {{ i "circle-dot" "w-3 h-3" }} 45 + <span>{{ . }}</span> 46 + </div> 47 + {{ end }} 48 + {{ with .PullCount.Open }} 49 + <div class="flex gap-1 items-center text-sm"> 50 + {{ i "git-pull-request-arrow" "w-3 h-3" }} 51 + <span>{{ . }}</span> 52 + </div> 53 + {{ end }} 54 + </div> 55 + {{ end }} 56 + 57 +
+2 -44
appview/pages/templates/user/profile.html
··· 260 </div> 261 <div id="repos" class="grid grid-cols-1 gap-4"> 262 {{ range .Repos }} 263 - <div 264 - id="repo-card" 265 - class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800"> 266 - <div id="repo-card-name" class="font-medium"> 267 - <a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}/{{ .Name }}" 268 - >{{ .Name }}</a 269 - > 270 - </div> 271 - {{ if .Description }} 272 - <div class="text-gray-600 dark:text-gray-300 text-sm"> 273 - {{ .Description }} 274 - </div> 275 - {{ end }} 276 - <div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto"> 277 - {{ if .RepoStats.StarCount }} 278 - <div class="flex gap-1 items-center text-sm"> 279 - {{ i "star" "w-3 h-3 fill-current" }} 280 - <span>{{ .RepoStats.StarCount }}</span> 281 - </div> 282 - {{ end }} 283 - </div> 284 - </div> 285 {{ else }} 286 <p class="px-6 dark:text-white">This user does not have any repos yet.</p> 287 {{ end }} ··· 295 <p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p> 296 <div id="collaborating" class="grid grid-cols-1 gap-4"> 297 {{ range .CollaboratingRepos }} 298 - <div 299 - id="repo-card" 300 - class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800"> 301 - <div id="repo-card-name" class="font-medium dark:text-white"> 302 - <a href="/{{ index $.DidHandleMap .Did }}/{{ .Name }}"> 303 - {{ index $.DidHandleMap .Did }}/{{ .Name }} 304 - </a> 305 - </div> 306 - {{ if .Description }} 307 - <div class="text-gray-600 dark:text-gray-300 text-sm"> 308 - {{ .Description }} 309 - </div> 310 - {{ end }} 311 - <div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto"> 312 - {{ if .RepoStats.StarCount }} 313 - <div class="flex gap-1 items-center text-sm"> 314 - {{ i "star" "w-3 h-3 fill-current" }} 315 - <span>{{ .RepoStats.StarCount }}</span> 316 - </div> 317 - {{ end }} 318 - </div> 319 - </div> 320 {{ else }} 321 <p class="px-6 dark:text-white">This user is not collaborating.</p> 322 {{ end }}
··· 260 </div> 261 <div id="repos" class="grid grid-cols-1 gap-4"> 262 {{ range .Repos }} 263 + {{ template "user/fragments/repoCard" (list $ . false) }} 264 {{ else }} 265 <p class="px-6 dark:text-white">This user does not have any repos yet.</p> 266 {{ end }} ··· 274 <p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p> 275 <div id="collaborating" class="grid grid-cols-1 gap-4"> 276 {{ range .CollaboratingRepos }} 277 + {{ template "user/fragments/repoCard" (list $ . true) }} 278 {{ else }} 279 <p class="px-6 dark:text-white">This user is not collaborating.</p> 280 {{ end }}
+1 -22
appview/pages/templates/user/repos.html
··· 22 <p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p> 23 <div id="repos" class="grid grid-cols-1 gap-4 mb-6"> 24 {{ range .Repos }} 25 - <div 26 - id="repo-card" 27 - class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800"> 28 - <div id="repo-card-name" class="font-medium"> 29 - <a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}/{{ .Name }}" 30 - >{{ .Name }}</a 31 - > 32 - </div> 33 - {{ if .Description }} 34 - <div class="text-gray-600 dark:text-gray-300 text-sm"> 35 - {{ .Description }} 36 - </div> 37 - {{ end }} 38 - <div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto"> 39 - {{ if .RepoStats.StarCount }} 40 - <div class="flex gap-1 items-center text-sm"> 41 - {{ i "star" "w-3 h-3 fill-current" }} 42 - <span>{{ .RepoStats.StarCount }}</span> 43 - </div> 44 - {{ end }} 45 - </div> 46 - </div> 47 {{ else }} 48 <p class="px-6 dark:text-white">This user does not have any repos yet.</p> 49 {{ end }}
··· 22 <p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p> 23 <div id="repos" class="grid grid-cols-1 gap-4 mb-6"> 24 {{ range .Repos }} 25 + {{ template "user/fragments/repoCard" (list $ . false) }} 26 {{ else }} 27 <p class="px-6 dark:text-white">This user does not have any repos yet.</p> 28 {{ end }}
+13 -3
appview/repo/repo.go
··· 106 return 107 } 108 109 - result, err := us.Tags(f.OwnerDid(), f.RepoName) 110 if err != nil { 111 log.Println("failed to reach knotserver", err) 112 return 113 } 114 115 tagMap := make(map[string][]string) 116 - for _, tag := range result.Tags { 117 hash := tag.Hash 118 if tag.Tag != nil { 119 hash = tag.Tag.Target.String() 120 } 121 tagMap[hash] = append(tagMap[hash], tag.Name) 122 } 123 124 user := rp.oauth.GetUser(r) ··· 154 VerifiedCommits: vc, 155 Pipelines: pipelines, 156 }) 157 - return 158 } 159 160 func (rp *Repo) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
··· 106 return 107 } 108 109 + tagResult, err := us.Tags(f.OwnerDid(), f.RepoName) 110 if err != nil { 111 log.Println("failed to reach knotserver", err) 112 return 113 } 114 115 tagMap := make(map[string][]string) 116 + for _, tag := range tagResult.Tags { 117 hash := tag.Hash 118 if tag.Tag != nil { 119 hash = tag.Tag.Target.String() 120 } 121 tagMap[hash] = append(tagMap[hash], tag.Name) 122 + } 123 + 124 + branchResult, err := us.Branches(f.OwnerDid(), f.RepoName) 125 + if err != nil { 126 + log.Println("failed to reach knotserver", err) 127 + return 128 + } 129 + 130 + for _, branch := range branchResult.Branches { 131 + hash := branch.Hash 132 + tagMap[hash] = append(tagMap[hash], branch.Name) 133 } 134 135 user := rp.oauth.GetUser(r) ··· 165 VerifiedCommits: vc, 166 Pipelines: pipelines, 167 }) 168 } 169 170 func (rp *Repo) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
+1 -1
appview/state/knotstream.go
··· 130 } 131 132 func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error { 133 - if record.Meta == nil && record.Meta.LangBreakdown == nil { 134 return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName) 135 } 136
··· 130 } 131 132 func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error { 133 + if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil { 134 return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName) 135 } 136
+9 -2
appview/state/profile.go
··· 50 log.Printf("getting profile data for %s: %s", ident.DID.String(), err) 51 } 52 53 - repos, err := db.GetAllReposByDid(s.db, ident.DID.String()) 54 if err != nil { 55 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 56 } ··· 171 log.Printf("getting profile data for %s: %s", ident.DID.String(), err) 172 } 173 174 - repos, err := db.GetAllReposByDid(s.db, ident.DID.String()) 175 if err != nil { 176 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 177 } ··· 192 s.pages.ReposPage(w, pages.ReposPageParams{ 193 LoggedInUser: loggedInUser, 194 Repos: repos, 195 Card: pages.ProfileCard{ 196 UserDid: ident.DID.String(), 197 UserHandle: ident.Handle.String(),
··· 50 log.Printf("getting profile data for %s: %s", ident.DID.String(), err) 51 } 52 53 + repos, err := db.GetRepos( 54 + s.db, 55 + db.FilterEq("did", ident.DID.String()), 56 + ) 57 if err != nil { 58 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 59 } ··· 174 log.Printf("getting profile data for %s: %s", ident.DID.String(), err) 175 } 176 177 + repos, err := db.GetRepos( 178 + s.db, 179 + db.FilterEq("did", ident.DID.String()), 180 + ) 181 if err != nil { 182 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 183 } ··· 198 s.pages.ReposPage(w, pages.ReposPageParams{ 199 LoggedInUser: loggedInUser, 200 Repos: repos, 201 + DidHandleMap: map[string]string{ident.DID.String(): ident.Handle.String()}, 202 Card: pages.ProfileCard{ 203 UserDid: ident.DID.String(), 204 UserHandle: ident.Handle.String(),