Label community contributed PRs (#34971)
This commit is contained in:
parent
91e1347aab
commit
eb6fa6367f
|
@ -1,10 +1,11 @@
|
||||||
name: Add Community Label
|
name: Add Community Label
|
||||||
|
|
||||||
on:
|
on:
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
@ -18,43 +19,16 @@ jobs:
|
||||||
issueId: ${{ steps.check.outputs.issueId }}
|
issueId: ${{ steps.check.outputs.issueId }}
|
||||||
run: ${{ steps.check.outputs.run }}
|
run: ${{ steps.check.outputs.run }}
|
||||||
steps:
|
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
|
- name: Check if user is a community contributor
|
||||||
id: check
|
run: node .github/workflows/scripts/is-community-contributor.js
|
||||||
uses: actions/github-script@v6
|
env:
|
||||||
with:
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
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' ]
|
|
||||||
} );
|
|
||||||
|
|
|
@ -1,92 +1,49 @@
|
||||||
const getUserFromContext = ( types, context ) => {
|
// Note you'll need to install this dependency as part of your workflow.
|
||||||
for ( const type of types ) {
|
const { Octokit } = require('@octokit/action');
|
||||||
if ( context.payload[ type ] && context.payload[ type ].user && context.payload[ type ].user.login ) {
|
|
||||||
return context.payload[ type ].user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 ) => {
|
const getIssueAuthor = (payload) => {
|
||||||
console.log( 'Checking for user %s in org %s', username, org );
|
return payload?.issue?.user?.login || payload?.pull_request?.user?.login || null;
|
||||||
try {
|
}
|
||||||
// First attempt to check memberships.
|
|
||||||
const result = await github.request( 'GET /orgs/{org}/memberships/{username}', {
|
const isCommunityContributor = async (owner, repo, username) => {
|
||||||
org,
|
if (username) {
|
||||||
|
const {data: {permission}} = await octokit.rest.repos.getCollaboratorPermissionLevel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
username,
|
username,
|
||||||
} );
|
});
|
||||||
|
|
||||||
if ( result && Math.floor( result.status / 100 ) === 2 ) {
|
return permission === 'read' || permission === 'none';
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( checked !== orgs.length ) {
|
return false;
|
||||||
throw new Error( "Unable to check user's membership in all orgs." );
|
}
|
||||||
|
|
||||||
|
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;
|
applyLabelToCommunityContributor();
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
Loading…
Reference in New Issue