diff --git a/.github/workflows/community-label.yml b/.github/workflows/community-label.yml index ac41cdd5757..61d8c130b29 100644 --- a/.github/workflows/community-label.yml +++ b/.github/workflows/community-label.yml @@ -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' ] - } ); \ No newline at end of file + run: node .github/workflows/scripts/is-community-contributor.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scripts/is-community-contributor.js b/.github/workflows/scripts/is-community-contributor.js index 8858d0f5ec2..8cb9501cdd1 100644 --- a/.github/workflows/scripts/is-community-contributor.js +++ b/.github/workflows/scripts/is-community-contributor.js @@ -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; \ No newline at end of file +applyLabelToCommunityContributor();