vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
|
73a732b1
Keenan Nemetz
10d ago
|
1 | package web |
| 2 | ||
|
bca9ec24
Keenan Nemetz
10d ago
|
3 | // The activity feed as a page of events. |
|
73a732b1
Keenan Nemetz
10d ago
|
4 | // |
|
bca9ec24
Keenan Nemetz
10d ago
|
5 | // The rows come from forge/index (`Events` and `EventCount`), which keeps a row |
| 6 | // per feed event with a (repository, time) index: a page is an index range read, | |
| 7 | // and an unfiltered page total is a stored count. Releases are rows there too, so | |
| 8 | // nothing is fetched from git to render a page. | |
|
73a732b1
Keenan Nemetz
10d ago
|
9 | |
| 10 | import ( | |
| 11 | "strings" | |
| 12 | "time" | |
| 13 | ||
|
d37d301f
Keenan Nemetz
19h ago
|
14 | "rodent.software/vole/forge/index" |
|
73a732b1
Keenan Nemetz
10d ago
|
15 | ) |
| 16 | ||
|
bca9ec24
Keenan Nemetz
10d ago
|
17 | // activityEventFromRow maps one indexed event to the event a template renders. |
| 18 | func (w *Web) activityEventFromRow(row index.EventRow, repoName string, withRepo bool) activityEvent { | |
|
73a732b1
Keenan Nemetz
10d ago
|
19 | ev := activityEvent{ |
|
bca9ec24
Keenan Nemetz
10d ago
|
20 | Time: time.Unix(row.At, 0).UTC(), ActorName: row.Actor, |
|
73a732b1
Keenan Nemetz
10d ago
|
21 | Title: row.Title, Assignees: splitAssignees(row.Assignees), |
| 22 | } | |
| 23 | if withRepo { | |
| 24 | ev.Repo = repoName | |
| 25 | } | |
|
ed03b3d1
Keenan Nemetz
6h ago
|
26 | base := "/" + repoName |
|
bca9ec24
Keenan Nemetz
10d ago
|
27 | switch row.Kind { |
| 28 | case "release": | |
| 29 | ev.Kind, ev.Icon, ev.Detail = "release", "tag", "released" | |
| 30 | ev.Actor = row.Actor // the tagger identity, as the tag records it | |
|
a6b42433
Keenan Nemetz
8d ago
|
31 | // A tag's tagger is a Git identity, not the credential that proves an |
| 32 | // account, so it is shown as text and never linked to a profile. | |
| 33 | ev.ActorName = "" | |
|
bca9ec24
Keenan Nemetz
10d ago
|
34 | ev.URL = base + "/releases/" + row.EntityID |
| 35 | return ev | |
| 36 | case "issue": | |
| 37 | ev.Kind, ev.Icon = "issue", "circle-dot" | |
| 38 | case "change": | |
| 39 | ev.Kind, ev.Icon = "change", "git-pull-request" | |
| 40 | } | |
| 41 | ev.Actor = w.who(row.Actor) | |
| 42 | switch row.Type { | |
| 43 | case "open": | |
| 44 | if row.Kind == "issue" { | |
| 45 | ev.Detail = "opened issue" | |
| 46 | ev.URL = base + "/issues/" + row.EntityID | |
| 47 | } else { | |
| 48 | ev.Detail = "opened change" | |
|
8fb7aabf
Keenan Nemetz
3d ago
|
49 | ev.URL = base + "/changes/" + row.EntityID |
|
bca9ec24
Keenan Nemetz
10d ago
|
50 | } |
| 51 | case "comment": | |
| 52 | ev.Icon = "message-square" | |
| 53 | if row.Kind == "issue" { | |
| 54 | ev.Detail = "commented on issue" | |
| 55 | ev.URL = base + "/issues/" + row.ParentID + "#c-" + row.CommentID | |
| 56 | } else { | |
| 57 | ev.Detail = "commented on change" | |
|
8fb7aabf
Keenan Nemetz
3d ago
|
58 | ev.URL = base + "/changes/" + row.ParentID + "#c-" + row.CommentID |
|
bca9ec24
Keenan Nemetz
10d ago
|
59 | } |
|
73a732b1
Keenan Nemetz
10d ago
|
60 | case "approval": |
|
bca9ec24
Keenan Nemetz
10d ago
|
61 | ev.Icon, ev.Detail = "check", "approved change" |
|
8fb7aabf
Keenan Nemetz
3d ago
|
62 | ev.URL = base + "/changes/" + row.EntityID |
|
bca9ec24
Keenan Nemetz
10d ago
|
63 | case "state": |
| 64 | if row.Kind == "issue" { | |
| 65 | ev.Detail = issueStateDetail(row.State) | |
| 66 | ev.URL = base + "/issues/" + row.EntityID | |
| 67 | } else { | |
| 68 | ev.Detail = requestStateDetail(row.State) | |
|
8fb7aabf
Keenan Nemetz
3d ago
|
69 | ev.URL = base + "/changes/" + row.EntityID |
|
bca9ec24
Keenan Nemetz
10d ago
|
70 | } |
|
73a732b1
Keenan Nemetz
10d ago
|
71 | } |
| 72 | return ev | |
| 73 | } | |
| 74 | ||
|
bca9ec24
Keenan Nemetz
10d ago
|
75 | // activityPage returns one page of the feed and the pager that describes it. |
| 76 | func (w *Web) activityPage(repoIDs []string, names map[string]string, | |
|
73a732b1
Keenan Nemetz
10d ago
|
77 | kind, actor, assignee string, withRepo bool, number, perPage int, |
| 78 | urlFor func(int) string, label string) ([]activityEvent, pager, error) { | |
|
bca9ec24
Keenan Nemetz
10d ago
|
79 | count, err := w.Index.EventCount(repoIDs, kind, actor, assignee) |
|
73a732b1
Keenan Nemetz
10d ago
|
80 | if err != nil { |
| 81 | return nil, pager{}, err | |
| 82 | } | |
|
bca9ec24
Keenan Nemetz
10d ago
|
83 | p := newPager(count, perPage, number, urlFor) |
|
73a732b1
Keenan Nemetz
10d ago
|
84 | p.Label = label |
| 85 | if p.Total == 0 { | |
| 86 | return nil, p, nil | |
| 87 | } | |
|
bca9ec24
Keenan Nemetz
10d ago
|
88 | from, _ := p.rows() |
| 89 | rows, err := w.Index.Events(repoIDs, kind, actor, assignee, perPage, from) | |
|
73a732b1
Keenan Nemetz
10d ago
|
90 | if err != nil { |
| 91 | return nil, p, err | |
| 92 | } | |
|
bca9ec24
Keenan Nemetz
10d ago
|
93 | events := make([]activityEvent, 0, len(rows)) |
|
73a732b1
Keenan Nemetz
10d ago
|
94 | for _, row := range rows { |
| 95 | events = append(events, w.activityEventFromRow(row, names[row.RepoID], withRepo)) | |
| 96 | } | |
|
bca9ec24
Keenan Nemetz
10d ago
|
97 | return events, p, nil |
|
73a732b1
Keenan Nemetz
10d ago
|
98 | } |
| 99 | ||
| 100 | func splitAssignees(s string) []string { | |
| 101 | if s == "" { | |
| 102 | return nil | |
| 103 | } | |
| 104 | return strings.Split(s, "\x1f") | |
| 105 | } |