Skip to content

Commit

Permalink
feat(github-actions): create an action to rerequest reviews for post …
Browse files Browse the repository at this point in the history
…approval changes for non-googlers

For non-googlers, if a change occurs after the approval has been given, the latest review is rerequested
to confirm that the approval still stands. For googlers, they can continue to make post approval changes
as they have been.
  • Loading branch information
josephperrott committed May 5, 2022
1 parent 01bb903 commit f556a9a
Show file tree
Hide file tree
Showing 9 changed files with 30,777 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/post-approval-changes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Post Approval Changes

on: [pull_request_target]

# Declare default permissions as read only.
permissions: read-all

jobs:
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2
- uses: ./github-actions/post-approval-changes
with:
lock-bot-key: ${{ secrets.LOCK_BOT_PRIVATE_KEY }}
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ github-actions/commit-message-based-labels/main.js
github-actions/commit-message-based-labels/post.js
github-actions/slash-commands/main.js
github-actions/slash-commands/post.js
github-actions/post-approval-changes/main.js
github-actions/post-approval-changes/post.js
tools/local-actions/changelog/main.js
tools/local-actions/changelog/post.js
19 changes: 19 additions & 0 deletions github-actions/post-approval-changes/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("//tools:defaults.bzl", "esbuild_checked_in")

package(default_visibility = ["//github-actions/post-approval-changes:__subpackages__"])

esbuild_checked_in(
name = "main",
entry_point = "//github-actions/post-approval-changes/lib:main.ts",
deps = [
"//github-actions/post-approval-changes/lib",
],
)

esbuild_checked_in(
name = "post",
entry_point = "//github-actions/post-approval-changes/lib:post.ts",
deps = [
"//github-actions/post-approval-changes/lib",
],
)
11 changes: 11 additions & 0 deletions github-actions/post-approval-changes/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: 'Post Approval Changes'
description: 'Ensure that unexpected post approval changes do not occur'
author: 'Angular'
inputs:
angular-robot-key:
description: 'The private key for the Angular Robot Github app.'
required: true
runs:
using: 'node12'
main: 'main.js'
post: 'post.js'
19 changes: 19 additions & 0 deletions github-actions/post-approval-changes/lib/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("//tools:defaults.bzl", "ts_library")

package(default_visibility = ["//github-actions/post-approval-changes:__subpackages__"])

ts_library(
name = "lib",
srcs = glob(
["*.ts"],
exclude = ["*.spec.ts"],
),
deps = [
"//github-actions:utils",
"@npm//@actions/core",
"@npm//@actions/github",
"@npm//@octokit/rest",
"@npm//@octokit/webhooks-types",
"@npm//@types/node",
],
)
116 changes: 116 additions & 0 deletions github-actions/post-approval-changes/lib/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as core from '@actions/core';
import {context} from '@actions/github';
import {PullRequestEvent} from '@octokit/webhooks-types';
import {Octokit} from '@octokit/rest';
import {ANGULAR_ROBOT, getAuthTokenFor} from '../../utils';

/** Allow list of googlers whose pull request are allowed to include post approval changes. */
const googlers = [
'alan-agius4',
'alxhub',
'amysorto',
'AndrewKushnir',
'andrewseguin',
'atscott',
'clydin',
'crisbeto',
'devversion',
'dgp1130',
'dylhunn',
'jelbourn',
'jessicajaniuk',
'josephperrott',
'madleinas',
'MarkTechson',
'mgechev',
'mmalerba',
'pkozlowski-opensource',
'thevis',
'trekladyone',
'twersky',
'wagnermaciel',
'zarend',
];

async function run(): Promise<void> {
if (context.eventName !== 'pull_request_target') {
throw Error('This action can only run for with pull_request_target events');
}
const {pull_request: pr} = context.payload as PullRequestEvent;

if (googlers.includes(pr.user.login)) {
core.info('PR author is a googler, skipping as post approval changes are allowed.');
return;
}

if ([...pr.requested_reviewers, pr.requested_teams].length > 0) {
core.info('Skipping check as there are still pending reviews.');
return;
}

/** Authenticated Github client. */
const client = new Octokit({auth: await getAuthTokenFor(ANGULAR_ROBOT)});
/** The repository and owner for the pull request. */
const {repo, owner} = context.issue;
/** The number of the pull request. */
const pull_number = context.issue.number;

/** List of reviews for the pull request. */
const allReviews = await client.paginate(client.pulls.listReviews, {owner, pull_number, repo});
/** Set of reviewers whose latest review has already been processed. */
const knownReviewers = new Set<string>();
/** The latest approving reviews for each reviewer on the pull request. */
const reviews = allReviews
// Use new instance of array before reversing it.
.concat()
.reverse()
.filter((review) => {
/** The username of the reviewer, since all reviewers are users this should always exist. */
const user = review.user!.login;
// Only consider reviews by Googlers for this check.
if (!googlers.includes(user)) {
return false;
}
if (knownReviewers.has(user)) {
return false;
}
knownReviewers.add(user);
return true;
});

if (reviews.length === 0) {
core.info('Skipping check as their are no reviews on the pull request.');
return;
}

if (reviews.filter((review) => review.state !== 'APPROVED')) {
core.info('Skipping check as there are still non-approved review states.');
return;
}

if (reviews.find((review) => review.commit_id === pr.head.sha)) {
core.info(`Passing check as at least one reviews is for the latest commit on the pull request`);
return;
}

const reviewToRerequest = reviews[0];
core.info(`Requesting a new review from ${reviewToRerequest.user!.login}`);
await client.pulls.requestReviewers({
owner,
pull_number,
repo,
reviewers: [reviewToRerequest.user!.login],
});
}

// Only run if the action is executed in a repository with is in the Angular org. This is in place
// to prevent the action from actually running in a fork of a repository with this action set up.
// Runs triggered via 'workflow_dispatch' are also allowed to run.
if (context.repo.owner === 'angular') {
run();
} else {
core.warning(
'Post Approvals changes check was skipped as this action is only meant to run in repos ' +
'belonging to the Angular organization.',
);
}
7 changes: 7 additions & 0 deletions github-actions/post-approval-changes/lib/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {revokeAuthTokenFor, ANGULAR_LOCK_BOT} from '../../utils';

async function run(): Promise<void> {
await revokeAuthTokenFor(ANGULAR_LOCK_BOT);
}

run();
Loading

0 comments on commit f556a9a

Please sign in to comment.