diff --git a/.github/workflows/require-assigned-issue.yml b/.github/workflows/require-assigned-issue.yml new file mode 100644 index 0000000000..8f5f5d684f --- /dev/null +++ b/.github/workflows/require-assigned-issue.yml @@ -0,0 +1,89 @@ +# This action requires that any PR references an issue via GitHub's closing +# keywords (e.g. "Fixes #1234") or manual linking, and that the PR author is +# assigned to that issue. PRs that don't meet both conditions are closed +# automatically. Draft PRs are exempt until marked ready for review. +# See CONTRIBUTING.md#issues-and-assignment for the full process. +# +# This uses pull_request_target (rather than pull_request) because PRs from +# forks only get a read-only GITHUB_TOKEN under pull_request, and commenting +# on / closing the PR requires write access. The job never checks out PR +# code, only calls the GitHub API, so this is safe to do. + +name: require-assigned-issue + +on: + pull_request_target: + types: [opened, reopened, synchronize, edited, ready_for_review] + +permissions: + contents: read + +jobs: + require-assigned-issue: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + issues: read + pull-requests: write + steps: + - name: Check for a linked, assigned issue + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const { owner, repo } = context.repo; + const pr = context.payload.pull_request; + const author = pr.user.login; + + const exemptActors = ["dependabot[bot]", "otelbot[bot]"]; + if (exemptActors.includes(author)) { + return; + } + + const { repository } = await github.graphql( + `query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + closingIssuesReferences(first: 10) { + nodes { + number + assignees(first: 20) { + nodes { login } + } + } + } + } + } + }`, + { owner, repo, number: pr.number } + ); + + const linkedIssues = repository.pullRequest.closingIssuesReferences.nodes; + const isAssignedToOne = linkedIssues.some((issue) => + issue.assignees.nodes.some((assignee) => assignee.login === author) + ); + + if (linkedIssues.length > 0 && isAssignedToOne) { + return; + } + + const contributingUrl = + "https://github.com/open-telemetry/opentelemetry-python/blob/main/CONTRIBUTING.md#issues-and-assignment"; + + const body = + linkedIssues.length === 0 + ? `Thanks for opening this PR! Every PR in this repository needs to reference an issue that has already been discussed and assigned to its author (see [CONTRIBUTING.md](${contributingUrl})).\n\nThis PR doesn't appear to be linked to an issue yet. Please open an issue describing the change (if one doesn't already exist), get assigned to it by a maintainer or approver, and link it from this PR's description (e.g. \`Fixes #1234\`).\n\nClosing for now — feel free to reopen once the above is in place.` + : `Thanks for opening this PR! It references ${linkedIssues.map((issue) => `#${issue.number}`).join(", ")}, but you aren't currently assigned to ${linkedIssues.length > 1 ? "any of those issues" : "that issue"}.\n\nPer our [contribution process](${contributingUrl}), a maintainer or approver needs to assign you to the issue before a PR is opened against it. Please ask on the issue, on [Slack](https://slack.cncf.io/), or during the Python SIG meeting, and reopen this PR once you've been assigned.`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pr.number, + body, + }); + + await github.rest.pulls.update({ + owner, + repo, + pull_number: pr.number, + state: "closed", + }); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f44e180455..b352ae8db6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -204,6 +204,39 @@ file in the package it is testing. Make sure that the file name begins with ## Pull Requests +### Issues and Assignment + +Every pull request in this repository needs to be tied to an issue, and its author needs to be +assigned to that issue. The process works like this: + +1. **Anyone can open an issue.** When you do, please explain what you would like to see addressed. + This gives maintainers, approvers and the rest of the community a chance to discuss the idea and + reach consensus on whether it is something we want to do, and how. +2. **Once the issue is considered valid**, an [Approver](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) + or [Maintainer](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer) + will assign it to whoever is going to work on the corresponding pull request. If you would like + to be the one implementing it, say so on the issue, or reach out on [Slack](https://slack.cncf.io/) + or during the Python SIG meeting, and a maintainer will assign it to you. +3. **Every pull request must reference the issue it addresses**, and its author must be the + assignee of that issue. Pull requests that are opened without a referenced issue, or whose + author is not assigned to that issue, will be closed automatically. Draft pull requests opened + to help illustrate a proposal that is still being discussed on its issue are exempt from this + until they are marked ready for review. + +We want to be clear that this process is not meant to discourage contributions or add unnecessary +bureaucracy — everyone is still very welcome to open issues and work on them. It exists because the +number of pull requests being generated with the help of AI coding tools has grown substantially, +and this makes it difficult for our relatively small group of maintainers and approvers to review +everything with the attention it deserves. Reaching agreement on an issue before any code is +written lets us focus our limited review time on changes the community has already agreed are +worth making. + +If writing some code helps you illustrate what you have in mind, you are welcome to open a +[draft pull request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) and link it +from the issue discussion, even before the issue has been assigned. It is a great way to make a +proposal concrete while it is still being discussed. Once the issue reaches consensus and is +assigned, you can mark the pull request ready for review. + ### How to Structure Pull Requests Smaller PRs get merged faster, improve review quality, and reduce the risk of conflicts. Please keep PRs small and focused: