Skip to content

Commit

Permalink
feat: github action to check if PR has requested labels before being …
Browse files Browse the repository at this point in the history
…merged (#19984)

* feat(action): check if pr includes requested labels

* fix(action): add missing QA label

* fix(action): check if no label prevents merging

* fix(action): remove QA label check

* fix(action): add missing reopened condition

* fix(action): increase list of labels which prevent merges
  • Loading branch information
gauthierpetetin authored Jul 28, 2023
1 parent 1a21871 commit 0c25193
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 18 deletions.
97 changes: 97 additions & 0 deletions .github/scripts/check-pr-has-required-labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as core from '@actions/core';
import { context, getOctokit } from '@actions/github';
import { GitHub } from '@actions/github/lib/utils';

main().catch((error: Error): void => {
console.error(error);
process.exit(1);
});

async function main(): Promise<void> {
// "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions.
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed('GITHUB_TOKEN not found');
process.exit(1);
}

// Initialise octokit, required to call Github GraphQL API
const octokit: InstanceType<typeof GitHub> = getOctokit(githubToken);

// Retrieve pull request info from context
const prRepoOwner = context.repo.owner;
const prRepoName = context.repo.repo;
const prNumber = context.payload.pull_request?.number;
if (!prNumber) {
core.setFailed('Pull request number not found');
process.exit(1);
}

// Retrieve pull request labels
const prLabels = await retrievePullRequestLabels(octokit, prRepoOwner, prRepoName, prNumber);

const preventMergeLabels = ["needs-qa", "QA'd but questions", "issues-found", "need-ux-ds-review", "blocked", "stale", "DO-NOT-MERGE"];

let hasTeamLabel = false;

// Check pull request has at least required QA label and team label
for (const label of prLabels) {
if (label.startsWith("team-") || label === "external-contributor") {
console.log(`PR contains a team label as expected: ${label}`);
hasTeamLabel = true;
}
if (preventMergeLabels.includes(label)) {
throw new Error(`PR cannot be merged because it still contains this label: ${label}`);
}
if (hasTeamLabel) {
return;
}
}

// Otherwise, throw an arror to prevent from merging
let errorMessage = '';
if (!hasTeamLabel) {
errorMessage += 'No team labels found on the PR. ';
}
errorMessage += 'Please add the required label(s) before merging the PR.';
throw new Error(errorMessage);

}

// This function retrieves the pull request on a specific repo
async function retrievePullRequestLabels(octokit: InstanceType<typeof GitHub>, repoOwner: string, repoName: string, prNumber: number): Promise<string[]> {

const retrievePullRequestLabelsQuery = `
query RetrievePullRequestLabels($repoOwner: String!, $repoName: String!, $prNumber: Int!) {
repository(owner: $repoOwner, name: $repoName) {
pullRequest(number: $prNumber) {
labels(first: 100) {
nodes {
name
}
}
}
}
}
`;

const retrievePullRequestLabelsResult: {
repository: {
pullRequest: {
labels: {
nodes: {
name: string;
}[];
}
};
};
} = await octokit.graphql(retrievePullRequestLabelsQuery, {
repoOwner,
repoName,
prNumber,
});

const pullRequestLabels = retrievePullRequestLabelsResult?.repository?.pullRequest?.labels?.nodes?.map(labelObject => labelObject?.name);

return pullRequestLabels || [];
}
38 changes: 38 additions & 0 deletions .github/workflows/check-pr-labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: "Check PR has required labels"
on:
pull_request:
branches:
- main
types:
- opened
- reopened
- synchronize
- labeled
- unlabeled

jobs:
check-pr-labels:
runs-on: ubuntu-latest
permissions:
pull-requests: read

steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # This is needed to checkout all branches

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn

- name: Install dependencies
run: yarn --immutable

- name: Check PR has required labels
id: check-pr-has-required-labels
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run check-pr-has-required-labels
17 changes: 0 additions & 17 deletions .github/workflows/do-not-merge.yml

This file was deleted.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"fitness-functions": "ts-node development/fitness-functions/index.ts",
"generate-beta-commit": "node ./development/generate-beta-commit.js",
"validate-branch-name": "validate-branch-name",
"add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts"
"add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts",
"check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts"
},
"resolutions": {
"simple-update-notifier@^1.0.0": "^2.0.0",
Expand Down

0 comments on commit 0c25193

Please sign in to comment.