Label community contributed PRs (#34971)
This commit is contained in:
parent
91e1347aab
commit
eb6fa6367f
|
@ -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 }}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue