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.

Say what each change is waiting for in the list

7cc36ba0f4ba96d3f4938b13758b2cc20c22375a · Keenan Nemetz · 5d ago · parent 946d2fd4

Say what each change is waiting for in the list

A change spends most of its life in states "open" cannot describe: held for a
maintainer to allow CI, running its checks, failed, waiting for a reviewer,
conflicting with its base. The list now says which of those it is, in one
column, with a tooltip that explains the label.

The answer comes from facts a list can afford: the approvals it already counts,
one batched run-queue query for every head on the page, the contributor
allowance (one ref listing, not one per row), and — only for a change that would
otherwise read as ready — whether it still merges into its base. The full
landing plan stays on the detail page.

The rest of the row follows the issue list: the author and opening moved under
the title, the comment count sits at the end of that line, and the last column
is "Updated", the newest commit over the request's directory. The list sorts by
it. The separate Author, Approvals and Age columns are gone.

Closes #194

server/web/change_status_test.go +84

@@ -0,0 +1,84 @@
1 package web
2
3 // The change list shows one status per row. These tests pin the words and the
4 // order that decides which answer wins when several are true at once.
5
6 import (
7 "strings"
8 "testing"
9 )
10
11 func TestChangeStatusSaysWhereTheChangeIs(t *testing.T) {
12 t.Parallel()
13 cases := []struct {
14 name string
15 state string
16 current int
17 required int
18 checks changeChecks
19 ciHeld string
20 conflict string
21 wantKey string
22 wantLabel string
23 wantNoteHas string
24 }{
25 {
26 name: "merged", state: "merged", required: 1,
27 wantKey: "merged", wantLabel: "Merged", wantNoteHas: "landed",
28 },
29 {
30 name: "closed", state: "closed", required: 1,
31 wantKey: "closed", wantLabel: "Closed", wantNoteHas: "without merging",
32 },
33 {
34 name: "a held contributor tip comes first", state: "open", required: 1,
35 checks: changeChecks{Pending: true}, ciHeld: "contrib/alice/fix",
36 wantKey: "waiting", wantLabel: "Waiting on CI approval",
37 wantNoteHas: "contrib/alice/fix",
38 },
39 {
40 name: "a failure beats the queue", state: "open", required: 1,
41 checks: changeChecks{Failed: true, Pending: true},
42 wantKey: "failed", wantLabel: "CI failed",
43 },
44 {
45 name: "running jobs are a wait", state: "open", required: 1,
46 checks: changeChecks{Pending: true},
47 wantKey: "waiting", wantLabel: "Waiting on CI jobs",
48 },
49 {
50 name: "review after the checks", state: "open", current: 0, required: 2,
51 wantKey: "review", wantLabel: "Waiting for review",
52 wantNoteHas: "0 of 2 required approvals",
53 },
54 {
55 name: "a conflict outranks the approval", state: "open", current: 1, required: 1,
56 conflict: "main",
57 wantKey: "conflict", wantLabel: "Merge conflict", wantNoteHas: "main",
58 },
59 {
60 name: "approved", state: "open", current: 1, required: 1,
61 wantKey: "ready", wantLabel: "Ready to merge", wantNoteHas: "required approvals are in",
62 },
63 {
64 name: "no review required", state: "open", required: 0,
65 wantKey: "ready", wantLabel: "Ready to merge", wantNoteHas: "No approval is required",
66 },
67 }
68 for _, tc := range cases {
69 tc := tc
70 t.Run(tc.name, func(t *testing.T) {
71 t.Parallel()
72 got := changeStatusFor(tc.state, "main", tc.current, tc.required, tc.checks, tc.ciHeld, tc.conflict)
73 if got.Key != tc.wantKey || got.Label != tc.wantLabel {
74 t.Fatalf("status = %q %q, want %q %q", got.Key, got.Label, tc.wantKey, tc.wantLabel)
75 }
76 if got.Icon == "" {
77 t.Fatal("status has no icon")
78 }
79 if tc.wantNoteHas != "" && !strings.Contains(got.Note, tc.wantNoteHas) {
80 t.Fatalf("note %q does not mention %q", got.Note, tc.wantNoteHas)
81 }
82 })
83 }
84 }