Skip to content
This repository has been archived by the owner on Feb 28, 2024. It is now read-only.

Commit

Permalink
Merge pull request #127 from AnalyticalGraphicsInc/first-time
Browse files Browse the repository at this point in the history
Identify first time contributors
  • Loading branch information
OmarShehata authored Feb 16, 2019
2 parents bb07d90 + 5c14c0a commit 9d2fb0b
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 21 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ The table below describes all the possible configuration variables, as well as t
| `repositories:{full_name}:contributorsPath` | `string` | Relative path from the root of the repository to the `CONTRIBUTORS.md` file. | X | _Disabled if not set._
| `repositories:{full_name}:maxDaysSinceUpdate` | `number` | "Bump" pull requests older than this number of days ago. | X | `30`
| `repositories:{full_name}:unitTestPath` | `string` | Relative path to the directory containing unit tests. _Example:`Specs/`_ | X | _Disabled if not set._
| `port` | `number` | Port on which to listen to incoming requests | X | `5000`
| `listenPath` | `string` | Path on which to listen for incoming requests | X | `"/"`
| `port` | `number` | Port on which to listen to incoming requests. | X | `5000`
| `listenPath` | `string` | Path on which to listen for incoming requests. | X | `"/"`
| `slackToken` | `string` | Slack API token for posting release reminders and fun stats to the Slack team. | X | _Disabled if not set._
| `slackConfigUrl` | `string` | The GitHub API URL to a YAML file containing the release schedule and other SlackBot config. | X | `""`

Expand Down
71 changes: 60 additions & 11 deletions lib/commentOnClosedIssue.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = commentOnClosedIssue;

/**
* Post a comment on an issue/pull request that just closed, reminding the users to update Google Group forum links (if there
* were any linked in the comments)
* were any linked in the comments) or congratulating them if this is their first contribution.
*
* @param {Object} body The GitHub event body.
* @param {Object} repositorySettings Headers to use for making additional GitHub requests.
Expand All @@ -23,27 +23,42 @@ function commentOnClosedIssue(body, repositorySettings) {
Check.typeOf.object('body', body);
Check.typeOf.object('repositorySettings', repositorySettings);

var url;
var commentsUrl;
var options = {};

var pullRequest = body.pull_request;
var issue = body.issue;
if (defined(pullRequest)) {
url = pullRequest.url;
commentsUrl = pullRequest.comments_url;
options.url = pullRequest.url;
options.isPullRequest = true;
options.baseBranch = pullRequest.base.ref;
options.baseApiUrl = pullRequest.base.repo.url;
options.commentsUrl = pullRequest.comments_url;
options.userName = pullRequest.user.login;
} else if (defined(issue)) {
url = issue.url;
commentsUrl = issue.comments_url;
options.url = issue.url;
options.isPullRequest = false;
options.commentsUrl = issue.comments_url;
} else {
return Promise.reject(new RuntimeError('Unknown body type'));
}

return commentOnClosedIssue._implementation(url, commentsUrl, repositorySettings);
options.repositorySettings = repositorySettings;

return commentOnClosedIssue._implementation(options);
}

commentOnClosedIssue._implementation = function (issueUrl, commentsUrl, repositorySettings) {
commentOnClosedIssue._implementation = function (options) {
var issueUrl = options.url;
var commentsUrl = options.commentsUrl;
var isPullRequest = options.isPullRequest;
var baseBranch = options.baseBranch;
var baseApiUrl = options.baseApiUrl;
var userName = options.userName;
var repositorySettings = options.repositorySettings;

var comments = [];
var issueHtmlUrl;
var isFirstContribution;

return repositorySettings.fetchSettings()
.then(function () {
Expand All @@ -57,6 +72,15 @@ commentOnClosedIssue._implementation = function (issueUrl, commentsUrl, reposito
issueHtmlUrl = issueResponse.html_url;
comments.push(issueResponse.body);
})
.then(function () {
if (!isPullRequest) {
return Promise.resolve(false);
}
return commentOnClosedIssue._isFirstContribution(userName, repositorySettings, baseBranch, baseApiUrl);
})
.then(function (result) {
isFirstContribution = result;
})
.then(function () {
return requestPromise.get({
url: commentsUrl,
Expand All @@ -70,7 +94,9 @@ commentOnClosedIssue._implementation = function (issueUrl, commentsUrl, reposito
});

var forum_links = getUniqueMatch(comments, commentOnClosedIssue._googleLinkRegex);
if (forum_links.length === 0) {
var foundForumLinks = forum_links.length !== 0;

if (!foundForumLinks && !isFirstContribution) {
return Promise.resolve();
}

Expand All @@ -80,12 +106,35 @@ commentOnClosedIssue._implementation = function (issueUrl, commentsUrl, reposito
body: {
body: repositorySettings.issueClosedTemplate({
html_url: issueHtmlUrl,
forum_links: forum_links
forum_links: forum_links,
isFirstContribution: isFirstContribution,
foundForumLinks: foundForumLinks,
userName: userName
})
},
json: true
});
});
};

commentOnClosedIssue._isFirstContribution = function (userName, repositorySettings, baseBranch, baseApiUrl) {
// This is a user's first contribution if the repo/branch they're merging _into_ does not have their name in the contributors.
if (!defined(repositorySettings.contributorsPath)) {
return Promise.resolve(false);
}

var url = baseApiUrl + '/contents/' + repositorySettings.contributorsPath + '?ref=' + baseBranch;

return requestPromise.get({
url: url,
headers: repositorySettings.headers,
json: true
})
.then(function (response) {
var contributorsFileContent = Buffer.from(response.content, 'base64').toString();
return contributorsFileContent.indexOf(userName) === -1;
});
};


commentOnClosedIssue._googleLinkRegex = /https?:\/\/groups\.google\.com\/[^\s.,:)\\]*cesium-dev\/[^\s.,:)\\]+/ig;
7 changes: 7 additions & 0 deletions lib/templates/issueClosed.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{{#if isFirstContribution}}
Thanks for the awesome work @{{ userName }}! Your contribution is about to launch to millions of users with the next release. :rocket:

Do you mind if we tweet about it? CC @OmarShehata @Slchow.
{{/if}}
{{#if foundForumLinks}}
Congratulations on closing the issue! I found these Cesium forum links in the comments above:

{{#each forum_links}}
Expand All @@ -7,3 +13,4 @@ Congratulations on closing the issue! I found these Cesium forum links in the co
If this issue affects any of these threads, please post a comment like the following:

> The issue at {{ html_url }} has just been closed and may resolve your issue. Look for the change in the next stable release of Cesium or get it now in the master branch on GitHub https://github.com/AnalyticalGraphicsInc/cesium.
{{/if}}
161 changes: 153 additions & 8 deletions specs/lib/commentOnClosedIssueSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ var RepositorySettings = require('../../lib/RepositorySettings');

describe('commentOnClosedIssue', function () {
var repositorySettings = new RepositorySettings();
repositorySettings.contributorsPath = 'CONTRIBUTORS.md';

var issueEventJson;
var pullRequestEventJson;
var issueUrl = 'issueUrl';
var commentsUrl = 'commentsUrl';
var baseBranch = 'master';
var baseApiUrl = 'https://api.github.com/repos/AnalyticalGraphicsInc/cesium';
var userName = 'Joan';

beforeEach(function () {
issueEventJson = {
Expand All @@ -25,7 +29,16 @@ describe('commentOnClosedIssue', function () {
pullRequestEventJson = {
pull_request: {
url: issueUrl,
comments_url: commentsUrl
comments_url: commentsUrl,
user: {
login: userName
},
base: {
ref: 'master',
repo: {
url: baseApiUrl
}
}
}
};
});
Expand All @@ -38,7 +51,7 @@ describe('commentOnClosedIssue', function () {

it('throws if `repositorySettings` is undefined', function () {
expect(function () {
commentOnClosedIssue(issueEventJson, undefined);
commentOnClosedIssue(issueEventJson);
}).toThrowError();
});

Expand All @@ -52,15 +65,32 @@ describe('commentOnClosedIssue', function () {
});

it('passes expected parameters to implementation for closed issue', function () {
var isPullRequest = false;

spyOn(commentOnClosedIssue, '_implementation');
commentOnClosedIssue(issueEventJson, repositorySettings);
expect(commentOnClosedIssue._implementation).toHaveBeenCalledWith(issueUrl, commentsUrl, repositorySettings);
expect(commentOnClosedIssue._implementation).toHaveBeenCalledWith({
url: issueUrl,
commentsUrl: commentsUrl,
isPullRequest: isPullRequest,
repositorySettings: repositorySettings
});
});

it('passes expected parameters to implementation for closed pull request', function () {
var isPullRequest = true;

spyOn(commentOnClosedIssue, '_implementation');
commentOnClosedIssue(pullRequestEventJson, repositorySettings);
expect(commentOnClosedIssue._implementation).toHaveBeenCalledWith(issueUrl, commentsUrl, repositorySettings);
expect(commentOnClosedIssue._implementation).toHaveBeenCalledWith({
url: issueUrl,
commentsUrl: commentsUrl,
isPullRequest: isPullRequest,
baseBranch: baseBranch,
baseApiUrl: baseApiUrl,
userName: userName,
repositorySettings: repositorySettings
});
});

function runTestWithLinks(forumLinks) {
Expand Down Expand Up @@ -89,7 +119,11 @@ describe('commentOnClosedIssue', function () {
});
spyOn(requestPromise, 'post');

return commentOnClosedIssue._implementation('issueUrl', 'commentsUrl', repositorySettings);
return commentOnClosedIssue._implementation({
url: issueUrl,
commentsUrl: commentsUrl,
repositorySettings: repositorySettings
});
}

it('commentOnClosedIssue._implementation fetches latest repository settings.', function (done) {
Expand Down Expand Up @@ -118,7 +152,8 @@ describe('commentOnClosedIssue', function () {
body: {
body: repositorySettings.issueClosedTemplate({
html_url: 'html_url',
forum_links: forumLinks
forum_links: forumLinks,
foundForumLinks: true
})
},
json: true
Expand Down Expand Up @@ -159,7 +194,13 @@ describe('commentOnClosedIssue', function () {
});
spyOn(requestPromise, 'post');

commentOnClosedIssue._implementation('issueUrl', 'commentsUrl', repositorySettings)
var options = {
url: issueUrl,
commentsUrl: commentsUrl,
repositorySettings: repositorySettings
};

commentOnClosedIssue._implementation(options)
.then(done.fail)
.catch(function (error) {
expect(error).toEqual('Bad request');
Expand Down Expand Up @@ -190,12 +231,116 @@ describe('commentOnClosedIssue', function () {
});
spyOn(requestPromise, 'post');

commentOnClosedIssue._implementation('issueUrl', 'commentsUrl', repositorySettings)
var options = {
url: issueUrl,
commentsUrl: commentsUrl,
repositorySettings: repositorySettings
};

commentOnClosedIssue._implementation(options)
.then(done.fail)
.catch(function (error) {
expect(error).toEqual('Bad request');
expect(requestPromise.post).not.toHaveBeenCalled();
done();
});
});

it('commentOnClosedIssue._implementation posts about first timer\'s contribution.', function (done) {
var contributorsUrl = baseApiUrl + '/contents/' + repositorySettings.contributorsPath + '?ref=' + baseBranch;

spyOn(repositorySettings, 'fetchSettings').and.callFake(function() {
return Promise.resolve(repositorySettings);
});
spyOn(requestPromise, 'get').and.callFake(function (options) {
if (options.url === issueUrl) {
return Promise.resolve(pullRequestEventJson);
}

if (options.url === contributorsUrl) {
var content = Buffer.from('* [Jane Doe](https://github.com/JaneDoe)').toString('base64');
return Promise.resolve({
content: content
});
}

if (options.url === commentsUrl) {
return Promise.resolve([]);
}

return Promise.reject('Unknown url: ' + options.url);
});
spyOn(requestPromise, 'post');

var options = {
url: issueUrl,
commentsUrl: commentsUrl,
isPullRequest: true,
baseBranch: baseBranch,
baseApiUrl: baseApiUrl,
userName: userName,
repositorySettings: repositorySettings
};

commentOnClosedIssue._implementation(options)
.then(function () {
expect(requestPromise.post).toHaveBeenCalledWith({
url: commentsUrl,
headers: repositorySettings.headers,
body: {
body: repositorySettings.issueClosedTemplate({
isFirstContribution: true,
userName: userName
})
},
json: true
});
done();
})
.catch(done.fail);
});

it('commentOnClosedIssue._implementation does not post first timers message if not first time.', function (done) {
var contributorsUrl = baseApiUrl + '/contents/' + repositorySettings.contributorsPath + '?ref=' + baseBranch;

spyOn(repositorySettings, 'fetchSettings').and.callFake(function() {
return Promise.resolve(repositorySettings);
});
spyOn(requestPromise, 'get').and.callFake(function (options) {
if (options.url === issueUrl) {
return Promise.resolve(pullRequestEventJson);
}

if (options.url === contributorsUrl) {
var content = Buffer.from('* [Jane Doe](https://github.com/JaneDoe)\n* [Boomer Jones](https://github.com/' + userName + ')').toString('base64');
return Promise.resolve({
content: content
});
}

if (options.url === commentsUrl) {
return Promise.resolve([]);
}

return Promise.reject('Unknown url: ' + options.url);
});
spyOn(requestPromise, 'post');

var options = {
url: issueUrl,
commentsUrl: commentsUrl,
isPullRequest: true,
baseBranch: baseBranch,
baseApiUrl: baseApiUrl,
userName: userName,
repositorySettings: repositorySettings
};

commentOnClosedIssue._implementation(options)
.then(function () {
expect(requestPromise.post).not.toHaveBeenCalled();
done();
})
.catch(done.fail);
});
});

0 comments on commit 9d2fb0b

Please sign in to comment.