Issue and PR Management #34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Issue and PR Management | |
| on: | |
| issues: | |
| types: [opened, edited, labeled, unlabeled] | |
| pull_request: | |
| types: [opened, edited, labeled, unlabeled, ready_for_review, review_requested] | |
| issue_comment: | |
| types: [created] | |
| schedule: | |
| # Run every day at 12 PM UTC to check stale issues | |
| - cron: '0 12 * * *' | |
| jobs: | |
| label-issues: | |
| if: github.event_name == 'issues' && github.event.action == 'opened' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| - name: Auto-label issues | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const issue = context.payload.issue; | |
| const title = issue.title.toLowerCase(); | |
| const body = issue.body?.toLowerCase() || ''; | |
| const labels = []; | |
| // Auto-label based on title/content | |
| if (title.includes('bug') || body.includes('bug') || title.includes('error') || title.includes('exception')) { | |
| labels.push('bug'); | |
| } | |
| if (title.includes('feature') || title.includes('enhancement') || body.includes('feature request')) { | |
| labels.push('enhancement'); | |
| } | |
| if (title.includes('documentation') || title.includes('docs') || body.includes('documentation')) { | |
| labels.push('documentation'); | |
| } | |
| if (title.includes('performance') || body.includes('performance') || title.includes('benchmark')) { | |
| labels.push('performance'); | |
| } | |
| if (title.includes('virtual thread') || body.includes('virtual thread')) { | |
| labels.push('virtual-threads'); | |
| } | |
| if (title.includes('structured concurrency') || body.includes('structured concurrency')) { | |
| labels.push('structured-concurrency'); | |
| } | |
| if (title.includes('test') || body.includes('test')) { | |
| labels.push('testing'); | |
| } | |
| if (title.includes('question') || title.includes('help') || title.includes('how to')) { | |
| labels.push('question'); | |
| } | |
| // Add priority labels based on keywords | |
| if (title.includes('urgent') || title.includes('critical') || body.includes('urgent')) { | |
| labels.push('priority:high'); | |
| } | |
| if (labels.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: labels | |
| }); | |
| console.log(`Added labels: ${labels.join(', ')}`); | |
| } | |
| - name: Welcome new contributors | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const issue = context.payload.issue; | |
| // Check if this is the user's first issue | |
| const issues = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| creator: issue.user.login, | |
| state: 'all' | |
| }); | |
| if (issues.data.length === 1) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| body: ` | |
| 👋 Welcome to the Java Concurrency Patterns project, @${issue.user.login}! | |
| Thank you for opening your first issue. Here are some helpful tips: | |
| 🔍 **For bugs**: Please include: | |
| - Java version you're using | |
| - Steps to reproduce the issue | |
| - Expected vs actual behavior | |
| - Relevant code snippets or stack traces | |
| 💡 **For feature requests**: Please describe: | |
| - The use case or problem you're trying to solve | |
| - Your proposed solution or suggestions | |
| - Any alternatives you've considered | |
| 📚 **Questions**: Check our [documentation](https://github.com/${context.repo.owner}/${context.repo.repo}#readme) first, and feel free to ask for clarification! | |
| A maintainer will review your issue soon. Thank you for contributing! 🚀 | |
| ` | |
| }); | |
| } | |
| label-pull-requests: | |
| if: github.event_name == 'pull_request' && github.event.action == 'opened' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - name: Auto-label pull requests | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const title = pr.title.toLowerCase(); | |
| const body = pr.body?.toLowerCase() || ''; | |
| const labels = []; | |
| // Auto-label based on title/content | |
| if (title.includes('fix') || title.includes('bug')) { | |
| labels.push('bug'); | |
| } | |
| if (title.includes('feat') || title.includes('add') || title.includes('new')) { | |
| labels.push('enhancement'); | |
| } | |
| if (title.includes('docs') || title.includes('documentation')) { | |
| labels.push('documentation'); | |
| } | |
| if (title.includes('test') || title.includes('testing')) { | |
| labels.push('testing'); | |
| } | |
| if (title.includes('perf') || title.includes('performance')) { | |
| labels.push('performance'); | |
| } | |
| if (title.includes('refactor') || title.includes('cleanup')) { | |
| labels.push('refactoring'); | |
| } | |
| // Check if it's a breaking change | |
| if (title.includes('break') || body.includes('breaking change')) { | |
| labels.push('breaking-change'); | |
| } | |
| // Add size labels based on changed files | |
| const files = await github.rest.pulls.listFiles({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number | |
| }); | |
| const changedFiles = files.data.length; | |
| if (changedFiles <= 5) { | |
| labels.push('size:small'); | |
| } else if (changedFiles <= 15) { | |
| labels.push('size:medium'); | |
| } else { | |
| labels.push('size:large'); | |
| } | |
| if (labels.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: labels | |
| }); | |
| } | |
| - name: Welcome new contributors PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| // Check if this is the user's first PR | |
| const prs = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| creator: pr.user.login, | |
| state: 'all' | |
| }); | |
| if (prs.data.length === 1) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: ` | |
| 🎉 Thank you for your first contribution to Java Concurrency Patterns, @${pr.user.login}! | |
| Your pull request will be reviewed by a maintainer soon. Here's what happens next: | |
| ✅ **Automated checks** will run to ensure code quality and tests pass | |
| 📊 **Code coverage** will be calculated and reported | |
| 🔍 **Manual review** by maintainers for code quality and design | |
| **Tips for a smooth review:** | |
| - Make sure all tests pass | |
| - Add tests for new functionality | |
| - Update documentation if needed | |
| - Keep the PR focused on a single feature/fix | |
| Thank you for making Java concurrency better! 🚀 | |
| ` | |
| }); | |
| } | |
| check-pr-requirements: | |
| if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'edited') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - name: Check PR requirements | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const title = pr.title; | |
| const body = pr.body || ''; | |
| const issues = []; | |
| // Check title format | |
| if (title.length < 10) { | |
| issues.push('- Title should be more descriptive (at least 10 characters)'); | |
| } | |
| // Check for description | |
| if (body.length < 30) { | |
| issues.push('- Please add a more detailed description of your changes'); | |
| } | |
| // Check for related issue reference | |
| if (!body.includes('#') && !body.toLowerCase().includes('fixes') && !body.toLowerCase().includes('closes')) { | |
| issues.push('- Consider referencing any related issues (e.g., "Fixes #123")'); | |
| } | |
| // Check for test section | |
| if (!body.toLowerCase().includes('test') && !title.toLowerCase().includes('docs')) { | |
| issues.push('- Please mention how you tested your changes'); | |
| } | |
| if (issues.length > 0) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: ` | |
| 📋 **PR Checklist Reminder** | |
| Thank you for your contribution! To help us review your PR more efficiently, please consider addressing these items: | |
| ${issues.join('\n')} | |
| *This is an automated reminder. Feel free to ignore if not applicable.* | |
| ` | |
| }); | |
| } | |
| manage-stale-issues: | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Mark stale issues and PRs | |
| uses: actions/stale@v9 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| # Issue settings | |
| stale-issue-message: | | |
| This issue has been automatically marked as stale because it has not had recent activity. | |
| It will be closed if no further activity occurs within the next 7 days. | |
| If you believe this issue is still relevant, please comment to keep it open. | |
| Thank you for your contributions! 🙏 | |
| close-issue-message: | | |
| This issue has been automatically closed due to inactivity. | |
| If you believe this issue should remain open, please reopen it and provide additional context. | |
| # PR settings | |
| stale-pr-message: | | |
| This pull request has been automatically marked as stale because it has not had recent activity. | |
| It will be closed if no further activity occurs within the next 7 days. | |
| If this PR is still relevant, please comment or push new commits to keep it open. | |
| close-pr-message: | | |
| This pull request has been automatically closed due to inactivity. | |
| If you'd like to continue working on this, please reopen it and address any feedback. | |
| # Timing settings | |
| days-before-stale: 60 | |
| days-before-close: 7 | |
| # Label settings | |
| stale-issue-label: 'stale' | |
| stale-pr-label: 'stale' | |
| # Exempt settings | |
| exempt-issue-labels: 'pinned,security,priority:high' | |
| exempt-pr-labels: 'pinned,security,priority:high,work-in-progress' | |
| # Operation limits | |
| operations-per-run: 30 | |
| manage-labels: | |
| if: github.event_name == 'issues' || github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Ensure required labels exist | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const labels = [ | |
| { name: 'bug', color: 'd73a4a', description: 'Something isn\'t working' }, | |
| { name: 'enhancement', color: 'a2eeef', description: 'New feature or request' }, | |
| { name: 'documentation', color: '0075ca', description: 'Improvements or additions to documentation' }, | |
| { name: 'question', color: 'd876e3', description: 'Further information is requested' }, | |
| { name: 'testing', color: '7057ff', description: 'Related to testing' }, | |
| { name: 'performance', color: 'ff6b35', description: 'Performance related' }, | |
| { name: 'virtual-threads', color: '00d4aa', description: 'Related to virtual threads' }, | |
| { name: 'structured-concurrency', color: '00aa55', description: 'Related to structured concurrency' }, | |
| { name: 'priority:high', color: 'b60205', description: 'High priority issue' }, | |
| { name: 'priority:medium', color: 'fbca04', description: 'Medium priority issue' }, | |
| { name: 'priority:low', color: '0e8a16', description: 'Low priority issue' }, | |
| { name: 'size:small', color: '90ee90', description: 'Small change' }, | |
| { name: 'size:medium', color: 'ffd700', description: 'Medium change' }, | |
| { name: 'size:large', color: 'ff6b6b', description: 'Large change' }, | |
| { name: 'stale', color: '8b8680', description: 'Stale issue or PR' }, | |
| { name: 'breaking-change', color: 'ff0000', description: 'Contains breaking changes' }, | |
| { name: 'work-in-progress', color: 'fef2c0', description: 'Work in progress' }, | |
| { name: 'refactoring', color: 'c5def5', description: 'Code refactoring' } | |
| ]; | |
| const existingLabels = await github.rest.issues.listLabelsForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const existingLabelNames = existingLabels.data.map(label => label.name); | |
| for (const label of labels) { | |
| if (!existingLabelNames.includes(label.name)) { | |
| try { | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label.name, | |
| color: label.color, | |
| description: label.description | |
| }); | |
| console.log(`Created label: ${label.name}`); | |
| } catch (error) { | |
| console.log(`Failed to create label ${label.name}: ${error.message}`); | |
| } | |
| } | |
| } |