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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

List a change's linked issues in a Details section

cad93cfe2b6d4ed4c3ed5a1431af4ecb194484ce · Keenan Nemetz · 10d ago · parent 5c22953e

List a change's linked issues in a Details section

The conversation sidebar showed linked issues as one comma-separated line, so there was no way to see what each link was or change it without the CLI. The Details section lists them with the same rows the overview uses for recent issues, and the change's author or a maintainer can link another issue or remove one while it is open. Both controls go through SetRequestCloses, so the browser and 'vole change update --closes' write the same metadata.

server/web/request_view.go +70−12

@@ -14,6 +14,7 @@ import (
14 14 "github.com/rodent-software/vole/ci/runs"
15 15 "github.com/rodent-software/vole/core/git"
16 16 "github.com/rodent-software/vole/forge/access"
17 "github.com/rodent-software/vole/forge/index"
17 18 "github.com/rodent-software/vole/forge/landing"
18 19 "github.com/rodent-software/vole/forge/repo"
19 20 "github.com/rodent-software/vole/metadata"
@@ -95,6 +96,16 @@ type requestLandingView struct {
95 96 MergeChecked bool
96 97 }
97 98
99 // requestIssue is one issue linked to a change request. It carries the same facts
100 // the overview's recent-issue rows show, plus the id the remove control posts.
101 type requestIssue struct {
102 ID string
103 Number int
104 Title string
105 Author string
106 Created time.Time
107 }
108
98 109 // requestSidebarView is the data the change sidebar fragments need. One type for
99 110 // the full page and the HTMX replacements, so an approval or state change can
100 111 // refresh the whole sidebar without rebuilding the rest of the review.
@@ -105,15 +116,21 @@ type requestSidebarView struct {
105 116 Request *metadata.Request
106 117 // Number is the `#42` alias, derived from creation order in history.
107 118 Number int
108 // Numbers is every alias in the repo, so the `closes` links and the thread can
109 // name issues they were not given directly.
110 Numbers map[string]int
111 Approvals []requestApproval
112 Checks []*runs.Run
113 Required int
114 Current int
115 CanApprove bool
116 CanClose bool
119 // LinkedIssues is the Details section: the issues landing this change closes.
120 LinkedIssues []requestIssue
121 Approvals []requestApproval
122 Checks []*runs.Run
123 Required int
124 Current int
125 CanApprove bool
126 CanClose bool
127 // CanEditIssues is the add and remove controls. Only an open request may be
128 // relinked, and only by its author or a maintainer, matching `change update`.
129 CanEditIssues bool
130 // DetailsError is the reason a link change did not happen, shown beside the
131 // controls. It is separate from Error because a link write replaces only the
132 // Details section.
133 DetailsError string
117 134 // HeadRefNote says when the branch the request follows no longer points at the
118 135 // commit under review ("branch moved", "branch deleted"). A push normally moves
119 136 // the head with the branch, so "branch moved" means that sync did not happen and
@@ -338,10 +355,12 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques
338 355 nums := w.entityNumbers(m)
339 356 return requestSidebarView{
340 357 Page: w.page(r, title), Name: m.Name, Request: req,
341 Number: nums[req.ID], Numbers: nums,
342 Approvals: approvals, Checks: checks, Required: required, Current: current,
358 Number: nums[req.ID],
359 LinkedIssues: w.requestLinkedIssues(m, req, nums),
360 Approvals: approvals, Checks: checks, Required: required, Current: current,
343 361 CanApprove: approvable, CanClose: canClose, HeadRefNote: headRefNote,
344 CanRetarget: canRetarget, BaseOptions: baseOptions, HeadOptions: headOptions,
362 CanEditIssues: req.Open() && canClose,
363 CanRetarget: canRetarget, BaseOptions: baseOptions, HeadOptions: headOptions,
345 364 CanMerge: mergeable, MergePending: mergePending, MergeArmed: mergeArmed,
346 365 MergeBlocked: mergeBlocked,
347 366 CombinedNeeded: combinedNeeded, CombinedSHA: combinedSHA,
@@ -352,6 +371,45 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques
352 371 }
353 372 }
354 373
374 // requestLinkedIssues reads the issues a request promises to close. The index
375 // supplies the rows the overview uses, and a row the index cannot name is read
376 // from the issue itself rather than dropped: the request still holds the link.
377 func (w *Web) requestLinkedIssues(m *repo.Meta, req *metadata.Request, nums map[string]int) []requestIssue {
378 if len(req.Closes) == 0 {
379 return nil
380 }
381 rows := make(map[string]index.IssueRow, len(req.Closes))
382 if w.Index != nil {
383 if err := w.Index.Ensure(m); err == nil {
384 if list, err := w.Index.Issues(m.ID, index.Filter{State: "all"}); err == nil {
385 for _, row := range list {
386 rows[row.ID] = row
387 }
388 }
389 }
390 }
391 out := make([]requestIssue, 0, len(req.Closes))
392 for _, id := range req.Closes {
393 if row, ok := rows[id]; ok {
394 out = append(out, requestIssue{
395 ID: row.ID, Number: row.Number, Title: row.Title,
396 Author: row.Author, Created: row.Created,
397 })
398 continue
399 }
400 iss, err := index.LoadIssue(m, id)
401 if err != nil {
402 out = append(out, requestIssue{ID: id, Number: nums[id], Title: "Linked issue unavailable"})
403 continue
404 }
405 out = append(out, requestIssue{
406 ID: id, Number: nums[id], Title: iss.Title,
407 Author: iss.Author, Created: iss.Created,
408 })
409 }
410 return out
411 }
412
355 413 // pendingContributorCI finds the contributor branch whose tip is head and whose
356 414 // CI still waits for a maintainer. It is keyed by the commit: a branch that has
357 415 // moved past this request is not what is under review here.