Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion aws/lambda/cross_repo_ci_relay/callback/callback_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ def _create_upstream_check_run(
workflow_name: str,
job_name: str | None,
details_url: str,
pr_number: str = "",
) -> None:
"""Create a new upstream check run mirroring the downstream job's status.

Expand All @@ -215,7 +216,7 @@ def _create_upstream_check_run(
Best-effort: a GitHub failure must not fail the callback.
"""
output = gh_helper.build_check_run_output(
status, conclusion, details_url, verified_repo
status, conclusion, details_url, verified_repo, pr_number
)
try:
upstream_token = gh_helper.get_repo_access_token(
Expand Down Expand Up @@ -369,6 +370,7 @@ def handle(config: RelayConfig, body: dict, verified_repo: str) -> dict:
if repo_level.value >= AllowlistLevel.L3.value:
pr_field = (body.get("payload") or {}).get("pull_request") or {}
head_sha = (pr_field.get("head") or {}).get("sha", "")
pr_number = str(pr_field.get("number", ""))
if head_sha:
conclusion = (body.get("workflow") or {}).get("conclusion")
details_url = f"https://github.com/{verified_repo}/actions/runs/{run_id}"
Expand Down Expand Up @@ -408,6 +410,7 @@ def handle(config: RelayConfig, body: dict, verified_repo: str) -> dict:
workflow_name=workflow_name,
job_name=job_name,
details_url=details_url,
pr_number=pr_number,
)

if status == "in_progress":
Expand Down
3 changes: 2 additions & 1 deletion aws/lambda/cross_repo_ci_relay/callback/cleanup_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def _finalize_timed_out_check_run(

pr_field = (body.get("payload") or {}).get("pull_request") or {}
head_sha = (pr_field.get("head") or {}).get("sha", "")
pr_number = str(pr_field.get("number", ""))
if not head_sha:
return

Expand All @@ -100,7 +101,7 @@ def _finalize_timed_out_check_run(
run_id = str(workflow.get("run_id"))
details_url = f"https://github.com/{verified_repo}/actions/runs/{run_id}"
output = gh_helper.build_check_run_output(
"completed", "timed_out", details_url, verified_repo
"completed", "timed_out", details_url, verified_repo, pr_number
)

try:
Expand Down
4 changes: 3 additions & 1 deletion aws/lambda/cross_repo_ci_relay/utils/gh_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def build_check_run_output(
conclusion: str,
details_url: str,
downstream_repo: str,
pr_number: str = "",
) -> dict:
"""Return a GitHub Check Run output dict shown in the detail panel."""
if status != "completed":
Expand All @@ -151,9 +152,10 @@ def build_check_run_output(
title = conclusion.capitalize()
else:
title = "Completed"
pr_part = f" for PR {pr_number}" if pr_number else ""
return {
"title": title,
"summary": f"{downstream_repo} workflow: {details_url}",
"summary": f"{downstream_repo} workflow{pr_part}: {details_url}",
Comment thread
KarhouTam marked this conversation as resolved.
Outdated
}


Expand Down
6 changes: 5 additions & 1 deletion aws/lambda/cross_repo_ci_relay/webhook/event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ def _handle_pr_labeled(config: RelayConfig, payload: dict) -> dict:
details_url=details_url,
external_id=str(run_id),
output=gh_helper.build_check_run_output(
job_status, job_conclusion, details_url, downstream_repo
job_status,
job_conclusion,
details_url,
downstream_repo,
pr_number,
),
)
created.append(f"{downstream_repo}/{job_name}")
Expand Down
27 changes: 15 additions & 12 deletions torchci/lib/bot/crcrOncallBot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,24 @@ export default function crcrOncallBot(app: Probot): void {
}

// Get the PRs this check run belongs to.
// checkRun.pull_requests is empty for cross-fork PRs (most pytorch
// contributions), so fall back to the commits API to resolve PRs
// from the head SHA — the same strategy used by the merge-blocking path.
let prNumbers: number[] = [];
if (checkRun.pull_requests && checkRun.pull_requests.length > 0) {
if (checkRun.output?.summary) {
Comment thread
KarhouTam marked this conversation as resolved.
Outdated
const match = checkRun.output.summary.match(/for PR (\d+)/);
if (match) {
prNumbers = [parseInt(match[1], 10)];
}
} else if (checkRun.pull_requests && checkRun.pull_requests.length > 0) {
prNumbers = checkRun.pull_requests.map((pr) => pr.number);
} else if (checkRun.head_sha) {
}

// Fall back to Search API if still no PR found (e.g., pr_number was empty
// on the Lambda side).
if (prNumbers.length === 0 && checkRun.head_sha) {
Comment thread
KarhouTam marked this conversation as resolved.
Outdated
try {
const result =
await ctx.octokit.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: checkRun.head_sha,
});
prNumbers = result.data.map((pr: any) => pr.number);
const result = await ctx.octokit.rest.search.issuesAndPullRequests({
q: `${checkRun.head_sha} type:pr repo:${owner}/${repo}`,
});
prNumbers = result.data.items.map((item: any) => item.number);
} catch (err) {
ctx.log(
{ err },
Expand Down
44 changes: 43 additions & 1 deletion torchci/test/crcrOncallBot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,56 @@ L3:
});
});

test("does not comment when no PR is associated", async () => {
test("does not comment when output has no PR number", async () => {
// output.summary exists but regex doesn't match (empty pr_number);
// falls through to Search API which also returns nothing
const scope = nock("https://api.github.com")
.get("/search/issues")
.query(true)
.reply(200, { total_count: 0, items: [] });

await probot.receive({
name: "check_run" as any,
payload: checkRunPayload({
pull_requests: [],
output: {
title: "In progress",
summary: "intel/torch-xpu-ops workflow for PR : https://example.com",
},
}) as any,
id: "8",
});
handleScope(scope);
});

test("posts comment when output contains PR number for cross-fork PR", async () => {
const scope = nock("https://api.github.com")
.get(`/repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments`)
.reply(200, [])
.post(
`/repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments`,
(body: any) => {
expect(body.body).toContain(
"<!-- crcr-oncall:intel/torch-xpu-ops -->"
);
expect(body.body).toContain("@oncall_xpu");
return true;
}
)
.reply(200);

await probot.receive({
name: "check_run" as any,
payload: checkRunPayload({
pull_requests: [], // empty — simulates cross-fork PR
output: {
title: "Failure",
summary: `intel/torch-xpu-ops workflow for PR ${PR_NUMBER}: https://example.com`,
},
}) as any,
id: "10",
});
handleScope(scope);
});

test("does nothing for unsupported org", async () => {
Expand Down
Loading