When a GitHub Actions workflow uses concurrency with cancel-in-progress, cancelled runs were counted as failures in `gh pr status` and `gh pr view`, even when a newer run for the same check name succeeded. The GitHub web UI and `gh pr checks` both handle this correctly. Three changes fix this: 1. Add a `cancelled` check status category. Cancelled runs are now excluded from all summary counts (passing/failing/pending) and subtracted from the total, matching the web UI behavior. 2. Move `eliminateDuplicates` from pkg/cmd/pr/checks to `api.EliminateDuplicateChecks` (exported). The function operates entirely on `api.CheckContext` and is now shared by both `pr checks` and `ChecksStatus()` (used by `pr status` and `pr view`). 3. Apply deduplication in the `ChecksStatus()` slow path, keeping only the most recent run per check name — consistent with `pr checks`. Fixes #12895 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
91 lines
2 KiB
Go
91 lines
2 KiB
Go
package checks
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
)
|
|
|
|
type check struct {
|
|
Name string `json:"name"`
|
|
State string `json:"state"`
|
|
StartedAt time.Time `json:"startedAt"`
|
|
CompletedAt time.Time `json:"completedAt"`
|
|
Link string `json:"link"`
|
|
Bucket string `json:"bucket"`
|
|
Event string `json:"event"`
|
|
Workflow string `json:"workflow"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type checkCounts struct {
|
|
Failed int
|
|
Passed int
|
|
Pending int
|
|
Skipping int
|
|
Canceled int
|
|
}
|
|
|
|
func (ch *check) ExportData(fields []string) map[string]interface{} {
|
|
return cmdutil.StructExportData(ch, fields)
|
|
}
|
|
|
|
func aggregateChecks(checkContexts []api.CheckContext, requiredChecks bool) (checks []check, counts checkCounts) {
|
|
for _, c := range api.EliminateDuplicateChecks(checkContexts) {
|
|
if requiredChecks && !c.IsRequired {
|
|
continue
|
|
}
|
|
|
|
state := string(c.State)
|
|
if state == "" {
|
|
if c.Status == "COMPLETED" {
|
|
state = string(c.Conclusion)
|
|
} else {
|
|
state = c.Status
|
|
}
|
|
}
|
|
|
|
link := c.DetailsURL
|
|
if link == "" {
|
|
link = c.TargetURL
|
|
}
|
|
|
|
name := c.Name
|
|
if name == "" {
|
|
name = c.Context
|
|
}
|
|
|
|
item := check{
|
|
Name: name,
|
|
State: state,
|
|
StartedAt: c.StartedAt,
|
|
CompletedAt: c.CompletedAt,
|
|
Link: link,
|
|
Event: c.CheckSuite.WorkflowRun.Event,
|
|
Workflow: c.CheckSuite.WorkflowRun.Workflow.Name,
|
|
Description: c.Description,
|
|
}
|
|
|
|
switch state {
|
|
case "SUCCESS":
|
|
item.Bucket = "pass"
|
|
counts.Passed++
|
|
case "SKIPPED", "NEUTRAL":
|
|
item.Bucket = "skipping"
|
|
counts.Skipping++
|
|
case "ERROR", "FAILURE", "TIMED_OUT", "ACTION_REQUIRED":
|
|
item.Bucket = "fail"
|
|
counts.Failed++
|
|
case "CANCELLED":
|
|
item.Bucket = "cancel"
|
|
counts.Canceled++
|
|
default: // "EXPECTED", "REQUESTED", "WAITING", "QUEUED", "PENDING", "IN_PROGRESS", "STALE"
|
|
item.Bucket = "pending"
|
|
counts.Pending++
|
|
}
|
|
|
|
checks = append(checks, item)
|
|
}
|
|
return
|
|
}
|