From 07502a6b266f34324e7f0ce88a0c4aec4f279c8a Mon Sep 17 00:00:00 2001 From: default Date: Fri, 13 Feb 2026 15:52:40 +0000 Subject: [PATCH] Added PR/Issue management actions --- .gitea/workflows/issue-management.yml | 73 ++++++++++++++++++++ .gitea/workflows/pr-management.yml | 9 ++- .gitea/workflows/pr-title-checker.yml | 14 ++-- .gitea/workflows/stale-issues.yml | 98 +++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 .gitea/workflows/issue-management.yml create mode 100644 .gitea/workflows/stale-issues.yml diff --git a/.gitea/workflows/issue-management.yml b/.gitea/workflows/issue-management.yml new file mode 100644 index 0000000..005fffd --- /dev/null +++ b/.gitea/workflows/issue-management.yml @@ -0,0 +1,73 @@ +name: Issue Management Bot + +on: + issues: + types: [opened, edited, labeled, unlabeled] + +jobs: + issue-triage: + runs-on: ubuntu-latest + env: + API_TOKEN: ${{ secrets.BOT_ACCESS_TOKEN }} + API_BASE_URL: https://git.psmattas.com/api/v1 + REPO: ${{ gitea.repository }} + ISSUE_NUMBER: ${{ gitea.event.issue.number }} + ISSUE_AUTHOR: ${{ gitea.event.issue.user.login }} + ISSUE_TITLE: ${{ gitea.event.issue.title }} + + steps: + - name: Welcome new contributors + if: gitea.event.action == 'opened' + run: | + # Check if this is the user's first issue + AUTHOR_ISSUES=$(curl -s -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues?state=all&created_by=$ISSUE_AUTHOR" | jq '. | length') + + if [ "$AUTHOR_ISSUES" -eq 1 ]; then + WELCOME_MSG="👋 Welcome @$ISSUE_AUTHOR! Thanks for opening your first issue in Evercatch!\n\nOur team will review this shortly. In the meantime:\n- Check our [documentation](https://docs.evercatch.dev) for common solutions\n- Join our [community](https://community.evercatch.dev) for discussions\n- Review our [Code of Conduct](CODE_OF_CONDUCT.md)\n\nWe appreciate your contribution! 🎉" + COMMENT_JSON=$(jq -n --arg body "$WELCOME_MSG" '{"body": $body}') + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/comments" + fi + + - name: Auto-label bug reports + if: contains(gitea.event.issue.title, '[BUG]') || contains(gitea.event.issue.labels.*.name, 'bug') + run: | + LABELS='{"labels": ["bug", "status: investigating"]}' + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABELS" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels" + + - name: Auto-label feature requests + if: contains(gitea.event.issue.title, '[FEATURE]') || contains(gitea.event.issue.labels.*.name, 'feature') + run: | + LABELS='{"labels": ["feature", "status: under-review"]}' + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABELS" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels" + + - name: Auto-label support requests + if: contains(gitea.event.issue.title, '[SUPPORT]') || contains(gitea.event.issue.labels.*.name, 'question') + run: | + LABELS='{"labels": ["question", "status: needs-response"]}' + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABELS" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels" + + - name: Auto-label documentation issues + if: contains(gitea.event.issue.title, '[DOCS]') || contains(gitea.event.issue.labels.*.name, 'documentation') + run: | + LABELS='{"labels": ["documentation", "status: under-review"]}' + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABELS" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels" + + - name: Assign to team for critical bugs + if: contains(gitea.event.issue.labels.*.name, 'priority: critical') + run: | + ASSIGNEES='{"assignees": ["psmattas"]}' + curl -s -f -X PATCH -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$ASSIGNEES" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER" + + URGENT_MSG="🚨 **Critical Issue Detected**\n\nThis issue has been marked as critical and assigned to @psmattas for immediate attention.\n\nExpected response time: **4 hours**" + COMMENT_JSON=$(jq -n --arg body "$URGENT_MSG" '{"body": $body}') + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/comments" + + - name: Request more info for incomplete issues + if: gitea.event.action == 'opened' && !contains(gitea.event.issue.body, 'Steps to Reproduce') && contains(gitea.event.issue.labels.*.name, 'bug') + run: | + INFO_MSG="Hi @$ISSUE_AUTHOR! 👋\n\nTo help us investigate this bug, could you please provide:\n\n1. **Steps to reproduce** the issue\n2. **Expected behavior** vs **actual behavior**\n3. Your **subscription tier**\n4. **Event IDs** or **timestamps** (if applicable)\n\nThis will help us resolve the issue faster. Thanks!" + COMMENT_JSON=$(jq -n --arg body "$INFO_MSG" '{"body": $body}') + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/comments" + + LABELS='{"labels": ["status: needs-info"]}' + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABELS" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels" diff --git a/.gitea/workflows/pr-management.yml b/.gitea/workflows/pr-management.yml index f8a5cda..f2e0a83 100644 --- a/.gitea/workflows/pr-management.yml +++ b/.gitea/workflows/pr-management.yml @@ -49,17 +49,16 @@ jobs: LABEL_JSON='{ "labels": ["status: needs-rebase"] }' LABELS_API_URL="$API_BASE_URL/repos/$REPO/issues/$PR_NUMBER/labels" curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABEL_JSON" "$LABELS_API_URL" - + - name: Remove 'needs-rebase' label if clean if: gitea.event.pull_request.merge_status != 'CONFLICT' run: | LABEL_TO_REMOVE="status: needs-rebase" ISSUES_API_URL="$API_BASE_URL/repos/$REPO/issues/$PR_NUMBER" - + CURRENT_LABELS_JSON=$(curl -s -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels") LABEL_ID=$(echo "$CURRENT_LABELS_JSON" | jq --arg name "$LABEL_TO_REMOVE" '.[] | select(.name == $name) | .id') - # Only try to delete the label if we found its ID if [ -n "$LABEL_ID" ]; then echo "Found and removing '$LABEL_TO_REMOVE' (ID: $LABEL_ID)..." curl -s -f -X DELETE -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels/$LABEL_ID" @@ -68,9 +67,9 @@ jobs: - name: Auto-assign reviewers run: | REVIEWERS_JSON='{ "reviewers": ["psmattas"] }' - + if [[ -n "$REVIEWERS_JSON" ]]; then echo "Requesting review from: $REVIEWERS_JSON" API_URL="$API_BASE_URL/repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$REVIEWERS_JSON" "$API_URL" - fi \ No newline at end of file + fi diff --git a/.gitea/workflows/pr-title-checker.yml b/.gitea/workflows/pr-title-checker.yml index 03bd706..e8269a5 100644 --- a/.gitea/workflows/pr-title-checker.yml +++ b/.gitea/workflows/pr-title-checker.yml @@ -22,7 +22,7 @@ jobs: The title of this pull request does not follow the required format. This PR is currently **blocked from merging**. To fix this, please update the title to match the following structure: - **`EC-[JIRA_NUMBER]: [TYPE]: [Your Description]`** + **`EC-[ISSUE_NUMBER]: [TYPE]: [Your Description]`** - **Valid [TYPE] values are:** `FEAT`, `FIX`, `DOCS`, `REFACTOR`, `STYLE`, `PERF`, `TEST`, `CHORE` - **Example:** `EC-42: FEAT: Add new user login endpoint` @@ -40,7 +40,7 @@ jobs: run: | WIP_LABEL="status: work-in-progress" - INVALID_LABEL="INVALID-TITLE-DO-NOT-MERGE" + INVALID_LABEL="status: invalid-title" WIP_REGEX="^WIP:" FORMAT_REGEX="^EC-[0-9]+: (FEAT|FIX|DOCS|REFACTOR|STYLE|PERF|TEST|CHORE): .+$" ISSUES_API_URL="$API_BASE_URL/repos/$REPO/issues/$PR_NUMBER" @@ -67,7 +67,7 @@ jobs: elif [[ "$PR_TITLE" =~ $FORMAT_REGEX ]]; then echo "✅ PR title format is correct and not a WIP." echo "Checking for labels to remove..." - + CURRENT_LABELS_JSON=$(curl -s -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels") INVALID_LABEL_ID=$(echo "$CURRENT_LABELS_JSON" | jq --arg name "$INVALID_LABEL" '.[] | select(.name == $name) | .id') @@ -81,15 +81,15 @@ jobs: echo "Found and removing '$WIP_LABEL' (ID: $WIP_LABEL_ID)..." curl -s -f -X DELETE -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels/$WIP_LABEL_ID" fi - + exit 0 - + else echo "❌ ERROR: PR title does not match the required format." echo "Adding '$INVALID_LABEL' label..." LABEL_JSON=$(echo "$INVALID_LABEL" | jq -R '{"labels": [.]}') curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABEL_JSON" "$ISSUES_API_URL/labels" - + # Remove WIP label just in case ENCODED_WIP_LABEL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$WIP_LABEL'))") curl -s -X DELETE -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels/$ENCODED_WIP_LABEL" || true @@ -102,4 +102,4 @@ jobs: curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$ISSUES_API_URL/comments" fi exit 1 - fi \ No newline at end of file + fi diff --git a/.gitea/workflows/stale-issues.yml b/.gitea/workflows/stale-issues.yml new file mode 100644 index 0000000..52b7583 --- /dev/null +++ b/.gitea/workflows/stale-issues.yml @@ -0,0 +1,98 @@ +name: Close Stale Issues + +on: + schedule: + - cron: '0 0 * * *' # Daily at midnight UTC + workflow_dispatch: # Manual trigger + +jobs: + stale: + runs-on: ubuntu-latest + env: + API_TOKEN: ${{ secrets.BOT_ACCESS_TOKEN }} + API_BASE_URL: https://git.psmattas.com/api/v1 + REPO: ${{ gitea.repository }} + + steps: + - name: Mark stale issues + run: | + # Issues with no activity for 30 days + THIRTY_DAYS_AGO=$(date -u -d '30 days ago' '+%Y-%m-%dT%H:%M:%SZ') + + # Get all open issues updated before 30 days ago + ISSUES=$(curl -s -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues?state=open&since=$THIRTY_DAYS_AGO&sort=updated&order=asc" | jq -c '.[]') + + echo "$ISSUES" | while IFS= read -r issue; do + ISSUE_NUMBER=$(echo "$issue" | jq -r '.number') + UPDATED_AT=$(echo "$issue" | jq -r '.updated_at') + HAS_STALE_LABEL=$(echo "$issue" | jq -r '.labels[] | select(.name == "status: stale") | .name') + + # Skip if already has stale label + if [ -n "$HAS_STALE_LABEL" ]; then + continue + fi + + echo "Marking issue #$ISSUE_NUMBER as stale (last updated: $UPDATED_AT)" + + # Add stale label + LABELS='{"labels": ["status: stale"]}' + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABELS" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels" + + # Add stale comment + STALE_MSG="This issue has been automatically marked as stale because it has not had recent activity.\n\nIt will be closed in **14 days** if no further activity occurs.\n\nIf this issue is still relevant, please:\n- Add a comment with updates\n- Remove the \`status: stale\` label\n\nThank you for your contributions! 🙏" + COMMENT_JSON=$(jq -n --arg body "$STALE_MSG" '{"body": $body}') + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/comments" + done + + - name: Close stale issues + run: | + # Issues marked stale for 14+ days + FOURTEEN_DAYS_AGO=$(date -u -d '14 days ago' '+%Y-%m-%dT%H:%M:%SZ') + + # Get stale issues + ISSUES=$(curl -s -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues?state=open&labels=status:stale" | jq -c '.[]') + + echo "$ISSUES" | while IFS= read -r issue; do + ISSUE_NUMBER=$(echo "$issue" | jq -r '.number') + UPDATED_AT=$(echo "$issue" | jq -r '.updated_at') + + # Check if updated_at is older than 14 days + if [[ "$UPDATED_AT" < "$FOURTEEN_DAYS_AGO" ]]; then + echo "Closing stale issue #$ISSUE_NUMBER (last updated: $UPDATED_AT)" + + # Close the issue + CLOSE_JSON='{"state": "closed"}' + curl -s -f -X PATCH -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$CLOSE_JSON" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER" + + # Add closing comment + CLOSE_MSG="This issue was automatically closed due to inactivity.\n\nIf you believe this was closed in error, please:\n1. Reopen the issue\n2. Provide updated information\n3. Remove the \`status: stale\` label\n\nThank you! 🙏" + COMMENT_JSON=$(jq -n --arg body "$CLOSE_MSG" '{"body": $body}') + curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/comments" + fi + done + + - name: Remove stale label on activity + run: | + # Get stale issues that were recently updated + ISSUES=$(curl -s -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues?state=open&labels=status:stale" | jq -c '.[]') + + echo "$ISSUES" | while IFS= read -r issue; do + ISSUE_NUMBER=$(echo "$issue" | jq -r '.number') + + # Get comments + COMMENTS=$(curl -s -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/comments") + LATEST_COMMENT_AUTHOR=$(echo "$COMMENTS" | jq -r '.[-1].user.login') + + # If latest comment is not from bot, remove stale label + if [ "$LATEST_COMMENT_AUTHOR" != "EC-bot" ] && [ "$LATEST_COMMENT_AUTHOR" != "gitea-actions" ]; then + echo "Removing stale label from issue #$ISSUE_NUMBER (recent activity detected)" + + # Get label ID + LABELS=$(curl -s -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels") + STALE_LABEL_ID=$(echo "$LABELS" | jq -r '.[] | select(.name == "status: stale") | .id') + + if [ -n "$STALE_LABEL_ID" ]; then + curl -s -f -X DELETE -H "Authorization: token $API_TOKEN" "$API_BASE_URL/repos/$REPO/issues/$ISSUE_NUMBER/labels/$STALE_LABEL_ID" + fi + fi + done