Commit 78b20e7d69
Verified · cmc
internal/httpd/builds.go +18 −1
| @@ -3,6 +3,7 @@ package httpd | ||
| 3 | 3 | import ( |
| 4 | 4 | "net/http" |
| 5 | 5 | "net/url" |
| 6 | "slices" | |
| 6 | 7 | "strconv" |
| 7 | 8 | |
| 8 | 9 | "gitbay.org/gitbay/internal/control" |
| @@ -61,6 +62,11 @@ func filterLinks(f buildFilter, jobs []control.JobOut) []buildFilterLink { | ||
| 61 | 62 | return links |
| 62 | 63 | } |
| 63 | 64 | |
| 65 | // maxBranchFacets caps the Branches group, the way topicFacets caps | |
| 66 | // topics: a page of builds names as many refs as it likes and the column | |
| 67 | // is not a branch listing. The field below the column takes any ref. | |
| 68 | const maxBranchFacets = 10 | |
| 69 | ||
| 64 | 70 | // buildFacets is the builds page's side column: filterLinks' rows split |
| 65 | 71 | // into their groups, plus one link per branch seen, which keeps status |
| 66 | 72 | // and job and clears itself when active. |
| @@ -77,7 +83,18 @@ func buildFacets(f buildFilter, jobs []control.JobOut, refs []string) []facetGro | ||
| 77 | 83 | } |
| 78 | 84 | branch := facetGroup{Title: "Branches"} |
| 79 | 85 | base := url.Values{"ref": {f.Ref}, "status": {f.Status}, "job": {f.Job}} |
| 80 | for _, ref := range refs { | |
| 86 | shown := refs | |
| 87 | if len(shown) > maxBranchFacets { | |
| 88 | shown = shown[:maxBranchFacets] | |
| 89 | // the ref in force belongs in the group wherever it sits, or the | |
| 90 | // filter it set cannot be cleared from the column. The reslice | |
| 91 | // caps the capacity so the append copies instead of writing | |
| 92 | // through to refs. | |
| 93 | if i := slices.Index(refs, f.Ref); i >= maxBranchFacets { | |
| 94 | shown = append(shown[:maxBranchFacets-1:maxBranchFacets-1], refs[i]) | |
| 95 | } | |
| 96 | } | |
| 97 | for _, ref := range shown { | |
| 81 | 98 | active := ref == f.Ref |
| 82 | 99 | href := facetHref(base, "ref", ref) |
| 83 | 100 | if active { |
internal/httpd/builds_test.go +34
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | package httpd |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "fmt" | |
| 4 | 5 | "reflect" |
| 5 | 6 | "testing" |
| 6 | 7 | |
| @@ -174,3 +175,36 @@ func TestBuildFacetsGroups(t *testing.T) { | ||
| 174 | 175 | t.Errorf("dev: %+v", b) |
| 175 | 176 | } |
| 176 | 177 | } |
| 178 | ||
| 179 | // The branch group caps, the way topicFacets caps topics: a page of | |
| 180 | // builds can name dozens of refs and the column is not a branch | |
| 181 | // listing. The ref in force is kept whatever its position (#237). | |
| 182 | func TestBuildFacetsCapsBranches(t *testing.T) { | |
| 183 | var refs []string | |
| 184 | for i := 0; i < 30; i++ { | |
| 185 | refs = append(refs, fmt.Sprintf("b%02d", i)) | |
| 186 | } | |
| 187 | groups := buildFacets(buildFilter{}, nil, refs) | |
| 188 | branches := groups[2].Items | |
| 189 | if len(branches) != maxBranchFacets { | |
| 190 | t.Fatalf("branches: %d", len(branches)) | |
| 191 | } | |
| 192 | if branches[0].Label != "b00" || branches[len(branches)-1].Label != "b09" { | |
| 193 | t.Errorf("kept the wrong refs: %+v", branches) | |
| 194 | } | |
| 195 | ||
| 196 | groups = buildFacets(buildFilter{Ref: "b29"}, nil, refs) | |
| 197 | branches = groups[2].Items | |
| 198 | if len(branches) != maxBranchFacets { | |
| 199 | t.Fatalf("branches with an active ref: %d", len(branches)) | |
| 200 | } | |
| 201 | var active *facetItem | |
| 202 | for i := range branches { | |
| 203 | if branches[i].Label == "b29" { | |
| 204 | active = &branches[i] | |
| 205 | } | |
| 206 | } | |
| 207 | if active == nil || !active.Active || active.Href != "?" { | |
| 208 | t.Errorf("active ref past the cap: %+v", branches) | |
| 209 | } | |
| 210 | } | |
internal/httpd/facets.go +9 −1
| @@ -41,7 +41,9 @@ func facetHref(base url.Values, key, value string) string { | ||
| 41 | 41 | // listFacets builds the issue or merge request list's column from the |
| 42 | 42 | // active parameters, the states the page offers, and the repository's |
| 43 | 43 | // labels and open milestones. Counts are the rows' own: a label's issue |
| 44 | // count on the issue list, its MR count on the MR list. | |
| 44 | // count on the issue list, its MR count on the MR list. A count of zero | |
| 45 | // is left out — it links to an empty list — unless it is the filter in | |
| 46 | // force, which stays so it can be cleared (#237). | |
| 45 | 47 | func listFacets(base url.Values, states []string, state string, labels []store.Label, ms []store.Milestone, forMRs bool) []facetGroup { |
| 46 | 48 | var st facetGroup |
| 47 | 49 | st.Title = "State" |
| @@ -55,6 +57,9 @@ func listFacets(base url.Values, states []string, state string, labels []store.L | ||
| 55 | 57 | n = l.MRs |
| 56 | 58 | } |
| 57 | 59 | active := base.Get("label") == l.Name |
| 60 | if n == 0 && !active { | |
| 61 | continue | |
| 62 | } | |
| 58 | 63 | href := facetHref(base, "label", l.Name) |
| 59 | 64 | if active { |
| 60 | 65 | href = facetHref(base, "label", "") |
| @@ -64,6 +69,9 @@ func listFacets(base url.Values, states []string, state string, labels []store.L | ||
| 64 | 69 | mg := facetGroup{Title: "Milestones"} |
| 65 | 70 | for _, m := range ms { |
| 66 | 71 | active := base.Get("milestone") == m.Title |
| 72 | if m.OpenItems == 0 && !active { | |
| 73 | continue | |
| 74 | } | |
| 67 | 75 | href := facetHref(base, "milestone", m.Title) |
| 68 | 76 | if active { |
| 69 | 77 | href = facetHref(base, "milestone", "") |
internal/httpd/facets_test.go +34 −6
| @@ -35,18 +35,46 @@ func TestListFacetsGroups(t *testing.T) { | ||
| 35 | 35 | t.Errorf("state items: %+v", st) |
| 36 | 36 | } |
| 37 | 37 | lb := groups[1].Items |
| 38 | // docs has no issues, so the issue list does not offer it (#237) | |
| 39 | if len(lb) != 1 { | |
| 40 | t.Fatalf("labels: %+v", lb) | |
| 41 | } | |
| 38 | 42 | if lb[0].Label != "bug" || lb[0].Count != 2 || !lb[0].Active || lb[0].Href != "?state=open" { |
| 39 | 43 | t.Errorf("active label clears itself: %+v", lb[0]) |
| 40 | 44 | } |
| 41 | if lb[1].Label != "docs" || lb[1].Count != 0 || lb[1].Active || lb[1].Href != "?label=docs&state=open" { | |
| 42 | t.Errorf("inactive label: %+v", lb[1]) | |
| 43 | } | |
| 44 | 45 | if m := groups[2].Items[0]; m.Label != "v2" || m.Count != 4 || m.Href != "?label=bug&milestone=v2&state=open" { |
| 45 | 46 | t.Errorf("milestone: %+v", m) |
| 46 | 47 | } |
| 47 | // on the MR list a label's count is its MR count | |
| 48 | // on the MR list a label's count is its MR count, and both labels | |
| 49 | // have merge requests, so both are offered | |
| 48 | 50 | mr := listFacets(base, []string{"open"}, "open", labels, nil, true) |
| 49 | if mr[1].Items[1].Count != 3 { | |
| 50 | t.Errorf("mr count: %+v", mr[1].Items[1]) | |
| 51 | if len(mr[1].Items) != 2 || mr[1].Items[1].Count != 3 { | |
| 52 | t.Errorf("mr counts: %+v", mr[1].Items) | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | // A count of zero is a link to an empty list, so it is left out — unless | |
| 57 | // it is the filter in force, which has to stay clickable to clear (#237). | |
| 58 | func TestListFacetsDropsEmpty(t *testing.T) { | |
| 59 | labels := []store.Label{{Name: "bug", Issues: 0}, {Name: "docs", Issues: 0}} | |
| 60 | ms := []store.Milestone{{Title: "v2", OpenItems: 0}, {Title: "v3", OpenItems: 1}} | |
| 61 | ||
| 62 | groups := listFacets(url.Values{"state": {"open"}}, []string{"open"}, "open", labels, ms, false) | |
| 63 | if n := len(groups[1].Items); n != 0 { | |
| 64 | t.Errorf("empty labels offered: %+v", groups[1].Items) | |
| 65 | } | |
| 66 | if n := len(groups[2].Items); n != 1 || groups[2].Items[0].Label != "v3" { | |
| 67 | t.Errorf("milestones: %+v", groups[2].Items) | |
| 68 | } | |
| 69 | ||
| 70 | base := url.Values{"state": {"open"}, "label": {"bug"}, "milestone": {"v2"}} | |
| 71 | groups = listFacets(base, []string{"open"}, "open", labels, ms, false) | |
| 72 | lb := groups[1].Items | |
| 73 | if len(lb) != 1 || lb[0].Label != "bug" || !lb[0].Active || lb[0].Href != "?milestone=v2&state=open" { | |
| 74 | t.Errorf("active empty label dropped: %+v", lb) | |
| 75 | } | |
| 76 | mg := groups[2].Items | |
| 77 | if len(mg) != 2 || mg[0].Label != "v2" || !mg[0].Active || mg[0].Href != "?label=bug&state=open" { | |
| 78 | t.Errorf("active empty milestone dropped: %+v", mg) | |
| 51 | 79 | } |
| 52 | 80 | } |