detached 3483340

checks.go RawHistoryFolder

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 rail 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	case repo.Settings.FailingChecksBlock:
115		sec.Note = "a failing or unfinished check blocks the merge."
116	default:
117		sec.Note = "checks do not block the merge in this repository."
118	}
119	return sec
120}
121
122// checksByRun keeps first-appearance order and the check order within each run.
123func checksByRun(checks []ports.CheckView) [][]ports.CheckView {
124	groups := make([][]ports.CheckView, 0)
125	indices := make(map[int64]int)
126	for _, c := range checks {
127		i, ok := indices[c.RunID]
128		if !ok {
129			i = len(groups)
130			indices[c.RunID] = i
131			groups = append(groups, nil)
132		}
133		groups[i] = append(groups[i], c)
134	}
135	return groups
136}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close