mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-25 08:11:54 +00:00
fix(prs): read StatusContext state when classifying CI
gh's statusCheckRollup mixes CheckRun entries (status/conclusion) with StatusContext entries, the commit statuses external CI such as Jenkins or CircleCI posts, which carry only a state. _parse_ci never read state, so a failing external CI showed as NONE, or as SUCCESS beside a passing check run, and the PR was classified READY instead of CI-FAIL in pr list, triage and the MCP PR tools. Fold state into the conclusions, with ERROR as a failure and PENDING/EXPECTED as pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b06e7033c
commit
8d39c75d02
2 files changed
+34
-3
No files matched your search
+7
-3
@@ -190,11 +190,15 @@ _CI_FAILURE_CONCLUSIONS = frozenset({"FAILURE", "CANCELLED", "TIMED_OUT", "ACTIO
|
||||
def _parse_ci(rollup: list) -> str:
|
||||
if not rollup:
|
||||
return "NONE"
|
||||
conclusions = {r.get("conclusion") for r in rollup if r.get("conclusion")}
|
||||
if conclusions & _CI_FAILURE_CONCLUSIONS:
|
||||
# statusCheckRollup mixes two entry types: CheckRun (GitHub Actions and other
|
||||
# check apps) carries status/conclusion, while StatusContext (commit statuses
|
||||
# from external CI such as Jenkins or CircleCI) carries only state, one of
|
||||
# SUCCESS, FAILURE, ERROR, PENDING or EXPECTED. Read both.
|
||||
conclusions = {r.get("conclusion") or r.get("state") for r in rollup} - {None, ""}
|
||||
if conclusions & (_CI_FAILURE_CONCLUSIONS | {"ERROR"}):
|
||||
return "FAILURE"
|
||||
statuses = {r.get("status") for r in rollup}
|
||||
if "IN_PROGRESS" in statuses or "QUEUED" in statuses:
|
||||
if "IN_PROGRESS" in statuses or "QUEUED" in statuses or conclusions & {"PENDING", "EXPECTED"}:
|
||||
return "PENDING"
|
||||
if "SUCCESS" in conclusions:
|
||||
return "SUCCESS"
|
||||
|
||||
@@ -134,6 +134,33 @@ class TestParseCi:
|
||||
assert _parse_ci(rollup) == "FAILURE"
|
||||
|
||||
|
||||
def test_status_context_failure_is_failure(self):
|
||||
"""A commit status from external CI (StatusContext) carries only `state`,
|
||||
no conclusion/status; a failing one must not read as NONE."""
|
||||
rollup = [{"__typename": "StatusContext", "context": "ci/jenkins", "state": "FAILURE"}]
|
||||
assert _parse_ci(rollup) == "FAILURE"
|
||||
|
||||
def test_status_context_error_is_failure(self):
|
||||
rollup = [{"__typename": "StatusContext", "context": "ci/jenkins", "state": "ERROR"}]
|
||||
assert _parse_ci(rollup) == "FAILURE"
|
||||
|
||||
def test_status_context_failure_beside_passing_check_run_is_failure(self):
|
||||
rollup = [
|
||||
{"__typename": "CheckRun", "conclusion": "SUCCESS", "status": "COMPLETED"},
|
||||
{"__typename": "StatusContext", "context": "ci/jenkins", "state": "FAILURE"},
|
||||
]
|
||||
assert _parse_ci(rollup) == "FAILURE"
|
||||
|
||||
def test_status_context_pending_is_pending(self):
|
||||
for state in ("PENDING", "EXPECTED"):
|
||||
rollup = [{"__typename": "StatusContext", "context": "ci/jenkins", "state": state}]
|
||||
assert _parse_ci(rollup) == "PENDING", state
|
||||
|
||||
def test_status_context_success_is_success(self):
|
||||
rollup = [{"__typename": "StatusContext", "context": "ci/jenkins", "state": "SUCCESS"}]
|
||||
assert _parse_ci(rollup) == "SUCCESS"
|
||||
|
||||
|
||||
# ── _path_match ───────────────────────────────────────────────────────────────
|
||||
|
||||
class TestPathMatch:
|
||||
|
||||
Reference in new issue
Block a user