Label community contributed PRs (#34971)

This commit is contained in:
Sam Seay 2022-10-10 11:34:52 +13:00 committed by GitHub
parent 91e1347aab
commit eb6fa6367f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 125 deletions

View File

@ -1,10 +1,11 @@
name: Add Community Label
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
issues:
types: [opened]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -18,43 +19,16 @@ jobs:
issueId: ${{ steps.check.outputs.issueId }}
run: ${{ steps.check.outputs.run }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Setup Node.js
id: check
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93
- name: Install Octokit
run: npm --prefix .github/workflows/scripts install @octokit/action
- name: Check if user is a community contributor
id: check
uses: actions/github-script@v6
with:
script: |
const isCommunityContributor = require( './.github/workflows/scripts/is-community-contributor.js' );
const config = {
types: [ 'pull_request', 'issue' ],
orgs: [ 'woocommerce', 'automattic' ],
};
( async ( { github, context, config } ) => {
try {
const isCommunity = await isCommunityContributor( { github, context, config } );
console.log( '::set-output name=run::%s', isCommunity ? 'true' : 'false' );
} catch ( e ) {
console.log( '::set-output name=run::false' );
}
} )( { github, context, config } );
label_community:
name: Label Community Issues and PRs
runs-on: ubuntu-20.04
needs: verify
if: needs.verify.outputs.run == 'true'
steps:
- name: label
uses: actions/github-script@v6
with:
script: |
github.rest.issues.addLabels( {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [ 'type: community contribution' ]
} );
run: node .github/workflows/scripts/is-community-contributor.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,92 +1,49 @@
const getUserFromContext = ( types, context ) => {
for ( const type of types ) {
if ( context.payload[ type ] && context.payload[ type ].user && context.payload[ type ].user.login ) {
return context.payload[ type ].user;
}
}
return false;
};
// Note you'll need to install this dependency as part of your workflow.
const { Octokit } = require('@octokit/action');
const userIsBot = user => user.type && user.type === 'Bot';
// Note that this script assumes you set GITHUB_TOKEN in env, if you don't
// this won't work.
const octokit = new Octokit();
const userInOrg = async ( github, username, org ) => {
console.log( 'Checking for user %s in org %s', username, org );
try {
// First attempt to check memberships.
const result = await github.request( 'GET /orgs/{org}/memberships/{username}', {
org,
const getIssueAuthor = (payload) => {
return payload?.issue?.user?.login || payload?.pull_request?.user?.login || null;
}
const isCommunityContributor = async (owner, repo, username) => {
if (username) {
const {data: {permission}} = await octokit.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username,
} );
if ( result && Math.floor( result.status / 100 ) === 2 ) {
console.log( 'User %s found in %s via membership check', username, org );
return true;
}
} catch ( error ) {
// A 404 status means the user is definitively not in the organization.
if ( error.status === 404 ) {
console.log( 'User %s NOT found in %s via membership check', username, org );
return false;
}
try {
// For anything else, we should also check public memberships.
const result = await github.request( 'GET /orgs/{org}/public_members/{username}', {
org,
username,
} );
if ( result && Math.floor( result.status / 100 ) === 2 ) {
console.log( 'User %s found in %s via public membership check', username, org );
return true;
}
} catch ( error ) {
if ( error.status === 404 ) {
console.log( 'User %s NOT found in %s via public membership check', username, org );
return false;
}
}
}
// If we've gotten to this point, status is unknown.
throw new Error( "Unable to determine user's membership in org" );
};
const userInNonCommunityOrgs = async ( orgs, github, username ) => {
let checked = 0;
for ( const org of orgs ) {
try {
const isUserInOrg = await userInOrg( github, username, org );
if ( isUserInOrg ) {
return true;
}
// If no error was thrown, increment checked.
checked++;
} catch( e ) {} // Do nothing.
});
return permission === 'read' || permission === 'none';
}
if ( checked !== orgs.length ) {
throw new Error( "Unable to check user's membership in all orgs." );
return false;
}
const addLabel = async(label, owner, repo, issueNumber) => {
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: [label],
});
}
const applyLabelToCommunityContributor = async () => {
const eventPayload = require(process.env.GITHUB_EVENT_PATH);
const username = getIssueAuthor(eventPayload);
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
const { number } = eventPayload?.issue || eventPayload?.pull_request;
const isCommunityUser = await isCommunityContributor(owner, repo, username);
if (isCommunityUser) {
console.log('Adding community contributor label');
await addLabel('type: community contribution', owner, repo, number);
}
}
return false;
};
const isCommunityContributor = async ( { github, context, config } ) => {
const user = getUserFromContext( config.types, context );
if ( ! user ) {
throw new Error( 'Unable to get user from context' );
}
console.log( 'Checking user %s', user.login );
if ( userIsBot( user ) ) {
// Bots are not community contributors.
console.log( 'User detected as bot, skipping' );
return false;
}
const isInNonCommunityOrgs = await userInNonCommunityOrgs( config.orgs, github, user.login );
return ! isInNonCommunityOrgs;
};
module.exports = isCommunityContributor;
applyLabelToCommunityContributor();