Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
155 commits
Select commit Hold shift + click to select a range
c20bbad
First pass.
desrosj Jan 31, 2022
05ee391
Fix syntax issue.
desrosj Jan 31, 2022
c5beb0c
Undo debugging.
desrosj Jan 31, 2022
1f98699
Display results for context.
desrosj Jan 31, 2022
2f3c4b8
Try quotes.
desrosj Jan 31, 2022
3143888
Trailing `,` 🤦🏼‍♂️
desrosj Jan 31, 2022
e9d6cb8
Use named parameters.
desrosj Jan 31, 2022
e5ace45
Await results
desrosj Jan 31, 2022
eebfc0a
More work.
desrosj Jan 31, 2022
ab60631
Correct parenthesis.
desrosj Jan 31, 2022
982b231
Try debug.
desrosj Jan 31, 2022
f7050bc
More tests.
desrosj Jan 31, 2022
ad36a96
Iterate through actual data.
desrosj Feb 1, 2022
ac27f87
Look up PR reviewer comments.
desrosj Feb 1, 2022
f136f32
Test with a GB pull.
desrosj Feb 1, 2022
1cb84fc
Output debug.
desrosj Feb 1, 2022
c6c0b28
Correct alignment.
desrosj Feb 1, 2022
06a772c
Fetch PR committers.
desrosj Feb 1, 2022
27ae7e0
Correctly reference authors.
desrosj Feb 1, 2022
622a1f8
Debug.
desrosj Feb 1, 2022
a5c239e
Test two ways of finding linked issues.
desrosj Feb 1, 2022
b913796
Change reference to commit author.
desrosj Feb 1, 2022
fb15f3d
Test a PR with user associated commits.
desrosj Feb 1, 2022
60ad455
Account for authors and committers being different.
desrosj Feb 1, 2022
8c7066e
Fix inline comments.
desrosj Feb 1, 2022
60a7170
Debug.
desrosj Feb 1, 2022
6ae6b4f
Fix syntax error.
desrosj Feb 1, 2022
653f172
Back to other PR.
desrosj Feb 1, 2022
6d5013f
When author or committer are null, look on the commit info.
desrosj Feb 1, 2022
90a830c
Some tests.
desrosj Feb 1, 2022
b16834a
Syntax error.
desrosj Feb 1, 2022
91c4cd3
Correct parameters.
desrosj Feb 1, 2022
7f5372a
Two requests in one action.
desrosj Feb 1, 2022
7f94ac0
More test.
desrosj Feb 1, 2022
7c77733
More testing.
desrosj Feb 1, 2022
ce465e6
More edits.
desrosj Feb 2, 2022
510f356
Test
desrosj Feb 2, 2022
9704a2c
Test
desrosj Feb 2, 2022
78aef1b
Fix typo
desrosj Feb 2, 2022
aa0aeff
Fix typo
desrosj Feb 2, 2022
8d20b21
More variable typos.
desrosj Feb 2, 2022
d417203
Adjustments.
desrosj Feb 2, 2022
5b2946a
🤞🏼
desrosj Feb 2, 2022
1812023
Typo
desrosj Feb 2, 2022
58ccb94
Change PR.
desrosj Feb 2, 2022
3be93c4
Use correct endpoint.
desrosj Feb 2, 2022
4dd9742
Try GraphQL.
desrosj Feb 2, 2022
3481923
Define coAuthorData.
desrosj Feb 2, 2022
de26758
Test
desrosj Feb 2, 2022
4d96ff8
Correct variables.
desrosj Feb 2, 2022
760ee0c
Typoe fix.
desrosj Feb 2, 2022
024914f
`Int` not `Integer`
desrosj Feb 2, 2022
87d6a33
Test
desrosj Feb 2, 2022
9cacf39
🤞🏼
desrosj Feb 2, 2022
697f3a2
Comment out last part.
desrosj Feb 2, 2022
85655b8
More debugging.
desrosj Feb 2, 2022
f5e2224
More.
desrosj Feb 2, 2022
a84513a
Digging too deep?
desrosj Feb 2, 2022
363822d
Debug.
desrosj Feb 2, 2022
761ca61
Add more info to author.
desrosj Feb 2, 2022
2b43d5b
More user info.
desrosj Feb 2, 2022
107a43a
Account for non-users making commits.
desrosj Feb 2, 2022
7b821b1
Test
desrosj Feb 2, 2022
bc0b685
Reverse?
desrosj Feb 2, 2022
2784ced
Typo
desrosj Feb 2, 2022
5fa110c
Correct.
desrosj Feb 2, 2022
b620265
Cleanup.
desrosj Feb 2, 2022
08ce9b6
Next step.
desrosj Feb 2, 2022
2c3af4e
Test
desrosj Feb 2, 2022
7975314
Skip bot users.
desrosj Feb 2, 2022
c0eef27
Fix syntax.
desrosj Feb 2, 2022
0d97223
Fix error.
desrosj Feb 2, 2022
c81d3ff
Correct properties.
desrosj Feb 2, 2022
29643da
Return data.
desrosj Feb 2, 2022
a925a2d
Test the returned value.
desrosj Feb 3, 2022
c60488b
Test
desrosj Feb 3, 2022
e1e9d98
More test.
desrosj Feb 3, 2022
9316d15
Test querying closing issues.
desrosj Feb 3, 2022
b4df17a
Fix nesting.
desrosj Feb 3, 2022
c0feb66
Try nesting.
desrosj Feb 3, 2022
7fcef4a
Try
desrosj Feb 3, 2022
f938523
Again?
desrosj Feb 3, 2022
7212cb7
Print comments.
desrosj Feb 3, 2022
3083b66
Don't map when there are none.
desrosj Feb 3, 2022
8efb3ee
test
desrosj Feb 3, 2022
decc940
Test
desrosj Feb 3, 2022
6b1502b
Indentation
desrosj Feb 3, 2022
44d8b69
Expand.
desrosj Feb 3, 2022
79e1670
Last?
desrosj Feb 3, 2022
d4a7b35
Loopdy loop
desrosj Feb 3, 2022
4173b92
Check linked issues for authors and commenters.
desrosj Feb 3, 2022
64c7fce
Typo
desrosj Feb 3, 2022
3d790ba
New PR testing.
desrosj Feb 3, 2022
6c7bd9f
Fix variable.
desrosj Feb 3, 2022
69fb880
Get user data for other groups.
desrosj Feb 3, 2022
77b4ec7
Spread correct.y
desrosj Feb 3, 2022
c880094
Try running from separate file.
desrosj Feb 3, 2022
c234993
Correct path.
desrosj Feb 3, 2022
cb72913
Keep in same file.
desrosj Feb 3, 2022
02d81db
Consolidation.
desrosj Feb 3, 2022
3dd8a07
Test return
desrosj Feb 3, 2022
410443f
Try commenting.
desrosj Feb 3, 2022
38cce8e
Correct some variables
desrosj Feb 3, 2022
8a38b73
Correct typo.,
desrosj Feb 3, 2022
3f8a324
Debugging.
desrosj Feb 3, 2022
fe5c7a9
New debug.
desrosj Feb 3, 2022
075b556
Correct variable.
desrosj Feb 3, 2022
1632b03
Test
desrosj Feb 3, 2022
285f75f
Test.
desrosj Feb 3, 2022
8d890c9
Testing.
desrosj Feb 3, 2022
eae943d
adjustments
desrosj Feb 3, 2022
9424e58
Check for users to scan for.
desrosj Feb 3, 2022
c3cd403
Correct nesting.
desrosj Feb 3, 2022
4d9acff
Print out `rest`
desrosj Feb 3, 2022
b7892d4
Test
desrosj Feb 3, 2022
e416e9c
Test
desrosj Feb 3, 2022
44167a4
Refinement.
desrosj Feb 3, 2022
f653d47
Test
desrosj Feb 3, 2022
bebb460
Adjust.
desrosj Feb 3, 2022
fb78a9a
Include new line.
desrosj Feb 3, 2022
83a6e68
Use a more active PR.
desrosj Feb 3, 2022
e65c570
Use correct comment syntax.
desrosj Feb 3, 2022
afefee0
Another PR. Fix skipping empty groups.
desrosj Feb 3, 2022
7d0db90
Go back.
desrosj Feb 3, 2022
2b80fe8
Improve formatting.
desrosj Feb 3, 2022
be07a8e
Improve formatting.
desrosj Feb 3, 2022
3276650
Cleanup.
desrosj Feb 3, 2022
a91a086
Move linked issue commenters into just commenters.
desrosj Feb 3, 2022
ad42827
Add some additional events for testing.
desrosj Feb 3, 2022
c964456
Cleanup
desrosj Feb 4, 2022
e108b29
Limit concurrent workflows.
desrosj Feb 4, 2022
c0525af
Prevent multiple workflow runs.
desrosj Feb 4, 2022
bdc0d2c
Testing.
desrosj Feb 4, 2022
9f64782
Adjust action events.
desrosj Feb 4, 2022
04e4f38
Test
desrosj Feb 4, 2022
6aa8e98
Test
desrosj Feb 4, 2022
7bb0234
Test
desrosj Feb 4, 2022
77aa9bb
Move script to different file.
desrosj Feb 4, 2022
f7f8a72
Typo.
desrosj Feb 4, 2022
fd708f5
use `async`.
desrosj Feb 4, 2022
2266e6e
Use correct context for PR number.
desrosj Feb 4, 2022
258003d
Test
desrosj Feb 4, 2022
548eb59
Move commenting to a separate script.
desrosj Feb 4, 2022
e01c27b
Adjust syntax.
desrosj Feb 4, 2022
d6f057a
Test
desrosj Feb 4, 2022
113f992
Debug.
desrosj Feb 4, 2022
0dd5062
Test
desrosj Feb 4, 2022
2084571
Add message back.
desrosj Feb 4, 2022
86c5ff9
Test 2.
desrosj Feb 4, 2022
ac79173
Coding standards fixes.
desrosj Feb 4, 2022
6ef9f46
Test both formats of props.
desrosj Feb 4, 2022
fde1acd
Fix.
desrosj Feb 4, 2022
5584351
Try again.
desrosj Feb 4, 2022
0c2bfa3
Stick with regular format.
desrosj Feb 4, 2022
a582d03
List username instead of display name.
desrosj Feb 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/participant-gathering.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Props Bot

on:
# Gathers all of the participants for pull requests to assist in giving proper credit.
pull_request:
pull_request_review:

# Cancels all previous workflow runs for pull requests that have not completed.
concurrency:
# The concurrency group contains the workflow name and the branch name for pull requests
# or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true

jobs:
pull_request_participants:
name: Gather pull request participants
runs-on: ubuntu-latest
timeout-minutes: 20

#
steps:
- name: Checkout repository
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0

- name: Compile contributor data
uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c # v5.1.0
id: contributor-data
with:
script: |
const contribution_collector = require( './tools/props-bot/contribution-collector.js' );
return await contribution_collector({
github,
context
});

- name: Comment on the pull request
uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c # v5.1.0
with:
script: |
const props_bot = require( './tools/props-bot/commenting.js' );
const generatedMessage = ${{ steps.contributor-data.outputs.result }};

await props_bot({
github,
context,
generatedMessage
});
56 changes: 56 additions & 0 deletions tools/props-bot/commenting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module.exports = async ( { github, context, generatedMessage } ) => {
let commentId;

const commentInfo = {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
};

const commentMessage =
'Here is a list of everyone that appears to have contributed to this PR and any linked issues:\n\n' +
'```\n' +
generatedMessage +
'\n```';

const comment = {
...commentInfo,
body: commentMessage + '\n\n<sub>contributor-collection-action</sub>',
};

const comments = ( await github.rest.issues.listComments( commentInfo ) )
.data;
for ( const currentComment of comments ) {
if (
currentComment.user.type === 'Bot' &&
/<sub>[\s\n]*contributor-collection-action/.test( currentComment.body )
) {
commentId = currentComment.id;
break;
}
}

if ( commentId ) {
console.log( `Updating previous comment #${ commentId }` );
try {
await github.rest.issues.updateComment( {
...context.repo,
comment_id: commentId,
body: comment.body,
} );
} catch ( e ) {
console.log( 'Error editing previous comment: ' + e.message );
commentId = null;
}
}

// no previous or edit failed
if ( ! commentId ) {
console.log( 'Creating new comment' );
try {
await github.rest.issues.createComment( comment );
} catch ( e ) {
console.log( `Error creating comment: ${ e.message }` );
}
}
};
243 changes: 243 additions & 0 deletions tools/props-bot/contribution-collector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
const coAuthorData = {
userData: [],
};
const contributorTypes = [
'committers',
'reviewers',
'commenters',
'reporters',
];

module.exports = async ( { github, context } ) => {
for ( const type of contributorTypes ) {
coAuthorData[ type ] = new Set();
}

/*
* Fetch the following data for the pull request:
* - Commits with author details.
* - Reviews with author logins.
* - Comments with author logins.
* - Linked issues with author logins.
* - Comments on linked issues with author logins.
*/
const contributorData = await github.graphql(
`query($owner:String!, $name:String!, $prNumber:Int!) {
repository(owner:$owner, name:$name) {
pullRequest(number:$prNumber) {
commits(first: 100) {
nodes {
commit {
author {
user {
databaseId
login
name
email
}
name
email
}
}
}
}
reviews(first: 100) {
nodes {
author {
login
}
}
}
comments(first: 100) {
nodes {
author {
login
}
}
}
closingIssuesReferences(first:100){
nodes {
author {
login
}
comments(first:100) {
nodes {
author {
login
}
}
}
}
}
}
}
}`,
{
owner: context.repo.owner,
name: context.repo.repo,
prNumber: context.payload.pull_request.number,
}
);

// Process pull request commits.
for ( const commit of contributorData.repository.pullRequest.commits
.nodes ) {
/*
* Commits are sometimes made by an email that is not associated with a GitHub account.
* For these, info that may help us guess later.
*/
if ( null === commit.commit.author.user ) {
coAuthorData.committers.add( commit.commit.author.email );
coAuthorData.userData[ commit.commit.author.email ] = {
name: commit.commit.author.name,
email: commit.commit.author.email,
};
} else {
if ( skipUser( commit.commit.author.user.login ) ) {
continue;
}

coAuthorData.committers.add( commit.commit.author.user.login );
coAuthorData.userData[ commit.commit.author.user.login ] =
commit.commit.author.user;
}
}

// Process pull request reviews.
for ( const review of contributorData.repository.pullRequest.reviews
.nodes ) {
if ( skipUser( review.author.login ) ) {
continue;
}

coAuthorData.reviewers.add( review.author.login );
}

// Process pull request comments.
for ( const comment of contributorData.repository.pullRequest.comments
.nodes ) {
if ( skipUser( comment.author.login ) ) {
continue;
}

coAuthorData.commenters.add( comment.author.login );
}

// Process reporters and commenters for linked issues.
for ( const linkedIssue of contributorData.repository.pullRequest
.closingIssuesReferences.nodes ) {
if ( ! skipUser( linkedIssue.author.login ) ) {
coAuthorData.reporters.add( linkedIssue.author.login );
}

for ( const issueComment of linkedIssue.comments.nodes ) {
if ( skipUser( issueComment.author.login ) ) {
continue;
}

coAuthorData.commenters.add( issueComment.author.login );
}
}

// We already have user info for committers, we need to grab it for everyone else.
if (
[
...coAuthorData.reviewers,
...coAuthorData.commenters,
...coAuthorData.reporters,
].length > 0
) {
const userData = await github.graphql(
'{' +
[
...coAuthorData.reviewers,
...coAuthorData.commenters,
...coAuthorData.reporters,
].map(
( user ) =>
escapeForGql( user ) +
`: user(login: "${ user }") {databaseId, login, name, email}`
) +
'}'
);

Object.values( userData ).forEach( ( user ) => {
coAuthorData.userData[ user.login ] = user;
} );
}

console.debug( coAuthorData );

return contributorTypes
.map( ( priority ) => {
// Skip an empty set of contributors.
if ( coAuthorData[ priority ].length === 0 ) {
return [];
}

// Add a header for each section.
const header =
'# ' +
priority.replace( /^./, ( char ) => char.toUpperCase() ) +
'\n';

// Generate each Co-authored-by entry, and join them into a single string.
return (
header +
[ ...coAuthorData[ priority ] ]
.map( ( username ) => {
const {
name,
databaseId,
email,
} = coAuthorData.userData[ username ];
const commitEmail =
email ||
`${ databaseId }+${ username }@users.noreply.github.com`;

return `Co-authored-by: ${ username } <${ commitEmail }>`;
} )
.join( '\n' )
);
} )
.join( '\n\n' );
};

const escapeForGql = ( string ) => '_' + string.replace( /[./-]/g, '_' );

/**
* Checks if a user should be skipped.
*
* @param {string} username Username to check.
*
* @return {boolean} true if the username should be skipped. false otherwise.
*/
function skipUser( username ) {
const skippedUsers = [ 'github-actions' ];

if (
-1 === skippedUsers.indexOf( username ) &&
! contributorAlreadyPresent( username )
) {
return false;
}

return true;
}

/**
* Checks if a user has already been added to the list of contributors to receive props.
*
* Contributors should only appear in the props list once, even when contributing in multiple ways.
*
* @param {string} username The username to check.
*
* @return {boolean} true if the username is already in the list. false otherwise.
*/
function contributorAlreadyPresent( username ) {
for ( const contributorType of contributorTypes ) {
if ( coAuthorData[ contributorType ].has( username ) ) {
return true;
}
}
}