diff --git a/.github/scripts/bot-gfi-assign-on-comment.js b/.github/scripts/bot-gfi-assign-on-comment.js new file mode 100644 index 000000000..14d0bbf07 --- /dev/null +++ b/.github/scripts/bot-gfi-assign-on-comment.js @@ -0,0 +1,98 @@ +// .github/scripts/gfi_assign_on_comment.js +// +// Assigns human user to Good First Issue when they comment "/assign". +// Posts a comment if the issue is already assigned. +// All other validation and additional GFI comments are handled by other existing bots which can be refactored with time. + +const GOOD_FIRST_ISSUE_LABEL = 'Good First Issue'; +const UNASSIGNED_GFI_SEARCH_URL = + 'https://github.com/hiero-ledger/hiero-sdk-python/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Good%20First%20Issue%22%20no%3Aassignee'; + +/// HELPERS FOR ASSIGNING /// + +/** + * Returns true if /assign appears at the start of a line or comment + * Optionally preceded or followed by whitespace + */ +function commentRequestsAssignment(body) { + return typeof body === 'string' && + /(^|\n)\s*\/assign(\s|$)/i.test(body); +} + +/** + * Returns true if the issue has the good first issue label. + */ +function issueIsGoodFirstIssue(issue) { + return issue?.labels?.some(label => label.name === GOOD_FIRST_ISSUE_LABEL); +} + +/// HELPERS FOR COMMENTING /// + +/** + * Returns a formatted @username for the current assignee of the issue. + */ +function getCurrentAssigneeMention(issue) { + const login = issue?.assignees?.[0]?.login; + return login ? `@${login}` : 'someone'; +} + +/** + * Builds a comment explaining that the issue is already assigned. + * Requester username is passed from main + */ +function commentAlreadyAssigned(requesterUsername, issue) { + return ( + `Hi @${requesterUsername} โ€” this issue is already assigned to ${getCurrentAssigneeMention(issue)}, so I canโ€™t assign it again. + +๐Ÿ‘‰ **Choose a different Good First Issue to work on next:** +[Browse unassigned Good First Issues](${UNASSIGNED_GFI_SEARCH_URL}) + +Once you find one you like, comment \`/assign\` to get started.` + ); +} + + +/// START OF SCRIPT /// +module.exports = async ({ github, context }) => { + const { issue, comment } = context.payload; + const { owner, repo } = context.repo; + + // Reject if issue, comment or comment user is missing, reject bots, or if no /assign message + if ( + !issue?.number || + !comment?.body || + !comment?.user?.login || + comment.user.type === 'Bot' || + !commentRequestsAssignment(comment.body) + ) { + return; + } + // Reject if issue is not a Good First Issue + if (!issueIsGoodFirstIssue(issue)) return; + + // Get requester username and issue number to enable comments and assignments + const requesterUsername = comment.user.login; + const issueNumber = issue.number; + + // Reject if issue is already assigned + // Comment failure to the requester + if (issue.assignees?.length > 0) { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body: commentAlreadyAssigned(requesterUsername, issue), + }); + return; + } + + // All validations passed and user has requested assignment on a GFI + // Assign the issue to the requester + // Do not comment on success + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: issueNumber, + assignees: [requesterUsername], + }); +}; diff --git a/.github/workflows/bot-gfi-assign-on-comment.yml b/.github/workflows/bot-gfi-assign-on-comment.yml new file mode 100644 index 000000000..3bd35dbb6 --- /dev/null +++ b/.github/workflows/bot-gfi-assign-on-comment.yml @@ -0,0 +1,38 @@ +name: GFI Assign on /assign + +on: + issue_comment: + types: + - created + +permissions: + issues: write + contents: read + +jobs: + gfi-assign: + # Only run on issue comments (not PR comments) + if: github.event.issue.pull_request == null + + runs-on: ubuntu-latest + + # Prevent race conditions: always wait for the first assignment request to finish processing + concurrency: + group: gfi-assign-${{ github.event.issue.number }} + cancel-in-progress: false + + steps: + - name: Harden runner + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Run GFI /assign handler + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0 + with: + script: | + const script = require('./.github/scripts/bot-gfi-assign-on-comment.js'); + await script({ github, context }); diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e6dc52a8..7b658bfff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. ## [Unreleased] ### Added +- Enable auto assignment to good first issues (#1312) - Added unit test for 'endpoint.py' to increase coverage. - Automated assignment guard for `advanced` issues; requires completion of at least one `good first issue` and one `intermediate` issue before assignment (exempts maintainers, committers, and triage members). (#1142) - Added Hbar object support for TransferTransaction HBAR transfers: