Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

Show open work on repository tabs and overview

a196e404c86a5b9607bd226c7229ffc89a37d47f · Keenan Nemetz · 11d ago · parent 0e35a6a0

Show open work on repository tabs and overview

The repository header now counts open issues and change requests, and the overview sidebar lists the newest open issues and change requests with links to their full lists. Closed and merged records stay out of both: the counts and the overview describe what is still being worked on.

Issue counts come from the derived index; change requests are read from the metadata branch, where their state lives. A failed read leaves a zero or an empty section rather than turning the overview into an error page.

server/web/overview.go +102−27

@@ -12,11 +12,14 @@ import (
12 12 "path"
13 13 "sort"
14 14 "strings"
15 "time"
15 16
16 17 "github.com/rodent-software/vole/ci/runs"
17 18 "github.com/rodent-software/vole/core/git"
19 "github.com/rodent-software/vole/forge/index"
18 20 "github.com/rodent-software/vole/forge/repo"
19 21 "github.com/rodent-software/vole/internal/format"
22 "github.com/rodent-software/vole/metadata"
20 23 )
21 24
22 25 // maxReadmeBytes is the largest README rendered on the overview. A file past it
@@ -38,6 +41,22 @@ type contributorView struct {
38 41 Tone int
39 42 }
40 43
44 // overviewEntity is the small shape the overview's issue and change sections
45 // need: enough to link, title, name the author and say how old it is. The list
46 // pages carry richer rows; the sidebar says only what fits beside a README.
47 type overviewEntity struct {
48 ID string
49 Title string
50 Author string
51 Created time.Time
52 Number int
53 }
54
55 // overviewSideLimit is how many open issues and change requests the overview
56 // shows. Like the commit and run sections, it is a pointer to the full list, not
57 // a replacement for it.
58 const overviewSideLimit = 5
59
41 60 func (w *Web) Overview(rw http.ResponseWriter, r *http.Request) {
42 61 m, ok := w.open(rw, r)
43 62 if !ok {
@@ -82,42 +101,98 @@ func (w *Web) Overview(rw http.ResponseWriter, r *http.Request) {
82 101 recentRuns, _ = w.Runs.Recent(m.Name, 5)
83 102 }
84 103
104 recentIssues, recentRequests := w.overviewRecent(m)
105
85 106 readme, readmeName := w.readmeAt(m, ref, commit)
86 107 site := w.siteURL(r, m.Name)
87 108
88 109 w.render(rw, "overview", struct {
89 110 Page
90 Name string
91 Ref string
92 Commit string
93 CloneURL string
94 SSHURL string
95 Site string
96 Readme template.HTML
97 ReadmeName string
98 Languages []languageShare
99 Commits []git.Commit
100 Runs []*runs.Run
101 Tags []git.Tag
102 Contributors []contributorView
111 Name string
112 Ref string
113 Commit string
114 CloneURL string
115 SSHURL string
116 Site string
117 Readme template.HTML
118 ReadmeName string
119 Languages []languageShare
120 Commits []git.Commit
121 RecentIssues []overviewEntity
122 RecentRequests []overviewEntity
123 Runs []*runs.Run
124 Tags []git.Tag
125 Contributors []contributorView
103 126 }{
104 Page: w.page(r, m.Name),
105 Name: m.Name,
106 Ref: ref,
107 Commit: commit,
108 CloneURL: w.cloneURL(r, m.Name),
109 SSHURL: sshURL(w.Cfg.SSHAddr, m.Name),
110 Site: site,
111 Readme: readme,
112 ReadmeName: readmeName,
113 Languages: languages,
114 Commits: commits,
115 Runs: recentRuns,
116 Tags: tags,
117 Contributors: contributors,
127 Page: w.page(r, m.Name),
128 Name: m.Name,
129 Ref: ref,
130 Commit: commit,
131 CloneURL: w.cloneURL(r, m.Name),
132 SSHURL: sshURL(w.Cfg.SSHAddr, m.Name),
133 Site: site,
134 Readme: readme,
135 ReadmeName: readmeName,
136 Languages: languages,
137 Commits: commits,
138 RecentIssues: recentIssues,
139 RecentRequests: recentRequests,
140 Runs: recentRuns,
141 Tags: tags,
142 Contributors: contributors,
118 143 }, 0)
119 144 }
120 145
146 // overviewRecent returns the newest open issues and change requests for the
147 // overview sidebar. Closed and merged records are history and already have a
148 // home in their own lists; the overview is a map of what is still being worked
149 // on. A failed read leaves an empty section rather than failing the README page.
150 func (w *Web) overviewRecent(m *repo.Meta) (issues, requests []overviewEntity) {
151 var numbers map[string]int
152 if w.Index != nil {
153 if err := w.Index.Ensure(m); err == nil {
154 if nums, err := w.Index.Numbers(m.ID); err == nil {
155 numbers = nums
156 }
157 if rows, err := w.Index.Issues(m.ID, index.Filter{State: "open"}); err == nil {
158 for _, row := range rows {
159 issues = append(issues, overviewEntity{
160 ID: row.ID, Title: row.Title, Author: row.Author,
161 Created: row.Created, Number: row.Number,
162 })
163 }
164 }
165 }
166 }
167 if list, err := metadata.LoadRequests(m.Git(), metadata.Ref); err == nil {
168 for _, req := range list {
169 if req.Open() {
170 requests = append(requests, overviewEntity{
171 ID: req.ID, Title: req.Title, Author: req.Author,
172 Created: req.Created, Number: numbers[req.ID],
173 })
174 }
175 }
176 }
177 return newestOverview(issues), newestOverview(requests)
178 }
179
180 // newestOverview puts the newest entity first and keeps only what the sidebar
181 // shows. Created is the human fact the row prints, so it decides the order;
182 // the id breaks a tie without depending on map iteration or a ULID's spelling.
183 func newestOverview(list []overviewEntity) []overviewEntity {
184 sort.SliceStable(list, func(i, j int) bool {
185 if !list[i].Created.Equal(list[j].Created) {
186 return list[i].Created.After(list[j].Created)
187 }
188 return list[i].ID > list[j].ID
189 })
190 if len(list) > overviewSideLimit {
191 list = list[:overviewSideLimit]
192 }
193 return list
194 }
195
121 196 // readmeAt returns the rendered README and the name it was found under. The name
122 197 // is shown above the body: a rendered file with no filename is harder to trust.
123 198 // The ref travels with it because a relative link in the README has to resolve to