| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| import { Label } from "./githubUtils"; | |
| const { NO_MATCHING_LABEL } = require("./constants"); | |
| export interface AssignReviewerOptions { | |
| labels?: (string | Label)[]; | |
| notices?: string[]; | |
| backupReviewers?: { [label: string]: string }; | |
| } | |
| // Custom notices for specific labels | |
| const LABEL_NOTICES: Record<string, string> = { | |
| core: 'This pull request likely touches a core component ("core" label). Please review with scrutiny.', | |
| }; | |
| function formatNotices( | |
| labelToReviewerMapping: any, | |
| options?: AssignReviewerOptions | |
| ): string { | |
| const notices = [...(options?.notices || [])]; | |
| const labels = [ | |
| ...(options?.labels || []), | |
| ...Object.keys(labelToReviewerMapping), | |
| ]; | |
| for (const label of labels) { | |
| const name = (typeof label === "string" ? label : label.name).toLowerCase(); | |
| if (LABEL_NOTICES[name] && !notices.includes(LABEL_NOTICES[name])) { | |
| notices.push(LABEL_NOTICES[name]); | |
| } | |
| } | |
| return notices.length ? `\n${notices.join("\n\n")}\n` : ""; | |
| } | |
| export function allChecksPassed(reviewersToNotify: string[]): string { | |
| return `All checks have passed: @${reviewersToNotify.join(" ")}`; | |
| } | |
| export function assignCommitter(committer: string): string { | |
| return `R: @${committer} for final approval`; | |
| } | |
| export function assignReviewer( | |
| labelToReviewerMapping: any, | |
| options?: AssignReviewerOptions | |
| ): string { | |
| let commentString = "Assigning reviewers:\n\n"; | |
| for (let label in labelToReviewerMapping) { | |
| let reviewer = labelToReviewerMapping[label]; | |
| let backupReviewer = options?.backupReviewers?.[label]; | |
| if (label === NO_MATCHING_LABEL) { | |
| commentString += `R: @${reviewer} added as fallback since no labels match configuration\n`; | |
| } else { | |
| commentString += `R: @${reviewer} for label ${label}.\n`; | |
| } | |
| if (backupReviewer) { | |
| commentString += `Backup reviewer: ${backupReviewer} (if the main reviewer is unresponsive, you can escalate to them by tagging their username)\n`; | |
| } | |
| } | |
| commentString += formatNotices(labelToReviewerMapping, options); | |
| commentString += ` | |
| Note: If you would like to opt out of this review, comment \`assign to next reviewer\`. | |
| Available commands: | |
| - \`stop reviewer notifications\` - opt out of the automated review tooling | |
| - \`remind me after tests pass\` - tag the comment author after tests pass | |
| - \`waiting on author\` - shift the attention set back to the author (any comment or push by the author will return the attention set to the reviewers) | |
| The PR bot will only process comments in the main thread (not review comments).`; | |
| return commentString; | |
| } | |
| export function failingChecksCantAssign(): string { | |
| return "Checks are failing. Will not request review until checks are succeeding. If you'd like to override that behavior, comment `assign set of reviewers`"; | |
| } | |
| export function someChecksFailing(reviewersToNotify: string[]): string { | |
| return `Some checks have failed: @${reviewersToNotify.join(" ")}`; | |
| } | |
| export function stopNotifications(reason: string): string { | |
| return `Stopping reviewer notifications for this pull request: ${reason}. If you'd like to restart, comment \`assign set of reviewers\``; | |
| } | |
| export function remindReviewerAfterTestsPass(requester: string): string { | |
| return `Ok - I'll remind @${requester} after tests pass`; | |
| } | |
| export function reviewersAlreadyAssigned(reviewers: string[]): string { | |
| return `Reviewers are already assigned to this PR: ${reviewers | |
| .map((reviewer) => "@" + reviewer) | |
| .join(" ")}`; | |
| } | |
| export function noLegalReviewers(): string { | |
| return "No reviewers could be found from any of the labels on the PR or in the fallback reviewers list. Check the config file to make sure reviewers are configured"; | |
| } | |
| export function updateReviewerConfig( | |
| reviewersAddedForLabels: { [reviewer: string]: string[] }, | |
| reviewersRemovedForLabels: { [reviewer: string]: string[] } | |
| ) { | |
| let commentString = `Adds and/or removes reviewers based on activity in the repo. | |
| If you have been added and would prefer not to be, you can avoid getting repeatedly suggested by adding yourself to that label's exclusionList. | |
| `; | |
| if (Object.keys(reviewersAddedForLabels).length > 0) { | |
| commentString += ` | |
| The following users have been added as reviewers to the configuration. | |
| If you choose to accept being added, you will be added to the rotation of users who are automatically added to pull requests for an initial review. | |
| A committer will still have to approve after your review (if you are not a committer), but you will be the initial touchpoint for PRs to which you are assigned. | |
| `; | |
| for (const reviewer of Object.keys(reviewersAddedForLabels)) { | |
| commentString += `${reviewer} added for label(s): ${reviewersAddedForLabels[ | |
| reviewer | |
| ].join(",")}\n`; | |
| } | |
| } | |
| if (Object.keys(reviewersRemovedForLabels).length > 0) { | |
| commentString += ` | |
| The following users have been removed as reviewers from the configuration. | |
| Users are removed if they haven't reviewed or completed a PR in the last 3 months. | |
| `; | |
| for (const reviewer of Object.keys(reviewersRemovedForLabels)) { | |
| commentString += `@${reviewer} removed for label(s): ${reviewersRemovedForLabels[ | |
| reviewer | |
| ].join(",")}\n`; | |
| } | |
| } | |
| return commentString; | |
| } | |
| export function assignNewReviewer( | |
| labelToReviewerMapping: { | |
| [label: string]: string; | |
| }, | |
| options?: AssignReviewerOptions | |
| ): string { | |
| let commentString = | |
| "Assigning new set of reviewers because Pr has gone too long without review. If you would like to opt out of this review, comment `assign to next reviewer`:\n\n"; | |
| for (const label in labelToReviewerMapping) { | |
| const reviewer = labelToReviewerMapping[label]; | |
| const backupReviewer = options?.backupReviewers?.[label]; | |
| if (label === NO_MATCHING_LABEL) { | |
| commentString += `R: @${reviewer} added as fallback since no labels match configuration\n`; | |
| } else { | |
| commentString += `R: @${reviewer} for label ${label}.\n`; | |
| } | |
| if (backupReviewer) { | |
| commentString += `Backup reviewer: ${backupReviewer} (if the main reviewer is unresponsive, you can escalate to them by tagging their username)\n`; | |
| } | |
| } | |
| commentString += formatNotices(labelToReviewerMapping, options); | |
| commentString += ` | |
| Available commands: | |
| - \`stop reviewer notifications\` - opt out of the automated review tooling | |
| - \`remind me after tests pass\` - tag the comment author after tests pass | |
| - \`waiting on author\` - shift the attention set back to the author (any comment or push by the author will return the attention set to the reviewers)`; | |
| return commentString; | |
| } | |
| export function slowReview(reviewers: string[]): string { | |
| let commentString = `Reminder, please take a look at this pr: `; | |
| for (const reviewer of reviewers) { | |
| commentString += `@${reviewer} `; | |
| } | |
| return commentString; | |
| } |