Commit dbec077010

dbec077010394f2955c9b20cfd81700ef6b2bdb9

parent: ce4ca463ba

Verified · cmc ci/build: success ci/test: success ci/vuln: success

cmc <hello@cleberg.net> · 2026-09-04 03:03 UTC

web: issue and merge request lists page at fifty

The lists rendered every row; a repository with a long history got one
page of hundreds. Fifty at a time now, with an "older" link that keeps
every active filter and pages on the number, the same keyset cursor the
commands use.

Ref #118

Layout: unified · split

internal/httpd/web.go +30 −4
@@ -1584,6 +1584,18 @@ func (s *Server) labelColors(repoID int64) map[string]template.CSS {
1584 return out 1584 return out
1585} 1585}
1586 1586
1587// listPage is how many issues or merge requests a list page shows before
1588// it offers the older ones (#118). Keyset paging on the number, the same
1589// cursor the commands use, so every filter carries across pages.
1590const listPage = 50
1591
1592// olderLink is the current URL with before=<number> set.
1593func olderLink(r *http.Request, before int64) string {
1594 q := r.URL.Query()
1595 q.Set("before", strconv.FormatInt(before, 10))
1596 return "?" + q.Encode()
1597}
1598
1587func (s *Server) issues(w http.ResponseWriter, r *http.Request) { 1599func (s *Server) issues(w http.ResponseWriter, r *http.Request) {
1588 p, ok := s.repoFor(w, r, "") 1600 p, ok := s.repoFor(w, r, "")
1589 if !ok { 1601 if !ok {
@@ -1598,12 +1610,18 @@ func (s *Server) issues(w http.ResponseWriter, r *http.Request) {
1598 // label chips and author links point here. 1610 // label chips and author links point here.
1599 qv := r.URL.Query() 1611 qv := r.URL.Query()
1600 f := store.IssueFilter{State: state, Label: qv.Get("label"), Assignee: qv.Get("assignee"), 1612 f := store.IssueFilter{State: state, Label: qv.Get("label"), Assignee: qv.Get("assignee"),
1601 Author: qv.Get("author"), Milestone: qv.Get("milestone")} 1613 Author: qv.Get("author"), Milestone: qv.Get("milestone"), Limit: listPage + 1}
1614 f.Before, _ = strconv.ParseInt(qv.Get("before"), 10, 64)
1602 issues, err := s.st.QueryIssues(p.Repo.ID, f) 1615 issues, err := s.st.QueryIssues(p.Repo.ID, f)
1603 if err != nil { 1616 if err != nil {
1604 http.Error(w, "internal error", http.StatusInternalServerError) 1617 http.Error(w, "internal error", http.StatusInternalServerError)
1605 return 1618 return
1606 } 1619 }
1620 older := ""
1621 if len(issues) > listPage {
1622 issues = issues[:listPage]
1623 older = olderLink(r, issues[len(issues)-1].Number)
1624 }
1607 if labels, err := s.st.ListIssueLabels(p.Repo.ID); err == nil { 1625 if labels, err := s.st.ListIssueLabels(p.Repo.ID); err == nil {
1608 for i := range issues { 1626 for i := range issues {
1609 issues[i].Labels = labels[issues[i].ID] 1627 issues[i].Labels = labels[issues[i].ID]
@@ -1616,8 +1634,9 @@ func (s *Server) issues(w http.ResponseWriter, r *http.Request) {
1616 Filters []listFilter 1634 Filters []listFilter
1617 Issues []store.Issue 1635 Issues []store.Issue
1618 LabelColors map[string]template.CSS 1636 LabelColors map[string]template.CSS
1637 Older string
1619 }{p, state, f.Label, activeFilters(state, [][2]string{{"label", f.Label}, {"assignee", f.Assignee}, {"author", f.Author}, {"milestone", f.Milestone}}), 1638 }{p, state, f.Label, activeFilters(state, [][2]string{{"label", f.Label}, {"assignee", f.Assignee}, {"author", f.Author}, {"milestone", f.Milestone}}),
1620 issues, s.labelColors(p.Repo.ID)}) 1639 issues, s.labelColors(p.Repo.ID), older})
1621} 1640}
1622 1641
1623func (s *Server) issue(w http.ResponseWriter, r *http.Request) { 1642func (s *Server) issue(w http.ResponseWriter, r *http.Request) {
@@ -1703,18 +1722,25 @@ func (s *Server) mrs(w http.ResponseWriter, r *http.Request) {
1703 state = "open" 1722 state = "open"
1704 } 1723 }
1705 qv := r.URL.Query() 1724 qv := r.URL.Query()
1706 mf := store.MRFilter{State: state, Author: qv.Get("author"), Milestone: qv.Get("milestone")} 1725 mf := store.MRFilter{State: state, Author: qv.Get("author"), Milestone: qv.Get("milestone"), Limit: listPage + 1}
1726 mf.Before, _ = strconv.ParseInt(qv.Get("before"), 10, 64)
1707 mrs, err := s.st.QueryMRs(p.Repo.ID, mf) 1727 mrs, err := s.st.QueryMRs(p.Repo.ID, mf)
1708 if err != nil { 1728 if err != nil {
1709 http.Error(w, "internal error", http.StatusInternalServerError) 1729 http.Error(w, "internal error", http.StatusInternalServerError)
1710 return 1730 return
1711 } 1731 }
1732 older := ""
1733 if len(mrs) > listPage {
1734 mrs = mrs[:listPage]
1735 older = olderLink(r, mrs[len(mrs)-1].Number)
1736 }
1712 s.render(w, "mrs.html", struct { 1737 s.render(w, "mrs.html", struct {
1713 repoPage 1738 repoPage
1714 State string 1739 State string
1715 Filters []listFilter 1740 Filters []listFilter
1716 MRs []store.MR 1741 MRs []store.MR
1717 }{p, state, activeFilters(state, [][2]string{{"author", mf.Author}, {"milestone", mf.Milestone}}), mrs}) 1742 Older string
1743 }{p, state, activeFilters(state, [][2]string{{"author", mf.Author}, {"milestone", mf.Milestone}}), mrs, older})
1718} 1744}
1719 1745
1720func (s *Server) mr(w http.ResponseWriter, r *http.Request) { 1746func (s *Server) mr(w http.ResponseWriter, r *http.Request) {
internal/web/templates/issues.html +1
@@ -22,4 +22,5 @@
22</li> 22</li>
23{{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}issues — open one with <code>gitbay issue create {{.Repo.OwnerName}}/{{.Repo.Name}} --title "..."</code></li>{{end}} 23{{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}issues — open one with <code>gitbay issue create {{.Repo.OwnerName}}/{{.Repo.Name}} --title "..."</code></li>{{end}}
24</ul> 24</ul>
25{{if .Older}}<p class="pager"><a href="{{.Older}}">older →</a></p>{{end}}
25{{end}} 26{{end}}
internal/web/templates/mrs.html +1
@@ -21,4 +21,5 @@
21</li> 21</li>
22{{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}merge requests — open one with <code>gitbay mr create {{.Repo.OwnerName}}/{{.Repo.Name}} --source ... --target {{.Repo.DefaultBranch}}</code></li>{{end}} 22{{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}merge requests — open one with <code>gitbay mr create {{.Repo.OwnerName}}/{{.Repo.Name}} --source ... --target {{.Repo.DefaultBranch}}</code></li>{{end}}
23</ul> 23</ul>
24{{if .Older}}<p class="pager"><a href="{{.Older}}">older →</a></p>{{end}}
24{{end}} 25{{end}}