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}
29
30// ChecksSection is the checks of the head of a merge request.
31type ChecksSection struct {
32 Rows []CheckRow
33 // Note explains the rule that applies to them.
34 Note string
35}
36
37// checksFor reads the checks of a commit as the caller of the request. A page
38// shows them when it can: a failure of the read is logged and the page goes on
39// without, since the code and the review do not depend on CI.
40func (s *Server) checksFor(ctx context.Context, repo domain.RepoName, sha string) []ports.CheckView {
41 if s.cfg.Runs == nil || sha == "" {
42 return nil
43 }
44 res, err := s.cfg.Runs.ListChecks(ctx, principal(ctx), repo, sha)
45 if err != nil {
46 if !errors.Is(err, domain.ErrNotFound) {
47 log := zerowrap.FromCtx(ctx)
48 log.Warn().Err(err).Str("repo", repo.String()).Msg("read the checks of a commit")
49 }
50 return nil
51 }
52 return res.Checks
53}
54
55// checksSummary reduces checks to the mark a list shows next to a commit.
56func checksSummary(repo domain.RepoName, sha string, checks []ports.CheckView) ChecksSummary {
57 if len(checks) == 0 {
58 return ChecksSummary{}
59 }
60 plain := make([]domain.CICheck, 0, len(checks))
61 passed := 0
62 for _, c := range checks {
63 plain = append(plain, c.CICheck)
64 if c.Status == domain.CICompleted && c.Conclusion.PassesMerge() {
65 passed++
66 }
67 }
68 sum := ChecksSummary{Total: len(checks), Href: repoHref(repo.String(), "actions"), Label: fmt.Sprintf("%d/%d", passed, len(checks))}
69 switch domain.CombineChecks(plain) {
70 case domain.CheckStateSuccess:
71 sum.Kind, sum.State = "ok", "checks passed"
72 case domain.CheckStateFailure:
73 sum.Kind, sum.State = "bad", "checks failed"
74 default:
75 sum.Kind, sum.State = "running", "checks pending"
76 }
77 sum.Title = sum.State + ": " + sum.Label
78 return sum
79}
80
81// checksSection is the checks of the rail of a merge request, with the rule of
82// the repository that applies to them.
83func checksSection(repo domain.Repo, checks []ports.CheckView) ChecksSection {
84 sec := ChecksSection{}
85 for _, c := range checks {
86 row := CheckRow{
87 Name: visibleText(c.Name), Kind: ciKind(c.Status, c.Conclusion), State: ciState(c.Status, c.Conclusion),
88 Waiting: c.WaitingForRunner,
89 }
90 if c.JobID != "" {
91 row.Href = jobHref(repo.Name.String(), c.RunID, c.JobID)
92 } else {
93 row.Href = runHref(repo.Name.String(), c.RunID)
94 }
95 sec.Rows = append(sec.Rows, row)
96 }
97 switch {
98 case len(checks) == 0:
99 sec.Note = "no check on this commit: nothing blocks the merge."
100 case repo.Settings.FailingChecksBlock:
101 sec.Note = "a failing or unfinished check blocks the merge."
102 default:
103 sec.Note = "checks do not block the merge in this repository."
104 }
105 return sec
106}