Add release-progress badges: Next/Latest Release, Medium/Low priority (#6537)

* Add Next Release badge and Medium/Low priority progress badges

Adds a "Next Release" badge (in-development version from H5public.h)
and extends the existing Critical/High priority progress badges with
Medium (P2) and Low (P3) priority tracking, all fed from the same
GitHub Project #39 gist-based badge pipeline.

* Remove in-progress 2.0 entry from release schedule diagram

The Release Schedule chart is meant to show only past releases that
have reached end of life; the in-development series is now tracked
separately by the Release Progress badges. Regenerate the PNG from
the updated PlantUML source and clarify the README wording.

* Add Latest Release badge and milestone target date to Next Release

Fetches the most recent published release in the current major
version series from the GitHub Releases API (e.g. "2.1.1
(2026-03-23)") and exposes it as a new "Latest Release" badge.
Also looks up the target due date of the matching GitHub milestone
(e.g. "HDF5 2.2.0") and appends it to the existing Next Release
badge when one is set. Both lookups are independent of the
project-board query and degrade gracefully to "N/A" on failure.
This commit is contained in:
Scot Breitenfeld
2026-07-20 22:10:41 -06:00
committed by GitHub
parent bc8f3cb38f
commit 5bcbe6c6d8
6 changed files with 375 additions and 27 deletions
+178 -2
View File
@@ -15,7 +15,14 @@
# BLOCKER_TOTAL - Total number of critical priority items
# MUSTDO_DONE - Number of completed high priority items
# MUSTDO_TOTAL - Total number of high priority items
# MEDIUM_DONE - Number of completed medium priority items
# MEDIUM_TOTAL - Total number of medium priority items
# LOW_DONE - Number of completed low priority items
# LOW_TOTAL - Total number of low priority items
# VERSION - Version string (e.g., "2.1") - optional
# LATEST_RELEASE_TAG - Most recent published release tag in the current major series - optional
# LATEST_RELEASE_DATE - Publish date (YYYY-MM-DD) of LATEST_RELEASE_TAG - optional
# MILESTONE_DUE_DATE - Target due date (YYYY-MM-DD) of the in-development milestone - optional
#
set -euo pipefail
@@ -33,6 +40,10 @@ required_vars=(
"BLOCKER_TOTAL"
"MUSTDO_DONE"
"MUSTDO_TOTAL"
"MEDIUM_DONE"
"MEDIUM_TOTAL"
"LOW_DONE"
"LOW_TOTAL"
)
for var in "${required_vars[@]}"; do
@@ -131,18 +142,48 @@ else
MUSTDO_COLOR=$(get_badge_color "$MUSTDO_PERCENTAGE")
fi
if [ "$MEDIUM_TOTAL" -eq 0 ]; then
MEDIUM_PERCENTAGE="-1.0"
else
MEDIUM_PERCENTAGE=$(awk "BEGIN {printf \"%.1f\", ($MEDIUM_DONE / $MEDIUM_TOTAL * 100)}")
fi
if [ "$LOW_TOTAL" -eq 0 ]; then
LOW_PERCENTAGE="-1.0"
else
LOW_PERCENTAGE=$(awk "BEGIN {printf \"%.1f\", ($LOW_DONE / $LOW_TOTAL * 100)}")
fi
if [ "$MEDIUM_PERCENTAGE" = "-1.0" ]; then
MEDIUM_COLOR="lightgrey"
else
MEDIUM_COLOR=$(get_badge_color "$MEDIUM_PERCENTAGE")
fi
if [ "$LOW_PERCENTAGE" = "-1.0" ]; then
LOW_COLOR="lightgrey"
else
LOW_COLOR=$(get_badge_color "$LOW_PERCENTAGE")
fi
# Determine badge labels - include version if available
if [ -n "${VERSION:-}" ] && [ "$VERSION" != "all" ]; then
BLOCKER_LABEL="${VERSION} Critical Priority"
MUSTDO_LABEL="${VERSION} High Priority"
MEDIUM_LABEL="${VERSION} Medium Priority"
LOW_LABEL="${VERSION} Low Priority"
else
BLOCKER_LABEL="Critical Priority"
MUSTDO_LABEL="High Priority"
MEDIUM_LABEL="Medium Priority"
LOW_LABEL="Low Priority"
fi
# Create badge JSONs using the shared function
BLOCKER_BADGE_JSON=$(create_badge_json "$BLOCKER_LABEL" "$BLOCKER_DONE" "$BLOCKER_TOTAL" "$BLOCKER_PERCENTAGE" "$BLOCKER_COLOR")
MUSTDO_BADGE_JSON=$(create_badge_json "$MUSTDO_LABEL" "$MUSTDO_DONE" "$MUSTDO_TOTAL" "$MUSTDO_PERCENTAGE" "$MUSTDO_COLOR")
MEDIUM_BADGE_JSON=$(create_badge_json "$MEDIUM_LABEL" "$MEDIUM_DONE" "$MEDIUM_TOTAL" "$MEDIUM_PERCENTAGE" "$MEDIUM_COLOR")
LOW_BADGE_JSON=$(create_badge_json "$LOW_LABEL" "$LOW_DONE" "$LOW_TOTAL" "$LOW_PERCENTAGE" "$LOW_COLOR")
# Validate JSONs were created successfully
if [ -z "$BLOCKER_BADGE_JSON" ] || ! echo "$BLOCKER_BADGE_JSON" | jq empty 2>/dev/null; then
@@ -153,18 +194,98 @@ if [ -z "$MUSTDO_BADGE_JSON" ] || ! echo "$MUSTDO_BADGE_JSON" | jq empty 2>/dev/
echo "::error::Failed to generate valid high priority badge JSON"
exit 1
fi
if [ -z "$MEDIUM_BADGE_JSON" ] || ! echo "$MEDIUM_BADGE_JSON" | jq empty 2>/dev/null; then
echo "::error::Failed to generate valid medium priority badge JSON"
exit 1
fi
if [ -z "$LOW_BADGE_JSON" ] || ! echo "$LOW_BADGE_JSON" | jq empty 2>/dev/null; then
echo "::error::Failed to generate valid low priority badge JSON"
exit 1
fi
# "Next Release" badge - reports the in-development version (derived from
# H5_VERS_MAJOR/H5_VERS_MINOR in src/H5public.h by update-progress.py), the
# same value used as the label prefix above, annotated with the matching
# milestone's target due date when one is set. Purely informational, so it
# isn't colored by completion percentage.
if [ -n "${VERSION:-}" ] && [ "$VERSION" != "all" ]; then
if [ -n "${MILESTONE_DUE_DATE:-}" ]; then
VERSION_MESSAGE="$VERSION (target: $MILESTONE_DUE_DATE)"
else
VERSION_MESSAGE="$VERSION"
fi
VERSION_COLOR="blue"
else
VERSION_MESSAGE="N/A"
VERSION_COLOR="lightgrey"
fi
VERSION_BADGE_JSON=$(jq -n \
--arg message "$VERSION_MESSAGE" \
--arg color "$VERSION_COLOR" \
'{
"schemaVersion": 1,
"label": "Next Release",
"message": $message,
"color": $color,
"style": "flat-square"
}')
if [ -z "$VERSION_BADGE_JSON" ] || ! echo "$VERSION_BADGE_JSON" | jq empty 2>/dev/null; then
echo "::error::Failed to generate valid next release badge JSON"
exit 1
fi
# "Latest Release" badge - reports the most recent published release in the
# current major version series (e.g. "2.1.1 (2026-03-23)"), fetched directly
# from the GitHub Releases API. Purely informational.
if [ -n "${LATEST_RELEASE_TAG:-}" ]; then
LATEST_RELEASE_MESSAGE="${LATEST_RELEASE_TAG} (${LATEST_RELEASE_DATE:-unknown date})"
LATEST_RELEASE_COLOR="blue"
else
LATEST_RELEASE_MESSAGE="N/A"
LATEST_RELEASE_COLOR="lightgrey"
fi
LATEST_RELEASE_BADGE_JSON=$(jq -n \
--arg message "$LATEST_RELEASE_MESSAGE" \
--arg color "$LATEST_RELEASE_COLOR" \
'{
"schemaVersion": 1,
"label": "Latest Release",
"message": $message,
"color": $color,
"style": "flat-square"
}')
if [ -z "$LATEST_RELEASE_BADGE_JSON" ] || ! echo "$LATEST_RELEASE_BADGE_JSON" | jq empty 2>/dev/null; then
echo "::error::Failed to generate valid latest release badge JSON"
exit 1
fi
# The filenames in the Gist
BLOCKER_GIST_NAME="release-blocker-${GITHUB_REPOSITORY##*/}.json"
MUSTDO_GIST_NAME="release-mustdo-${GITHUB_REPOSITORY##*/}.json"
echo "::notice::Updating Gist files: $BLOCKER_GIST_NAME, $MUSTDO_GIST_NAME"
MEDIUM_GIST_NAME="release-medium-${GITHUB_REPOSITORY##*/}.json"
LOW_GIST_NAME="release-low-${GITHUB_REPOSITORY##*/}.json"
VERSION_GIST_NAME="release-version-${GITHUB_REPOSITORY##*/}.json"
LATEST_RELEASE_GIST_NAME="release-latest-${GITHUB_REPOSITORY##*/}.json"
echo "::notice::Updating Gist files: $BLOCKER_GIST_NAME, $MUSTDO_GIST_NAME, $MEDIUM_GIST_NAME, $LOW_GIST_NAME, $VERSION_GIST_NAME, $LATEST_RELEASE_GIST_NAME"
# Create the request payload with both files
# Create the request payload with all six files
REQUEST_PAYLOAD=$(jq -n \
--arg blocker_filename "$BLOCKER_GIST_NAME" \
--arg mustdo_filename "$MUSTDO_GIST_NAME" \
--arg medium_filename "$MEDIUM_GIST_NAME" \
--arg low_filename "$LOW_GIST_NAME" \
--arg version_filename "$VERSION_GIST_NAME" \
--arg latest_release_filename "$LATEST_RELEASE_GIST_NAME" \
--argjson blocker_content "$BLOCKER_BADGE_JSON" \
--argjson mustdo_content "$MUSTDO_BADGE_JSON" \
--argjson medium_content "$MEDIUM_BADGE_JSON" \
--argjson low_content "$LOW_BADGE_JSON" \
--argjson version_content "$VERSION_BADGE_JSON" \
--argjson latest_release_content "$LATEST_RELEASE_BADGE_JSON" \
'{
"files": {
($blocker_filename): {
@@ -172,6 +293,18 @@ REQUEST_PAYLOAD=$(jq -n \
},
($mustdo_filename): {
"content": ($mustdo_content | tostring)
},
($medium_filename): {
"content": ($medium_content | tostring)
},
($low_filename): {
"content": ($low_content | tostring)
},
($version_filename): {
"content": ($version_content | tostring)
},
($latest_release_filename): {
"content": ($latest_release_content | tostring)
}
}
}')
@@ -200,15 +333,28 @@ echo "::notice::Gist updated successfully"
# Generate badge URLs for use in README
BLOCKER_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${BLOCKER_GIST_NAME}"
MUSTDO_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${MUSTDO_GIST_NAME}"
MEDIUM_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${MEDIUM_GIST_NAME}"
LOW_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${LOW_GIST_NAME}"
VERSION_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${VERSION_GIST_NAME}"
LATEST_RELEASE_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${LATEST_RELEASE_GIST_NAME}"
PROJECT_URL="https://github.com/${GITHUB_REPOSITORY}/projects/39"
RELEASES_URL="https://github.com/${GITHUB_REPOSITORY}/releases"
echo "::notice::Critical Priority Badge URL: $BLOCKER_BADGE_URL"
echo "::notice::High Priority Badge URL: $MUSTDO_BADGE_URL"
echo "::notice::Medium Priority Badge URL: $MEDIUM_BADGE_URL"
echo "::notice::Low Priority Badge URL: $LOW_BADGE_URL"
echo "::notice::Next Release Badge URL: $VERSION_BADGE_URL"
echo "::notice::Latest Release Badge URL: $LATEST_RELEASE_BADGE_URL"
# Output to GitHub Actions if GITHUB_OUTPUT is set
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "blocker_badge_url=$BLOCKER_BADGE_URL" >> "$GITHUB_OUTPUT"
echo "mustdo_badge_url=$MUSTDO_BADGE_URL" >> "$GITHUB_OUTPUT"
echo "medium_badge_url=$MEDIUM_BADGE_URL" >> "$GITHUB_OUTPUT"
echo "low_badge_url=$LOW_BADGE_URL" >> "$GITHUB_OUTPUT"
echo "version_badge_url=$VERSION_BADGE_URL" >> "$GITHUB_OUTPUT"
echo "latest_release_badge_url=$LATEST_RELEASE_BADGE_URL" >> "$GITHUB_OUTPUT"
echo "project_url=$PROJECT_URL" >> "$GITHUB_OUTPUT"
fi
@@ -228,11 +374,29 @@ if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
**Progress:** ${MUSTDO_DONE} of ${MUSTDO_TOTAL} completed (${MUSTDO_PERCENTAGE}%)
**Badge Color:** ${MUSTDO_COLOR}
### Medium Priority
**Progress:** ${MEDIUM_DONE} of ${MEDIUM_TOTAL} completed (${MEDIUM_PERCENTAGE}%)
**Badge Color:** ${MEDIUM_COLOR}
### Low Priority
**Progress:** ${LOW_DONE} of ${LOW_TOTAL} completed (${LOW_PERCENTAGE}%)
**Badge Color:** ${LOW_COLOR}
**Gist ID:** ${GIST_ID}
### Next Release
**Version:** ${VERSION_MESSAGE}
### Latest Release
**Release:** ${LATEST_RELEASE_MESSAGE}
### Badge URLs
**Critical Priority Markdown:** \`[![Critical Priority Progress](${BLOCKER_BADGE_URL})](${PROJECT_URL})\`
**High Priority Markdown:** \`[![High Priority Progress](${MUSTDO_BADGE_URL})](${PROJECT_URL})\`
**Medium Priority Markdown:** \`[![Medium Priority Progress](${MEDIUM_BADGE_URL})](${PROJECT_URL})\`
**Low Priority Markdown:** \`[![Low Priority Progress](${LOW_BADGE_URL})](${PROJECT_URL})\`
**Next Release Markdown:** \`[![Next Release](${VERSION_BADGE_URL})](${PROJECT_URL})\`
**Latest Release Markdown:** \`[![Latest Release](${LATEST_RELEASE_BADGE_URL})](${RELEASES_URL})\`
### Badge JSON Preview
\`\`\`json
@@ -241,6 +405,18 @@ ${BLOCKER_BADGE_JSON}
// High Priority Badge
${MUSTDO_BADGE_JSON}
// Medium Priority Badge
${MEDIUM_BADGE_JSON}
// Low Priority Badge
${LOW_BADGE_JSON}
// Next Release Badge
${VERSION_BADGE_JSON}
// Latest Release Badge
${LATEST_RELEASE_BADGE_JSON}
\`\`\`
EOF
fi
+148 -13
View File
@@ -28,6 +28,8 @@ FIELD_STATUS = "Status"
# Expected values for Priority field
VALUE_CRITICAL = "P0 - Critical"
VALUE_HIGH = "P1 - High"
VALUE_MEDIUM = "P2 - Medium"
VALUE_LOW = "P3 - Low"
# Expected value for Status field when an item is completed
VALUE_STATUS_DONE = "Done"
@@ -129,16 +131,21 @@ class GitHubProjectTracker:
def fetch_release_blocker_stats(self) -> Dict[str, int]:
"""
Fetches critical and high priority issue statistics from the GitHub project.
Fetches critical, high, medium, and low priority issue statistics from the GitHub project.
Returns:
Dict with 'total', 'done', 'percentage', 'blocker_total', 'blocker_done',
'mustdo_total', 'mustdo_done' keys
'mustdo_total', 'mustdo_done', 'medium_total', 'medium_done',
'low_total', 'low_done' keys
"""
blocker_total = 0
blocker_done = 0
mustdo_total = 0
mustdo_done = 0
medium_total = 0
medium_done = 0
low_total = 0
low_done = 0
cursor = None
# Track if we've seen the expected fields at least once
@@ -206,6 +213,14 @@ class GitHubProjectTracker:
mustdo_total += 1
if status == VALUE_STATUS_DONE:
mustdo_done += 1
elif priority == VALUE_MEDIUM:
medium_total += 1
if status == VALUE_STATUS_DONE:
medium_done += 1
elif priority == VALUE_LOW:
low_total += 1
if status == VALUE_STATUS_DONE:
low_done += 1
# Check for next page
page_info = items.get("pageInfo", {})
@@ -218,7 +233,7 @@ class GitHubProjectTracker:
if not seen_priority:
print(f"ERROR: Critical field '{FIELD_PRIORITY}' not found in any project items.",
file=sys.stderr)
print("This field is required to identify critical and high priority items.",
print("This field is required to identify priority items.",
file=sys.stderr)
print("Possible causes:", file=sys.stderr)
print(f" 1. Field '{FIELD_PRIORITY}' was renamed in the project", file=sys.stderr)
@@ -238,22 +253,22 @@ class GitHubProjectTracker:
print("Action required: Update FIELD_STATUS constant in this script.", file=sys.stderr)
raise ProjectFieldMissingError(f"Critical field '{FIELD_STATUS}' not found")
total = blocker_total + mustdo_total
done = blocker_done + mustdo_done
total = blocker_total + mustdo_total + medium_total + low_total
done = blocker_done + mustdo_done + medium_done + low_done
# Validate that we found at least some items
# If total is 0, either the project is empty or field matching failed
if total == 0:
if self.milestone_filter:
print(f"INFO: No critical or high priority items found for milestone '{self.milestone_filter}'.", file=sys.stderr)
print(f"INFO: No priority items found for milestone '{self.milestone_filter}'.", file=sys.stderr)
print("This may be expected if no items exist for this milestone yet.", file=sys.stderr)
# Don't fail - return N/A indicators when filtering by milestone with no items
percentage = -1.0 # Use -1 to indicate N/A
else:
print("ERROR: No critical or high priority items found (total=0).", file=sys.stderr)
print("ERROR: No priority items found (total=0).", file=sys.stderr)
print("This likely indicates:", file=sys.stderr)
print(f" 1. The '{FIELD_PRIORITY}' field values changed", file=sys.stderr)
print(f" Expected values: '{VALUE_CRITICAL}' or '{VALUE_HIGH}'", file=sys.stderr)
print(f" Expected values: '{VALUE_CRITICAL}', '{VALUE_HIGH}', '{VALUE_MEDIUM}', or '{VALUE_LOW}'", file=sys.stderr)
print(" 2. Project has no items with these field values", file=sys.stderr)
print(" 3. Field matching logic needs to be updated", file=sys.stderr)
print("Refusing to report 0% or 100% with no items to prevent false positives.", file=sys.stderr)
@@ -268,6 +283,10 @@ class GitHubProjectTracker:
'blocker_total': blocker_total,
'blocker_done': blocker_done,
'mustdo_total': mustdo_total,
'medium_total': medium_total,
'medium_done': medium_done,
'low_total': low_total,
'low_done': low_done,
'mustdo_done': mustdo_done
}
@@ -306,6 +325,84 @@ def get_hdf5_version_from_header(header_path: str = "../../src/H5public.h") -> O
return None
def get_latest_series_release(token: str, repo: str, major_version: str) -> Optional[Dict[str, str]]:
"""
Finds the most recent published (non-draft, non-prerelease) GitHub Release
whose tag belongs to the given major version series (e.g. "2" matches "2.1.1").
Returns a dict with 'tag' and 'date' (YYYY-MM-DD) keys, or None if no
matching release was found or the API call failed. Failures here are
non-fatal - this is purely informational, unlike the priority field checks.
"""
try:
headers = {"Accept": "application/vnd.github.v3+json"}
if token:
headers["Authorization"] = f"token {token}"
response = requests.get(
f"https://api.github.com/repos/{repo}/releases",
headers=headers,
params={"per_page": 30},
timeout=30
)
response.raise_for_status()
releases = response.json()
prefix = f"{major_version}."
for release in releases:
if release.get("draft") or release.get("prerelease"):
continue
tag = release.get("tag_name", "")
if tag.startswith(prefix):
published_at = release.get("published_at", "")
return {
"tag": tag,
"date": published_at.split("T")[0] if published_at else ""
}
print(f"INFO: No published releases found for series '{major_version}.x'", file=sys.stderr)
return None
except requests.RequestException as e:
print(f"Warning: Could not fetch latest release from GitHub API: {e}", file=sys.stderr)
return None
def get_milestone_due_date(token: str, repo: str, milestone_filter: str) -> Optional[str]:
"""
Finds the open GitHub milestone whose title contains milestone_filter
(e.g. "2.2" matches "HDF5 2.2.0", the same substring match used for
project item filtering) and returns its due date (YYYY-MM-DD), or None
if no matching milestone exists or it has no due date set. Non-fatal.
"""
try:
headers = {"Accept": "application/vnd.github.v3+json"}
if token:
headers["Authorization"] = f"token {token}"
response = requests.get(
f"https://api.github.com/repos/{repo}/milestones",
headers=headers,
params={"state": "open", "per_page": 100},
timeout=30
)
response.raise_for_status()
milestones = response.json()
for milestone in milestones:
title = milestone.get("title", "")
if milestone_filter in title:
due_on = milestone.get("due_on")
return due_on.split("T")[0] if due_on else None
print(f"INFO: No open milestone matching '{milestone_filter}' found", file=sys.stderr)
return None
except requests.RequestException as e:
print(f"Warning: Could not fetch milestone due date from GitHub API: {e}", file=sys.stderr)
return None
def main():
"""Main function to run the tracker."""
# Configuration - can be overridden by environment variables
@@ -326,12 +423,42 @@ def main():
else:
print("No milestone filter - counting all release items", file=sys.stderr)
# Look up the most recent published release in the current major version series
# (e.g. "2" -> "2.1.1"), independent of the project-board data above.
REPO = os.getenv("GITHUB_REPOSITORY", "HDFGroup/hdf5")
MAJOR_VERSION = MILESTONE_FILTER.split(".")[0] if MILESTONE_FILTER else None
latest_release = get_latest_series_release(TOKEN, REPO, MAJOR_VERSION) if MAJOR_VERSION else None
LATEST_RELEASE_TAG = latest_release["tag"] if latest_release else ""
LATEST_RELEASE_DATE = latest_release["date"] if latest_release else ""
# Look up the target due date for the in-development milestone (e.g. "HDF5 2.2.0"),
# to annotate the Next Release badge.
MILESTONE_DUE_DATE = get_milestone_due_date(TOKEN, REPO, MILESTONE_FILTER) if MILESTONE_FILTER else None
MILESTONE_DUE_DATE = MILESTONE_DUE_DATE or ""
# Write these independent of the project-board query below, so a project-board
# failure doesn't discard release/milestone data that was already fetched.
github_output = os.getenv("GITHUB_OUTPUT")
if github_output:
with open(github_output, "a") as f:
f.write(f"version={MILESTONE_FILTER or 'all'}\n")
f.write(f"latest_release_tag={LATEST_RELEASE_TAG}\n")
f.write(f"latest_release_date={LATEST_RELEASE_DATE}\n")
f.write(f"milestone_due_date={MILESTONE_DUE_DATE}\n")
print(f"version={MILESTONE_FILTER or 'all'}")
print(f"latest_release_tag={LATEST_RELEASE_TAG}")
print(f"latest_release_date={LATEST_RELEASE_DATE}")
print(f"milestone_due_date={MILESTONE_DUE_DATE}")
if LATEST_RELEASE_TAG:
print(f"Latest {MAJOR_VERSION}.x release: {LATEST_RELEASE_TAG} ({LATEST_RELEASE_DATE})")
if MILESTONE_DUE_DATE:
print(f"Milestone due date: {MILESTONE_DUE_DATE}")
try:
tracker = GitHubProjectTracker(TOKEN, OWNER, PROJECT_NUMBER, MILESTONE_FILTER)
stats = tracker.fetch_release_blocker_stats()
# Output for GitHub Actions
github_output = os.getenv("GITHUB_OUTPUT")
if github_output:
with open(github_output, "a") as f:
f.write(f"percentage={stats['percentage']}\n")
@@ -341,7 +468,10 @@ def main():
f.write(f"blocker_done={stats['blocker_done']}\n")
f.write(f"mustdo_total={stats['mustdo_total']}\n")
f.write(f"mustdo_done={stats['mustdo_done']}\n")
f.write(f"version={MILESTONE_FILTER or 'all'}\n")
f.write(f"medium_total={stats['medium_total']}\n")
f.write(f"medium_done={stats['medium_done']}\n")
f.write(f"low_total={stats['low_total']}\n")
f.write(f"low_done={stats['low_done']}\n")
# Also output to stdout for local testing
print(f"percentage={stats['percentage']}")
@@ -349,14 +479,19 @@ def main():
print(f"blocker_total={stats['blocker_total']}")
print(f"mustdo_done={stats['mustdo_done']}")
print(f"mustdo_total={stats['mustdo_total']}")
print(f"version={MILESTONE_FILTER or 'all'}")
print(f"medium_done={stats['medium_done']}")
print(f"medium_total={stats['medium_total']}")
print(f"low_done={stats['low_done']}")
print(f"low_total={stats['low_total']}")
print(f"Calculated progress: {stats['percentage']}%")
print(f"Done / Total: {stats['done']} / {stats['total']}")
print(f"Critical Priority: {stats['blocker_done']} / {stats['blocker_total']}")
print(f"High Priority: {stats['mustdo_done']} / {stats['mustdo_total']}")
print(f"Medium Priority: {stats['medium_done']} / {stats['medium_total']}")
print(f"Low Priority: {stats['low_done']} / {stats['low_total']}")
if MILESTONE_FILTER:
print(f"Milestone filter: {MILESTONE_FILTER}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
+31 -3
View File
@@ -115,12 +115,19 @@ jobs:
exit 1
fi
# Extract critical and high priority counts
# Extract critical, high, medium, and low priority counts
BLOCKER_DONE=$(grep "^blocker_done=" progress_output.txt | cut -d'=' -f2 | head -1)
BLOCKER_TOTAL=$(grep "^blocker_total=" progress_output.txt | cut -d'=' -f2 | head -1)
MUSTDO_DONE=$(grep "^mustdo_done=" progress_output.txt | cut -d'=' -f2 | head -1)
MUSTDO_TOTAL=$(grep "^mustdo_total=" progress_output.txt | cut -d'=' -f2 | head -1)
MEDIUM_DONE=$(grep "^medium_done=" progress_output.txt | cut -d'=' -f2 | head -1)
MEDIUM_TOTAL=$(grep "^medium_total=" progress_output.txt | cut -d'=' -f2 | head -1)
LOW_DONE=$(grep "^low_done=" progress_output.txt | cut -d'=' -f2 | head -1)
LOW_TOTAL=$(grep "^low_total=" progress_output.txt | cut -d'=' -f2 | head -1)
VERSION=$(grep "^version=" progress_output.txt | cut -d'=' -f2 | head -1)
LATEST_RELEASE_TAG=$(grep "^latest_release_tag=" progress_output.txt | cut -d'=' -f2 | head -1)
LATEST_RELEASE_DATE=$(grep "^latest_release_date=" progress_output.txt | cut -d'=' -f2 | head -1)
MILESTONE_DUE_DATE=$(grep "^milestone_due_date=" progress_output.txt | cut -d'=' -f2 | head -1)
# Set outputs for use in subsequent steps
echo "percentage=$PERCENTAGE" >> $GITHUB_OUTPUT
@@ -130,10 +137,18 @@ jobs:
echo "blocker_total=$BLOCKER_TOTAL" >> $GITHUB_OUTPUT
echo "mustdo_done=$MUSTDO_DONE" >> $GITHUB_OUTPUT
echo "mustdo_total=$MUSTDO_TOTAL" >> $GITHUB_OUTPUT
echo "medium_done=$MEDIUM_DONE" >> $GITHUB_OUTPUT
echo "medium_total=$MEDIUM_TOTAL" >> $GITHUB_OUTPUT
echo "low_done=$LOW_DONE" >> $GITHUB_OUTPUT
echo "low_total=$LOW_TOTAL" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "latest_release_tag=$LATEST_RELEASE_TAG" >> $GITHUB_OUTPUT
echo "latest_release_date=$LATEST_RELEASE_DATE" >> $GITHUB_OUTPUT
echo "milestone_due_date=$MILESTONE_DUE_DATE" >> $GITHUB_OUTPUT
echo "::notice::Progress calculation successful: ${PERCENTAGE}% (${DONE}/${TOTAL})"
echo "::notice:: Version: ${VERSION}, Critical Priority: ${BLOCKER_DONE}/${BLOCKER_TOTAL}, High Priority: ${MUSTDO_DONE}/${MUSTDO_TOTAL}"
echo "::notice:: Version: ${VERSION}, Critical Priority: ${BLOCKER_DONE}/${BLOCKER_TOTAL}, High Priority: ${MUSTDO_DONE}/${MUSTDO_TOTAL}, Medium Priority: ${MEDIUM_DONE}/${MEDIUM_TOTAL}, Low Priority: ${LOW_DONE}/${LOW_TOTAL}"
echo "::notice:: Latest release: ${LATEST_RELEASE_TAG} (${LATEST_RELEASE_DATE}), Milestone due date: ${MILESTONE_DUE_DATE}"
# Clean up
rm -f progress_output.txt
@@ -154,7 +169,14 @@ jobs:
BLOCKER_TOTAL: ${{ steps.progress.outputs.blocker_total }}
MUSTDO_DONE: ${{ steps.progress.outputs.mustdo_done }}
MUSTDO_TOTAL: ${{ steps.progress.outputs.mustdo_total }}
MEDIUM_DONE: ${{ steps.progress.outputs.medium_done }}
MEDIUM_TOTAL: ${{ steps.progress.outputs.medium_total }}
LOW_DONE: ${{ steps.progress.outputs.low_done }}
LOW_TOTAL: ${{ steps.progress.outputs.low_total }}
VERSION: ${{ steps.progress.outputs.version }}
LATEST_RELEASE_TAG: ${{ steps.progress.outputs.latest_release_tag }}
LATEST_RELEASE_DATE: ${{ steps.progress.outputs.latest_release_date }}
MILESTONE_DUE_DATE: ${{ steps.progress.outputs.milestone_due_date }}
run: |
# Execute dedicated badge generation script
# This separates concerns: YAML orchestrates, scripts implement logic
@@ -172,6 +194,8 @@ jobs:
REPO_NAME="${GITHUB_REPOSITORY##*/}"
BLOCKER_GIST_NAME="release-blocker-${REPO_NAME}.json"
MUSTDO_GIST_NAME="release-mustdo-${REPO_NAME}.json"
MEDIUM_GIST_NAME="release-medium-${REPO_NAME}.json"
LOW_GIST_NAME="release-low-${REPO_NAME}.json"
FAILURE_BADGE=$(jq -n '{
"schemaVersion": 1,
@@ -184,11 +208,15 @@ jobs:
REQUEST_PAYLOAD=$(jq -n \
--arg blocker_filename "$BLOCKER_GIST_NAME" \
--arg mustdo_filename "$MUSTDO_GIST_NAME" \
--arg medium_filename "$MEDIUM_GIST_NAME" \
--arg low_filename "$LOW_GIST_NAME" \
--argjson content "$FAILURE_BADGE" \
'{
"files": {
($blocker_filename): { "content": ($content | tostring) },
($mustdo_filename): { "content": ($content | tostring) }
($mustdo_filename): { "content": ($content | tostring) },
($medium_filename): { "content": ($content | tostring) },
($low_filename): { "content": ($content | tostring) }
}
}')