From b8c0a8bad1a14ded22a79ad53a5a51005fdb48fd Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Thu, 12 Mar 2026 13:57:55 -0400 Subject: [PATCH] [bin] Add gh stat generators --- bin/.local/bin/gh_pulls.sh | 83 +++++++++++++++++++ bin/.local/bin/gh_stats.sh | 160 +++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100755 bin/.local/bin/gh_pulls.sh create mode 100755 bin/.local/bin/gh_stats.sh diff --git a/bin/.local/bin/gh_pulls.sh b/bin/.local/bin/gh_pulls.sh new file mode 100755 index 0000000..b8455d4 --- /dev/null +++ b/bin/.local/bin/gh_pulls.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# pr-engagement-stats.sh + +USERNAME="${GH_USERNAME:-$(gh api user --jq '.login')}" + +echo "=== PR Engagement Stats for $USERNAME ===" +echo "" + +# Use GraphQL for accurate data +QUERY='{ + user(login: "'"$USERNAME"'") { + pullRequests(first: 100, states: [OPEN, CLOSED, MERGED]) { + nodes { + comments(first: 100) { + nodes { + author { login } + } + } + reviews(first: 100) { + nodes { + author { login } + state + } + } + } + } + } +}' + +RESULT=$(gh api graphql -f query="$QUERY" 2>/dev/null) + +if [ -z "$RESULT" ] || [ "$RESULT" = "null" ]; then + echo "❌ Failed to fetch data. Check gh authentication." + exit 1 +fi + +# Count comments by you +COMMENTS=$(echo "$RESULT" | jq --arg user "$USERNAME" ' + [.data.user.pullRequests.nodes[].comments.nodes[] | + select(.author.login == $user)] | length +') + +# Count reviews by you +REVIEWS=$(echo "$RESULT" | jq --arg user "$USERNAME" ' + [.data.user.pullRequests.nodes[].reviews.nodes[] | + select(.author.login == $user)] | length +') + +# Count review types +APPROVED=$(echo "$RESULT" | jq --arg user "$USERNAME" ' + [.data.user.pullRequests.nodes[].reviews.nodes[] | + select(.author.login == $user and .state == "APPROVED")] | length +') + +CHANGES_REQUESTED=$(echo "$RESULT" | jq --arg user "$USERNAME" ' + [.data.user.pullRequests.nodes[].reviews.nodes[] | + select(.author.login == $user and .state == "CHANGES_REQUESTED")] | length +') + +echo "📊 Engagement Metrics:" +echo " PR Comments: $COMMENTS" +echo " Formal Reviews: $REVIEWS" +echo " - Approved: $APPROVED" +echo " - Changes Requested: $CHANGES_REQUESTED" +echo "" + +# Ratio +if [ "$REVIEWS" -gt 0 ]; then + RATIO=$(echo "scale=2; $COMMENTS / $REVIEWS" | bc) + echo "📈 Comment/Review Ratio: $RATIO" + + TOTAL=$((COMMENTS + REVIEWS)) + COMMENT_PCT=$(echo "scale=1; $COMMENTS * 100 / $TOTAL" | bc) + REVIEW_PCT=$(echo "scale=1; $REVIEWS * 100 / $TOTAL" | bc) + + echo " Comments: ${COMMENT_PCT}%" + echo " Reviews: ${REVIEW_PCT}%" +else + echo "📈 Comment/Review Ratio: N/A (no reviews)" +fi + +echo "" +echo "Generated: $(date)" diff --git a/bin/.local/bin/gh_stats.sh b/bin/.local/bin/gh_stats.sh new file mode 100755 index 0000000..9cd24c9 --- /dev/null +++ b/bin/.local/bin/gh_stats.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +# GitHub PR Statistics Dashboard +# Requires: gh CLI tool installed and authenticated + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +USERNAME="${GH_USERNAME:-$(gh api user --jq '.login')}" +REPO_FILTER="${REPO_FILTER:-}" # Leave empty for all repos, or specify "owner/repo" + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} GitHub PR Statistics for: $USERNAME${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Function to print section headers +print_header() { + echo -e "${YELLOW}--- $1 ---${NC}" +} + +# 1. PRs Opened by You +print_header "PRs Opened" +OPENED_COUNT=$(gh pr list --author "$USERNAME" --state open --limit 1000 --json number --jq 'length') +echo -e "${GREEN}Currently Open: $OPENED_COUNT${NC}" + +OPENED_ALL=$(gh pr list --author "$USERNAME" --state all --limit 1000 --json number --jq 'length') +echo "Total Ever Opened: $OPENED_ALL" + +# 2. PRs Merged by You +print_header "PRs Merged" +MERGED_COUNT=$(gh pr list --author "$USERNAME" --state merged --limit 1000 --json number --jq 'length') +echo -e "${GREEN}Total Merged: $MERGED_COUNT${NC}" + +# 3. PRs Closed (not merged) by You +print_header "PRs Closed (Not Merged)" +CLOSED_COUNT=$(gh pr list --author "$USERNAME" --state closed --limit 1000 --json number,state --jq '[.[] | select(.state == "CLOSED")] | length') +echo -e "${RED}Total Closed: $CLOSED_COUNT${NC}" + +# 4. Merge Rate +print_header "Merge Rate" +if [ $OPENED_ALL -gt 0 ]; then + MERGE_RATE=$(echo "scale=2; $MERGED_COUNT * 100 / $OPENED_ALL" | bc) + echo -e "${GREEN}Merge Rate: ${MERGE_RATE}%${NC}" +else + echo "No PRs opened yet" +fi + +# 5. PRs Reviewed by You +print_header "PRs Reviewed" +# This requires searching through PRs you've commented on with review keywords +REVIEWED_COUNT=$(gh pr list --search "reviewed-by:$USERNAME" --state all --limit 1000 --json number --jq 'length' 2>/dev/null || echo "0") +echo "PRs Where You're a Reviewer: $REVIEWED_COUNT" + +# Alternative: Check recent activity +echo "Checking recent review activity..." +RECENT_REVIEWS=$(gh api "/users/$USERNAME/events" --jq '[.[] | select(.type == "PullRequestReviewEvent")] | length') +echo "Recent Review Events: $RECENT_REVIEWS" + +# 6. Comments Made +print_header "Comments Made" +# Get issue and PR comments +PR_COMMENTS=$(gh api "/users/$USERNAME/events" --jq '[.[] | select(.type == "IssueCommentEvent" and .payload.issue.pull_request != null)] | length') +ISSUE_COMMENTS=$(gh api "/users/$USERNAME/events" --jq '[.[] | select(.type == "IssueCommentEvent" and .payload.issue.pull_request == null)] | length') +echo "PR Comments: $PR_COMMENTS" +echo "Issue Comments: $ISSUE_COMMENTS" +echo -e "${GREEN}Total Comments: $((PR_COMMENTS + ISSUE_COMMENTS))${NC}" + +# Add this section after the "Comments Made" section + +# 7. Comment vs Review Ratio +print_header "Comment vs Review Ratio" + +# Count PR Reviews (formal reviews with approve/request changes) +REVIEW_EVENTS=$(gh api "/users/$USERNAME/events" --jq '[.[] | select(.type == "PullRequestReviewEvent")] | length' 2>/dev/null || echo "0") + +# Count PR Comments (comments on PRs, not formal reviews) +PR_COMMENT_EVENTS=$(gh api "/users/$USERNAME/events" --jq '[.[] | select(.type == "IssueCommentEvent" and .payload.issue.pull_request != null)] | length' 2>/dev/null || echo "0") + +echo "Formal PR Reviews: $REVIEW_EVENTS" +echo "PR Comments: $PR_COMMENT_EVENTS" + +# Calculate ratio +if [ "$REVIEW_EVENTS" -gt 0 ]; then + COMMENTS_PER_REVIEW=$(echo "scale=2; $PR_COMMENT_EVENTS / $REVIEW_EVENTS" | bc) + echo -e "${GREEN}Comments per Review: $COMMENTS_PER_REVIEW${NC}" + + # Also show percentage breakdown + TOTAL_ENGAGEMENT=$((PR_COMMENT_EVENTS + REVIEW_EVENTS)) + if [ "$TOTAL_ENGAGEMENT" -gt 0 ]; then + COMMENT_PCT=$(echo "scale=1; $PR_COMMENT_EVENTS * 100 / $TOTAL_ENGAGEMENT" | bc) + REVIEW_PCT=$(echo "scale=1; $REVIEW_EVENTS * 100 / $TOTAL_ENGAGEMENT" | bc) + echo -e "${BLUE}Engagement Breakdown:${NC}" + echo " Comments: ${COMMENT_PCT}%" + echo " Reviews: ${REVIEW_PCT}%" + fi +else + echo -e "${YELLOW}No formal reviews yet - ratio unavailable${NC}" +fi + +# Engagement Style Indicator +if [ "$REVIEW_EVENTS" -gt 0 ] && [ "$PR_COMMENT_EVENTS" -gt 0 ]; then + RATIO=$(echo "scale=2; $PR_COMMENT_EVENTS / $REVIEW_EVENTS" | bc) + echo -e "${BLUE}Engagement Style:${NC}" + if (( $(echo "$RATIO > 3" | bc -l) )); then + echo -e "${YELLOW} 💬 Comment-Heavy (prefers discussions over formal reviews)${NC}" + elif (( $(echo "$RATIO < 0.5" | bc -l) )); then + echo -e "${GREEN} ✅ Review-Focused (prefers formal approvals)${NC}" + else + echo -e "${GREEN} ⚖️ Balanced (mix of comments and reviews)${NC}" + fi +fi + +# 7. PRs Awaiting Your Review +print_header "PRs Awaiting Your Review" +AWAITING_REVIEW=$(gh pr list --search "review-required" --state open --limit 1000 --json number,title --jq 'length') +echo -e "${YELLOW}PRs Needing Review: $AWAITING_REVIEW${NC}" + +# 8. Recent Activity Summary +print_header "Recent Activity (Last 30 Days)" +RECENT_PRS=$(gh pr list --author "$USERNAME" --state all --limit 100 --json createdAt --jq '[.[] | select(.createdAt > (now - 2592000) | tostring)] | length') +echo "PRs Created (30 days): $RECENT_PRS" + +# 9. Top Repos by PR Activity +print_header "Top Repos by PR Activity" +gh pr list --author "$USERNAME" --state all --limit 1000 --json repository --jq '[.[].repository.name] | group_by(.) | map({repo: .[0], count: length}) | sort_by(-.count) | .[0:5] | .[] | "\(.repo): \(.count)"' 2>/dev/null || echo "Unable to fetch repo stats" + +# 10. Additional Useful Stats +print_header "Additional Stats" +echo "GitHub Username: $USERNAME" +echo "Script Generated: $(date)" + +# Optional: Export to JSON +if [ "$1" == "--json" ]; then + echo "" + print_header "JSON Export" + cat << EOF +{ + "username": "$USERNAME", + "prs_opened": $OPENED_COUNT, + "prs_total": $OPENED_ALL, + "prs_merged": $MERGED_COUNT, + "prs_closed": $CLOSED_COUNT, + "merge_rate": "${MERGE_RATE:-0}", + "generated_at": "$(date -Iseconds)" +} +EOF +fi + +echo "" +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} Stats generated successfully!${NC}" +echo -e "${BLUE}========================================${NC}"