diff --git a/.github/workflows/repo-stats.yml b/.github/workflows/repo-stats.yml new file mode 100644 index 0000000..af0336d --- /dev/null +++ b/.github/workflows/repo-stats.yml @@ -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<> $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