Add PR workflow commands (#742)
Co-authored-by: UltralyticsAssistant <[email protected]>
This commit is contained in:
co-authored by
UltralyticsAssistant
parent
000482b900
commit
1960672ebc
+27
-14
@@ -9,8 +9,14 @@ from datetime import datetime
|
||||
from .utils import ACTIONS_CREDIT, GITHUB_API_URL, Action
|
||||
|
||||
# Configuration
|
||||
RUN_CI_KEYWORD = "@ultralytics/run-ci" # and then to merge "@ultralytics/run-ci-and-merge"
|
||||
WORKFLOW_FILES = ["ci.yml", "docker.yml"]
|
||||
RUN_CI_KEYWORD = "@ultralytics/run-ci"
|
||||
RUN_ALL_KEYWORD = "@ultralytics/run-all"
|
||||
RUN_DOCKER_KEYWORD = "@ultralytics/run-docker"
|
||||
WORKFLOW_FILES = {
|
||||
RUN_CI_KEYWORD: ["ci.yml"],
|
||||
RUN_ALL_KEYWORD: ["ci.yml", "docker.yml"],
|
||||
RUN_DOCKER_KEYWORD: ["docker.yml"],
|
||||
}
|
||||
|
||||
|
||||
def get_pr_branch(event) -> tuple[str, str | None]:
|
||||
@@ -66,21 +72,23 @@ def get_pr_branch(event) -> tuple[str, str | None]:
|
||||
return head.get("ref", "main"), None
|
||||
|
||||
|
||||
def trigger_and_get_workflow_info(event, branch: str, temp_branch: str | None = None) -> list[dict]:
|
||||
def trigger_and_get_workflow_info(
|
||||
event, branch: str, workflow_files: list[str], temp_branch: str | None = None
|
||||
) -> list[dict]:
|
||||
"""Triggers workflows and returns their information, deleting temp branch if provided."""
|
||||
repo = event.repository
|
||||
results = []
|
||||
|
||||
try:
|
||||
# Trigger all workflows
|
||||
for file in WORKFLOW_FILES:
|
||||
for file in workflow_files:
|
||||
event.post(f"{GITHUB_API_URL}/repos/{repo}/actions/workflows/{file}/dispatches", json={"ref": branch})
|
||||
|
||||
# Wait for workflows to be created and start
|
||||
time.sleep(60)
|
||||
|
||||
# Collect information about all workflows
|
||||
for file in WORKFLOW_FILES:
|
||||
for file in workflow_files:
|
||||
# Get workflow name
|
||||
response = event.get(f"{GITHUB_API_URL}/repos/{repo}/actions/workflows/{file}")
|
||||
name = file.replace(".yml", "").title()
|
||||
@@ -108,7 +116,7 @@ def trigger_and_get_workflow_info(event, branch: str, temp_branch: str | None =
|
||||
return results
|
||||
|
||||
|
||||
def update_comment(event, comment_body: str, triggered_actions: list[dict]):
|
||||
def update_comment(event, comment_body: str, command: str, triggered_actions: list[dict]):
|
||||
"""Updates the comment with workflow information."""
|
||||
if not triggered_actions:
|
||||
return
|
||||
@@ -120,7 +128,8 @@ def update_comment(event, comment_body: str, triggered_actions: list[dict]):
|
||||
|
||||
{ACTIONS_CREDIT}
|
||||
|
||||
GitHub Actions below triggered via workflow dispatch for this PR at {timestamp} with `{RUN_CI_KEYWORD}` command:
|
||||
GitHub Actions below triggered via workflow dispatch for this PR at {timestamp} with `{command}` command
|
||||
(available commands are `{RUN_ALL_KEYWORD}`, `{RUN_CI_KEYWORD}`, and `{RUN_DOCKER_KEYWORD}`):
|
||||
|
||||
"""
|
||||
|
||||
@@ -128,7 +137,7 @@ GitHub Actions below triggered via workflow dispatch for this PR at {timestamp}
|
||||
run_info = f" run {action['run_number']}" if action["run_number"] else ""
|
||||
summary += f"* ✅ [{action['name']}]({action['url']}): `{action['file']}`{run_info}\n"
|
||||
|
||||
new_body = comment_body.replace(RUN_CI_KEYWORD, summary).strip()
|
||||
new_body = comment_body.replace(command, summary).strip()
|
||||
comment_id = event.event_data["comment"]["id"]
|
||||
event.patch(f"{GITHUB_API_URL}/repos/{event.repository}/issues/comments/{comment_id}", json={"body": new_body})
|
||||
|
||||
@@ -149,11 +158,15 @@ def main(*args, **kwargs):
|
||||
comment_body = event.event_data["comment"].get("body") or ""
|
||||
username = event.event_data["comment"]["user"]["login"]
|
||||
|
||||
# Check for keyword without surrounding backticks to avoid triggering on replies
|
||||
has_keyword = RUN_CI_KEYWORD in comment_body and comment_body.count(RUN_CI_KEYWORD) > comment_body.count(
|
||||
f"`{RUN_CI_KEYWORD}`"
|
||||
command = next(
|
||||
(
|
||||
keyword
|
||||
for keyword in WORKFLOW_FILES
|
||||
if keyword in comment_body and comment_body.count(keyword) > comment_body.count(f"`{keyword}`")
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not has_keyword or not event.is_org_member(username):
|
||||
if not command or not event.is_org_member(username):
|
||||
return
|
||||
|
||||
# Get branch, trigger workflows, and update comment
|
||||
@@ -161,8 +174,8 @@ def main(*args, **kwargs):
|
||||
branch, temp_branch = get_pr_branch(event)
|
||||
print(f"Triggering workflows on branch: {branch}" + (" (temp)" if temp_branch else ""))
|
||||
|
||||
triggered_actions = trigger_and_get_workflow_info(event, branch, temp_branch)
|
||||
update_comment(event, comment_body, triggered_actions)
|
||||
triggered_actions = trigger_and_get_workflow_info(event, branch, WORKFLOW_FILES[command], temp_branch)
|
||||
update_comment(event, comment_body, command, triggered_actions)
|
||||
event.toggle_eyes_reaction(False)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ from datetime import datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from actions.dispatch_actions import (
|
||||
RUN_ALL_KEYWORD,
|
||||
RUN_CI_KEYWORD,
|
||||
RUN_DOCKER_KEYWORD,
|
||||
get_pr_branch,
|
||||
main,
|
||||
trigger_and_get_workflow_info,
|
||||
@@ -79,8 +81,8 @@ def test_trigger_and_get_workflow_info():
|
||||
mock_event.get.side_effect = get_side_effect
|
||||
|
||||
# Use patch to skip time.sleep and limit to one workflow
|
||||
with patch("time.sleep"), patch("actions.dispatch_actions.WORKFLOW_FILES", ["ci.yml"]):
|
||||
results = trigger_and_get_workflow_info(mock_event, "feature-branch")
|
||||
with patch("time.sleep"):
|
||||
results = trigger_and_get_workflow_info(mock_event, "feature-branch", ["ci.yml"])
|
||||
|
||||
# Check results
|
||||
assert len(results) == 1
|
||||
@@ -113,8 +115,7 @@ def test_trigger_and_get_workflow_info_with_temp_branch():
|
||||
mock_event.get.side_effect = get_side_effect
|
||||
|
||||
with patch("time.sleep"):
|
||||
with patch("actions.dispatch_actions.WORKFLOW_FILES", ["ci.yml"]):
|
||||
trigger_and_get_workflow_info(mock_event, "temp-ci-123-456", temp_branch="temp-ci-123-456")
|
||||
trigger_and_get_workflow_info(mock_event, "temp-ci-123-456", ["ci.yml"], temp_branch="temp-ci-123-456")
|
||||
|
||||
# Verify temp branch was deleted
|
||||
mock_event.delete.assert_called_once_with("https://api.github.com/repos/test/repo/git/refs/heads/temp-ci-123-456")
|
||||
@@ -140,7 +141,7 @@ def test_update_comment_function():
|
||||
with patch("actions.dispatch_actions.datetime") as mock_datetime:
|
||||
mock_datetime.now.return_value = datetime(2023, 1, 1, 12, 0, 0)
|
||||
# Call without capturing return value
|
||||
update_comment(mock_event, comment_body, triggered_actions)
|
||||
update_comment(mock_event, comment_body, RUN_CI_KEYWORD, triggered_actions)
|
||||
|
||||
# Check that patch was called with expected content
|
||||
mock_event.patch.assert_called_once()
|
||||
@@ -151,12 +152,15 @@ def test_update_comment_function():
|
||||
assert "Actions Trigger" in kwargs["json"]["body"]
|
||||
assert "CI Workflow" in kwargs["json"]["body"]
|
||||
assert "2023-01-01 12:00:00 UTC" in kwargs["json"]["body"]
|
||||
assert (
|
||||
f"(available commands are `{RUN_ALL_KEYWORD}`, `{RUN_CI_KEYWORD}`, and `{RUN_DOCKER_KEYWORD}`):"
|
||||
in kwargs["json"]["body"]
|
||||
)
|
||||
|
||||
|
||||
def test_main_triggers_workflows():
|
||||
"""Test main function when comment contains trigger keyword."""
|
||||
def test_main_triggers_ci_only():
|
||||
"""Test main function triggers only CI for the CI command."""
|
||||
with patch("actions.dispatch_actions.Action") as MockAction:
|
||||
# Configure mock
|
||||
mock_event = MockAction.return_value
|
||||
mock_event.event_name = "issue_comment"
|
||||
mock_event.repository = "test/repo"
|
||||
@@ -167,21 +171,62 @@ def test_main_triggers_workflows():
|
||||
}
|
||||
mock_event.is_org_member.return_value = True
|
||||
|
||||
# Create minimal patches for the functions called by main
|
||||
with patch("actions.dispatch_actions.get_pr_branch") as mock_get_branch:
|
||||
with patch("actions.dispatch_actions.trigger_and_get_workflow_info") as mock_trigger:
|
||||
with patch("actions.dispatch_actions.update_comment"):
|
||||
# Set return values
|
||||
mock_get_branch.return_value = ("feature-branch", None)
|
||||
mock_trigger.return_value = [{"name": "CI", "file": "ci.yml", "url": "url", "run_number": 1}]
|
||||
|
||||
# Call the function
|
||||
main()
|
||||
|
||||
# Verify main component calls were made
|
||||
mock_event.is_org_member.assert_called_once_with("testuser")
|
||||
mock_get_branch.assert_called_once()
|
||||
mock_trigger.assert_called_once()
|
||||
mock_trigger.assert_called_once_with(mock_event, "feature-branch", ["ci.yml"], None)
|
||||
|
||||
|
||||
def test_main_triggers_all_workflows():
|
||||
"""Test main function triggers CI and Docker for the all command."""
|
||||
with patch("actions.dispatch_actions.Action") as MockAction:
|
||||
mock_event = MockAction.return_value
|
||||
mock_event.event_name = "issue_comment"
|
||||
mock_event.repository = "test/repo"
|
||||
mock_event.event_data = {
|
||||
"action": "created",
|
||||
"issue": {"pull_request": {}},
|
||||
"comment": {"body": f"Please run all {RUN_ALL_KEYWORD}", "user": {"login": "testuser"}, "id": 789},
|
||||
}
|
||||
mock_event.is_org_member.return_value = True
|
||||
|
||||
with patch("actions.dispatch_actions.get_pr_branch") as mock_get_branch:
|
||||
with patch("actions.dispatch_actions.trigger_and_get_workflow_info") as mock_trigger:
|
||||
with patch("actions.dispatch_actions.update_comment"):
|
||||
mock_get_branch.return_value = ("feature-branch", None)
|
||||
mock_trigger.return_value = [{"name": "CI", "file": "ci.yml", "url": "url", "run_number": 1}]
|
||||
main()
|
||||
|
||||
mock_trigger.assert_called_once_with(mock_event, "feature-branch", ["ci.yml", "docker.yml"], None)
|
||||
|
||||
|
||||
def test_main_triggers_docker_only():
|
||||
"""Test main function triggers only docker workflow for docker command."""
|
||||
with patch("actions.dispatch_actions.Action") as MockAction:
|
||||
mock_event = MockAction.return_value
|
||||
mock_event.event_name = "issue_comment"
|
||||
mock_event.repository = "test/repo"
|
||||
mock_event.event_data = {
|
||||
"action": "created",
|
||||
"issue": {"pull_request": {}},
|
||||
"comment": {"body": f"Please run Docker {RUN_DOCKER_KEYWORD}", "user": {"login": "testuser"}, "id": 789},
|
||||
}
|
||||
mock_event.is_org_member.return_value = True
|
||||
|
||||
with patch("actions.dispatch_actions.get_pr_branch") as mock_get_branch:
|
||||
with patch("actions.dispatch_actions.trigger_and_get_workflow_info") as mock_trigger:
|
||||
with patch("actions.dispatch_actions.update_comment"):
|
||||
mock_get_branch.return_value = ("feature-branch", None)
|
||||
mock_trigger.return_value = [
|
||||
{"name": "Docker", "file": "docker.yml", "url": "url", "run_number": 1}
|
||||
]
|
||||
main()
|
||||
|
||||
mock_trigger.assert_called_once_with(mock_event, "feature-branch", ["docker.yml"], None)
|
||||
|
||||
|
||||
def test_main_skips_non_pr_comments():
|
||||
|
||||
Reference in New Issue
Block a user