From 86759552da4d66272f3685bf72f550cf6a6e338e Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 16:05:19 +1100 Subject: [PATCH 1/4] feat: add release tracking to comment summary template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fetch releases published in the report date range via GitHub API - Display releases with 📦 emoji at top of each repo section, before PRs - Include release name, tag, and link to GitHub release page - Works for both organization repos and external tracked repos - Update docs and CHANGELOG with release tracking details - Auto-detect contributors and use markdown links (carried from template branch) --- CHANGELOG.md | 8 ++ docs/comment-summary-template.md | 15 ++-- generate-report.sh | 140 +++++++++++++++++++++++++++---- 3 files changed, 140 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f254161..5c453ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Groups PRs by repository with placeholder sections for human curation - New `comment-template` action output for downstream workflow steps - Labels from PRs are included to aid thematic categorization + - Auto-detects contributors from merged PRs (filters bots and Copilot) + - Uses markdown link format for clean GitHub rendering - See [Comment Summary Template docs](docs/comment-summary-template.md) for details +- **Release tracking in summary template**: Repos that publish releases now show them in the template + - Fetches releases published in the report date range via GitHub API + - Displays releases with 📦 emoji at the top of each repo section, before PRs + - Includes release name, tag, and link to the GitHub release page + - Works for both organization repos and external tracked repos + - Helps summarize important milestones alongside PR activity ## [2.2.0] - 2025-10-23 diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md index cc97f77..6c6ea6f 100644 --- a/docs/comment-summary-template.md +++ b/docs/comment-summary-template.md @@ -36,8 +36,9 @@ See [QuantEcon/meta#280](https://github.com/QuantEcon/meta/issues/280) for an ex The template is written to `comment-template.md` (default) and contains: 1. **Placeholder summary** — Editable topic bullets for high-level themes -2. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author -3. **Contributor acknowledgement** — Template for thanking contributors +2. **Releases by repository** — Published releases in the period with 📦 emoji, name/tag, and link +3. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author +4. **Contributor acknowledgement** — Auto-generated from PR authors (bots and Copilot excluded) ### Example Output @@ -50,15 +51,17 @@ The template is written to `comment-template.md` (default) and contains: --- **lecture-python.myst:** - - Update consumer problem lecture https://github.com/QuantEcon/lecture-python.myst/pull/757 [maintenance] (@HumphreyYang) - - Fix typo in GDP lecture https://github.com/QuantEcon/lecture-python.myst/pull/756 (@contributor) + - 📦 Release: [publish-2026feb19b](https://github.com/QuantEcon/lecture-python.myst/releases/tag/publish-2026feb19b) + - [Update consumer problem lecture](https://github.com/QuantEcon/lecture-python.myst/pull/757) [maintenance] (@HumphreyYang) + - [Fix typo in GDP lecture](https://github.com/QuantEcon/lecture-python.myst/pull/756) (@contributor) **quantecon-book-theme:** - - Major refactor for responsiveness https://github.com/QuantEcon/quantecon-book-theme/pull/335 [infrastructure, priority: high] (@mmcky) + - 📦 Release: [v0.17.0](https://github.com/QuantEcon/quantecon-book-theme/releases/tag/v0.17.0) + - [Major refactor for responsiveness](https://github.com/QuantEcon/quantecon-book-theme/pull/335) [infrastructure, priority: high] (@mmcky) --- -thanks @contributor1 and @contributor2 for all your contributions. +thanks @HumphreyYang, @mmcky, and @contributor for all your contributions. ``` ## How Labels Help diff --git a/generate-report.sh b/generate-report.sh index 8fe27d2..bacda50 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -366,6 +366,29 @@ process_external_repo() { local commits=0 commits=$(echo "$commits_response" | jq 'if type == "array" then length else 0 end' 2>/dev/null || echo 0) + # Collect releases published in the date range (for summary template) + local local_ext_release_details="" + if [ "$SUMMARY_TEMPLATE" = "true" ]; then + local releases_response + releases_response=$(api_call "/repos/${full_repo}/releases") + if [ $? -eq 0 ]; then + local_ext_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$full_repo" --arg org "$repo_org" ' + if type == "array" then + [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | + { + repo: $repo, + org: $org, + tag: .tag_name, + name: (.name // .tag_name), + url: .html_url, + published_at: .published_at, + body: (.body // "" | split("\n")[0:3] | join(" ")) + }] | .[] + else empty end + ' 2>/dev/null) + fi + fi + # Handle null/empty values current_issues=${current_issues:-0} opened_issues=${opened_issues:-0} @@ -375,12 +398,16 @@ process_external_repo() { commits=${commits:-0} # Return values as JSON for easy parsing - # Include PR details as a nested array for summary template + # Include PR details and release details as nested arrays for summary template local pr_details_json="[]" if [ -n "$local_ext_pr_details" ]; then pr_details_json=$(echo "$local_ext_pr_details" | jq -s '.') fi - echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"commits\":$commits,\"pr_details\":$pr_details_json}" + local release_details_json="[]" + if [ -n "$local_ext_release_details" ]; then + release_details_json=$(echo "$local_ext_release_details" | jq -s '.') + fi + echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"commits\":$commits,\"pr_details\":$pr_details_json,\"release_details\":$release_details_json}" } # Get all repositories and filter by activity @@ -576,8 +603,9 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then fi # Initialize summary template data collection -# We collect merged PR details as newline-delimited JSON records +# We collect merged PR details and release info as newline-delimited JSON records summary_template_data="" +summary_release_data="" # Process each repository repo_count=0 @@ -693,6 +721,35 @@ ${local_pr_details}" fi fi + # Collect releases published in the date range (for summary template) + if [ "$SUMMARY_TEMPLATE" = "true" ]; then + releases_response=$(api_call "/repos/${ORGANIZATION}/${repo}/releases") + if [ $? -eq 0 ]; then + local_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$repo" --arg org "$ORGANIZATION" ' + if type == "array" then + [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | + { + repo: $repo, + org: $org, + tag: .tag_name, + name: (.name // .tag_name), + url: .html_url, + published_at: .published_at, + body: (.body // "" | split("\n")[0:3] | join(" ")) + }] | .[] + else empty end + ' 2>/dev/null) + if [ -n "$local_release_details" ]; then + if [ -n "$summary_release_data" ]; then + summary_release_data="${summary_release_data} +${local_release_details}" + else + summary_release_data="$local_release_details" + fi + fi + fi + fi + # Handle null/empty values current_issues=${current_issues:-0} opened_issues=${opened_issues:-0} @@ -822,6 +879,17 @@ ${ext_pr_details}" summary_template_data="$ext_pr_details" fi fi + + # Extract release details for summary template + ext_release_details=$(echo "$repo_result" | jq -c '.release_details[]?' 2>/dev/null) + if [ -n "$ext_release_details" ]; then + if [ -n "$summary_release_data" ]; then + summary_release_data="${summary_release_data} +${ext_release_details}" + else + summary_release_data="$ext_release_details" + fi + fi fi # Add to totals @@ -902,10 +970,25 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then ---" - # Group PRs by repository and generate sections + # Determine the full list of repos that have either PRs or releases + all_template_repos="" if [ -n "$summary_template_data" ]; then - # Get unique repos from the collected data - repo_list=$(echo "$summary_template_data" | jq -r '.repo' | sort -u) + all_template_repos=$(echo "$summary_template_data" | jq -r '.repo') + fi + if [ -n "$summary_release_data" ]; then + release_repos=$(echo "$summary_release_data" | jq -r '.repo') + if [ -n "$all_template_repos" ]; then + all_template_repos="${all_template_repos} +${release_repos}" + else + all_template_repos="$release_repos" + fi + fi + + # Group PRs and releases by repository and generate sections + if [ -n "$all_template_repos" ]; then + # Get unique repos from both PR and release data + repo_list=$(echo "$all_template_repos" | sort -u) while IFS= read -r template_repo; do [ -z "$template_repo" ] && continue @@ -914,25 +997,48 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then **${template_repo}:**" - # Get PRs for this repo, sorted by merged_at - repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c) + # Show releases for this repo first (if any) + if [ -n "$summary_release_data" ]; then + repo_releases=$(echo "$summary_release_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.published_at) | reverse | .[]' -c 2>/dev/null) + if [ -n "$repo_releases" ]; then + while IFS= read -r release_json; do + [ -z "$release_json" ] && continue + release_name=$(echo "$release_json" | jq -r '.name') + release_tag=$(echo "$release_json" | jq -r '.tag') + release_url=$(echo "$release_json" | jq -r '.url') + # Use name if different from tag, otherwise just show tag + if [ "$release_name" != "$release_tag" ] && [ -n "$release_name" ]; then + template_content="${template_content} + - 📦 Release: [${release_name} (${release_tag})](${release_url})" + else + template_content="${template_content} + - 📦 Release: [${release_tag}](${release_url})" + fi + done <<< "$repo_releases" + fi + fi - while IFS= read -r pr_json; do - [ -z "$pr_json" ] && continue - pr_title=$(echo "$pr_json" | jq -r '.title') - pr_url=$(echo "$pr_json" | jq -r '.url') - pr_author=$(echo "$pr_json" | jq -r '.author') - pr_labels=$(echo "$pr_json" | jq -r '.labels | if length > 0 then " [" + join(", ") + "]" else "" end') + # Show PRs for this repo + if [ -n "$summary_template_data" ]; then + repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c 2>/dev/null) - template_content="${template_content} + while IFS= read -r pr_json; do + [ -z "$pr_json" ] && continue + pr_title=$(echo "$pr_json" | jq -r '.title') + pr_url=$(echo "$pr_json" | jq -r '.url') + pr_author=$(echo "$pr_json" | jq -r '.author') + pr_labels=$(echo "$pr_json" | jq -r '.labels | if length > 0 then " [" + join(", ") + "]" else "" end') + + template_content="${template_content} - [${pr_title}](${pr_url})${pr_labels} (@${pr_author})" - done <<< "$repo_prs" + done <<< "$repo_prs" + fi done <<< "$repo_list" else template_content="${template_content} -_No merged PRs found in the report period._" +_No merged PRs or releases found in the report period._" fi # Build contributors line from actual PR authors From a226d63a50e2a45ffb33ad10734ab2a6eeef9a8e Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 16:17:14 +1100 Subject: [PATCH 2/4] refactor: use single releases table at top of template (Option A) - Replace per-repo inline release bullets with a single consolidated table - Table columns: Repository, Release (name/tag with link), Date - Sorted by publication date (newest first) - Releases section omitted entirely when no releases in the period - PR listings per repo remain unchanged below the table - Update docs example and CHANGELOG --- CHANGELOG.md | 5 +- docs/comment-summary-template.md | 12 +++-- generate-report.sh | 86 ++++++++++++++++++-------------- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c453ae..035b1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - See [Comment Summary Template docs](docs/comment-summary-template.md) for details - **Release tracking in summary template**: Repos that publish releases now show them in the template - Fetches releases published in the report date range via GitHub API - - Displays releases with 📦 emoji at the top of each repo section, before PRs - - Includes release name, tag, and link to the GitHub release page + - Displays a single releases table at the top of the template (omitted if no releases) + - Table columns: Repository, Release (name/tag with link), Date + - Sorted by publication date (newest first) - Works for both organization repos and external tracked repos - Helps summarize important milestones alongside PR activity diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md index 6c6ea6f..1bfe0e8 100644 --- a/docs/comment-summary-template.md +++ b/docs/comment-summary-template.md @@ -36,7 +36,7 @@ See [QuantEcon/meta#280](https://github.com/QuantEcon/meta/issues/280) for an ex The template is written to `comment-template.md` (default) and contains: 1. **Placeholder summary** — Editable topic bullets for high-level themes -2. **Releases by repository** — Published releases in the period with 📦 emoji, name/tag, and link +2. **Releases table** — All releases published in the period in a single table (omitted if none) 3. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author 4. **Contributor acknowledgement** — Auto-generated from PR authors (bots and Copilot excluded) @@ -50,13 +50,19 @@ The template is written to `comment-template.md` (default) and contains: --- +## 📦 Releases + +| Repository | Release | Date | +|------------|---------|------| +| lecture-python.myst | [publish-2026feb19b](https://github.com/.../publish-2026feb19b) | Feb 19 | +| quantecon-book-theme | [v0.17.0](https://github.com/.../v0.17.0) | Feb 19 | +| QuantEcon.py | [v0.11.0](https://github.com/.../v0.11.0) | Feb 15 | + **lecture-python.myst:** - - 📦 Release: [publish-2026feb19b](https://github.com/QuantEcon/lecture-python.myst/releases/tag/publish-2026feb19b) - [Update consumer problem lecture](https://github.com/QuantEcon/lecture-python.myst/pull/757) [maintenance] (@HumphreyYang) - [Fix typo in GDP lecture](https://github.com/QuantEcon/lecture-python.myst/pull/756) (@contributor) **quantecon-book-theme:** - - 📦 Release: [v0.17.0](https://github.com/QuantEcon/quantecon-book-theme/releases/tag/v0.17.0) - [Major refactor for responsiveness](https://github.com/QuantEcon/quantecon-book-theme/pull/335) [infrastructure, priority: high] (@mmcky) --- diff --git a/generate-report.sh b/generate-report.sh index bacda50..7f09a2e 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -985,10 +985,42 @@ ${release_repos}" fi fi - # Group PRs and releases by repository and generate sections - if [ -n "$all_template_repos" ]; then - # Get unique repos from both PR and release data - repo_list=$(echo "$all_template_repos" | sort -u) + # --- Releases table (Option A: single table at top, omitted if no releases) --- + if [ -n "$summary_release_data" ]; then + template_content="${template_content} + +## 📦 Releases + +| Repository | Release | Date | +|------------|---------|------|" + + # Sort all releases by published_at descending + sorted_releases=$(echo "$summary_release_data" | jq -s 'sort_by(.published_at) | reverse | .[]' -c 2>/dev/null) + + while IFS= read -r release_json; do + [ -z "$release_json" ] && continue + release_repo=$(echo "$release_json" | jq -r '.repo') + release_name=$(echo "$release_json" | jq -r '.name') + release_tag=$(echo "$release_json" | jq -r '.tag') + release_url=$(echo "$release_json" | jq -r '.url') + release_date=$(echo "$release_json" | jq -r '.published_at' | cut -d'T' -f1) + # Format the date as "Feb 21" style + release_date_display=$(date -j -f "%Y-%m-%d" "$release_date" "+%b %d" 2>/dev/null || echo "$release_date") + # Use name if different from tag, otherwise just show tag + if [ "$release_name" != "$release_tag" ] && [ -n "$release_name" ]; then + release_label="${release_name} (${release_tag})" + else + release_label="${release_tag}" + fi + template_content="${template_content} +| ${release_repo} | [${release_label}](${release_url}) | ${release_date_display} |" + done <<< "$sorted_releases" + fi + + # --- PR listings per repository --- + if [ -n "$summary_template_data" ]; then + # Get unique repos from the collected PR data + repo_list=$(echo "$summary_template_data" | jq -r '.repo' | sort -u) while IFS= read -r template_repo; do [ -z "$template_repo" ] && continue @@ -997,45 +1029,23 @@ ${release_repos}" **${template_repo}:**" - # Show releases for this repo first (if any) - if [ -n "$summary_release_data" ]; then - repo_releases=$(echo "$summary_release_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.published_at) | reverse | .[]' -c 2>/dev/null) - if [ -n "$repo_releases" ]; then - while IFS= read -r release_json; do - [ -z "$release_json" ] && continue - release_name=$(echo "$release_json" | jq -r '.name') - release_tag=$(echo "$release_json" | jq -r '.tag') - release_url=$(echo "$release_json" | jq -r '.url') - # Use name if different from tag, otherwise just show tag - if [ "$release_name" != "$release_tag" ] && [ -n "$release_name" ]; then - template_content="${template_content} - - 📦 Release: [${release_name} (${release_tag})](${release_url})" - else - template_content="${template_content} - - 📦 Release: [${release_tag}](${release_url})" - fi - done <<< "$repo_releases" - fi - fi + # Get PRs for this repo, sorted by merged_at + repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c 2>/dev/null) - # Show PRs for this repo - if [ -n "$summary_template_data" ]; then - repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c 2>/dev/null) + while IFS= read -r pr_json; do + [ -z "$pr_json" ] && continue + pr_title=$(echo "$pr_json" | jq -r '.title') + pr_url=$(echo "$pr_json" | jq -r '.url') + pr_author=$(echo "$pr_json" | jq -r '.author') + pr_labels=$(echo "$pr_json" | jq -r '.labels | if length > 0 then " [" + join(", ") + "]" else "" end') - while IFS= read -r pr_json; do - [ -z "$pr_json" ] && continue - pr_title=$(echo "$pr_json" | jq -r '.title') - pr_url=$(echo "$pr_json" | jq -r '.url') - pr_author=$(echo "$pr_json" | jq -r '.author') - pr_labels=$(echo "$pr_json" | jq -r '.labels | if length > 0 then " [" + join(", ") + "]" else "" end') - - template_content="${template_content} + template_content="${template_content} - [${pr_title}](${pr_url})${pr_labels} (@${pr_author})" - done <<< "$repo_prs" - fi + done <<< "$repo_prs" done <<< "$repo_list" - else + elif [ -z "$summary_release_data" ]; then + # No PRs and no releases template_content="${template_content} _No merged PRs or releases found in the report period._" From 43f3b4ca3aac34cda6377e3ce730f665e489d8b1 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 16:26:38 +1100 Subject: [PATCH 3/4] feat: add Releases count column to report table - Fetch releases unconditionally (not just when summary-template enabled) - Add Releases column between Merged PRs and Commits in report table - Release count links to repo's releases page when > 0 - Include in totals row and Details section - Add to external repos table and totals - Include in summary string output --- generate-report.sh | 91 ++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/generate-report.sh b/generate-report.sh index 7f09a2e..35f36ae 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -366,27 +366,29 @@ process_external_repo() { local commits=0 commits=$(echo "$commits_response" | jq 'if type == "array" then length else 0 end' 2>/dev/null || echo 0) - # Collect releases published in the date range (for summary template) + # Count releases published in the date range + local releases_response + releases_response=$(api_call "/repos/${full_repo}/releases") + local releases=0 + releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end' 2>/dev/null || echo 0) + + # Collect release details for summary template local local_ext_release_details="" - if [ "$SUMMARY_TEMPLATE" = "true" ]; then - local releases_response - releases_response=$(api_call "/repos/${full_repo}/releases") - if [ $? -eq 0 ]; then - local_ext_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$full_repo" --arg org "$repo_org" ' - if type == "array" then - [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | - { - repo: $repo, - org: $org, - tag: .tag_name, - name: (.name // .tag_name), - url: .html_url, - published_at: .published_at, - body: (.body // "" | split("\n")[0:3] | join(" ")) - }] | .[] - else empty end - ' 2>/dev/null) - fi + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ "$releases" -gt 0 ]; then + local_ext_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$full_repo" --arg org "$repo_org" ' + if type == "array" then + [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | + { + repo: $repo, + org: $org, + tag: .tag_name, + name: (.name // .tag_name), + url: .html_url, + published_at: .published_at, + body: (.body // "" | split("\n")[0:3] | join(" ")) + }] | .[] + else empty end + ' 2>/dev/null) fi # Handle null/empty values @@ -396,6 +398,7 @@ process_external_repo() { merged_prs=${merged_prs:-0} opened_prs=${opened_prs:-0} commits=${commits:-0} + releases=${releases:-0} # Return values as JSON for easy parsing # Include PR details and release details as nested arrays for summary template @@ -407,7 +410,7 @@ process_external_repo() { if [ -n "$local_ext_release_details" ]; then release_details_json=$(echo "$local_ext_release_details" | jq -s '.') fi - echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"commits\":$commits,\"pr_details\":$pr_details_json,\"release_details\":$release_details_json}" + echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"releases\":$releases,\"commits\":$commits,\"pr_details\":$pr_details_json,\"release_details\":$release_details_json}" } # Get all repositories and filter by activity @@ -568,6 +571,7 @@ total_closed_issues=0 total_merged_prs=0 total_commits=0 total_opened_prs=0 +total_releases=0 failed_repos=0 rate_limited_repos=0 report_content="" @@ -597,8 +601,8 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then ## Summary -| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Commits | -|------------|----------------|---------------|---------------|------------|------------|---------|" +| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Releases | Commits | +|------------|----------------|---------------|---------------|------------|------------|----------|---------|" echo "DEBUG: Initial report content set, length: ${#report_content}" fi @@ -721,10 +725,13 @@ ${local_pr_details}" fi fi - # Collect releases published in the date range (for summary template) - if [ "$SUMMARY_TEMPLATE" = "true" ]; then - releases_response=$(api_call "/repos/${ORGANIZATION}/${repo}/releases") - if [ $? -eq 0 ]; then + # Count releases published in the date range + releases_response=$(api_call "/repos/${ORGANIZATION}/${repo}/releases") + if [ $? -eq 0 ]; then + releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end') + + # Collect release details for summary template + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ "$releases" -gt 0 ]; then local_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$repo" --arg org "$ORGANIZATION" ' if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | @@ -748,6 +755,8 @@ ${local_release_details}" fi fi fi + else + releases=0 fi # Handle null/empty values @@ -757,6 +766,7 @@ ${local_release_details}" merged_prs=${merged_prs:-0} opened_prs=${opened_prs:-0} commits=${commits:-0} + releases=${releases:-0} # Add to totals total_current_issues=$((total_current_issues + current_issues)) @@ -765,10 +775,11 @@ ${local_release_details}" total_merged_prs=$((total_merged_prs + merged_prs)) total_opened_prs=$((total_opened_prs + opened_prs)) total_commits=$((total_commits + commits)) + total_releases=$((total_releases + releases)) # Only include repos with actual activity (exclude repos with all zeros in activity columns) # Note: We check activity metrics only, not current_issues (which is current state, not activity) - if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits)) -gt 0 ]; then + if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits + releases)) -gt 0 ]; then repos_with_activity=$((repos_with_activity + 1)) if [ "$OUTPUT_FORMAT" = "markdown" ]; then # Create GitHub search URLs for clickable metrics @@ -778,6 +789,7 @@ ${local_release_details}" closed_issues_url="https://github.com/${ORGANIZATION}/${repo}/issues?q=is:issue+closed:${start_display}..${end_display}" opened_prs_url="https://github.com/${ORGANIZATION}/${repo}/pulls?q=is:pr+created:${start_display}..${end_display}" merged_prs_url="https://github.com/${ORGANIZATION}/${repo}/pulls?q=is:pr+merged:${start_display}..${end_display}" + releases_url="https://github.com/${ORGANIZATION}/${repo}/releases" commits_url="https://github.com/${ORGANIZATION}/${repo}/commits?since=${start_display}&until=${end_display}" # Format metrics as clickable links if > 1 @@ -786,10 +798,11 @@ ${local_release_details}" closed_issues_display=$(format_metric "$closed_issues" "$closed_issues_url") opened_prs_display=$(format_metric "$opened_prs" "$opened_prs_url") merged_prs_display=$(format_metric "$merged_prs" "$merged_prs_url") + releases_display=$(format_metric "$releases" "$releases_url") commits_display=$(format_metric "$commits" "$commits_url") report_content="${report_content} -| $repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $commits_display |" +| $repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $releases_display | $commits_display |" fi else echo "DEBUG: Skipping $repo from report (no activity: all metrics are zero)" @@ -805,7 +818,7 @@ echo "DEBUG: Final report content length: ${#report_content}" # Add summary to report if [ "$OUTPUT_FORMAT" = "markdown" ]; then report_content="${report_content} -|**Total**|**$total_current_issues**|**$total_opened_issues**|**$total_closed_issues**|**$total_opened_prs**|**$total_merged_prs**|**$total_commits**| +|**Total**|**$total_current_issues**|**$total_opened_issues**|**$total_closed_issues**|**$total_opened_prs**|**$total_merged_prs**|**$total_releases**|**$total_commits**| ## Details @@ -815,6 +828,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then - **Total Issues Closed:** $total_closed_issues - **Total PRs Opened:** $total_opened_prs - **Total PRs Merged:** $total_merged_prs +- **Total Releases:** $total_releases - **Total Commits:** $total_commits" # Add warnings about incomplete data if any API calls failed @@ -848,6 +862,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then ext_total_merged_prs=0 ext_total_commits=0 ext_total_opened_prs=0 + ext_total_releases=0 ext_repos_with_activity=0 ext_report_content="" @@ -866,6 +881,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then closed_issues=$(echo "$repo_result" | jq -r '.closed_issues') opened_prs=$(echo "$repo_result" | jq -r '.opened_prs') merged_prs=$(echo "$repo_result" | jq -r '.merged_prs') + releases=$(echo "$repo_result" | jq -r '.releases') commits=$(echo "$repo_result" | jq -r '.commits') # Extract PR details for summary template @@ -899,9 +915,10 @@ ${ext_release_details}" ext_total_merged_prs=$((ext_total_merged_prs + merged_prs)) ext_total_opened_prs=$((ext_total_opened_prs + opened_prs)) ext_total_commits=$((ext_total_commits + commits)) + ext_total_releases=$((ext_total_releases + releases)) # Only include repos with activity - if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits)) -gt 0 ]; then + if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits + releases)) -gt 0 ]; then ext_repos_with_activity=$((ext_repos_with_activity + 1)) # Create GitHub search URLs @@ -910,6 +927,7 @@ ${ext_release_details}" closed_issues_url="https://github.com/${full_repo}/issues?q=is:issue+closed:${start_display}..${end_display}" opened_prs_url="https://github.com/${full_repo}/pulls?q=is:pr+created:${start_display}..${end_display}" merged_prs_url="https://github.com/${full_repo}/pulls?q=is:pr+merged:${start_display}..${end_display}" + releases_url="https://github.com/${full_repo}/releases" commits_url="https://github.com/${full_repo}/commits?since=${start_display}&until=${end_display}" # Format metrics as clickable links if > 0 @@ -918,10 +936,11 @@ ${ext_release_details}" closed_issues_display=$(format_metric "$closed_issues" "$closed_issues_url") opened_prs_display=$(format_metric "$opened_prs" "$opened_prs_url") merged_prs_display=$(format_metric "$merged_prs" "$merged_prs_url") + releases_display=$(format_metric "$releases" "$releases_url") commits_display=$(format_metric "$commits" "$commits_url") ext_report_content="${ext_report_content} -| $full_repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $commits_display |" +| $full_repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $releases_display | $commits_display |" else echo "DEBUG: Skipping $full_repo from report (no activity: all metrics are zero)" fi @@ -933,9 +952,9 @@ ${ext_release_details}" ## External Repositories -| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Commits | -|------------|----------------|---------------|---------------|------------|------------|---------|${ext_report_content} -|**Total**|**$ext_total_current_issues**|**$ext_total_opened_issues**|**$ext_total_closed_issues**|**$ext_total_opened_prs**|**$ext_total_merged_prs**|**$ext_total_commits**|" +| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Releases | Commits | +|------------|----------------|---------------|---------------|------------|------------|----------|---------|${ext_report_content} +|**Total**|**$ext_total_current_issues**|**$ext_total_opened_issues**|**$ext_total_closed_issues**|**$ext_total_opened_prs**|**$ext_total_merged_prs**|**$ext_total_releases**|**$ext_total_commits**|" echo "DEBUG: External repositories with activity: $ext_repos_with_activity" echo "DEBUG: External repos totals - Issues: $ext_total_opened_issues opened, $ext_total_closed_issues closed | PRs: $ext_total_opened_prs opened, $ext_total_merged_prs merged | Commits: $ext_total_commits" @@ -950,7 +969,7 @@ ${ext_release_details}" fi # Create summary -summary="Summary: $total_current_issues current open issues, $total_opened_issues issues opened, $total_closed_issues issues closed, $total_opened_prs PRs opened, $total_merged_prs PRs merged, $total_commits commits" +summary="Summary: $total_current_issues current open issues, $total_opened_issues issues opened, $total_closed_issues issues closed, $total_opened_prs PRs opened, $total_merged_prs PRs merged, $total_releases releases, $total_commits commits" # Generate comment summary template if enabled if [ "$SUMMARY_TEMPLATE" = "true" ]; then From 422c709b05855c6894e4fe9e0aa5b728fcee76f0 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 16:48:53 +1100 Subject: [PATCH 4/4] fix: address Copilot review feedback - Add rate_limited_repos/failed_repos error handling for releases API call - Use cross-platform date formatting (GNU first, BSD fallback) - Remove unused all_template_repos dead code block - Add CHANGELOG entry for Releases column in main report table --- CHANGELOG.md | 5 +++++ generate-report.sh | 24 +++++++----------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 035b1fd..57368a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sorted by publication date (newest first) - Works for both organization repos and external tracked repos - Helps summarize important milestones alongside PR activity +- **Releases column in main report**: Include release activity directly in the primary report table + - Adds a **Releases** column to the main activity table for each repository + - Counts non-draft releases published in the report date range and includes them in the totals row + - Clickable link to repo releases page when count > 0 + - Keeps release metrics alongside issues, PRs, and commits for a unified overview ## [2.2.0] - 2025-10-23 diff --git a/generate-report.sh b/generate-report.sh index 35f36ae..357a36d 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -757,6 +757,11 @@ ${local_release_details}" fi else releases=0 + if echo "$releases_response" | jq -e '.error' 2>/dev/null | grep -q "rate_limit"; then + rate_limited_repos=$((rate_limited_repos + 1)) + else + failed_repos=$((failed_repos + 1)) + fi fi # Handle null/empty values @@ -989,21 +994,6 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then ---" - # Determine the full list of repos that have either PRs or releases - all_template_repos="" - if [ -n "$summary_template_data" ]; then - all_template_repos=$(echo "$summary_template_data" | jq -r '.repo') - fi - if [ -n "$summary_release_data" ]; then - release_repos=$(echo "$summary_release_data" | jq -r '.repo') - if [ -n "$all_template_repos" ]; then - all_template_repos="${all_template_repos} -${release_repos}" - else - all_template_repos="$release_repos" - fi - fi - # --- Releases table (Option A: single table at top, omitted if no releases) --- if [ -n "$summary_release_data" ]; then template_content="${template_content} @@ -1023,8 +1013,8 @@ ${release_repos}" release_tag=$(echo "$release_json" | jq -r '.tag') release_url=$(echo "$release_json" | jq -r '.url') release_date=$(echo "$release_json" | jq -r '.published_at' | cut -d'T' -f1) - # Format the date as "Feb 21" style - release_date_display=$(date -j -f "%Y-%m-%d" "$release_date" "+%b %d" 2>/dev/null || echo "$release_date") + # Format the date as "Feb 21" style (GNU date -d first, then BSD date -j -f, then fallback) + release_date_display=$(date -d "$release_date" "+%b %d" 2>/dev/null || date -j -f "%Y-%m-%d" "$release_date" "+%b %d" 2>/dev/null || echo "$release_date") # Use name if different from tag, otherwise just show tag if [ "$release_name" != "$release_tag" ] && [ -n "$release_name" ]; then release_label="${release_name} (${release_tag})"