Go · 2748 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package checks
4
5 import (
6 "context"
7
8 "github.com/jackc/pgx/v5"
9
10 checksdb "github.com/tenseleyFlow/shithub/internal/checks/sqlc"
11 )
12
13 // rollupSuiteInTx recomputes the suite's status + conclusion from its
14 // runs and persists the result. Called from Create and Update inside
15 // the same tx so the API response reflects the latest derived state.
16 //
17 // Per spec design table:
18 //
19 // status = 'completed' iff every run is completed
20 // conclusion priority (when status='completed'):
21 // failure > timed_out > cancelled > action_required >
22 // success > neutral > skipped > stale
23 //
24 // The "first failure-class wins" ordering matches GitHub's roll-up.
25 // Any non-completed run forces status to 'in_progress' (or keeps the
26 // existing 'queued' if every run is queued — we treat queued+completed
27 // as 'in_progress' too, since *something* moved).
28 func rollupSuiteInTx(ctx context.Context, tx pgx.Tx, suiteID int64) error {
29 q := checksdb.New()
30 runs, err := q.ListCheckRunsBySuite(ctx, tx, suiteID)
31 if err != nil {
32 return err
33 }
34 status, conclusion := DeriveSuiteRollup(runs)
35 conclusionParam := checksdb.NullCheckConclusion{}
36 if conclusion != "" {
37 conclusionParam = checksdb.NullCheckConclusion{
38 CheckConclusion: checksdb.CheckConclusion(conclusion),
39 Valid: true,
40 }
41 }
42 return q.UpdateCheckSuiteRollup(ctx, tx, checksdb.UpdateCheckSuiteRollupParams{
43 ID: suiteID,
44 Status: checksdb.CheckStatus(status),
45 Conclusion: conclusionParam,
46 })
47 }
48
49 // DeriveSuiteRollup is the pure-function form of the rollup so tests
50 // can exercise the priority order without a DB. Public so the API
51 // layer can preview the rollup if needed.
52 func DeriveSuiteRollup(runs []checksdb.CheckRun) (status, conclusion string) {
53 if len(runs) == 0 {
54 return "queued", ""
55 }
56 allCompleted := true
57 anyMoved := false
58 for _, r := range runs {
59 if r.Status != checksdb.CheckStatusCompleted {
60 allCompleted = false
61 }
62 if r.Status != checksdb.CheckStatusQueued {
63 anyMoved = true
64 }
65 }
66 if !allCompleted {
67 if anyMoved {
68 return "in_progress", ""
69 }
70 return "queued", ""
71 }
72 // Every run completed → derive aggregate conclusion.
73 priority := []string{
74 "failure", "timed_out", "cancelled", "action_required",
75 "success", "neutral", "skipped", "stale",
76 }
77 rank := map[string]int{}
78 for i, p := range priority {
79 rank[p] = i
80 }
81 bestIdx := -1
82 for _, r := range runs {
83 if !r.Conclusion.Valid {
84 continue
85 }
86 c := string(r.Conclusion.CheckConclusion)
87 idx, ok := rank[c]
88 if !ok {
89 continue
90 }
91 if bestIdx < 0 || idx < bestIdx {
92 bestIdx = idx
93 }
94 }
95 if bestIdx < 0 {
96 return "completed", "neutral"
97 }
98 return "completed", priority[bestIdx]
99 }
100