diff options
Diffstat (limited to 'Meta/label-pull-requests.js')
-rw-r--r-- | Meta/label-pull-requests.js | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/Meta/label-pull-requests.js b/Meta/label-pull-requests.js new file mode 100644 index 0000000000..1c203bc515 --- /dev/null +++ b/Meta/label-pull-requests.js @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org> + * Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +const Label = { + CommunityApproved: "✅ pr-community-approved", + HasConflicts: "⚠️ pr-has-conflicts", + IsBlocked: "⛔️ pr-is-blocked", + MaintainerApprovedButAwaitingCi: "✅ pr-maintainer-approved-but-awaiting-ci", + NeedsReview: "👀 pr-needs-review", + Unclear: "🤔 pr-unclear", + WaitingForAuthor: "⏳ pr-waiting-for-author", +}; + +const subjectiveLabels = [Label.IsBlocked, Label.Unclear]; + +function removeExistingPrLabels(currentLabels, keepSubjectiveLabels) { + return currentLabels.filter( + label => + !label.includes("pr-") || + label === Label.HasConflicts || + (keepSubjectiveLabels && subjectiveLabels.includes(label)) + ); +} + +async function labelsForGenericPullRequestChange(currentLabels) { + const filteredLabels = removeExistingPrLabels(currentLabels, true); + filteredLabels.push(Label.NeedsReview); + return filteredLabels; +} + +async function labelsForPullRequestEffectivelyClosed(currentLabels) { + return removeExistingPrLabels(currentLabels, false); +} + +function apiErrorHandler(error) { + console.log( + "::warning::Encountered error during event handling, not updating labels. Error:", + error + ); +} + +module.exports = ({ github, context }) => { + async function labelsForPullRequestReviewSubmitted(currentLabels, { pull_request, review }) { + let newLabels = currentLabels; + const isBlocked = currentLabels.some(label => label === Label.IsBlocked); + + if (review.state.toLowerCase() === "approved") { + const maintainers = ( + await github.rest.teams.listMembersInOrg({ + org: "SerenityOS", + team_slug: "maintainers", + }) + ).data; + + const approvedByMaintainer = maintainers.some( + maintainerInArray => maintainerInArray.login === review.user.login + ); + + if (approvedByMaintainer) { + newLabels = newLabels.filter( + label => !(label === Label.NeedsReview || label === Label.WaitingForAuthor) + ); + + if (!newLabels.includes(Label.MaintainerApprovedButAwaitingCi)) + newLabels.push(Label.MaintainerApprovedButAwaitingCi); + } else { + if (!newLabels.includes(Label.CommunityApproved)) + newLabels.push(Label.CommunityApproved); + } + } else if (!isBlocked) { + // Remove approval labels. + newLabels = newLabels.filter( + label => + !( + label === Label.CommunityApproved || + label === Label.MaintainerApprovedButAwaitingCi + ) + ); + + if (review.user.login === pull_request.user.login) newLabels.push(Label.NeedsReview); + else newLabels.push(Label.WaitingForAuthor); + } + + return newLabels; + } + + const eventHandlers = { + opened: labelsForGenericPullRequestChange, + reopened: labelsForGenericPullRequestChange, + submitted: labelsForPullRequestReviewSubmitted, + dismissed: labelsForGenericPullRequestChange, + converted_to_draft: labelsForPullRequestEffectivelyClosed, + ready_for_review: labelsForGenericPullRequestChange, + synchronize: labelsForGenericPullRequestChange, // synchronize is triggered when the branch is changed + edited: labelsForGenericPullRequestChange, + review_requested: labelsForGenericPullRequestChange, + closed: labelsForPullRequestEffectivelyClosed, + }; + + const eventName = context.payload.action; + const handlerForCurrentEvent = eventHandlers[eventName]; + + async function updateLabels(currentLabelsAsObjects) { + const currentLabels = []; + currentLabelsAsObjects.forEach(labelObject => currentLabels.push(labelObject.name)); + + const isEffectivelyClosed = + context.payload.pull_request.draft || + context.payload.pull_request.state.toLowerCase() === "closed"; + + const newLabels = await (isEffectivelyClosed + ? labelsForPullRequestEffectivelyClosed(currentLabels, context.payload) + : handlerForCurrentEvent(currentLabels, context.payload)); + + console.log( + `Received '${eventName}' event for ${ + isEffectivelyClosed ? "draft/closed" : "open" + } pull request, changing labels from '${currentLabels}' to '${newLabels}'` + ); + + return github.rest.issues.setLabels({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: newLabels, + }); + } + + if (handlerForCurrentEvent) { + github.rest.issues + .listLabelsOnIssue({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + }) + .then(result => updateLabels(result.data)) + .catch(apiErrorHandler); + } else { + console.log(`::warning::No handler for the '${eventName}' event, not updating labels.`); + } +}; |