Add GitHub report workflow (#778)
Co-authored-by: UltralyticsAssistant <[email protected]>
This commit is contained in:
co-authored by
UltralyticsAssistant
parent
e13016ac68
commit
c4961b4dd0
@@ -0,0 +1,30 @@
|
||||
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
||||
|
||||
# Summarize open PRs and failed scheduled GitHub Actions across public Ultralytics repositories
|
||||
|
||||
name: GitHub Report
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days:
|
||||
description: "Days of scheduled workflow history to include"
|
||||
required: false
|
||||
type: number
|
||||
default: 1
|
||||
schedule:
|
||||
- cron: "0 6 * * *" # daily at 06:00 UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
github-report:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ultralytics/actions/github-report@main
|
||||
with:
|
||||
token: ${{ secrets._GITHUB_TOKEN }}
|
||||
visibility: public
|
||||
days: ${{ inputs.days || 1 }}
|
||||
@@ -1,22 +0,0 @@
|
||||
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
||||
|
||||
# List Open PRs across Ultralytics repositories and auto-merge eligible GitHub Actions update PRs
|
||||
|
||||
name: Scan PRs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 6 * * *" # daily at 06:00 UTC
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
scan-prs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ultralytics/actions/scan-prs@main
|
||||
with:
|
||||
token: ${{ secrets._GITHUB_TOKEN }}
|
||||
@@ -10,7 +10,7 @@ Welcome to [Ultralytics Actions](https://github.com/ultralytics/actions) - a col
|
||||
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/ci.yml)
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/format.yml)
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/scan-prs.yml)
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/github_report.yml)
|
||||
[](https://app.codecov.io/github/ultralytics/actions)
|
||||
|
||||
[](https://discord.com/invite/ultralytics)
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/ci.yml)
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/format.yml)
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/scan-prs.yml)
|
||||
[](https://github.com/ultralytics/actions/actions/workflows/github_report.yml)
|
||||
[](https://app.codecov.io/github/ultralytics/actions)
|
||||
|
||||
[](https://discord.com/invite/ultralytics)
|
||||
|
||||
@@ -10,13 +10,19 @@ import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from actions.scan_prs import get_repo_filter, parse_visibility
|
||||
|
||||
FAILED_CONCLUSIONS = {"failure", "timed_out", "action_required", "startup_failure", "cancelled"}
|
||||
|
||||
|
||||
def run_time(run):
|
||||
"""Return the scheduled run timestamp used for recency filtering and latest-run selection."""
|
||||
value = run.get("run_started_at") or run.get("created_at") or ""
|
||||
return datetime.fromisoformat(value.replace("Z", "+00:00")) if value else datetime.min.replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def github_get(path, params=None, token=None, allow_skip=False):
|
||||
"""Fetch JSON from the GitHub REST API."""
|
||||
token = token or os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN")
|
||||
@@ -62,9 +68,10 @@ def paginate(path, params=None, key=None, max_pages=100, token=None, allow_skip=
|
||||
|
||||
|
||||
def collect_failed_scheduled_actions(
|
||||
org="ultralytics", visibility="public", repo_visibility="public", max_run_pages=3, token=None
|
||||
org="ultralytics", visibility="public", repo_visibility="public", max_run_pages=3, days=1, token=None
|
||||
):
|
||||
"""Collect latest failed scheduled workflow runs on each non-archived repo's default branch."""
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(days=days) if days else None
|
||||
filter_config = get_repo_filter(parse_visibility(visibility, repo_visibility))
|
||||
repos = [
|
||||
r
|
||||
@@ -87,13 +94,11 @@ def collect_failed_scheduled_actions(
|
||||
for run in runs:
|
||||
key = run.get("workflow_id") or run.get("name")
|
||||
current = latest.get(key)
|
||||
started = run.get("run_started_at") or run.get("created_at") or ""
|
||||
current_started = (current or {}).get("run_started_at") or (current or {}).get("created_at") or ""
|
||||
if not current or started > current_started:
|
||||
if not current or run_time(run) > run_time(current):
|
||||
latest[key] = run
|
||||
|
||||
for run in latest.values():
|
||||
if run.get("conclusion") in FAILED_CONCLUSIONS:
|
||||
if run.get("conclusion") in FAILED_CONCLUSIONS and (not cutoff or run_time(run) >= cutoff):
|
||||
failures.append(
|
||||
{
|
||||
"repo": full_name,
|
||||
@@ -146,12 +151,14 @@ def run():
|
||||
"""Write failed scheduled Actions report to stdout and the GitHub step summary."""
|
||||
org = os.getenv("ORG", "ultralytics")
|
||||
max_run_pages = int(os.getenv("MAX_RUN_PAGES", "3"))
|
||||
days = int(os.getenv("REPORT_DAYS", "1"))
|
||||
report = format_report(
|
||||
collect_failed_scheduled_actions(
|
||||
org,
|
||||
visibility=os.getenv("VISIBILITY", "public"),
|
||||
repo_visibility=os.getenv("REPO_VISIBILITY", "public"),
|
||||
max_run_pages=max_run_pages,
|
||||
days=days,
|
||||
),
|
||||
org,
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
org: ultralytics
|
||||
visibility: private,internal
|
||||
days: 1
|
||||
```
|
||||
|
||||
## Inputs
|
||||
@@ -37,6 +38,7 @@ jobs:
|
||||
| `failed_scheduled_actions` | Include failed scheduled Actions summary | No | `true` |
|
||||
| `auto_merge_actions_prs` | Auto-merge eligible GitHub Actions update PRs in the PR section | No | `false` |
|
||||
| `max_run_pages` | Pages of scheduled workflow runs to inspect per repository | No | `3` |
|
||||
| `days` | Days of scheduled workflow history to include | No | `1` |
|
||||
|
||||
## Output
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ inputs:
|
||||
description: "Pages of scheduled workflow runs to inspect per repository"
|
||||
required: false
|
||||
default: "3"
|
||||
days:
|
||||
description: "Days of scheduled workflow history to include"
|
||||
required: false
|
||||
default: "1"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -73,6 +77,7 @@ runs:
|
||||
REPORT_FAILED_SCHEDULED_ACTIONS: ${{ inputs.failed_scheduled_actions }}
|
||||
AUTO_MERGE_ACTIONS_PRS: ${{ inputs.auto_merge_actions_prs }}
|
||||
MAX_RUN_PAGES: ${{ inputs.max_run_pages }}
|
||||
REPORT_DAYS: ${{ inputs.days }}
|
||||
run: |
|
||||
echo "::group::GitHub Report"
|
||||
python -m actions.github_report
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from actions import failed_scheduled_actions, github_report
|
||||
|
||||
|
||||
@@ -119,6 +121,37 @@ def test_collect_failed_scheduled_actions_ignores_latest_success(monkeypatch):
|
||||
assert failed_scheduled_actions.collect_failed_scheduled_actions(token="token") == []
|
||||
|
||||
|
||||
def test_collect_failed_scheduled_actions_respects_days_window(monkeypatch):
|
||||
"""Failed scheduled runs older than the requested window should be omitted."""
|
||||
old_date = (datetime.now(timezone.utc) - timedelta(days=2)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
def fake_paginate(path, params=None, key=None, max_pages=100, token=None, allow_skip=False):
|
||||
if path == "/orgs/ultralytics/repos":
|
||||
return [
|
||||
{
|
||||
"full_name": "ultralytics/repo",
|
||||
"default_branch": "main",
|
||||
"visibility": "public",
|
||||
"archived": False,
|
||||
}
|
||||
]
|
||||
if path == "/repos/ultralytics/repo/actions/runs":
|
||||
return [
|
||||
{
|
||||
"workflow_id": 1,
|
||||
"name": "Nightly",
|
||||
"conclusion": "failure",
|
||||
"run_started_at": old_date,
|
||||
}
|
||||
]
|
||||
raise AssertionError(path)
|
||||
|
||||
monkeypatch.setattr(failed_scheduled_actions, "paginate", fake_paginate)
|
||||
|
||||
assert failed_scheduled_actions.collect_failed_scheduled_actions(days=1, token="token") == []
|
||||
assert failed_scheduled_actions.collect_failed_scheduled_actions(days=3, token="token")
|
||||
|
||||
|
||||
def test_format_report_links_failures():
|
||||
"""Report output includes concise failure details and backlinks."""
|
||||
report = failed_scheduled_actions.format_report(
|
||||
|
||||
Reference in New Issue
Block a user