Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: github action to check if PR has requested labels before being merged #19984

Merged
merged 6 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
DDDDDanica marked this conversation as resolved.
Show resolved Hide resolved

- 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 @@ -95,7 +95,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": {
"@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch",
Expand Down