diff --git a/lib/src/Gren.js b/lib/src/Gren.js index bbc27a10..a3aca04c 100644 --- a/lib/src/Gren.js +++ b/lib/src/Gren.js @@ -353,6 +353,32 @@ class Gren { return this.repo._request('GET', `/repos/${this.repo.__fullname}/releases`, options); } + /** + * Get the merged pull requests from the repo + * + * @private + * + * @param {number} page + * @param {number} limit + * + * @return {Promise[]} The promises which returns pull requests + */ + async _getMergedPullRequests(page = 1, limit = 100) { + const results = await this.repo.listPullRequests({ + state: 'closed', + per_page: limit, + page + }); + const { headers: { link }, data: prs } = results; + const totalPages = this._getLastPage(link); + const filterPrs = prs.filter(pr => pr.merged_at); + if (totalPages && +page < totalPages) { + return this._getMergedPullRequests(page + 1).then(prsResults => prsResults.concat(filterPrs)); + } + + return filterPrs; + } + /** * Get the last page from a Hypermedia link * @@ -488,7 +514,7 @@ class Gren { * * @return {string} */ - _templateIssueBody(body, rangeBody) { + _templateBody(body, rangeBody) { if (Array.isArray(body) && body.length) { return body.join('\n') + '\n'; } @@ -673,8 +699,7 @@ class Gren { async _getClosedIssues(releaseRanges) { const type = { issues: 'Issues', - milestones: 'Issues', - prs: 'Pull Requests' + milestones: 'Issues' }[this.options.dataSource]; const loaded = utils.task(this, `Getting all closed ${type}`); const { data: issues } = await this.issues.listIssues({ @@ -788,6 +813,18 @@ class Gren { !((this.options.onlyMilestones || dataSource === 'milestones') && !issue.milestone); } + /** + * Filter the pull request based on gren options and labels + * @private + * + * @param {Object} pullRequest + * + * @return {Boolean} + */ + _filterPullRequest(pullRequest) { + return !this._lablesAreIgnored(pullRequest.labels) && !(this.options.onlyMilestones && !pullRequest.milestone); + } + /** * Filter the issue based on the date range, or if is in the release * milestone. @@ -812,6 +849,29 @@ class Gren { ); } + /** + * Filter the pull requests in case the release is milestone, + * or otherwise by dates range. + * + * @private + * + * @param {Array} range The release ranges + * @param {Object} pullRequest GitHub pull request + * + * @return {Boolean} + */ + _filterBlockPullRequest(range, pullRequest) { + if (this.options.dataSource === 'milestones') { + return this.options.milestoneMatch.replace('{{tag_name}}', range[0].name) === pullRequest.milestone.title; + } + + return utils.isInRange( + Date.parse(pullRequest.merged_at), + Date.parse(range[1].date), + Date.parse(range[0].date) + ); + } + /** * Get the blocks of issues based on release dates * @@ -830,18 +890,48 @@ class Gren { .filter(this._filterIssue.bind(this)) .filter(this._filterBlockIssue.bind(this, range)); const body = (!range[0].body || this.options.override) && this._groupBy(filteredIssues); - return { id: range[0].id, release: range[0].name, name: this.options.prefix + range[0].name, published_at: range[0].date, - body: this._templateIssueBody(body, range[0].body) + body: this._templateBody(body, range[0].body) }; }); return release; } + /** + * Get the blocks of pull requests based on the release dates + * + * @private + * + * @param {Array} releaseRanges The array of date ranges + * + * @return {Promise[]} + */ + async _getPullRequestsBlocks(releaseRanges) { + const loaded = utils.task(this, `Getting all merged pull requests`); + const prs = await this._getMergedPullRequests(); + let totalPrs = 0; + const release = releaseRanges + .map(range => { + const filteredPullRequests = Array.from(prs) + .filter(this._filterPullRequest.bind(this)) + .filter(this._filterBlockPullRequest.bind(this, range)); + totalPrs += filteredPullRequests.length; + const body = (!range[0].body || this.options.override) && this._groupBy(filteredPullRequests); + return { + id: range[0].id, + release: range[0].name, + name: this.options.prefix + range[0].name, + published_at: range[0].date, + body: this._templateBody(body, range[0].body) + }; + }); + loaded(`Pull Requests found: ${totalPrs}`); + return release; + } /** * Sort releases by dates @@ -898,7 +988,7 @@ class Gren { issues: this._getIssueBlocks.bind(this), commits: this._getCommitBlocks.bind(this), milestones: this._getIssueBlocks.bind(this), - prs: this._getIssueBlocks.bind(this) + prs: this._getPullRequestsBlocks.bind(this) }; const releases = await this._getListReleases(); this.tasks['Getting releases'].text = 'Getting tags'; diff --git a/test/Gren.spec.js b/test/Gren.spec.js index 55cc4283..be425661 100644 --- a/test/Gren.spec.js +++ b/test/Gren.spec.js @@ -239,7 +239,7 @@ describe('Gren', () => { }); }); - describe('_templateIssueBody', () => { + describe('_templateBody', () => { it('Should always return a string', () => { const body = [ 'First', @@ -248,10 +248,10 @@ describe('Gren', () => { ]; const rangeBody = 'This is one body'; - assert.isString(gren._templateIssueBody(body), 'Passing only the body'); - assert.isString(gren._templateIssueBody(false, rangeBody), 'Passing only the rangeBody'); - assert.isString(gren._templateIssueBody(), 'No parameters'); - assert.isString(gren._templateIssueBody('This is not an Array!'), 'No parameters'); + assert.isString(gren._templateBody(body), 'Passing only the body'); + assert.isString(gren._templateBody(false, rangeBody), 'Passing only the rangeBody'); + assert.isString(gren._templateBody(), 'No parameters'); + assert.isString(gren._templateBody('This is not an Array!'), 'No parameters'); }); });