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
···
import (
"database/sql"
"fmt"
+
"log"
+
"slices"
"strings"
"time"
···
}
func GetRepos(e Execer, filters ...filter) ([]Repo, error) {
-
repoMap := make(map[syntax.ATURI]Repo)
+
repoMap := make(map[syntax.ATURI]*Repo)
var conditions []string
var args []any
···
repo.Spindle = spindle.String
}
-
repoMap[repo.RepoAt()] = repo
+
repo.RepoStats = &RepoStats{}
+
repoMap[repo.RepoAt()] = &repo
}
if err = rows.Err(); err != nil {
···
inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ")
args = make([]any, len(repoMap))
+
+
i := 0
for _, r := range repoMap {
-
args = append(args, r.RepoAt())
+
args[i] = r.RepoAt()
+
i++
+
}
+
+
languageQuery := fmt.Sprintf(
+
`
+
select
+
repo_at, language
+
from
+
repo_languages r1
+
where
+
repo_at IN (%s)
+
and is_default_ref = 1
+
and id = (
+
select id
+
from repo_languages r2
+
where r2.repo_at = r1.repo_at
+
and r2.is_default_ref = 1
+
order by bytes desc
+
limit 1
+
);
+
`,
+
inClause,
+
)
+
rows, err = e.Query(languageQuery, args...)
+
if err != nil {
+
return nil, fmt.Errorf("failed to execute lang query: %w ", err)
+
}
+
for rows.Next() {
+
var repoat, lang string
+
if err := rows.Scan(&repoat, &lang); err != nil {
+
log.Println("err", "err", err)
+
continue
+
}
+
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
+
r.RepoStats.Language = lang
+
}
+
}
+
if err = rows.Err(); err != nil {
+
return nil, fmt.Errorf("failed to execute lang query: %w ", err)
}
starCountQuery := fmt.Sprintf(
···
var repoat string
var count int
if err := rows.Scan(&repoat, &count); err != nil {
+
log.Println("err", "err", err)
continue
}
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
···
var repoat string
var open, closed int
if err := rows.Scan(&repoat, &open, &closed); err != nil {
+
log.Println("err", "err", err)
continue
}
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
···
var repoat string
var open, merged, closed, deleted int
if err := rows.Scan(&repoat, &open, &merged, &closed, &deleted); err != nil {
+
log.Println("err", "err", err)
continue
}
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
···
var repos []Repo
for _, r := range repoMap {
-
repos = append(repos, r)
+
repos = append(repos, *r)
}
+
slices.SortFunc(repos, func(a, b Repo) int {
+
if a.Created.After(b.Created) {
+
return 1
+
}
+
return -1
+
})
+
return repos, nil
}
···
}
func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) {
-
var repos []Repo
-
-
rows, err := e.Query(
-
`select
-
r.did, r.name, r.knot, r.rkey, r.description, r.created, count(s.id) as star_count
-
from
-
repos r
-
join
-
collaborators c on r.id = c.repo
-
left join
-
stars s on r.at_uri = s.repo_at
-
where
-
c.did = ?
-
group by
-
r.id;`, collaborator)
+
rows, err := e.Query(`select repo from collaborators where did = ?`, collaborator)
if err != nil {
return nil, err
}
defer rows.Close()
+
var repoIds []int
for rows.Next() {
-
var repo Repo
-
var repoStats RepoStats
-
var createdAt string
-
var nullableDescription sql.NullString
-
-
err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount)
+
var id int
+
err := rows.Scan(&id)
if err != nil {
return nil, err
}
-
-
if nullableDescription.Valid {
-
repo.Description = nullableDescription.String
-
} else {
-
repo.Description = ""
-
}
-
-
createdAtTime, err := time.Parse(time.RFC3339, createdAt)
-
if err != nil {
-
repo.Created = time.Now()
-
} else {
-
repo.Created = createdAtTime
-
}
-
-
repo.RepoStats = &repoStats
-
-
repos = append(repos, repo)
+
repoIds = append(repoIds, id)
}
-
if err := rows.Err(); err != nil {
return nil, err
}
+
if repoIds == nil {
+
return nil, nil
+
}
-
return repos, nil
+
return GetRepos(e, FilterIn("id", repoIds))
}
type RepoStats struct {
+
Language string
StarCount int
IssueCount IssueCount
PullCount PullCount
+2
appview/pages/funcmap.go
···
"time"
"github.com/dustin/go-humanize"
+
"github.com/go-enry/go-enry/v2"
"github.com/microcosm-cc/bluemonday"
"tangled.sh/tangled.sh/core/appview/filetree"
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
},
"tinyAvatar": p.tinyAvatar,
+
"langColor": enry.GetColor,
}
}
+57
appview/pages/templates/user/fragments/repoCard.html
···
+
{{ define "user/fragments/repoCard" }}
+
{{ $root := index . 0 }}
+
{{ $repo := index . 1 }}
+
{{ $fullName := index . 2 }}
+
+
{{ with $repo }}
+
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
+
<div class="font-medium dark:text-white">
+
{{- if $fullName -}}
+
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ index $root.DidHandleMap .Did }}/{{ .Name }}</a>
+
{{- else -}}
+
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ .Name }}</a>
+
{{- end -}}
+
</div>
+
{{ with .Description }}
+
<div class="text-gray-600 dark:text-gray-300 text-sm">
+
{{ . }}
+
</div>
+
{{ end }}
+
+
{{ if .RepoStats }}
+
{{ block "repoStats" .RepoStats }} {{ end }}
+
{{ end }}
+
</div>
+
{{ end }}
+
{{ end }}
+
+
{{ define "repoStats" }}
+
<div class="text-gray-400 pt-4 text-sm font-mono inline-flex gap-4 mt-auto">
+
{{ with .Language }}
+
<div class="flex gap-2 items-center text-sm">
+
<div class="size-2 rounded-full" style="background-color: {{ langColor . }};"></div>
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
{{ with .StarCount }}
+
<div class="flex gap-1 items-center text-sm">
+
{{ i "star" "w-3 h-3 fill-current" }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
{{ with .IssueCount.Open }}
+
<div class="flex gap-1 items-center text-sm">
+
{{ i "circle-dot" "w-3 h-3" }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
{{ with .PullCount.Open }}
+
<div class="flex gap-1 items-center text-sm">
+
{{ i "git-pull-request-arrow" "w-3 h-3" }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
</div>
+
{{ end }}
+
+
+2 -44
appview/pages/templates/user/profile.html
···
</div>
<div id="repos" class="grid grid-cols-1 gap-4">
{{ range .Repos }}
-
<div
-
id="repo-card"
-
class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
-
<div id="repo-card-name" class="font-medium">
-
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}/{{ .Name }}"
-
>{{ .Name }}</a
-
>
-
</div>
-
{{ if .Description }}
-
<div class="text-gray-600 dark:text-gray-300 text-sm">
-
{{ .Description }}
-
</div>
-
{{ end }}
-
<div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto">
-
{{ if .RepoStats.StarCount }}
-
<div class="flex gap-1 items-center text-sm">
-
{{ i "star" "w-3 h-3 fill-current" }}
-
<span>{{ .RepoStats.StarCount }}</span>
-
</div>
-
{{ end }}
-
</div>
-
</div>
+
{{ template "user/fragments/repoCard" (list $ . false) }}
{{ else }}
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
{{ end }}
···
<p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p>
<div id="collaborating" class="grid grid-cols-1 gap-4">
{{ range .CollaboratingRepos }}
-
<div
-
id="repo-card"
-
class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
-
<div id="repo-card-name" class="font-medium dark:text-white">
-
<a href="/{{ index $.DidHandleMap .Did }}/{{ .Name }}">
-
{{ index $.DidHandleMap .Did }}/{{ .Name }}
-
</a>
-
</div>
-
{{ if .Description }}
-
<div class="text-gray-600 dark:text-gray-300 text-sm">
-
{{ .Description }}
-
</div>
-
{{ end }}
-
<div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto">
-
{{ if .RepoStats.StarCount }}
-
<div class="flex gap-1 items-center text-sm">
-
{{ i "star" "w-3 h-3 fill-current" }}
-
<span>{{ .RepoStats.StarCount }}</span>
-
</div>
-
{{ end }}
-
</div>
-
</div>
+
{{ template "user/fragments/repoCard" (list $ . true) }}
{{ else }}
<p class="px-6 dark:text-white">This user is not collaborating.</p>
{{ end }}
+1 -22
appview/pages/templates/user/repos.html
···
<p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p>
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
{{ range .Repos }}
-
<div
-
id="repo-card"
-
class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
-
<div id="repo-card-name" class="font-medium">
-
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}/{{ .Name }}"
-
>{{ .Name }}</a
-
>
-
</div>
-
{{ if .Description }}
-
<div class="text-gray-600 dark:text-gray-300 text-sm">
-
{{ .Description }}
-
</div>
-
{{ end }}
-
<div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto">
-
{{ if .RepoStats.StarCount }}
-
<div class="flex gap-1 items-center text-sm">
-
{{ i "star" "w-3 h-3 fill-current" }}
-
<span>{{ .RepoStats.StarCount }}</span>
-
</div>
-
{{ end }}
-
</div>
-
</div>
+
{{ template "user/fragments/repoCard" (list $ . false) }}
{{ else }}
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
{{ end }}
+13 -3
appview/repo/repo.go
···
return
}
-
result, err := us.Tags(f.OwnerDid(), f.RepoName)
+
tagResult, err := us.Tags(f.OwnerDid(), f.RepoName)
if err != nil {
log.Println("failed to reach knotserver", err)
return
}
tagMap := make(map[string][]string)
-
for _, tag := range result.Tags {
+
for _, tag := range tagResult.Tags {
hash := tag.Hash
if tag.Tag != nil {
hash = tag.Tag.Target.String()
}
tagMap[hash] = append(tagMap[hash], tag.Name)
+
}
+
+
branchResult, err := us.Branches(f.OwnerDid(), f.RepoName)
+
if err != nil {
+
log.Println("failed to reach knotserver", err)
+
return
+
}
+
+
for _, branch := range branchResult.Branches {
+
hash := branch.Hash
+
tagMap[hash] = append(tagMap[hash], branch.Name)
}
user := rp.oauth.GetUser(r)
···
VerifiedCommits: vc,
Pipelines: pipelines,
})
-
return
}
func (rp *Repo) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
+1 -1
appview/state/knotstream.go
···
}
func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error {
-
if record.Meta == nil && record.Meta.LangBreakdown == nil {
+
if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil {
return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName)
}
+9 -2
appview/state/profile.go
···
log.Printf("getting profile data for %s: %s", ident.DID.String(), err)
}
-
repos, err := db.GetAllReposByDid(s.db, ident.DID.String())
+
repos, err := db.GetRepos(
+
s.db,
+
db.FilterEq("did", ident.DID.String()),
+
)
if err != nil {
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
}
···
log.Printf("getting profile data for %s: %s", ident.DID.String(), err)
}
-
repos, err := db.GetAllReposByDid(s.db, ident.DID.String())
+
repos, err := db.GetRepos(
+
s.db,
+
db.FilterEq("did", ident.DID.String()),
+
)
if err != nil {
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
}
···
s.pages.ReposPage(w, pages.ReposPageParams{
LoggedInUser: loggedInUser,
Repos: repos,
+
DidHandleMap: map[string]string{ident.DID.String(): ident.Handle.String()},
Card: pages.ProfileCard{
UserDid: ident.DID.String(),
UserHandle: ident.Handle.String(),