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 3 import ( 4 4 "database/sql" 5 5 "fmt" 6 + "log" 7 + "slices" 6 8 "strings" 7 9 "time" 8 10 ··· 72 74 } 73 75 74 76 func GetRepos(e Execer, filters ...filter) ([]Repo, error) { 75 - repoMap := make(map[syntax.ATURI]Repo) 77 + repoMap := make(map[syntax.ATURI]*Repo) 76 78 77 79 var conditions []string 78 80 var args []any ··· 139 141 repo.Spindle = spindle.String 140 142 } 141 143 142 - repoMap[repo.RepoAt()] = repo 144 + repo.RepoStats = &RepoStats{} 145 + repoMap[repo.RepoAt()] = &repo 143 146 } 144 147 145 148 if err = rows.Err(); err != nil { ··· 148 151 149 152 inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ") 150 153 args = make([]any, len(repoMap)) 154 + 155 + i := 0 151 156 for _, r := range repoMap { 152 - args = append(args, r.RepoAt()) 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) 153 197 } 154 198 155 199 starCountQuery := fmt.Sprintf( ··· 168 212 var repoat string 169 213 var count int 170 214 if err := rows.Scan(&repoat, &count); err != nil { 215 + log.Println("err", "err", err) 171 216 continue 172 217 } 173 218 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 196 241 var repoat string 197 242 var open, closed int 198 243 if err := rows.Scan(&repoat, &open, &closed); err != nil { 244 + log.Println("err", "err", err) 199 245 continue 200 246 } 201 247 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 236 282 var repoat string 237 283 var open, merged, closed, deleted int 238 284 if err := rows.Scan(&repoat, &open, &merged, &closed, &deleted); err != nil { 285 + log.Println("err", "err", err) 239 286 continue 240 287 } 241 288 if r, ok := repoMap[syntax.ATURI(repoat)]; ok { ··· 251 298 252 299 var repos []Repo 253 300 for _, r := range repoMap { 254 - repos = append(repos, r) 301 + repos = append(repos, *r) 255 302 } 256 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 + 257 311 return repos, nil 258 312 } 259 313 ··· 509 563 } 510 564 511 565 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) 566 + rows, err := e.Query(`select repo from collaborators where did = ?`, collaborator) 527 567 if err != nil { 528 568 return nil, err 529 569 } 530 570 defer rows.Close() 531 571 572 + var repoIds []int 532 573 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) 574 + var id int 575 + err := rows.Scan(&id) 539 576 if err != nil { 540 577 return nil, err 541 578 } 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) 579 + repoIds = append(repoIds, id) 559 580 } 560 - 561 581 if err := rows.Err(); err != nil { 562 582 return nil, err 563 583 } 584 + if repoIds == nil { 585 + return nil, nil 586 + } 564 587 565 - return repos, nil 588 + return GetRepos(e, FilterIn("id", repoIds)) 566 589 } 567 590 568 591 type RepoStats struct { 592 + Language string 569 593 StarCount int 570 594 IssueCount IssueCount 571 595 PullCount PullCount
+2
appview/pages/funcmap.go
··· 17 17 "time" 18 18 19 19 "github.com/dustin/go-humanize" 20 + "github.com/go-enry/go-enry/v2" 20 21 "github.com/microcosm-cc/bluemonday" 21 22 "tangled.sh/tangled.sh/core/appview/filetree" 22 23 "tangled.sh/tangled.sh/core/appview/pages/markup" ··· 241 242 }, 242 243 243 244 "tinyAvatar": p.tinyAvatar, 245 + "langColor": enry.GetColor, 244 246 } 245 247 } 246 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 260 </div> 261 261 <div id="repos" class="grid grid-cols-1 gap-4"> 262 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> 263 + {{ template "user/fragments/repoCard" (list $ . false) }} 285 264 {{ else }} 286 265 <p class="px-6 dark:text-white">This user does not have any repos yet.</p> 287 266 {{ end }} ··· 295 274 <p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p> 296 275 <div id="collaborating" class="grid grid-cols-1 gap-4"> 297 276 {{ 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> 277 + {{ template "user/fragments/repoCard" (list $ . true) }} 320 278 {{ else }} 321 279 <p class="px-6 dark:text-white">This user is not collaborating.</p> 322 280 {{ end }}
+1 -22
appview/pages/templates/user/repos.html
··· 22 22 <p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p> 23 23 <div id="repos" class="grid grid-cols-1 gap-4 mb-6"> 24 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> 25 + {{ template "user/fragments/repoCard" (list $ . false) }} 47 26 {{ else }} 48 27 <p class="px-6 dark:text-white">This user does not have any repos yet.</p> 49 28 {{ end }}
+13 -3
appview/repo/repo.go
··· 106 106 return 107 107 } 108 108 109 - result, err := us.Tags(f.OwnerDid(), f.RepoName) 109 + tagResult, err := us.Tags(f.OwnerDid(), f.RepoName) 110 110 if err != nil { 111 111 log.Println("failed to reach knotserver", err) 112 112 return 113 113 } 114 114 115 115 tagMap := make(map[string][]string) 116 - for _, tag := range result.Tags { 116 + for _, tag := range tagResult.Tags { 117 117 hash := tag.Hash 118 118 if tag.Tag != nil { 119 119 hash = tag.Tag.Target.String() 120 120 } 121 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) 122 133 } 123 134 124 135 user := rp.oauth.GetUser(r) ··· 154 165 VerifiedCommits: vc, 155 166 Pipelines: pipelines, 156 167 }) 157 - return 158 168 } 159 169 160 170 func (rp *Repo) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
+1 -1
appview/state/knotstream.go
··· 130 130 } 131 131 132 132 func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error { 133 - if record.Meta == nil && record.Meta.LangBreakdown == nil { 133 + if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil { 134 134 return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName) 135 135 } 136 136
+9 -2
appview/state/profile.go
··· 50 50 log.Printf("getting profile data for %s: %s", ident.DID.String(), err) 51 51 } 52 52 53 - repos, err := db.GetAllReposByDid(s.db, ident.DID.String()) 53 + repos, err := db.GetRepos( 54 + s.db, 55 + db.FilterEq("did", ident.DID.String()), 56 + ) 54 57 if err != nil { 55 58 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 56 59 } ··· 171 174 log.Printf("getting profile data for %s: %s", ident.DID.String(), err) 172 175 } 173 176 174 - repos, err := db.GetAllReposByDid(s.db, ident.DID.String()) 177 + repos, err := db.GetRepos( 178 + s.db, 179 + db.FilterEq("did", ident.DID.String()), 180 + ) 175 181 if err != nil { 176 182 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 177 183 } ··· 192 198 s.pages.ReposPage(w, pages.ReposPageParams{ 193 199 LoggedInUser: loggedInUser, 194 200 Repos: repos, 201 + DidHandleMap: map[string]string{ident.DID.String(): ident.Handle.String()}, 195 202 Card: pages.ProfileCard{ 196 203 UserDid: ident.DID.String(), 197 204 UserHandle: ident.Handle.String(),