1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6 "context"
7 "errors"
8 "fmt"
9
10 "github.com/bnema/zerowrap"
11
12 "bnema.dev/ermite/internal/domain"
13 "bnema.dev/ermite/internal/ports"
14)
15
16// ChecksSummary is the combined state of the checks of a commit.
17type ChecksSummary struct {
18 Total int
19 Kind, State string
20 Label, Title string
21 Href string
22}
23
24// CheckRow is a check of a commit or a merge request.
25type CheckRow struct {
26 Name, Kind, State, Href string
27 Waiting bool
28 GroupLabel, GroupHref string
29}
30
31// ChecksSection is the checks of the head of a merge request.
32type ChecksSection struct {
33 Rows []CheckRow
34 // Note explains the rule that applies to them.
35 Note string
36}
37
38// checksFor reads the checks of a commit as the caller of the request. A page
39// shows them when it can: a failure of the read is logged and the page goes on
40// without, since the code and the review do not depend on CI.
41func (s *Server) checksFor(ctx context.Context, repo domain.RepoName, sha string) []ports.CheckView {
42 if s.cfg.Runs == nil || sha == "" {
43 return nil
44 }
45 res, err := s.cfg.Runs.ListChecks(ctx, principal(ctx), repo, sha)
46 if err != nil {
47 if !errors.Is(err, domain.ErrNotFound) {
48 log := zerowrap.FromCtx(ctx)
49 log.Warn().Err(err).Str("repo", repo.String()).Msg("read the checks of a commit")
50 }
51 return nil
52 }
53 return res.Checks
54}
55
56// checksSummary reduces checks to the mark a list shows next to a commit.
57//
58//hexcheck:ignore suspicious-business-logic-in-adapter renders domain check outcomes as a commit-list mark, passed count, label and actions link
59func checksSummary(repo domain.RepoName, sha string, checks []ports.CheckView) ChecksSummary {
60 if len(checks) == 0 {
61 return ChecksSummary{}
62 }
63 plain := make([]domain.CICheck, 0, len(checks))
64 passed := 0
65 for _, c := range checks {
66 plain = append(plain, c.CICheck)
67 if c.Status == domain.CICompleted && c.Conclusion.PassesMerge() {
68 passed++
69 }
70 }
71 sum := ChecksSummary{Total: len(checks), Href: repoHref(repo.String(), "actions"), Label: fmt.Sprintf("%d/%d", passed, len(checks))}
72 switch domain.CombineChecks(plain) {
73 case domain.CheckStateSuccess:
74 sum.Kind, sum.State = "ok", "checks passed"
75 case domain.CheckStateFailure:
76 sum.Kind, sum.State = "bad", "checks failed"
77 default:
78 sum.Kind, sum.State = "running", "checks pending"
79 }
80 sum.Title = sum.State + ": " + sum.Label
81 return sum
82}
83
84// checksSection is the checks of the footer of a merge request, with the rule of
85// the repository that applies to them.
86func checksSection(repo domain.Repo, checks []ports.CheckView) ChecksSection {
87 sec := ChecksSection{}
88 groups := checksByRun(checks)
89 for _, group := range groups {
90 for i, c := range group {
91 row := CheckRow{
92 Name: visibleText(c.Name), Kind: ciKind(c.Status, c.Conclusion), State: ciState(c.Status, c.Conclusion),
93 Waiting: c.WaitingForRunner,
94 }
95 if c.JobID != "" {
96 row.Href = jobHref(repo.Name.String(), c.RunID, c.JobID)
97 } else {
98 row.Href = runHref(repo.Name.String(), c.RunID)
99 }
100 if len(groups) > 1 && i == 0 {
101 event := visibleText(c.Event)
102 if event == "" {
103 event = fmt.Sprintf("run %d", c.RunID)
104 }
105 row.GroupLabel = visibleText(c.WorkflowName) + " · " + event
106 row.GroupHref = runHref(repo.Name.String(), c.RunID)
107 }
108 sec.Rows = append(sec.Rows, row)
109 }
110 }
111 switch {
112 case len(checks) == 0:
113 sec.Note = "no check on this commit: nothing blocks the merge."
114 default:
115 sec.Note = "a failing or unfinished check blocks the merge."
116 }
117 return sec
118}
119
120// checksByRun keeps first-appearance order and the check order within each run.
121func checksByRun(checks []ports.CheckView) [][]ports.CheckView {
122 groups := make([][]ports.CheckView, 0)
123 indices := make(map[int64]int)
124 for _, c := range checks {
125 i, ok := indices[c.RunID]
126 if !ok {
127 i = len(groups)
128 indices[c.RunID] = i
129 groups = append(groups, nil)
130 }
131 groups[i] = append(groups[i], c)
132 }
133 return groups
134}