Skip to content

Update Mattermost PR Dashboard #4236

Update Mattermost PR Dashboard

Update Mattermost PR Dashboard #4236

Workflow file for this run

name: Update Mattermost PR Dashboard
on:
schedule:
# Runs every 5 minutes (may be delayed during high GitHub Actions load)
- cron: '*/5 * * * *'
workflow_dispatch:
repository_dispatch:
pull_request:
types: [opened, synchronize, reopened]
jobs:
update-dashboard:
runs-on: ubuntu-latest
steps:
- name: Update Mattermost Dashboard Post
env:
GH_TOKEN: ${{ secrets.ORG_READ_PAT }}
MM_URL: ${{ secrets.MATTERMOST_URL }}
MM_TOKEN: ${{ secrets.MATTERMOST_TOKEN }}
POST_ID: ${{ secrets.MATTERMOST_POST_ID }}
TARGET_ORGS: "acep-devops acep-aedg acep-uaf"
run: |
ORG_FILTER=""
for org in $TARGET_ORGS; do
ORG_FILTER="$ORG_FILTER org:$org"
done
QUERY="query { search(query: \"$ORG_FILTER is:pr is:open draft:false archived:false\", type: ISSUE, first: 50) { issueCount nodes { ... on PullRequest { number title url reviewDecision mergeable createdAt updatedAt author { login } repository { name } reviewRequests(first: 1) { totalCount } } } } }"
export RAW_JSON=$(gh api graphql -f query="$QUERY")
python3 -c '
import os, json, sys, urllib.request
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
raw_str = os.environ.get("RAW_JSON", "")
if not raw_str:
print("::error::RAW_JSON returned empty from GitHub API.")
sys.exit(1)
data = json.loads(raw_str)
if "errors" in data:
print("::error::GitHub API Error:", json.dumps(data["errors"]))
sys.exit(1)
search = data.get("data", {}).get("search", {})
nodes = search.get("nodes", [])
total_count = search.get("issueCount", len(nodes))
if not nodes:
message = "### Live PR Queue\n\n🎉 *No open PRs waiting for review!*"
else:
needs_review, changes_requested, approved = [], [], []
# Set up local timezone
local_tz = ZoneInfo("America/Anchorage")
now = datetime.now(local_tz)
# Configuration for stale PRs
STALE_THRESHOLD_DAYS = 14
for pr in nodes:
repo = pr.get("repository", {}).get("name", "repo")
num = pr.get("number")
url = pr.get("url")
title = pr.get("title", "")
author = pr.get("author", {}).get("login", "unknown") if pr.get("author") else "unknown"
decision = pr.get("reviewDecision")
created_raw = pr.get("createdAt")
mergeable = pr.get("mergeable", "UNKNOWN")
review_requests_data = pr.get("reviewRequests") or {}
review_requests_count = review_requests_data.get("totalCount", 0)
days_old = 0
if created_raw:
try:
# Parse the UTC time from GitHub
dt_utc = datetime.fromisoformat(created_raw.replace("Z", "+00:00"))
# Convert to your local Anchorage time
dt_local = dt_utc.astimezone(local_tz)
# Compare just the calendar dates (Year-Month-Day)
days_old = (now.date() - dt_local.date()).days
days_old = max(0, days_old) # Prevent negative numbers just in case
except Exception:
pass
if days_old == 0:
time_str = "Today"
elif days_old == 1:
time_str = "Yesterday"
else:
time_str = f"{days_old}d ago"
# Apply the stale warning based on our threshold variable
stale = " ⚠️" if days_old >= STALE_THRESHOLD_DAYS else ""
conflict = " 🚨 Resolve Merge Conflict" if mergeable == "CONFLICTING" else ""
# Bot check
is_bot = author.lower() in ["dependabot", "renovate", "github-actions", "dependabot[bot]"]
bot_icon = " 🤖" if is_bot else ""
# Clean title for tooltip ONLY
safe_title = title.replace(chr(34), "").replace(chr(39), "")
# Build the line: Link (with title in tooltip) -> Bot icon -> Author -> Conflict/Stale -> Days
line = f"**[{repo}#{num}]({url} {chr(34)}{safe_title}{chr(34)})**{bot_icon} (*@ {author}*){conflict}{stale} `{time_str}`"
if decision == "APPROVED":
approved.append(line)
elif decision == "CHANGES_REQUESTED":
# If changes were requested, but a review is actively requested again,
# we override and put it in "Needs Review"
if review_requests_count > 0:
needs_review.append(line)
else:
changes_requested.append(line)
else:
needs_review.append(line)
message = f"### Open PRs ({total_count}) \n\n **Orgs: acep-devops, acep-uaf, acep-aedg**\n\n *Runs every 5 minutes (may be delayed during high GitHub Actions load) OR is instant when repo has the trigger-dashboard.yml action*\n\n *Hover over link to see PR title*\n\n ---\n"
if approved:
message += "#### ✅ Approved\n" + "\n".join(approved) + "\n\n"
if changes_requested:
message += "#### 🔁 Changes Requested\n" + "\n".join(changes_requested) + "\n\n"
if needs_review:
message += "#### 👀 Needs Review\n" + "\n".join(needs_review) + "\n\n"
# Check if there are more PRs than what fit in the query response
if total_count > len(nodes):
more_count = total_count - len(nodes)
message += f"*... and {more_count} more open PRs not shown*\n\n"
refresh_time = now.strftime("%b %d, %I:%M %p %Z")
message += f"---\n*🔄 Last updated: {refresh_time}*"
mm_url = os.environ.get("MM_URL", "").rstrip("/")
post_id = os.environ.get("POST_ID", "")
mm_token = os.environ.get("MM_TOKEN", "")
if not mm_url or not post_id or not mm_token:
print("::error::Missing required environment variables.")
sys.exit(1)
endpoint = f"{mm_url}/api/v4/posts/{post_id}"
payload = json.dumps({"id": post_id, "message": message}).encode("utf-8")
req = urllib.request.Request(
endpoint,
data=payload,
headers={"Authorization": f"Bearer {mm_token}", "Content-Type": "application/json"},
method="PUT"
)
try:
with urllib.request.urlopen(req) as resp:
print(f"Successfully updated Mattermost! HTTP Status: {resp.status}")
except Exception as e:
print(f"::error::Failed to update Mattermost: {e}")
sys.exit(1)
'