diff --git a/.github/workflows/pr-auto-comments-test.yml b/.github/workflows/pr-auto-comments-test.yml new file mode 100644 index 0000000..e4ab9a9 --- /dev/null +++ b/.github/workflows/pr-auto-comments-test.yml @@ -0,0 +1,131 @@ +name: PR Automated Comments Test + +on: + # Automatic triggers + pull_request_target: + types: [opened, ready_for_review, closed] + + # Manual trigger for testing + workflow_dispatch: + inputs: + test_event_type: + description: 'Event type to simulate for testing' + required: true + type: choice + options: + - opened + - ready_for_review + - merged + +jobs: + # When manually triggered, we need to simulate the PR event context + prepare-test-context: + name: Prepare Test Context + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + outputs: + is_external: 'true' + is_first_pr: ${{ steps.set-context.outputs.is_first_pr }} + event_type: ${{ steps.set-context.outputs.event_type }} + steps: + - name: Set test context variables + id: set-context + run: | + EVENT_TYPE="${{ github.event.inputs.test_event_type }}" + echo "event_type=$EVENT_TYPE" >> $GITHUB_OUTPUT + + # For the opened event, we need to simulate first PR status + if [[ "$EVENT_TYPE" == "opened" ]]; then + echo "is_first_pr=true" >> $GITHUB_OUTPUT + else + echo "is_first_pr=false" >> $GITHUB_OUTPUT + fi + + # Real workflow for actual PR events + pr-comments-real: + name: PR Comments (Real) + if: github.event_name == 'pull_request_target' + uses: ./.github/workflows/pr-auto-comments.yml@${{ github.ref_name }} + with: + org_name: "RequestNetwork" + # You can specify additional internal users here if needed + additional_internal_users: "" + # Use default messages + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + # Test workflow for manual triggering - using the actual reusable workflow + first-pr-comment-test: + name: First PR Comment (Test) + needs: prepare-test-context + if: github.event_name == 'workflow_dispatch' && needs.prepare-test-context.outputs.event_type == 'opened' + uses: ./.github/workflows/pr-auto-comments.yml@${{ github.ref_name }} + with: + org_name: "RequestNetwork" + first_pr_comment: "Hello @{{username}}, thank you for submitting your first pull request to the {{repository}} repository. We value your contribution and encourage you to review our contribution guidelines to ensure your submission meets our standards. Please note that every merged PR is automatically enrolled in our Best PR Initiative, offering a chance to win $500 each quarter. Our team is available via GitHub Discussions or Discord if you have any questions. Welcome aboard! (TEST)" + ready_for_review_comment: "" + merged_pr_comment: "" + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + ready-for-review-comment-test: + name: Ready for Review Comment (Test) + needs: prepare-test-context + if: github.event_name == 'workflow_dispatch' && needs.prepare-test-context.outputs.event_type == 'ready_for_review' + uses: ./.github/workflows/pr-auto-comments.yml@${{ github.ref_name }} + with: + org_name: "RequestNetwork" + first_pr_comment: "" + ready_for_review_comment: "Thank you for your submission! As you prepare for the review process, please ensure that your PR title, description, and any linked issues fully comply with our contribution guidelines. A clear explanation of your changes and their context will help expedite the review process. Every merged PR is automatically entered into our Best PR Initiative, offering a chance to win $500 every quarter. We appreciate your attention to detail and look forward to reviewing your contribution! (TEST)" + merged_pr_comment: "" + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + merged-pr-comment-test: + name: Merged PR Comment (Test) + needs: prepare-test-context + if: github.event_name == 'workflow_dispatch' && needs.prepare-test-context.outputs.event_type == 'merged' + uses: ./.github/workflows/pr-auto-comments.yml@${{ github.ref_name }} + with: + org_name: "RequestNetwork" + first_pr_comment: "" + ready_for_review_comment: "" + merged_pr_comment: "Congratulations, your pull request has been merged! Thank you for your valuable contribution to Request Network. As a reminder, every merged PR is automatically entered into our Best PR Initiative, offering a quarterly prize of $500. Your work significantly supports our project's growth, and we encourage you to continue engaging with our community. Additionally, if you want to build or add crypto payments and invoicing features, explore how our API can reduce deployment time from months to hours while offering advanced features. Book a call with our expert to learn more and fast-track your development. (TEST)" + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + # Add completion notification + notify-test-completion: + name: Notify Test Completion + needs: [prepare-test-context] + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Create notification issue comment + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const eventType = '${{ needs.prepare-test-context.outputs.event_type }}'; + const repoName = context.repo.repo; + const orgName = context.repo.owner; + + const issue_number = context.issue.number || 1; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + body: `## Test for "${eventType}" PR Event Initiated + + A test of the \`${eventType}\` PR event comment has been initiated. + + **Note:** Since this is a manual test, this will use hard-coded test variables: + - Repository: ${repoName} + - Organization: ${orgName} + - Testing as an external contributor + + Check the [Actions tab](https://github.com/${orgName}/${repoName}/actions/workflows/repo-pr-comments.yml) to see the workflow execution. + + *This is a notification from the manual test trigger.*` + }); \ No newline at end of file diff --git a/.github/workflows/pr-auto-comments.yml b/.github/workflows/pr-auto-comments.yml new file mode 100644 index 0000000..93e17cb --- /dev/null +++ b/.github/workflows/pr-auto-comments.yml @@ -0,0 +1,209 @@ +name: PR Automated Comments + +on: + workflow_call: + inputs: + org_name: + description: 'GitHub organization name to identify internal team members' + required: true + type: string + additional_internal_users: + description: 'Additional comma-separated list of usernames to consider as internal' + required: false + type: string + default: '' + first_pr_comment: + description: 'Message for first PR comments (leave empty to disable first PR comments)' + required: false + type: string + default: 'Hello @{{username}}, thank you for submitting your first pull request to the {{repository}} repository. We value your contribution and encourage you to review our contribution guidelines to ensure your submission meets our standards. Please note that every merged PR is automatically enrolled in our Best PR Initiative, offering a chance to win $500 each quarter. Our team is available via GitHub Discussions or Discord if you have any questions. Welcome aboard!' + ready_for_review_comment: + description: 'Message for PR ready for review comments (leave empty to disable ready for review comments)' + required: false + type: string + default: 'Thank you for your submission! As you prepare for the review process, please ensure that your PR title, description, and any linked issues fully comply with our contribution guidelines. A clear explanation of your changes and their context will help expedite the review process. Every merged PR is automatically entered into our Best PR Initiative, offering a chance to win $500 every quarter. We appreciate your attention to detail and look forward to reviewing your contribution!' + merged_pr_comment: + description: 'Message for merged PR comments (leave empty to disable merged PR comments)' + required: false + type: string + default: 'Congratulations, your pull request has been merged! Thank you for your valuable contribution to Request Network. As a reminder, every merged PR is automatically entered into our Best PR Initiative, offering a quarterly prize of $500. Your work significantly supports our project\'s growth, and we encourage you to continue engaging with our community. Additionally, if you want to build or add crypto payments and invoicing features, explore how our API can reduce deployment time from months to hours while offering advanced features. Book a call with our expert to learn more and fast-track your development.' + secrets: + token: + description: 'GitHub token with org:read permission' + required: false + +jobs: + # Central job to check if contributor is external and determine PR status + check-contributor: + name: Check Contributor Status + runs-on: ubuntu-latest + outputs: + is_external: ${{ steps.check.outputs.is_external }} + is_first_pr: ${{ steps.check.outputs.is_first_pr }} + event_type: ${{ steps.event.outputs.type }} + steps: + - name: Determine event type + id: event + run: | + if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then + if [[ "${{ github.event.action }}" == "opened" ]]; then + echo "type=opened" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.action }}" == "ready_for_review" ]]; then + echo "type=ready_for_review" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.action }}" == "closed" && "${{ github.event.pull_request.merged }}" == "true" ]]; then + echo "type=merged" >> $GITHUB_OUTPUT + else + echo "type=other" >> $GITHUB_OUTPUT + fi + else + echo "type=other" >> $GITHUB_OUTPUT + fi + + - name: Check if external contributor + id: check + run: | + # Get the PR author + PR_AUTHOR="${{ github.event.pull_request.user.login }}" + ORG_NAME="${{ inputs.org_name }}" + ADDITIONAL_USERS="${{ inputs.additional_internal_users }}" + + # First check if user is in the additional internal users list + if [[ "$ADDITIONAL_USERS" == *"$PR_AUTHOR"* ]]; then + echo "User is in additional internal users list, skipping comment" + echo "is_external=false" >> $GITHUB_OUTPUT + echo "is_first_pr=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if user is an org member + echo "Checking if user is a member of organization $ORG_NAME" + ORG_MEMBER=$(gh api -H "Accept: application/vnd.github+json" \ + "/orgs/$ORG_NAME/members/$PR_AUTHOR" --silent || echo "not_found") + + if [[ "$ORG_MEMBER" != "not_found" ]]; then + echo "User is an organization member, skipping comment" + echo "is_external=false" >> $GITHUB_OUTPUT + echo "is_first_pr=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # User is external + echo "is_external=true" >> $GITHUB_OUTPUT + + # Only check if this is their first PR if needed + if [[ "${{ steps.event.outputs.type }}" == "opened" ]]; then + # Check if this is their first PR to the repo + PR_COUNT=$(gh api graphql -f query=' + query($owner:String!, $repo:String!, $author:String!) { + repository(owner:$owner, name:$repo) { + pullRequests(first:100, states:[OPEN, CLOSED, MERGED], author:$author) { + totalCount + } + } + }' -f owner=${{ github.repository_owner }} -f repo=${{ github.event.repository.name }} -f author=$PR_AUTHOR | jq '.data.repository.pullRequests.totalCount') + + if [ "$PR_COUNT" -eq 1 ]; then + echo "First PR from this contributor" + echo "is_first_pr=true" >> $GITHUB_OUTPUT + else + echo "Not the first PR from this contributor" + echo "is_first_pr=false" >> $GITHUB_OUTPUT + fi + else + echo "is_first_pr=false" >> $GITHUB_OUTPUT + fi + env: + GH_TOKEN: ${{ secrets.token || github.token }} + + # Leave a comment on first PRs + first-pr-comment: + name: First PR Comment + needs: check-contributor + if: needs.check-contributor.outputs.event_type == 'opened' && needs.check-contributor.outputs.is_external == 'true' && needs.check-contributor.outputs.is_first_pr == 'true' && inputs.first_pr_comment != '' + runs-on: ubuntu-latest + steps: + - name: Leave first PR comment + uses: actions/github-script@v6 + with: + github-token: ${{ github.token }} + script: | + const variables = { + username: context.payload.pull_request.user.login, + repository: context.payload.repository.name, + org: context.repo.owner, + }; + + let commentBody = `${{ inputs.first_pr_comment }}`; + Object.entries(variables).forEach(([key, value]) => { + const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g'); + commentBody = commentBody.replace(regex, value); + }); + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + + # Leave a comment when PR is marked ready for review + ready-for-review-comment: + name: Ready for Review Comment + needs: check-contributor + if: needs.check-contributor.outputs.event_type == 'ready_for_review' && needs.check-contributor.outputs.is_external == 'true' && inputs.ready_for_review_comment != '' + runs-on: ubuntu-latest + steps: + - name: Leave ready for review comment + uses: actions/github-script@v6 + with: + github-token: ${{ github.token }} + script: | + const variables = { + username: context.payload.pull_request.user.login, + repository: context.payload.repository.name, + org: context.repo.owner, + }; + + let commentBody = `${{ inputs.ready_for_review_comment }}`; + Object.entries(variables).forEach(([key, value]) => { + const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g'); + commentBody = commentBody.replace(regex, value); + }); + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + + # Leave a comment when PR is merged + merged-pr-comment: + name: Merged PR Comment + needs: check-contributor + if: needs.check-contributor.outputs.event_type == 'merged' && needs.check-contributor.outputs.is_external == 'true' && inputs.merged_pr_comment != '' + runs-on: ubuntu-latest + steps: + - name: Leave merged PR comment + uses: actions/github-script@v6 + with: + github-token: ${{ github.token }} + script: | + const variables = { + username: context.payload.pull_request.user.login, + repository: context.payload.repository.name, + org: context.repo.owner, + }; + + let commentBody = `${{ inputs.merged_pr_comment }}`; + Object.entries(variables).forEach(([key, value]) => { + const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g'); + commentBody = commentBody.replace(regex, value); + }); + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); diff --git a/README.md b/README.md index eecf790..f9244cd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,164 @@ -# auto-comments -Reusable workflow for automated comments on Pull Requests in Github +# Automated PR Comments for GitHub Actions + +This reusable workflow adds automated comments on Pull Requests from external contributors. It identifies external contributors as users who are not members of your GitHub organization. + +## Features + +The workflow can leave comments in these situations: + +- On a contributor's **first** Pull Request to your repository +- When a Pull Request is marked as **ready for review** +- When a Pull Request is **merged** + +Each comment type can be individually enabled or disabled. + +## Setup + +To use this workflow in your repository, create a new workflow file in your `.github/workflows` directory: + +```yaml +name: PR Comments + +on: + pull_request_target: + types: [opened, ready_for_review, closed] + +jobs: + pr-comments: + uses: your-org/auto-comments/.github/workflows/pr-auto-comments.yml@main + with: + org_name: "your-organization-name" + # Optional: override the default comments + first_pr_comment: | + # Welcome to our project! + + Thanks for your first contribution, @{{username}}. We're glad you're here. + secrets: + token: ${{ secrets.GITHUB_TOKEN }} +``` + +## Workflow Updates and Versioning + +This workflow uses a reference to the branch (`@main`) rather than a specific version tag. This means: + +- **Automatic Updates**: When the workflow code is updated in the `main` branch, all repositories referencing it will automatically use the latest version without requiring any changes in those repositories. +- **Breaking Changes**: Be cautious when making changes to the workflow in the `main` branch, as they will immediately affect all dependent repositories. Test significant changes thoroughly before merging them into `main`. + +### Recommendations for Stability + +If you need more stability and control over updates, consider: +1. Using version tags (e.g., `@v1`, `@v2`) instead of `@main`. +2. Having repositories explicitly opt-in to new versions by updating their workflow reference. + +For example, to use a specific version tag: +```yaml +uses: your-org/auto-comments/.github/workflows/pr-auto-comments.yml@v1 +``` + +## Configuration + +### Required Inputs + +| Input | Description | +|-------|-------------| +| `org_name` | GitHub organization name to identify internal team members | + +### Optional Inputs + +| Input | Description | Default | Behavior if empty string | +|-------|-------------|---------|--------------------------| +| `additional_internal_users` | Additional comma-separated list of usernames to consider as internal | `''` | No additional users excluded | +| `first_pr_comment` | Message for first PR comments | Default welcome message | First PR comments disabled | +| `ready_for_review_comment` | Message for ready for review comments | Default guidelines message | Ready for review comments disabled | +| `merged_pr_comment` | Message for merged PR comments | Default thank you message | Merged PR comments disabled | + +### Secrets + +| Secret | Description | Required | Default | +|--------|-------------|----------|---------| +| `token` | GitHub token with org:read permission | No | `github.token` | + +## Default Messages + +The workflow includes default messages for each comment type: + +### First PR Comment +A welcome message that mentions the contributor by username, introduces them to the project, and highlights the Best PR Initiative with its $500 quarterly prize. + +### Ready for Review Comment +A reminder about contribution guidelines and the Best PR Initiative, encouraging clear PR descriptions to expedite the review process. + +### Merged PR Comment +A congratulatory message that thanks the contributor, reminds them about the Best PR Initiative, and promotes the Request Network API for crypto payments and invoicing features. + +## Enabling and Disabling Comment Types + +By default, all comment types are enabled with predefined messages. You can: + +- **Override** a default comment by providing your own message text +- **Disable** a comment type by providing an empty string `''` + +For example, to disable ready for review comments: + +```yaml +ready_for_review_comment: '' # Explicitly set to empty string to disable +``` + +## Dynamic Content with Variable Placeholders + +You can include dynamic content in your messages using placeholders with the format `{{variable}}`. The following variables are available: + +| Variable | Description | Example | +|----------|-------------|---------| +| `{{username}}` | The PR author's username | `octocat` | +| `{{repository}}` | The repository name | `auto-comments` | +| `{{org}}` | The organization/owner name | `your-org` | + +Example usage in a custom message: + +```yaml +first_pr_comment: | + # Welcome @{{username}}! + + Thank you for your first contribution to the {{repository}} repository. + We at {{org}} appreciate your interest in our project. +``` + +## Comment Formatting + +You can use full Markdown syntax in your comment messages, including: + +- Headings (`# Heading`) +- Lists (`- Item`) +- Formatting (**bold**, *italic*) +- Links (`[text](url)`) +- Code blocks +- Emojis (`:tada:`) + +## Special Placeholders + +The first PR comment supports the `@` placeholder, which will be automatically replaced with the PR author's username. + +## How It Works + +1. The workflow first checks the PR author in a central job: + - Determines if they are an external contributor (not in your org or additional users list) + - For first PR comments, checks if this is their first contribution + +2. Based on the event type (opened/ready for review/merged) and author status: + - Runs only the appropriate comment job + - Only if the corresponding comment text is not empty + +3. Each enabled job posts its specific comment to the PR + +This architecture ensures contributor checks run only once per workflow execution, making the process more efficient. + +## Security Considerations + +This workflow uses `pull_request_target` to ensure it has the necessary permissions to comment on PRs. Since this event has access to secrets, the workflow is designed to only perform safe operations (commenting) and does not check out code from external PRs. + +The workflow requires a token with `org:read` permission to check organization membership. + +## License + +MIT diff --git a/example-usage.yml b/example-usage.yml new file mode 100644 index 0000000..eaad5eb --- /dev/null +++ b/example-usage.yml @@ -0,0 +1,41 @@ +name: PR Comments + +on: + pull_request_target: + types: [opened, ready_for_review, closed] + +jobs: + # Example 1: Using default comments + pr-comments-defaults: + uses: your-org/auto-comments/.github/workflows/pr-auto-comments.yml@main + with: + org_name: "your-organization-name" + additional_internal_users: "external-consultant,bot-account" + # No comment content provided - will use defaults + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + # Example 2: Customizing some comments, disabling others + pr-comments-custom: + uses: your-org/auto-comments/.github/workflows/pr-auto-comments.yml@main + with: + org_name: "your-organization-name" + + # Custom first PR comment with variable placeholders + first_pr_comment: | + # Welcome @{{username}}! + + Thank you for your first contribution to the {{repository}} repository. + Our {{org}} team will review your changes soon. + + # Disable ready for review comments + ready_for_review_comment: '' + + # Custom merged PR comment with variable placeholders + merged_pr_comment: | + # Congratulations on your merged PR! 🎉 + + Thank you @{{username}} for your contribution to {{repository}}! + Your changes are now part of the {{org}} codebase. + secrets: + token: ${{ secrets.GITHUB_TOKEN }}