activity.go
·
136 lines
·
3.5 KB
| 1 | package repo |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "sort" |
| 7 | "time" |
| 8 | |
| 9 | "polar/internal/git" |
| 10 | ) |
| 11 | |
| 12 | type ActivityItem struct { |
| 13 | Type string // "commit", "pull", "issue", "release" |
| 14 | Title string |
| 15 | Subtitle string |
| 16 | AuthorUsername string |
| 17 | AuthorAvatar string |
| 18 | Link string |
| 19 | Icon string |
| 20 | Badge string |
| 21 | CreatedAt time.Time |
| 22 | } |
| 23 | |
| 24 | type ActivityStats struct { |
| 25 | CommitCount int |
| 26 | OpenIssues int |
| 27 | ClosedIssues int |
| 28 | OpenPRs int |
| 29 | MergedPRs int |
| 30 | ReleaseCount int |
| 31 | } |
| 32 | |
| 33 | func (s *Service) GetRepoActivity(ctx context.Context, r *Repository, diskPath string) ([]ActivityItem, *ActivityStats, error) { |
| 34 | var items []ActivityItem |
| 35 | |
| 36 | // 1. Recent commits from default branch |
| 37 | commits, _ := git.ListCommits(ctx, diskPath, r.DefaultBranch, 25, 0) |
| 38 | for _, c := range commits { |
| 39 | items = append(items, ActivityItem{ |
| 40 | Type: "commit", |
| 41 | Title: c.Subject, |
| 42 | Subtitle: c.ShortHash, |
| 43 | AuthorUsername: c.AuthorName, |
| 44 | AuthorAvatar: "", |
| 45 | Link: fmt.Sprintf("/%s/%s/commit/%s", r.OwnerUsername, r.Name, c.Hash), |
| 46 | Icon: "git-commit", |
| 47 | Badge: "commit", |
| 48 | CreatedAt: c.AuthoredAt, |
| 49 | }) |
| 50 | } |
| 51 | |
| 52 | // 2. Recent Pull Requests |
| 53 | pulls, _ := s.ListPullRequests(ctx, r.ID, "all") |
| 54 | var openPRs, mergedPRs int |
| 55 | for _, pr := range pulls { |
| 56 | switch pr.Status { |
| 57 | case "open": |
| 58 | openPRs++ |
| 59 | case "merged": |
| 60 | mergedPRs++ |
| 61 | } |
| 62 | badge := pr.Status |
| 63 | icon := "git-pull-request" |
| 64 | items = append(items, ActivityItem{ |
| 65 | Type: "pull", |
| 66 | Title: fmt.Sprintf("#%d %s", pr.Number, pr.Title), |
| 67 | Subtitle: fmt.Sprintf("%s → %s", pr.HeadBranch, pr.BaseBranch), |
| 68 | AuthorUsername: pr.AuthorUsername, |
| 69 | AuthorAvatar: pr.AuthorAvatar, |
| 70 | Link: fmt.Sprintf("/%s/%s/pulls/%d", r.OwnerUsername, r.Name, pr.Number), |
| 71 | Icon: icon, |
| 72 | Badge: badge, |
| 73 | CreatedAt: pr.CreatedAt, |
| 74 | }) |
| 75 | } |
| 76 | |
| 77 | // 3. Recent Issues |
| 78 | openIssList, _ := s.ListIssues(ctx, r.ID, false) |
| 79 | closedIssList, _ := s.ListIssues(ctx, r.ID, true) |
| 80 | openIssues := len(openIssList) |
| 81 | closedIssues := len(closedIssList) |
| 82 | allIssues := append(openIssList, closedIssList...) |
| 83 | for _, iss := range allIssues { |
| 84 | badge := "open" |
| 85 | if iss.IsClosed { |
| 86 | badge = "closed" |
| 87 | } |
| 88 | items = append(items, ActivityItem{ |
| 89 | Type: "issue", |
| 90 | Title: fmt.Sprintf("#%d %s", iss.Number, iss.Title), |
| 91 | Subtitle: "", |
| 92 | AuthorUsername: iss.AuthorUsername, |
| 93 | AuthorAvatar: iss.AuthorAvatar, |
| 94 | Link: fmt.Sprintf("/%s/%s/issues/%d", r.OwnerUsername, r.Name, iss.Number), |
| 95 | Icon: "issue", |
| 96 | Badge: badge, |
| 97 | CreatedAt: iss.CreatedAt, |
| 98 | }) |
| 99 | } |
| 100 | |
| 101 | // 4. Recent Releases |
| 102 | releases, _ := s.ListReleases(ctx, r.ID) |
| 103 | for _, rel := range releases { |
| 104 | items = append(items, ActivityItem{ |
| 105 | Type: "release", |
| 106 | Title: fmt.Sprintf("%s (%s)", rel.Title, rel.TagName), |
| 107 | Subtitle: "", |
| 108 | AuthorUsername: rel.AuthorUsername, |
| 109 | AuthorAvatar: rel.AuthorAvatar, |
| 110 | Link: fmt.Sprintf("/%s/%s/releases#%s", r.OwnerUsername, r.Name, rel.TagName), |
| 111 | Icon: "tag", |
| 112 | Badge: "release", |
| 113 | CreatedAt: rel.PublishedAt, |
| 114 | }) |
| 115 | } |
| 116 | |
| 117 | // Sort combined timeline in reverse chronological order |
| 118 | sort.Slice(items, func(i, j int) bool { |
| 119 | return items[i].CreatedAt.After(items[j].CreatedAt) |
| 120 | }) |
| 121 | |
| 122 | if len(items) > 50 { |
| 123 | items = items[:50] |
| 124 | } |
| 125 | |
| 126 | stats := &ActivityStats{ |
| 127 | CommitCount: len(commits), |
| 128 | OpenIssues: openIssues, |
| 129 | ClosedIssues: closedIssues, |
| 130 | OpenPRs: openPRs, |
| 131 | MergedPRs: mergedPRs, |
| 132 | ReleaseCount: len(releases), |
| 133 | } |
| 134 | |
| 135 | return items, stats, nil |
| 136 | } |
| 137 |