diff --git a/appview/db/issues.go b/appview/db/issues.go index 029449a5..74231b7a 100644 --- a/appview/db/issues.go +++ b/appview/db/issues.go @@ -12,15 +12,19 @@ type Issue struct { OwnerDid string IssueId int IssueAt string - Created *time.Time + Created time.Time Title string Body string Open bool + + // optionally, populate this when querying for reverse mappings + // like comment counts, parent repo etc. Metadata *IssueMetadata } type IssueMetadata struct { CommentCount int + Repo *Repo // labels, assignee etc. } @@ -143,7 +147,7 @@ func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool) ([]Issue, error) { if err != nil { return nil, err } - issue.Created = &createdTime + issue.Created = createdTime issue.Metadata = &metadata issues = append(issues, issue) @@ -156,7 +160,9 @@ func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool) ([]Issue, error) { return issues, nil } -func GetIssuesByOwnerDid(e Execer, ownerDid string) ([]Issue, error) { +// timeframe here is directly passed into the sql query filter, and any +// timeframe in the past should be negative; e.g.: "-3 months" +func GetIssuesByOwnerDid(e Execer, ownerDid string, timeframe string) ([]Issue, error) { var issues []Issue rows, err := e.Query( @@ -168,18 +174,20 @@ func GetIssuesByOwnerDid(e Execer, ownerDid string) ([]Issue, error) { i.title, i.body, i.open, - count(c.id) + r.did, + r.name, + r.knot, + r.rkey, + r.created from issues i - left join - comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id + join + repos r on i.repo_at = r.at_uri where - i.owner_did = ? - group by - i.id, i.owner_did, i.repo_at, i.issue_id, i.created, i.title, i.body, i.open + i.owner_did = ? and i.created >= date ('now', ?) order by i.created desc`, - ownerDid) + ownerDid, timeframe) if err != nil { return nil, err } @@ -187,19 +195,41 @@ func GetIssuesByOwnerDid(e Execer, ownerDid string) ([]Issue, error) { for rows.Next() { var issue Issue - var createdAt string - var metadata IssueMetadata - err := rows.Scan(&issue.OwnerDid, &issue.RepoAt, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount) + var issueCreatedAt, repoCreatedAt string + var repo Repo + err := rows.Scan( + &issue.OwnerDid, + &issue.RepoAt, + &issue.IssueId, + &issueCreatedAt, + &issue.Title, + &issue.Body, + &issue.Open, + &repo.Did, + &repo.Name, + &repo.Knot, + &repo.Rkey, + &repoCreatedAt, + ) if err != nil { return nil, err } - createdTime, err := time.Parse(time.RFC3339, createdAt) + issueCreatedTime, err := time.Parse(time.RFC3339, issueCreatedAt) if err != nil { return nil, err } - issue.Created = &createdTime - issue.Metadata = &metadata + issue.Created = issueCreatedTime + + repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt) + if err != nil { + return nil, err + } + repo.Created = repoCreatedTime + + issue.Metadata = &IssueMetadata{ + Repo: &repo, + } issues = append(issues, issue) } @@ -226,7 +256,7 @@ func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) { if err != nil { return nil, err } - issue.Created = &createdTime + issue.Created = createdTime return &issue, nil } @@ -246,7 +276,7 @@ func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, [ if err != nil { return nil, nil, err } - issue.Created = &createdTime + issue.Created = createdTime comments, err := GetComments(e, repoAt, issueId) if err != nil { diff --git a/appview/db/profile.go b/appview/db/profile.go index 2e2fccb1..49e9cf03 100644 --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -2,69 +2,140 @@ package db import ( "fmt" - "sort" "time" ) -type ProfileTimelineEvent struct { - EventAt time.Time - Type string - *Issue - *Pull - *Repo - - // optional: populate only if Repo is a fork +type RepoEvent struct { + Repo *Repo Source *Repo } -func MakeProfileTimeline(e Execer, forDid string) ([]ProfileTimelineEvent, error) { - timeline := []ProfileTimelineEvent{} - limit := 30 +type ProfileTimeline struct { + ByMonth []ByMonth +} + +type ByMonth struct { + RepoEvents []RepoEvent + IssueEvents IssueEvents + PullEvents PullEvents +} + +func (b ByMonth) IsEmpty() bool { + return len(b.RepoEvents) == 0 && + len(b.IssueEvents.Items) == 0 && + len(b.PullEvents.Items) == 0 +} + +type IssueEvents struct { + Items []*Issue +} + +type IssueEventStats struct { + Open int + Closed int +} + +func (i IssueEvents) Stats() IssueEventStats { + var open, closed int + for _, issue := range i.Items { + if issue.Open { + open += 1 + } else { + closed += 1 + } + } + + return IssueEventStats{ + Open: open, + Closed: closed, + } +} + +type PullEvents struct { + Items []*Pull +} + +func (p PullEvents) Stats() PullEventStats { + var open, merged, closed int + for _, pull := range p.Items { + switch pull.State { + case PullOpen: + open += 1 + case PullMerged: + merged += 1 + case PullClosed: + closed += 1 + } + } + + return PullEventStats{ + Open: open, + Merged: merged, + Closed: closed, + } +} + +type PullEventStats struct { + Closed int + Open int + Merged int +} + +const TimeframeMonths = 3 - pulls, err := GetPullsByOwnerDid(e, forDid) +func MakeProfileTimeline(e Execer, forDid string) (*ProfileTimeline, error) { + timeline := ProfileTimeline{ + ByMonth: make([]ByMonth, TimeframeMonths), + } + currentMonth := time.Now().Month() + timeframe := fmt.Sprintf("-%d months", TimeframeMonths) + + pulls, err := GetPullsByOwnerDid(e, forDid, timeframe) if err != nil { - return timeline, fmt.Errorf("error getting pulls by owner did: %w", err) + return nil, fmt.Errorf("error getting pulls by owner did: %w", err) } + // group pulls by month for _, pull := range pulls { - repo, err := GetRepoByAtUri(e, string(pull.RepoAt)) - if err != nil { - return timeline, fmt.Errorf("error getting repo by at uri: %w", err) + pullMonth := pull.Created.Month() + + if currentMonth-pullMonth > TimeframeMonths { + // shouldn't happen; but times are weird + continue } - timeline = append(timeline, ProfileTimelineEvent{ - EventAt: pull.Created, - Type: "pull", - Pull: &pull, - Repo: repo, - }) + idx := currentMonth - pullMonth + items := &timeline.ByMonth[idx].PullEvents.Items + + *items = append(*items, &pull) } - issues, err := GetIssuesByOwnerDid(e, forDid) + issues, err := GetIssuesByOwnerDid(e, forDid, timeframe) if err != nil { - return timeline, fmt.Errorf("error getting issues by owner did: %w", err) + return nil, fmt.Errorf("error getting issues by owner did: %w", err) } for _, issue := range issues { - repo, err := GetRepoByAtUri(e, string(issue.RepoAt)) - if err != nil { - return timeline, fmt.Errorf("error getting repo by at uri: %w", err) + issueMonth := issue.Created.Month() + + if currentMonth-issueMonth > TimeframeMonths { + // shouldn't happen; but times are weird + continue } - timeline = append(timeline, ProfileTimelineEvent{ - EventAt: *issue.Created, - Type: "issue", - Issue: &issue, - Repo: repo, - }) + idx := currentMonth - issueMonth + items := &timeline.ByMonth[idx].IssueEvents.Items + + *items = append(*items, &issue) } repos, err := GetAllReposByDid(e, forDid) if err != nil { - return timeline, fmt.Errorf("error getting all repos by did: %w", err) + return nil, fmt.Errorf("error getting all repos by did: %w", err) } for _, repo := range repos { + // TODO: get this in the original query; requires COALESCE because nullable var sourceRepo *Repo if repo.Source != "" { sourceRepo, err = GetRepoByAtUri(e, repo.Source) @@ -73,21 +144,21 @@ func MakeProfileTimeline(e Execer, forDid string) ([]ProfileTimelineEvent, error } } - timeline = append(timeline, ProfileTimelineEvent{ - EventAt: repo.Created, - Type: "repo", - Repo: &repo, - Source: sourceRepo, - }) - } + repoMonth := repo.Created.Month() - sort.Slice(timeline, func(i, j int) bool { - return timeline[i].EventAt.After(timeline[j].EventAt) - }) + if currentMonth-repoMonth > TimeframeMonths { + // shouldn't happen; but times are weird + continue + } - if len(timeline) > limit { - timeline = timeline[:limit] + idx := currentMonth - repoMonth + + items := &timeline.ByMonth[idx].RepoEvents + *items = append(*items, RepoEvent{ + Repo: &repo, + Source: sourceRepo, + }) } - return timeline, nil + return &timeline, nil } diff --git a/appview/db/pulls.go b/appview/db/pulls.go index 2b2e80da..037612db 100644 --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -64,6 +64,9 @@ type Pull struct { // meta Created time.Time PullSource *PullSource + + // optionally, populate this when querying for reverse mappings + Repo *Repo } type PullSource struct { @@ -522,23 +525,32 @@ func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) { return &pull, nil } -func GetPullsByOwnerDid(e Execer, did string) ([]Pull, error) { +// timeframe here is directly passed into the sql query filter, and any +// timeframe in the past should be negative; e.g.: "-3 months" +func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]Pull, error) { var pulls []Pull rows, err := e.Query(` select - owner_did, - repo_at, - pull_id, - created, - title, - state + p.owner_did, + p.repo_at, + p.pull_id, + p.created, + p.title, + p.state, + r.did, + r.name, + r.knot, + r.rkey, + r.created from - pulls + pulls p + join + repos r on p.repo_at = r.at_uri where - owner_did = ? + p.owner_did = ? and p.created >= date ('now', ?) order by - created desc`, did) + p.created desc`, did, timeframe) if err != nil { return nil, err } @@ -546,24 +558,38 @@ func GetPullsByOwnerDid(e Execer, did string) ([]Pull, error) { for rows.Next() { var pull Pull - var createdAt string + var repo Repo + var pullCreatedAt, repoCreatedAt string err := rows.Scan( &pull.OwnerDid, &pull.RepoAt, &pull.PullId, - &createdAt, + &pullCreatedAt, &pull.Title, &pull.State, + &repo.Did, + &repo.Name, + &repo.Knot, + &repo.Rkey, + &repoCreatedAt, ) if err != nil { return nil, err } - createdTime, err := time.Parse(time.RFC3339, createdAt) + pullCreatedTime, err := time.Parse(time.RFC3339, pullCreatedAt) if err != nil { return nil, err } - pull.Created = createdTime + pull.Created = pullCreatedTime + + repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt) + if err != nil { + return nil, err + } + repo.Created = repoCreatedTime + + pull.Repo = &repo pulls = append(pulls, pull) } diff --git a/appview/db/repos.go b/appview/db/repos.go index 915f48d5..8c201448 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -77,7 +77,9 @@ func GetAllReposByDid(e Execer, did string) ([]Repo, error) { where r.did = ? group by - r.at_uri`, did) + r.at_uri + order by r.created desc`, + did) if err != nil { return nil, err } diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 4c99bfd2..a2a19aff 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -178,9 +178,10 @@ type ProfilePageParams struct { CollaboratingRepos []db.Repo ProfileStats ProfileStats FollowStatus db.FollowStatus - DidHandleMap map[string]string AvatarUri string - ProfileTimeline []db.ProfileTimelineEvent + ProfileTimeline *db.ProfileTimeline + + DidHandleMap map[string]string } type ProfileStats struct { diff --git a/appview/pages/templates/repo/blob.html b/appview/pages/templates/repo/blob.html index 071c9052..6736c712 100644 --- a/appview/pages/templates/repo/blob.html +++ b/appview/pages/templates/repo/blob.html @@ -29,7 +29,7 @@ > / {{ else }} - {{ index . 0 }} {{ end }} diff --git a/appview/pages/templates/repo/tree.html b/appview/pages/templates/repo/tree.html index bb619938..6ececaf3 100644 --- a/appview/pages/templates/repo/tree.html +++ b/appview/pages/templates/repo/tree.html @@ -17,11 +17,11 @@ {{ $containerstyle := "py-1" }} {{ $linkstyle := "no-underline hover:underline" }} -
ACTIVITY
- {{ range .ProfileTimeline }} - {{ if eq .Type "issue" }} -ACTIVITY
+{{$idx}} month{{$s}} ago
+ {{ end }} + + {{ if .IsEmpty }} +- {{ .Issue.Title }} #{{ .Issue.IssueId }} - on - {{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }} - -
+ {{ else }} +This user does not have any activity yet.
+ {{ end }} + {{ end }} +- - {{ if .Source }} - forked - - {{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }} - - to - {{ .Repo.Name }} - {{ else }} - created - {{ .Repo.Name }} - {{ end }} - -
+This user does not have any activity yet.
{{ end }}