Skip to content

Commit

Permalink
feat: add pr-label-by-review
Browse files Browse the repository at this point in the history
  • Loading branch information
EdieLemoine committed Apr 20, 2023
1 parent b16462c commit bafc1e9
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ This is a collection of reusable composite actions for GitHub Actions workflows.
- [GitHub](#github)
- [get-github-token](#get-github-token)
- [pr-assign-author](#pr-assign-author)
- [pr-label-by-review](#pr-label-by-review)
- [pr-validate-title-conventional](#pr-validate-title-conventional)
- [Miscellaneous](#miscellaneous)
- [bundlewatch](#bundlewatch)
Expand Down Expand Up @@ -654,6 +655,49 @@ Assign the author of a pull request to the pull request. For use with the `pull_
| false | `app-id` | The app ID of the app. | `${{ secrets.APP_ID }}` ||
| false | `private-key` | The private key of the app. | `${{ secrets.APP_PRIVATE_KEY }}` ||

#### pr-label-by-review

[Source](pr-label-by-review/action.yml)

Label a pull request based on the review state. For use with the `pull_request_review` event.

1. Gets the amount of reviews needed.
- Uses `inputs.reviews-required` if passed.
- If this input does not exist, the action will get the data via branch protections. You'll need an app or access token with the `read settings` permission for this.
2. Calculates whether the PR is approved or changes are requested.
3. Labels the PR accordingly, removing labels that are no longer relevant.
- If the PR is approved: `inputs.label-approved` is added, `inputs.label-changes-requested` is removed if it exists.
- If changes are requested: `inputs.label-changes-requested` is added, `inputs.label-approved` is removed if it exists.
- If the PR is not approved and no changes are requested: `inputs.label-approved` and `inputs.label-changes-requested` are removed if they exist.

##### Example

```yaml
- uses: myparcelnl/actions/pr-label-by-review@v3
with:
# Either a GitHub token or a GitHub app is required.
token: ${{ secrets.GITHUB_TOKEN }}

# Omit `token` and pass the following if you want to use a GitHub app:
app-id: ${{ secrets.GITHUB_APP_ID }}
private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}

label-approved: 'yay' # The label to add when the PR is approved.
label-changes-requested: 'nay' # The label to add when changes are requested.
reviews-required: 2 # The number of reviews required to merge the PR. Should be passed only if you don't have an access token or app with the read settings permission.
```

##### Inputs

| Required | Name | Description | Example | Default |
| -------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------- |
| false | `token` | GitHub token to use. If passed, takes precedence over the `app-id` and `app-private-key` inputs. | `${{ secrets.GITHUB_TOKEN }}` ||
| false | `app-id` | The app ID of the app. | `${{ secrets.APP_ID }}` ||
| false | `private-key` | The private key of the app. | `${{ secrets.APP_PRIVATE_KEY }}` ||
| false | `reviews-required` | The number of reviews required to merge the PR. Can be passed if you don't have an access token or app with the read settings permission. | `2` ||
| false | `label-approved` | The label to add when the PR is approved. | `ready to merge` | `approved` |
| false | `label-changes-requested` | The label to add when changes are requested. | `needs work` | `changes requested` |

#### pr-validate-title-conventional

[Source](pr-validate-title-conventional/action.yml)
Expand Down
140 changes: 140 additions & 0 deletions pr-label-by-review/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: 'Label PR based on approvals'
description: 'Adds a label to a PR when it is approved or changes are requested.'

inputs:
token:
description: 'GitHub token to use. If passed, takes precedence over the `app-id` and `app-private-key` inputs.'

app-id:
description: 'The app ID of the app.'

private-key:
description: 'The private key of the app.'

required-approvals:
description: 'Amount of approvals needed. If passed, skips fetching branch protections.'

label-approved:
description: 'Label to add when PR status is approved.'
default: 'approved'

label-changes-requested:
description: 'Label to add when PR status is changes requested.'
default: 'changes requested'

runs:
using: composite
steps:
- uses: actions/checkout@v3

- uses: myparcelnl/actions/get-github-token@v3
id: token
with:
token: ${{ inputs.token }}
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}

- name: 'Get required approvals from branch protection'
id: branch-protection
if: inputs.required-approvals == ''
uses: actions/github-script@v6
with:
github-token: ${{ steps.token.outputs.token }}
script: |
const branchProtection = await github.rest.repos.getBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: context.payload.pull_request.base.ref
});
const requiredApprovals = branchProtection.data.required_pull_request_reviews?.required_approving_review_count ?? 0;
core.setOutput('required-approvals', requiredApprovals);
- name: 'Collect pull request reviews'
id: check
uses: actions/github-script@v6
with:
github-token: ${{ steps.token.outputs.token }}
script: |
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
// Filter out duplicate reviews from the same user and keep only the last review from each user
const userReviews = new Map();
reviews.data.forEach(review => {
const { login } = review.user;
if (!userReviews.has(login)) {
userReviews.set(login, review);
} else if (userReviews.get(login).submitted_at < review.submitted_at) {
userReviews.set(login, review);
}
});
const userReviewsArray = Array.from(userReviews.values());
const changesRequested = userReviewsArray.some(review => review.state === 'CHANGES_REQUESTED');
const approvals = userReviewsArray.filter(review => review.state === 'APPROVED');
const requiredApprovals = Number(${{ inputs.required-approvals || steps.branch-protection.outputs.required-approvals }});
const isApproved = !changesRequested && approvals.length >= requiredApprovals;
core.setOutput('approved', isApproved ? 'true' : 'false');
core.setOutput('changes-requested', changesRequested ? 'true' : 'false');
- name: 'Add "approved" label'
uses: actions/github-script@v6
if: steps.check.outputs.approved == 'true' && !contains(github.event.pull_request.labels.*.name, inputs.label-approved)
with:
github-token: ${{ steps.token.outputs.token }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['${{ inputs.label-approved }}']
});
- name: 'Add "changes requested" label'
uses: actions/github-script@v6
if: steps.check.outputs.changes-requested == 'true' && !contains(github.event.pull_request.labels.*.name, inputs.label-changes-requested)
with:
github-token: ${{ steps.token.outputs.token }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['${{ inputs.label-changes-requested }}']
});
- name: 'Remove "approved" label'
uses: actions/github-script@v6
if: contains(github.event.pull_request.labels.*.name, inputs.label-approved) && (steps.check.outputs.approved == 'false')
with:
github-token: ${{ steps.token.outputs.token }}
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: '${{ inputs.label-approved }}'
});
- name: 'Remove "changes requested" label'
uses: actions/github-script@v6
if: contains(github.event.pull_request.labels.*.name, inputs.label-changes-requested) && (steps.check.outputs.changes-requested == 'false')
with:
github-token: ${{ steps.token.outputs.token }}
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: '${{ inputs.label-changes-requested }}'
});

0 comments on commit bafc1e9

Please sign in to comment.