- introduce new flag allowing to fetch reviewers who approved PRs

- add new testcase which verifies approvers being included
  - adjust config for some test cases to verify reviewers not being pulled if disabled
This commit is contained in:
Mike Penz
2022-04-08 12:13:21 +02:00
parent df9a35580f
commit 1e867df2de
13 changed files with 181 additions and 11 deletions
+8
View File
@@ -18,6 +18,7 @@ it('Should have empty changelog (tags)', async () => {
toTag: 'v0.0.2', toTag: 'v0.0.2',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -36,6 +37,7 @@ it('Should match generated changelog (tags)', async () => {
toTag: 'v0.0.3', toTag: 'v0.0.3',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -62,6 +64,7 @@ it('Should match generated changelog (refs)', async () => {
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa', toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -96,6 +99,7 @@ it('Should match generated changelog and replace all occurrences (refs)', async
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa', toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -132,6 +136,7 @@ it('Should match ordered ASC', async () => {
toTag: 'v0.5.0', toTag: 'v0.5.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -155,6 +160,7 @@ it('Should match ordered DESC', async () => {
toTag: 'v0.5.0', toTag: 'v0.5.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -178,6 +184,7 @@ it('Should ignore PRs not merged into develop branch', async () => {
toTag: 'v1.4.0', toTag: 'v1.4.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -199,6 +206,7 @@ it('Should ignore PRs not merged into main branch', async () => {
toTag: 'v1.4.0', toTag: 'v1.4.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
+39
View File
@@ -16,6 +16,7 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -43,6 +44,7 @@ it('Should match generated changelog (unspecified tags)', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -67,6 +69,7 @@ it('Should use empty placeholder', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -92,6 +95,7 @@ it('Should fill empty placeholders', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -119,6 +123,7 @@ it('Should fill `template` placeholders', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -146,6 +151,7 @@ it('Should fill `template` placeholders, ignore', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -173,6 +179,7 @@ it('Uncategorized category', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
false, false,
configuration configuration
) )
@@ -200,6 +207,7 @@ it('Verify commit based changelog', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
true, true,
configuration configuration
) )
@@ -227,6 +235,7 @@ it('Verify commit based changelog, with emoji categorisation', async () => {
false, false,
false, false,
false, false,
false, // enable to fetch reviewers
true, true,
configuration configuration
) )
@@ -254,6 +263,7 @@ it('Verify default inclusion of open PRs', async () => {
true, // includeOpen true, // includeOpen
false, // failOnError false, // failOnError
false, // ignorePrePrelease false, // ignorePrePrelease
false, // enable to fetch reviewers
false, // commitMode false, // commitMode
configuration // configuration configuration // configuration
) )
@@ -281,6 +291,7 @@ it('Verify custom categorisation of open PRs', async () => {
true, // includeOpen true, // includeOpen
false, // failOnError false, // failOnError
false, // ignorePrePrelease false, // ignorePrePrelease
false, // enable to fetch reviewers
false, // commitMode false, // commitMode
configuration // configuration configuration // configuration
) )
@@ -291,3 +302,31 @@ it('Verify custom categorisation of open PRs', async () => {
`## 🚀 Features Merged\n\n- A feature to be going to v2 (nr3) -- (#3) [merged] {feature}\n\n## 🚀 Features Open\n\n- New feature to keep open (nr5) -- (#7) [open] {feature}\n\n` `## 🚀 Features Merged\n\n- A feature to be going to v2 (nr3) -- (#3) [merged] {feature}\n\n## 🚀 Features Open\n\n- New feature to keep open (nr5) -- (#7) [open] {feature}\n\n`
) )
}) })
it('Verify reviewers who approved are fetched', async () => {
const configuration = resolveConfiguration(
'',
'configs_test/configuration_approvers.json'
)
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
null, // token
'.', // repoPath
'mikepenz', // user
'release-changelog-builder-action-playground', // repo
'1.5.0', // fromTag
'2.0.0', // toTag
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
true, // enable to fetch reviewers
false, // commitMode
configuration // configuration
)
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(
`## 🚀 Features\n\n- A feature to be going to v2 (nr3) -- (#3) [merged] --- \n- New feature to keep open (nr5) -- (#7) [open] --- gabrielpopa\n\n`
)
})
+21
View File
@@ -43,6 +43,7 @@ mergedPullRequests.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
}, },
{ {
@@ -60,6 +61,7 @@ mergedPullRequests.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
}, },
{ {
@@ -77,6 +79,7 @@ mergedPullRequests.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
}, },
{ {
@@ -94,6 +97,7 @@ mergedPullRequests.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
} }
) )
@@ -113,6 +117,7 @@ const pullRequestWithLabelInBody: PullRequestInfo = {
body: '[Issue][Feature][AB-1234321] - no magic body for this matter', body: '[Issue][Feature][AB-1234321] - no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
} }
@@ -132,6 +137,7 @@ it('Extract label from title, combined regex', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -160,6 +166,7 @@ it('Extract label from title and body, combined regex', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -190,6 +197,7 @@ it('Extract label from title, split regex', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -220,6 +228,7 @@ it('Extract label from title, match', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -245,6 +254,7 @@ it('Extract label from title, match multiple', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -271,6 +281,7 @@ it('Extract label from title, match multiple, custon non matching label', async
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration configuration
}) })
@@ -298,6 +309,7 @@ pullRequestsWithLabels.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
}, },
{ {
@@ -315,6 +327,7 @@ pullRequestsWithLabels.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
}, },
{ {
@@ -332,6 +345,7 @@ pullRequestsWithLabels.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
}, },
{ {
@@ -349,6 +363,7 @@ pullRequestsWithLabels.push(
body: 'no magic body for this matter', body: 'no magic body for this matter',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: "merged" status: "merged"
} }
) )
@@ -380,6 +395,7 @@ it('Match multiple labels exhaustive for category', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration: customConfig configuration: customConfig
}) })
@@ -404,6 +420,7 @@ it('Deduplicate duplicated PRs', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration: customConfig configuration: customConfig
}) })
@@ -429,6 +446,7 @@ it('Deduplicate duplicated PRs DESC', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration: customConfig configuration: customConfig
}) })
@@ -450,6 +468,7 @@ it('Commit SHA-1 in commitMode', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: true, commitMode: true,
configuration: customConfig configuration: customConfig
}) })
@@ -470,6 +489,7 @@ it('Release Diff', async () => {
toTag: 'v2.8.1', toTag: 'v2.8.1',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: true, commitMode: true,
configuration: customConfig configuration: customConfig
}) })
@@ -508,6 +528,7 @@ it('Use exclude labels to not include a PR within a category.', async () => {
toTag: '2.0.0', toTag: '2.0.0',
includeOpen: false, includeOpen: false,
failOnError: false, failOnError: false,
fetchReviewers: false,
commitMode: false, commitMode: false,
configuration: customConfig configuration: customConfig
}) })
@@ -0,0 +1,4 @@
{
"template": "${{CHANGELOG}}",
"pr_template": "- ${{TITLE}} -- (#${{NUMBER}}) [${{STATUS}}] ${{REVIEWERS}} --- ${{APPROVERS}}"
}
@@ -12,5 +12,5 @@
} }
], ],
"template": "${{CHANGELOG}}", "template": "${{CHANGELOG}}",
"pr_template": "- ${{TITLE}} -- (#${{NUMBER}}) [${{STATUS}}] {${{LABELS}}}" "pr_template": "- ${{TITLE}} -- (#${{NUMBER}}) [${{STATUS}}] {${{LABELS}}}${{APPROVERS}}"
} }
@@ -6,5 +6,5 @@
} }
], ],
"template": "${{CHANGELOG}}\n\n\nUncategorized\n${{UNCATEGORIZED}}\n\n\nOpen\n${{OPEN}}", "template": "${{CHANGELOG}}\n\n\nUncategorized\n${{UNCATEGORIZED}}\n\n\nOpen\n${{OPEN}}",
"pr_template": "- ${{TITLE}} (#${{NUMBER}}) ${{STATUS}}" "pr_template": "- ${{TITLE}} (#${{NUMBER}}) ${{STATUS}}${{APPROVERS}}"
} }
Generated Vendored
+50 -5
View File
@@ -380,8 +380,9 @@ function run() {
const includeOpen = core.getInput('includeOpen') === 'true'; const includeOpen = core.getInput('includeOpen') === 'true';
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'; const ignorePreReleases = core.getInput('ignorePreReleases') === 'true';
const failOnError = core.getInput('failOnError') === 'true'; const failOnError = core.getInput('failOnError') === 'true';
const fetchReviewers = core.getInput('fetchReviewers') === 'true';
const commitMode = core.getInput('commitMode') === 'true'; const commitMode = core.getInput('commitMode') === 'true';
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, commitMode, configuration).build(); const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, fetchReviewers, commitMode, configuration).build();
core.setOutput('changelog', result); core.setOutput('changelog', result);
// write the result in changelog to file if possible // write the result in changelog to file if possible
const outputFile = core.getInput('outputFile'); const outputFile = core.getInput('outputFile');
@@ -551,6 +552,34 @@ class PullRequests {
return sortPullRequests(openPrs, true); return sortPullRequests(openPrs, true);
}); });
} }
getReviewers(owner, repo, pr) {
var e_3, _a;
return __awaiter(this, void 0, void 0, function* () {
const options = this.octokit.pulls.listReviews.endpoint.merge({
owner,
repo,
pull_number: pr.number
});
try {
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
const response = _c.value;
const reviews = response.data;
pr.approvedReviewers = reviews
.filter(r => r.state === 'APPROVED')
.map(r => { var _a; return (_a = r.user) === null || _a === void 0 ? void 0 : _a.login; })
.filter(r => !!r);
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_3) throw e_3.error; }
}
return [];
});
}
} }
exports.PullRequests = PullRequests; exports.PullRequests = PullRequests;
function sortPullRequests(pullRequests, ascending) { function sortPullRequests(pullRequests, ascending) {
@@ -605,6 +634,7 @@ const mapPullRequest = (pr, status = 'open') => {
body: pr.body || '', body: pr.body || '',
assignees: ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.map(asignee => (asignee === null || asignee === void 0 ? void 0 : asignee.login) || '')) || [], assignees: ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.map(asignee => (asignee === null || asignee === void 0 ? void 0 : asignee.login) || '')) || [],
requestedReviewers: ((_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(reviewer => (reviewer === null || reviewer === void 0 ? void 0 : reviewer.login) || '')) || [], requestedReviewers: ((_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(reviewer => (reviewer === null || reviewer === void 0 ? void 0 : reviewer.login) || '')) || [],
approvedReviewers: [],
status status
}); });
}; };
@@ -714,7 +744,7 @@ class ReleaseNotes {
} }
getMergedPullRequests(octokit) { getMergedPullRequests(octokit) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const { owner, repo, includeOpen, configuration } = this.options; const { owner, repo, includeOpen, fetchReviewers, configuration } = this.options;
const commits = yield this.getCommitHistory(octokit); const commits = yield this.getCommitHistory(octokit);
if (commits.length === 0) { if (commits.length === 0) {
return []; return [];
@@ -761,7 +791,7 @@ class ReleaseNotes {
return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu'); return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu');
}); });
// return only prs if the baseBranch is matching the configuration // return only prs if the baseBranch is matching the configuration
return allPullRequests.filter(pr => { const finalPrs = allPullRequests.filter(pr => {
if (baseBranches.length !== 0) { if (baseBranches.length !== 0) {
return baseBranchPatterns.some(pattern => { return baseBranchPatterns.some(pattern => {
return pr.baseBranch.match(pattern) !== null; return pr.baseBranch.match(pattern) !== null;
@@ -769,6 +799,17 @@ class ReleaseNotes {
} }
return true; return true;
}); });
if (fetchReviewers) {
core.info(`️ Fetching reviewers was enabled`);
// update PR information with reviewers who approved
for (const pr of finalPrs) {
yield pullRequestsApi.getReviewers(owner, repo, pr);
if (pr.approvedReviewers.length > 0) {
core.info(`️ Retrieved ${pr.approvedReviewers.length} reviewer(s) for PR ${owner}/${repo}/#${pr.number}`);
}
}
}
return finalPrs;
}); });
} }
generateCommitPRs(octokit) { generateCommitPRs(octokit) {
@@ -797,6 +838,7 @@ class ReleaseNotes {
body: commit.message || '', body: commit.message || '',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: 'merged' status: 'merged'
}; };
}); });
@@ -855,7 +897,7 @@ const tags_1 = __nccwpck_require__(7532);
const utils_1 = __nccwpck_require__(918); const utils_1 = __nccwpck_require__(918);
const transform_1 = __nccwpck_require__(1644); const transform_1 = __nccwpck_require__(1644);
class ReleaseNotesBuilder { class ReleaseNotesBuilder {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, commitMode, configuration) { constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchReviewers = false, commitMode, configuration) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.token = token; this.token = token;
this.repositoryPath = repositoryPath; this.repositoryPath = repositoryPath;
@@ -866,6 +908,7 @@ class ReleaseNotesBuilder {
this.includeOpen = includeOpen; this.includeOpen = includeOpen;
this.failOnError = failOnError; this.failOnError = failOnError;
this.ignorePreReleases = ignorePreReleases; this.ignorePreReleases = ignorePreReleases;
this.fetchReviewers = fetchReviewers;
this.commitMode = commitMode; this.commitMode = commitMode;
this.configuration = configuration; this.configuration = configuration;
} }
@@ -925,6 +968,7 @@ class ReleaseNotesBuilder {
toTag: this.toTag, toTag: this.toTag,
includeOpen: this.includeOpen, includeOpen: this.includeOpen,
failOnError: this.failOnError, failOnError: this.failOnError,
fetchReviewers: this.fetchReviewers,
commitMode: this.commitMode, commitMode: this.commitMode,
configuration: this.configuration configuration: this.configuration
}; };
@@ -1499,7 +1543,7 @@ function haveEveryElements(arr1, arr2) {
return arr1.every(item => arr2.has(item)); return arr1.every(item => arr2.has(item));
} }
function fillTemplate(pr, template) { function fillTemplate(pr, template) {
var _a, _b, _c, _d, _e; var _a, _b, _c, _d, _e, _f;
let transformed = template; let transformed = template;
transformed = transformed.replace(/\${{NUMBER}}/g, pr.number.toString()); transformed = transformed.replace(/\${{NUMBER}}/g, pr.number.toString());
transformed = transformed.replace(/\${{TITLE}}/g, pr.title); transformed = transformed.replace(/\${{TITLE}}/g, pr.title);
@@ -1514,6 +1558,7 @@ function fillTemplate(pr, template) {
transformed = transformed.replace(/\${{BODY}}/g, pr.body); transformed = transformed.replace(/\${{BODY}}/g, pr.body);
transformed = transformed.replace(/\${{ASSIGNEES}}/g, ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.join(', ')) || ''); transformed = transformed.replace(/\${{ASSIGNEES}}/g, ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.join(', ')) || '');
transformed = transformed.replace(/\${{REVIEWERS}}/g, ((_e = pr.requestedReviewers) === null || _e === void 0 ? void 0 : _e.join(', ')) || ''); transformed = transformed.replace(/\${{REVIEWERS}}/g, ((_e = pr.requestedReviewers) === null || _e === void 0 ? void 0 : _e.join(', ')) || '');
transformed = transformed.replace(/\${{APPROVERS}}/g, ((_f = pr.approvedReviewers) === null || _f === void 0 ? void 0 : _f.join(', ')) || '');
return transformed; return transformed;
} }
function transform(filled, transformers) { function transform(filled, transformers) {
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -35,6 +35,7 @@ async function run(): Promise<void> {
const includeOpen = core.getInput('includeOpen') === 'true' const includeOpen = core.getInput('includeOpen') === 'true'
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true' const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'
const failOnError = core.getInput('failOnError') === 'true' const failOnError = core.getInput('failOnError') === 'true'
const fetchReviewers = core.getInput('fetchReviewers') === 'true'
const commitMode = core.getInput('commitMode') === 'true' const commitMode = core.getInput('commitMode') === 'true'
const result = await new ReleaseNotesBuilder( const result = await new ReleaseNotesBuilder(
@@ -48,6 +49,7 @@ async function run(): Promise<void> {
includeOpen, includeOpen,
failOnError, failOnError,
ignorePreReleases, ignorePreReleases,
fetchReviewers,
commitMode, commitMode,
configuration configuration
).build() ).build()
+27
View File
@@ -18,6 +18,7 @@ export interface PullRequestInfo {
body: string body: string
assignees: string[] assignees: string[]
requestedReviewers: string[] requestedReviewers: string[]
approvedReviewers: string[]
status: 'open' | 'merged' status: 'open' | 'merged'
} }
@@ -26,6 +27,9 @@ type PullData = RestEndpointMethodTypes['pulls']['get']['response']['data']
type PullsListData = type PullsListData =
RestEndpointMethodTypes['pulls']['list']['response']['data'] RestEndpointMethodTypes['pulls']['list']['response']['data']
type PullReviewData =
RestEndpointMethodTypes['pulls']['listReviews']['response']['data']
export class PullRequests { export class PullRequests {
constructor(private octokit: Octokit) {} constructor(private octokit: Octokit) {}
@@ -127,6 +131,28 @@ export class PullRequests {
return sortPullRequests(openPrs, true) return sortPullRequests(openPrs, true)
} }
async getReviewers(
owner: string,
repo: string,
pr: PullRequestInfo
): Promise<PullReviewData[]> {
const options = this.octokit.pulls.listReviews.endpoint.merge({
owner,
repo,
pull_number: pr.number
})
for await (const response of this.octokit.paginate.iterator(options)) {
const reviews: PullReviewData = response.data as PullReviewData
pr.approvedReviewers = reviews
.filter(r => r.state === 'APPROVED')
.map(r => r.user?.login)
.filter(r => !!r) as string[]
}
return []
}
} }
export function sortPullRequests( export function sortPullRequests(
@@ -192,5 +218,6 @@ const mapPullRequest = (
assignees: pr.assignees?.map(asignee => asignee?.login || '') || [], assignees: pr.assignees?.map(asignee => asignee?.login || '') || [],
requestedReviewers: requestedReviewers:
pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [], pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [],
approvedReviewers: [],
status status
}) })
+20 -2
View File
@@ -13,6 +13,7 @@ export interface ReleaseNotesOptions {
toTag: string // the tag/ref up to toTag: string // the tag/ref up to
includeOpen: boolean // defines if we should also fetch open pull requests includeOpen: boolean // defines if we should also fetch open pull requests
failOnError: boolean // defines if we should fail the action in case of an error failOnError: boolean // defines if we should fail the action in case of an error
fetchReviewers: boolean // defines if the action should fetch the reviewers for PRs - approved reviewers are not included in the default PR listing
commitMode: boolean // defines if we use the alternative commit based mode. note: this is only partially supported commitMode: boolean // defines if we use the alternative commit based mode. note: this is only partially supported
configuration: Configuration // the configuration as defined in `configuration.ts` configuration: Configuration // the configuration as defined in `configuration.ts`
} }
@@ -81,7 +82,8 @@ export class ReleaseNotes {
private async getMergedPullRequests( private async getMergedPullRequests(
octokit: Octokit octokit: Octokit
): Promise<PullRequestInfo[]> { ): Promise<PullRequestInfo[]> {
const {owner, repo, includeOpen, configuration} = this.options const {owner, repo, includeOpen, fetchReviewers, configuration} =
this.options
const commits = await this.getCommitHistory(octokit) const commits = await this.getCommitHistory(octokit)
if (commits.length === 0) { if (commits.length === 0) {
@@ -169,7 +171,7 @@ export class ReleaseNotes {
}) })
// return only prs if the baseBranch is matching the configuration // return only prs if the baseBranch is matching the configuration
return allPullRequests.filter(pr => { const finalPrs = allPullRequests.filter(pr => {
if (baseBranches.length !== 0) { if (baseBranches.length !== 0) {
return baseBranchPatterns.some(pattern => { return baseBranchPatterns.some(pattern => {
return pr.baseBranch.match(pattern) !== null return pr.baseBranch.match(pattern) !== null
@@ -177,6 +179,21 @@ export class ReleaseNotes {
} }
return true return true
}) })
if (fetchReviewers) {
core.info(`️ Fetching reviewers was enabled`)
// update PR information with reviewers who approved
for (const pr of finalPrs) {
await pullRequestsApi.getReviewers(owner, repo, pr)
if (pr.approvedReviewers.length > 0) {
core.info(
`️ Retrieved ${pr.approvedReviewers.length} reviewer(s) for PR ${owner}/${repo}/#${pr.number}`
)
}
}
}
return finalPrs
} }
private async generateCommitPRs( private async generateCommitPRs(
@@ -213,6 +230,7 @@ export class ReleaseNotes {
body: commit.message || '', body: commit.message || '',
assignees: [], assignees: [],
requestedReviewers: [], requestedReviewers: [],
approvedReviewers: [],
status: 'merged' status: 'merged'
} }
}) })
+3 -1
View File
@@ -15,9 +15,10 @@ export class ReleaseNotesBuilder {
private repo: string | null, private repo: string | null,
private fromTag: string | null, private fromTag: string | null,
private toTag: string | null, private toTag: string | null,
private includeOpen: boolean, private includeOpen: boolean = false,
private failOnError: boolean, private failOnError: boolean,
private ignorePreReleases: boolean, private ignorePreReleases: boolean,
private fetchReviewers: boolean = false,
private commitMode: boolean, private commitMode: boolean,
private configuration: Configuration private configuration: Configuration
) {} ) {}
@@ -91,6 +92,7 @@ export class ReleaseNotesBuilder {
toTag: this.toTag, toTag: this.toTag,
includeOpen: this.includeOpen, includeOpen: this.includeOpen,
failOnError: this.failOnError, failOnError: this.failOnError,
fetchReviewers: this.fetchReviewers,
commitMode: this.commitMode, commitMode: this.commitMode,
configuration: this.configuration configuration: this.configuration
} }
+4
View File
@@ -335,6 +335,10 @@ function fillTemplate(pr: PullRequestInfo, template: string): string {
/\${{REVIEWERS}}/g, /\${{REVIEWERS}}/g,
pr.requestedReviewers?.join(', ') || '' pr.requestedReviewers?.join(', ') || ''
) )
transformed = transformed.replace(
/\${{APPROVERS}}/g,
pr.approvedReviewers?.join(', ') || ''
)
return transformed return transformed
} }