Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions .github/workflows/repo-stats.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
name: Repository stats (PRs & Issues)

on:
workflow_dispatch:
inputs:
days:
description: "Number of days to look back"
required: false
default: 7
type: number
post_to_slack:
description: "Post report to Slack"
required: false
default: false
type: boolean
schedule:
# Weekly on Monday at 09:00 UTC
- cron: "0 9 * * 1"

jobs:
gather-stats:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
steps:
- name: Gather PR and issue stats
id: stats
env:
GH_REPO: ${{ github.repository }}
GH_SERVER: ${{ github.server_url }}
DAYS_RAW: ${{ github.event.inputs.days || 7 }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Clamp days to 1–365
DAYS=$DAYS_RAW
if ! [[ "$DAYS" =~ ^[0-9]+$ ]] || [[ "$DAYS" -lt 1 ]]; then DAYS=7; fi
if [[ "$DAYS" -gt 365 ]]; then DAYS=365; fi

START=$(date -u -d "${DAYS} days ago" +%Y-%m-%d)
END=$(date -u +%Y-%m-%d)

echo "start_date=$START" >> $GITHUB_OUTPUT
echo "end_date=$END" >> $GITHUB_OUTPUT

OWNER="${GH_REPO%%/*}"
REPO="${GH_REPO#*/}"
API="https://api.github.com/repos/${OWNER}/${REPO}"

# Fetch all data
curl -sS -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" \
"${API}/pulls?state=all&sort=created&direction=desc&per_page=100" > /tmp/pulls_all.json
curl -sS -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" \
"${API}/pulls?state=closed&sort=updated&direction=desc&per_page=100" > /tmp/pulls_closed.json
curl -sS -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" \
"${API}/issues?state=all&sort=created&direction=desc&per_page=100" > /tmp/issues_all.json
curl -sS -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" \
"${API}/issues?state=closed&sort=updated&direction=desc&per_page=100" > /tmp/issues_closed.json

# Counts for the period
PR_OPENED=$(jq -r --arg start "$START" '[.[] | select(.created_at[:10] >= $start)] | length' /tmp/pulls_all.json)
PR_MERGED=$(jq -r --arg start "$START" '[.[] | select(.merged_at != null and (.merged_at[:10] >= $start))] | length' /tmp/pulls_closed.json)
PR_CLOSED=$(jq -r --arg start "$START" '[.[] | select(.merged_at == null and .closed_at != null and (.closed_at[:10] >= $start))] | length' /tmp/pulls_closed.json)
ISSUES_OPENED=$(jq -r --arg start "$START" '[.[] | select(.pull_request == null and .created_at[:10] >= $start)] | length' /tmp/issues_all.json)
ISSUES_CLOSED=$(jq -r --arg start "$START" '[.[] | select(.pull_request == null and .closed_at != null and (.closed_at[:10] >= $start))] | length' /tmp/issues_closed.json)

BASE="$GH_SERVER/$GH_REPO"

# --- detail.md: full overview with counts in headings ---
echo "## Detailed repository activity: $START → $END" > detail.md
echo "" >> detail.md

echo "### Pull requests created ($PR_OPENED)" >> detail.md
echo "" >> detail.md
jq -r --arg start "$START" --arg base "$BASE" '.[] | select(.created_at[:10] >= $start) | "- [\(.state | ascii_upcase)] [#\(.number) \(.title) (created: \(.created_at[:10]))](\($base)/pull/\(.number))"' /tmp/pulls_all.json > /tmp/pr_created.txt 2>/dev/null || true
[ -s /tmp/pr_created.txt ] && cat /tmp/pr_created.txt >> detail.md || echo "- No PRs in this period" >> detail.md
echo "" >> detail.md

echo "### Pull requests merged ($PR_MERGED)" >> detail.md
echo "" >> detail.md
jq -r --arg start "$START" --arg base "$BASE" '.[] | select(.merged_at != null and (.merged_at[:10] >= $start)) | "- [#\(.number) \(.title) (merged: \(.merged_at[:10]))](\($base)/pull/\(.number))"' /tmp/pulls_closed.json > /tmp/pr_merged.txt 2>/dev/null || true
[ -s /tmp/pr_merged.txt ] && cat /tmp/pr_merged.txt >> detail.md || echo "- No merged PRs in this period" >> detail.md
echo "" >> detail.md

echo "### Pull requests closed ($PR_CLOSED)" >> detail.md
echo "" >> detail.md
jq -r --arg start "$START" --arg base "$BASE" '.[] | select(.merged_at == null and .closed_at != null and (.closed_at[:10] >= $start)) | "- [#\(.number) \(.title) (closed: \(.closed_at[:10]))](\($base)/pull/\(.number))"' /tmp/pulls_closed.json > /tmp/pr_closed.txt 2>/dev/null || true
[ -s /tmp/pr_closed.txt ] && cat /tmp/pr_closed.txt >> detail.md || echo "- No closed (unmerged) PRs in this period" >> detail.md
echo "" >> detail.md

echo "### Issues opened ($ISSUES_OPENED)" >> detail.md
echo "" >> detail.md
jq -r --arg start "$START" --arg base "$BASE" '.[] | select(.pull_request == null and .created_at[:10] >= $start) | "- [\(.state | ascii_upcase)] [#\(.number) \(.title) (opened: \(.created_at[:10]))](\($base)/issues/\(.number))"' /tmp/issues_all.json > /tmp/issue_opened.txt 2>/dev/null || true
[ -s /tmp/issue_opened.txt ] && cat /tmp/issue_opened.txt >> detail.md || echo "- No issues in this period" >> detail.md
echo "" >> detail.md

echo "### Issues closed ($ISSUES_CLOSED)" >> detail.md
echo "" >> detail.md
jq -r --arg start "$START" --arg base "$BASE" '.[] | select(.pull_request == null and .closed_at != null and (.closed_at[:10] >= $start)) | "- [#\(.number) \(.title) (closed: \(.closed_at[:10]))](\($base)/issues/\(.number))"' /tmp/issues_closed.json > /tmp/issue_closed.txt 2>/dev/null || true
[ -s /tmp/issue_closed.txt ] && cat /tmp/issue_closed.txt >> detail.md || echo "- No closed issues in this period" >> detail.md

# --- slack.md + slack_report.txt: Slack summary (PRs opened/closed/merged, Issues opened/closed) ---
{
echo ":books: *Weekly Docs Update (Git Repository)*"
echo ""
echo "*Dev Docs*"
echo ""
echo "*PRs*"
echo ":inbox_tray: $PR_OPENED PRs opened"
echo ":white_check_mark: $PR_MERGED PRs merged"
echo ":lock: $PR_CLOSED PRs closed"
echo ""
echo "*Issues*"
echo ":inbox_tray: $ISSUES_OPENED issues opened"
echo ":ladybug: $ISSUES_CLOSED issues closed"
echo ""
echo "View workflow run: $BASE/actions/runs/${{ github.run_id }}"
} > slack.md
echo "" >> slack.md
echo '```' >> slack.md
cat detail.md >> slack.md
echo '```' >> slack.md
cp slack.md slack_report.txt

- name: Upload report
uses: actions/upload-artifact@v4
with:
name: repo-stats-report-${{ matrix.repo }}
path: |
detail.md
slack.md

- name: Print detail report
run: cat detail.md

- name: Prepare Slack payload
id: slack_payload
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.post_to_slack == 'true') || (github.event_name == 'schedule')
env:
START_DATE: ${{ steps.stats.outputs.start_date }}
END_DATE: ${{ steps.stats.outputs.end_date }}
GH_SERVER: ${{ github.server_url }}
GH_REPO: ${{ github.repository }}
GITHUB_RUN_ID: ${{ github.run_id }}
run: |
# Split slack_report.txt into ≤3000-char chunks at newline boundaries
awk '
BEGIN { n=0; cur=""; len=0 }
{
l = length($0) + 1
if (len + l > 3000 && len > 0) {
printf "%s", cur > ("/tmp/sw_chunk_" n ".txt")
n++; cur = $0 "\n"; len = l
} else { cur = cur $0 "\n"; len += l }
}
END {
if (len > 0) { printf "%s", cur > ("/tmp/sw_chunk_" n ".txt"); n++ }
print n > "/tmp/sw_chunk_count.txt"
}
' slack_report.txt

NUM_CHUNKS=$(cat /tmp/sw_chunk_count.txt)
if ! [[ "$NUM_CHUNKS" =~ ^[0-9]+$ ]] || [[ "$NUM_CHUNKS" -lt 1 ]]; then NUM_CHUNKS=1; fi
RAWFILE_ARGS=()
SECTION_EXPRS=()
for i in $(seq 0 $((NUM_CHUNKS - 1))); do
RAWFILE_ARGS+=(--rawfile "c${i}" "/tmp/sw_chunk_${i}.txt")
SECTION_EXPRS+=("{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\$c${i}}}")
done
SECTIONS_STR=$(IFS=','; echo "${SECTION_EXPRS[*]}")

jq -n "${RAWFILE_ARGS[@]}" \
--arg start "$START_DATE" --arg end "$END_DATE" \
--arg link "$GH_SERVER/$GH_REPO/actions/runs/$GITHUB_RUN_ID" \
'{blocks:([{type:"header",text:{type:"plain_text",text:"📚 Weekly API Docs Update (Git Repository)",emoji:true}},{type:"context",elements:[{type:"mrkdwn",text:($start+" → "+$end+" | <"+$link+"|View run>")}]},{type:"divider"}]+['"$SECTIONS_STR"'])}' \
> payload.json
echo "payload<<EOF" >> $GITHUB_OUTPUT
cat payload.json >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT

- name: Post to Slack
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.post_to_slack == 'true') || (github.event_name == 'schedule')
uses: slackapi/slack-github-action@v1.24.0
with:
payload: ${{ steps.slack_payload.outputs.payload }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_NOTIFICATIONS_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK