-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Project Management: Prompt user to link GitHub account to WordPress.org profile #21221
Merged
Merged
Changes from 6 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
dedce7f
Project Management Automation: Add getAssociatedPullRequest utility
aduth 4e23623
Project Management Automation: Add First-Time Contributor label on push
aduth d3226da
Project Management Automation: Include as dependency in root package
aduth 6d09964
Project Management Automation: Prompt user to link GitHub account to …
aduth 7a5f6ee
Project Management Automation: Update documentation for first contrib…
aduth 9bd7760
Project Management Automation: Check for single commit of author
aduth eb1dff6
Project Management Automation: Fetch profile by https HEAD
aduth 83ef4d6
Project Management Automation: Fix hostname to use hostname
aduth 26ddea3
Project Management Automation: Add hasWordPressProfile tests
aduth 0d338e5
Project Management Automation: Add request User-Agent header
aduth 6bedde8
Project Management Automation: Update contributor prompt text
aduth 7f00af6
Project Management Automation: Rename addFirstTimeContributorLabel to…
aduth d41dbff
Project Management Automation: Expand function comment to include new…
aduth 4493c22
Project Management Automation: Expand CHANGELOG to include rename
aduth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 97 additions & 16 deletions
113
packages/project-management-automation/lib/add-first-time-contributor-label.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,131 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
const got = /** @type {*} */ ( require( 'got' ) ); // See: https://github.com/sindresorhus/got/issues/1137 | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
const debug = require( './debug' ); | ||
const getAssociatedPullRequest = require( './get-associated-pull-request' ); | ||
|
||
/** @typedef {import('@actions/github').GitHub} GitHub */ | ||
/** @typedef {import('@octokit/webhooks').WebhookPayloadPullRequest} WebhookPayloadPullRequest */ | ||
/** @typedef {import('@octokit/webhooks').WebhookPayloadPush} WebhookPayloadPush */ | ||
/** @typedef {import('./get-associated-pull-request').WebhookPayloadPushCommit} WebhookPayloadPushCommit */ | ||
|
||
/** | ||
* Base endpoint URL for WordPress.org profile lookup by GitHub username. | ||
* | ||
* @type {string} | ||
*/ | ||
const BASE_PROFILE_LOOKUP_API_URL = | ||
'https://profiles.wordpress.org/wp-json/wporg-github/v1/lookup/'; | ||
|
||
/** | ||
* Message of comment prompting contributor to link their GitHub account from | ||
* their WordPress.org profile for props credit. | ||
* | ||
* @type {string} | ||
*/ | ||
const ACCOUNT_LINK_PROMPT = | ||
'Congratulations on your first merged pull request! We would like to ' + | ||
'give you credit for your contribution in the next WordPress release, but ' + | ||
'we were unable to find a WordPress.org profile associated with your ' + | ||
'GitHub account. At your convenience, please visit the following URL and ' + | ||
'click "link your GitHub account" under "GitHub Username" to initiate ' + | ||
'the process to link your accounts:\n\nhttps://profiles.wordpress.org/me/profile/edit/\n\n' + | ||
'If you do not have a WordPress.org account, you can create one at the ' + | ||
'following page:\n\nhttps://login.wordpress.org/register'; | ||
|
||
/** | ||
* Adds the 'First Time Contributor' label to PRs opened by contributors that | ||
* have not yet made a commit. | ||
* Adds the 'First Time Contributor' label to PRs merged on behalf of | ||
* contributors that have not yet made a commit. | ||
* | ||
* @param {WebhookPayloadPullRequest} payload Pull request event payload. | ||
* @param {GitHub} octokit Initialized Octokit REST client. | ||
* @param {WebhookPayloadPush} payload Push event payload. | ||
* @param {GitHub} octokit Initialized Octokit REST client. | ||
*/ | ||
async function addFirstTimeContributorLabel( payload, octokit ) { | ||
const owner = payload.repository.owner.login; | ||
const repo = payload.repository.name; | ||
const author = payload.pull_request.user.login; | ||
if ( payload.ref !== 'refs/heads/master' ) { | ||
debug( | ||
'add-first-time-contributor-label: Commit is not to `master`. Aborting' | ||
); | ||
return; | ||
} | ||
|
||
const commit = | ||
/** @type {WebhookPayloadPushCommit} */ ( payload.commits[ 0 ] ); | ||
const pullRequest = getAssociatedPullRequest( commit ); | ||
if ( ! pullRequest ) { | ||
debug( | ||
'add-first-time-contributor-label: Cannot determine pull request associated with commit. Aborting' | ||
); | ||
return; | ||
} | ||
|
||
const repo = payload.repository.name; | ||
const owner = payload.repository.owner.login; | ||
const author = commit.author.username; | ||
debug( | ||
`add-first-time-contributor-label: Searching for commits in ${ owner }/${ repo } by @${ author }` | ||
); | ||
|
||
const { | ||
data: { total_count: totalCount }, | ||
} = await octokit.search.commits( { | ||
q: `repo:${ owner }/${ repo }+author:${ author }`, | ||
const { data: commits } = await octokit.repos.listCommits( { | ||
owner, | ||
repo, | ||
author, | ||
} ); | ||
|
||
if ( totalCount !== 0 ) { | ||
if ( commits.length > 1 ) { | ||
debug( | ||
`add-first-time-contributor-label: ${ totalCount } commits found. Aborting` | ||
`add-first-time-contributor-label: Not the first commit for author. Aborting` | ||
); | ||
return; | ||
} | ||
|
||
debug( | ||
`add-first-time-contributor-label: Adding 'First Time Contributor' label to issue #${ payload.pull_request.number }` | ||
`add-first-time-contributor-label: Adding 'First Time Contributor' label to issue #${ pullRequest }` | ||
); | ||
|
||
await octokit.issues.addLabels( { | ||
owner, | ||
repo, | ||
issue_number: payload.pull_request.number, | ||
issue_number: pullRequest, | ||
labels: [ 'First-time Contributor' ], | ||
} ); | ||
|
||
debug( | ||
`add-first-time-contributor-label: Checking for WordPress username associated with @${ author }` | ||
); | ||
|
||
let dotOrgUsername; | ||
try { | ||
const response = await got( | ||
BASE_PROFILE_LOOKUP_API_URL + author, | ||
/** @type {import('got').Options} */ ( { | ||
responseType: 'json', | ||
} ) | ||
); | ||
dotOrgUsername = response.body.slug; | ||
} catch ( error ) { | ||
debug( | ||
`add-first-time-contributor-label: Error retrieving from profile API:\n\n${ error.toString() }` | ||
); | ||
return; | ||
} | ||
|
||
if ( dotOrgUsername ) { | ||
debug( | ||
`add-first-time-contributor-label: User already known as ${ dotOrgUsername }. No need to prompt for account link!` | ||
); | ||
return; | ||
} | ||
|
||
await octokit.issues.createComment( { | ||
owner, | ||
repo, | ||
issue_number: pullRequest, | ||
body: ACCOUNT_LINK_PROMPT, | ||
} ); | ||
} | ||
|
||
module.exports = addFirstTimeContributorLabel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
packages/project-management-automation/lib/get-associated-pull-request.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* @typedef WebhookPayloadPushCommitAuthor | ||
* | ||
* @property {string} name Author name. | ||
* @property {string} email Author email. | ||
* @property {string} username Author username. | ||
*/ | ||
|
||
/** | ||
* Minimal type detail of GitHub Push webhook event payload, for lack of their | ||
* own. | ||
* | ||
* TODO: If GitHub improves this on their own webhook payload types, this type | ||
* should no longer be necessary. | ||
* | ||
* @typedef {Record<string,*>} WebhookPayloadPushCommit | ||
* | ||
* @property {string} message Commit message. | ||
* @property {WebhookPayloadPushCommitAuthor} author Commit author. | ||
* | ||
* @see https://developer.github.com/v3/activity/events/types/#pushevent | ||
*/ | ||
|
||
/** | ||
* Given a commit object, returns a promise resolving with the pull request | ||
* number associated with the commit, or null if an associated pull request | ||
* cannot be determined. | ||
* | ||
* @param {WebhookPayloadPushCommit} commit Commit object. | ||
* | ||
* @return {number?} Pull request number, or null if it cannot be | ||
* determined. | ||
*/ | ||
function getAssociatedPullRequest( commit ) { | ||
const match = commit.message.match( /\(#(\d+)\)$/m ); | ||
return match && Number( match[ 1 ] ); | ||
} | ||
|
||
module.exports = getAssociatedPullRequest; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to display the prompt regardless of branch? If they are opening a PR, it shows they are interested in contributing. Why not catch them the first time regardless of branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, it's not possible, related to this point from the original comment:
A commit pushed to a branch originating from a fork would fall under these restrictions.
It works for commits to master, since it's within the source repository, committed by someone with permissions to push to master.