From 6efcc28b5e84900e4495d86eed4b722ce06ea3f5 Mon Sep 17 00:00:00 2001 From: Antoine Ceol Date: Mon, 3 May 2021 23:45:09 +0200 Subject: [PATCH 1/6] [FEATURE] :sparkles: Add reviews in PR slack messages --- common/services/github.js | 103 +++++++++++++++++------- test/unit/build/services/github_test.js | 46 ++++++++--- 2 files changed, 105 insertions(+), 44 deletions(-) diff --git a/common/services/github.js b/common/services/github.js index 97a3e658..17904d2e 100644 --- a/common/services/github.js +++ b/common/services/github.js @@ -1,6 +1,7 @@ const { Octokit } = require('@octokit/rest'); const axios = require('axios'); const settings = require('../../config'); +const { zipWith, countBy, entries, noop } = require('lodash'); const color = { 'team-evaluation': '#FDEEC1', @@ -10,26 +11,42 @@ const color = { 'team-acces': '#A2DCC1', }; -function getUrlForGithub(label) { +function createOctokit() { + return new Octokit({ + auth: settings.github.token, + log: { + debug: noop, + info: noop, + warn: console.warn, + error: console.error + }, + }); +} + +async function getPullReviewsFromGithub(label){ + const owner = settings.github.owner; + label = label.replace(/ /g, '%20'); - return `https://api.github.com/search/issues?q=is:pr+is:open+archived:false+sort:updated-desc+user:1024pix+label:${label}`; + const octokit = createOctokit(); + const { data } = await octokit.search.issuesAndPullRequests({ + q: `is:pr+is:open+archived:false+user:${owner}+label:${label}`, + sort: 'updated', + order: 'desc' + }); + + return data.items; } -async function getDataFromGithub(label) { - const url = getUrlForGithub(label); - const githubToken = settings.github.token; - const config = { - headers: { - 'Authorization': 'token ' + githubToken - } - }; - return axios.get(url, config) - .then(response => { - return response.data.items; - }) - .catch(error => { - console.log(error); - }); +async function getReviewsFromGithub(pull_number){ + const owner = settings.github.owner; + const repo = settings.github.repository; + const octokit = createOctokit(); + const { data } = await octokit.pulls.listReviews({ + owner, + repo, + pull_number + }); + return data; } function getEmojis(pullRequests) { @@ -40,20 +57,33 @@ function getEmojis(pullRequests) { return labelsEmojis.filter(Boolean).join(' '); } -function createResponseForSlack(pullRequests, label) { - const attachments = pullRequests.map((pullRequests) => { - const emojis = getEmojis(pullRequests); +function getReviewsLabel(reviews) { + const countByState = countBy(reviews, 'state'); + return entries(countByState) + .map(([label, times]) => { + switch(label) { + case 'COMMENTED': return `💬x${times}`; + case 'APPROVED': return `✅x${times}`; + case 'CHANGES_REQUESTED': return `❌x${times}`; + } + }).join('|'); +} + +function createResponseForSlack(data, label) { + const attachments = data.map(({pullRequest, reviews}) => { + const emojis = getEmojis(pullRequest); + const reviewsLabel = getReviewsLabel(reviews); return { color: color[label], pretext: '', - fields:[ {value: `${emojis}<${pullRequests.html_url}|${pullRequests.title}>`, short: false},], + fields:[ {value: `[${reviewsLabel}]${emojis}<${pullRequest.html_url}|${pullRequest.title}>`, short: false},], }; - }).sort().reverse(); + }); const response = { response_type: 'in_channel', text: 'PRs à review pour ' + label, - attachments + attachments: attachments.sort().reverse() }; return response; @@ -69,7 +99,7 @@ async function getLastCommitUrl({ branchName, tagName }) { async function getBranchLastCommitUrl(branch) { const owner = settings.github.owner; const repo = settings.github.repository; - const octokit = new Octokit({ auth: settings.github.token }); + const octokit = createOctokit(); const { data } = await octokit.repos.getBranch({ owner, repo, @@ -92,7 +122,7 @@ async function getLatestRelease(repoOwner, repoName) { } async function getTags(repoOwner, repoName) { - const { repos } = new Octokit({ auth: settings.github.token }); + const { repos } = createOctokit(); const { data } = await repos.listTags({ owner: repoOwner, repo: repoName, @@ -101,7 +131,7 @@ async function getTags(repoOwner, repoName) { } async function getDefaultBranch(repoOwner, repoName) { - const { repos } = new Octokit({ auth: settings.github.token }); + const { repos } = createOctokit(); const { data } = await repos.get({ owner: repoOwner, repo: repoName, @@ -111,7 +141,7 @@ async function getDefaultBranch(repoOwner, repoName) { async function _getMergedPullRequestsSortedByDescendingDate(repoOwner, repoName) { const defaultBranch = await getDefaultBranch(repoOwner, repoName); - const { pulls } = new Octokit({ auth: settings.github.token }); + const { pulls } = createOctokit(); const { data } = await pulls.list({ owner: repoOwner, repo: repoName, @@ -134,7 +164,7 @@ async function _getLatestReleaseTagName(repoOwner, repoName) { } async function _getCommitAtURL(commitUrl) { - const { request } = new Octokit({ auth: settings.github.token }); + const { request } = createOctokit(); const { data } = await request(commitUrl); return data.commit; } @@ -149,8 +179,19 @@ async function _getLatestReleaseDate(repoOwner, repoName) { module.exports = { async getPullRequests(label) { - const pullRequests = await getDataFromGithub(label); - return createResponseForSlack(pullRequests, label); + const pullRequests = await getPullReviewsFromGithub(label); + const reviewsByPR = await Promise.all( + pullRequests.map(({number}) => getReviewsFromGithub(number)) + ); + + const data = zipWith(pullRequests, reviewsByPR, (pullRequest, reviews) => { + return { + pullRequest, + reviews, + }; + }); + + return createResponseForSlack(data, label); }, async getLatestReleaseTag(repoName = settings.github.repository) { @@ -173,7 +214,7 @@ module.exports = { const githubCICheckName = 'build-test-and-deploy'; const commitUrl = await getLastCommitUrl({ branchName, tagName }); const commitStatusUrl = commitUrl + '/check-runs'; - const octokit = new Octokit({ auth: settings.github.token }); + const octokit = createOctokit(); const { data } = await octokit.request(commitStatusUrl); const runs = data.check_runs; const ciRuns = runs.filter((run) => run.name === githubCICheckName); diff --git a/test/unit/build/services/github_test.js b/test/unit/build/services/github_test.js index b74eaa2b..ca57c2af 100644 --- a/test/unit/build/services/github_test.js +++ b/test/unit/build/services/github_test.js @@ -1,26 +1,35 @@ const { describe, it } = require('mocha'); const { expect } = require('chai'); -const { sinon, nock } = require('../../../test-helper'); -const axios = require('axios'); +const { nock } = require('../../../test-helper'); const githubService = require('../../../../common/services/github'); describe('#getPullRequests', function() { - const items = [ - { html_url: 'http://test1.fr', title: 'PR1', labels: [ { name: 'team certif'} ]}, - { html_url: 'http://test2.fr', title: 'PR2', labels: [ { name: ':construction: toto'}, { name: ':idea: team certif'}] }, - ]; - beforeEach(() => { - sinon.stub(axios, 'get').resolves({ data: { items: items } }); - }); + it('should return the response for slack', async function() { // given + const items = [ + { html_url: 'http://test1.fr', number: 0, title: 'PR1', labels: [ { name: 'team certif'} ]}, + { html_url: 'http://test2.fr', number: 1, title: 'PR2', labels: [ { name: ':construction: toto'}, { name: ':idea: team certif'}] }, + ]; + + nock('https://api.github.com') + .get(`/search/issues?q=is%3Apr+is%3Aopen+archived%3Afalse+user%3Agithub-owner+label%3Ateam-certif&sort=updated&order=desc`) + .reply(200, {items}) + + nock('https://api.github.com') + .get('/repos/github-owner/github-repository/pulls/0/reviews') + .reply(200, [{state: 'COMMENTED'}, {state: 'APPROVED'}]); + nock('https://api.github.com') + .get('/repos/github-owner/github-repository/pulls/1/reviews') + .reply(200, [{state: 'CHANGES_REQUESTED'}]); + const expectedResponse = { response_type: 'in_channel', text: 'PRs à review pour team-certif', attachments: [ - { color: '#B7CEF5', pretext: '', fields: [{ value: ':construction: :idea:', short: false }] }, - { color: '#B7CEF5', pretext: '', fields: [{ value: '', short: false }] } + { color: '#B7CEF5', pretext: '', fields: [{ value: '[❌x1]:construction: :idea:', short: false }] }, + { color: '#B7CEF5', pretext: '', fields: [{ value: '[💬x1|✅x1]', short: false }] } ] }; @@ -33,13 +42,24 @@ describe('#getPullRequests', function() { it('should call the Github API with the label without space', async function() { // given - const expectedUrl = 'https://api.github.com/search/issues?q=is:pr+is:open+archived:false+sort:updated-desc+user:1024pix+label:Tech%20Review%20Needed'; + let scopePr, scopeReview; + const items = [ + { html_url: 'http://test1.fr', number: 0, title: 'PR1', labels: [ { name: 'team certif'} ]}, + ]; + scopePr = nock('https://api.github.com') + .get(`/search/issues?q=is%3Apr+is%3Aopen+archived%3Afalse+user%3Agithub-owner+label%3ATech%2520Review%2520Needed&sort=updated&order=desc`) + .reply(200, {items}) + + scopeReview = nock('https://api.github.com') + .get('/repos/github-owner/github-repository/pulls/0/reviews') + .reply(200, []); // when await githubService.getPullRequests('Tech Review Needed'); // then - sinon.assert.calledWith(axios.get, expectedUrl); + expect(scopePr.isDone()); + expect(scopeReview.isDone()); }); }); From d75159179e58dd45428be2cd9d273593eeb0b64b Mon Sep 17 00:00:00 2001 From: Antoine Ceol Date: Mon, 10 May 2021 15:17:44 +0200 Subject: [PATCH 2/6] Sort PR with in progress last --- common/services/github.js | 23 ++++++++++++++++++----- test/unit/build/services/github_test.js | 24 ++++++++++++------------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/common/services/github.js b/common/services/github.js index 17904d2e..fb831366 100644 --- a/common/services/github.js +++ b/common/services/github.js @@ -1,5 +1,4 @@ const { Octokit } = require('@octokit/rest'); -const axios = require('axios'); const settings = require('../../config'); const { zipWith, countBy, entries, noop } = require('lodash'); @@ -66,29 +65,43 @@ function getReviewsLabel(reviews) { case 'APPROVED': return `✅x${times}`; case 'CHANGES_REQUESTED': return `❌x${times}`; } - }).join('|'); + }).join(' '); } function createResponseForSlack(data, label) { const attachments = data.map(({pullRequest, reviews}) => { const emojis = getEmojis(pullRequest); const reviewsLabel = getReviewsLabel(reviews); + const link = `<${pullRequest.html_url}|${pullRequest.title}>`; + const message = [reviewsLabel, emojis, link].filter(Boolean).join(' | '); return { color: color[label], pretext: '', - fields:[ {value: `[${reviewsLabel}]${emojis}<${pullRequest.html_url}|${pullRequest.title}>`, short: false},], + fields:[ {value: message, short: false},], }; - }); + }).sort(sortWithInProgressLast); const response = { response_type: 'in_channel', text: 'PRs à review pour ' + label, - attachments: attachments.sort().reverse() + attachments }; return response; } +function sortWithInProgressLast(prA, prB) { + const fieldA = prA.fields[0].value; + const fieldB = prB.fields[0].value; + const inProgressIcon = ':construction:'; + const isAinProgress = fieldA.indexOf(inProgressIcon) !== -1; + const isBinProgress = fieldB.indexOf(inProgressIcon) !== -1; + + if(isAinProgress && !isBinProgress) return 1; + if(!isAinProgress && isBinProgress) return -1; + return fieldA.localeCompare(fieldB); +} + async function getLastCommitUrl({ branchName, tagName }) { if (branchName) { return await getBranchLastCommitUrl(branchName); diff --git a/test/unit/build/services/github_test.js b/test/unit/build/services/github_test.js index ca57c2af..6b0e10b8 100644 --- a/test/unit/build/services/github_test.js +++ b/test/unit/build/services/github_test.js @@ -14,22 +14,22 @@ describe('#getPullRequests', function() { ]; nock('https://api.github.com') - .get(`/search/issues?q=is%3Apr+is%3Aopen+archived%3Afalse+user%3Agithub-owner+label%3Ateam-certif&sort=updated&order=desc`) - .reply(200, {items}) + .get('/search/issues?q=is%3Apr+is%3Aopen+archived%3Afalse+user%3Agithub-owner+label%3Ateam-certif&sort=updated&order=desc') + .reply(200, {items}); nock('https://api.github.com') - .get('/repos/github-owner/github-repository/pulls/0/reviews') - .reply(200, [{state: 'COMMENTED'}, {state: 'APPROVED'}]); + .get('/repos/github-owner/github-repository/pulls/0/reviews') + .reply(200, [{state: 'COMMENTED'}, {state: 'APPROVED'}]); nock('https://api.github.com') - .get('/repos/github-owner/github-repository/pulls/1/reviews') - .reply(200, [{state: 'CHANGES_REQUESTED'}]); + .get('/repos/github-owner/github-repository/pulls/1/reviews') + .reply(200, [{state: 'CHANGES_REQUESTED'}]); const expectedResponse = { response_type: 'in_channel', text: 'PRs à review pour team-certif', attachments: [ - { color: '#B7CEF5', pretext: '', fields: [{ value: '[❌x1]:construction: :idea:', short: false }] }, - { color: '#B7CEF5', pretext: '', fields: [{ value: '[💬x1|✅x1]', short: false }] } + { color: '#B7CEF5', pretext: '', fields: [{ value: '💬x1 ✅x1 | ', short: false }] }, + { color: '#B7CEF5', pretext: '', fields: [{ value: '❌x1 | :construction: :idea: | ', short: false }] }, ] }; @@ -47,12 +47,12 @@ describe('#getPullRequests', function() { { html_url: 'http://test1.fr', number: 0, title: 'PR1', labels: [ { name: 'team certif'} ]}, ]; scopePr = nock('https://api.github.com') - .get(`/search/issues?q=is%3Apr+is%3Aopen+archived%3Afalse+user%3Agithub-owner+label%3ATech%2520Review%2520Needed&sort=updated&order=desc`) - .reply(200, {items}) + .get('/search/issues?q=is%3Apr+is%3Aopen+archived%3Afalse+user%3Agithub-owner+label%3ATech%2520Review%2520Needed&sort=updated&order=desc') + .reply(200, {items}); scopeReview = nock('https://api.github.com') - .get('/repos/github-owner/github-repository/pulls/0/reviews') - .reply(200, []); + .get('/repos/github-owner/github-repository/pulls/0/reviews') + .reply(200, []); // when await githubService.getPullRequests('Tech Review Needed'); From b0b112c6df5125e4d2f75367181a60a9f1d4a844 Mon Sep 17 00:00:00 2001 From: Vincent Hardouin Date: Wed, 12 May 2021 11:35:44 +0200 Subject: [PATCH 3/6] BSR: Add _ prefix for not exported method in github service --- common/services/github.js | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/common/services/github.js b/common/services/github.js index fb831366..8a951db1 100644 --- a/common/services/github.js +++ b/common/services/github.js @@ -10,7 +10,7 @@ const color = { 'team-acces': '#A2DCC1', }; -function createOctokit() { +function _createOctokit() { return new Octokit({ auth: settings.github.token, log: { @@ -22,11 +22,11 @@ function createOctokit() { }); } -async function getPullReviewsFromGithub(label){ +async function _getPullReviewsFromGithub(label){ const owner = settings.github.owner; label = label.replace(/ /g, '%20'); - const octokit = createOctokit(); + const octokit = _createOctokit(); const { data } = await octokit.search.issuesAndPullRequests({ q: `is:pr+is:open+archived:false+user:${owner}+label:${label}`, sort: 'updated', @@ -36,10 +36,10 @@ async function getPullReviewsFromGithub(label){ return data.items; } -async function getReviewsFromGithub(pull_number){ +async function _getReviewsFromGithub(pull_number){ const owner = settings.github.owner; const repo = settings.github.repository; - const octokit = createOctokit(); + const octokit = _createOctokit(); const { data } = await octokit.pulls.listReviews({ owner, repo, @@ -48,7 +48,7 @@ async function getReviewsFromGithub(pull_number){ return data; } -function getEmojis(pullRequests) { +function _getEmojis(pullRequests) { const labelsEmojis = pullRequests.labels.map(label => { const match = label.name.match(/^:[A-z,_,-]*:/); return match ? match[0] : ''; @@ -56,7 +56,7 @@ function getEmojis(pullRequests) { return labelsEmojis.filter(Boolean).join(' '); } -function getReviewsLabel(reviews) { +function _getReviewsLabel(reviews) { const countByState = countBy(reviews, 'state'); return entries(countByState) .map(([label, times]) => { @@ -68,10 +68,10 @@ function getReviewsLabel(reviews) { }).join(' '); } -function createResponseForSlack(data, label) { +function _createResponseForSlack(data, label) { const attachments = data.map(({pullRequest, reviews}) => { - const emojis = getEmojis(pullRequest); - const reviewsLabel = getReviewsLabel(reviews); + const emojis = _getEmojis(pullRequest); + const reviewsLabel = _getReviewsLabel(reviews); const link = `<${pullRequest.html_url}|${pullRequest.title}>`; const message = [reviewsLabel, emojis, link].filter(Boolean).join(' | '); return { @@ -79,7 +79,7 @@ function createResponseForSlack(data, label) { pretext: '', fields:[ {value: message, short: false},], }; - }).sort(sortWithInProgressLast); + }).sort(_sortWithInProgressLast); const response = { response_type: 'in_channel', @@ -90,7 +90,7 @@ function createResponseForSlack(data, label) { return response; } -function sortWithInProgressLast(prA, prB) { +function _sortWithInProgressLast(prA, prB) { const fieldA = prA.fields[0].value; const fieldB = prB.fields[0].value; const inProgressIcon = ':construction:'; @@ -104,15 +104,15 @@ function sortWithInProgressLast(prA, prB) { async function getLastCommitUrl({ branchName, tagName }) { if (branchName) { - return await getBranchLastCommitUrl(branchName); + return await _getBranchLastCommitUrl(branchName); } - return getTagCommitUrl(tagName); + return _getTagCommitUrl(tagName); } -async function getBranchLastCommitUrl(branch) { +async function _getBranchLastCommitUrl(branch) { const owner = settings.github.owner; const repo = settings.github.repository; - const octokit = createOctokit(); + const octokit = _createOctokit(); const { data } = await octokit.repos.getBranch({ owner, repo, @@ -121,21 +121,21 @@ async function getBranchLastCommitUrl(branch) { return data.commit.url; } -async function getTagCommitUrl(tagName) { +async function _getTagCommitUrl(tagName) { const owner = settings.github.owner; const repo = settings.github.repository; - const tags = await getTags(owner, repo); + const tags = await _getTags(owner, repo); const tag = tags.find((tag) => tag.name === tagName); return tag.commit.url; } async function getLatestRelease(repoOwner, repoName) { - const tags = await getTags(repoOwner, repoName); + const tags = await _getTags(repoOwner, repoName); return tags[0]; } -async function getTags(repoOwner, repoName) { - const { repos } = createOctokit(); +async function _getTags(repoOwner, repoName) { + const { repos } = _createOctokit(); const { data } = await repos.listTags({ owner: repoOwner, repo: repoName, @@ -143,8 +143,8 @@ async function getTags(repoOwner, repoName) { return data; } -async function getDefaultBranch(repoOwner, repoName) { - const { repos } = createOctokit(); +async function _getDefaultBranch(repoOwner, repoName) { + const { repos } = _createOctokit(); const { data } = await repos.get({ owner: repoOwner, repo: repoName, @@ -153,8 +153,8 @@ async function getDefaultBranch(repoOwner, repoName) { } async function _getMergedPullRequestsSortedByDescendingDate(repoOwner, repoName) { - const defaultBranch = await getDefaultBranch(repoOwner, repoName); - const { pulls } = createOctokit(); + const defaultBranch = await _getDefaultBranch(repoOwner, repoName); + const { pulls } = _createOctokit(); const { data } = await pulls.list({ owner: repoOwner, repo: repoName, @@ -177,7 +177,7 @@ async function _getLatestReleaseTagName(repoOwner, repoName) { } async function _getCommitAtURL(commitUrl) { - const { request } = createOctokit(); + const { request } = _createOctokit(); const { data } = await request(commitUrl); return data.commit; } @@ -192,9 +192,9 @@ async function _getLatestReleaseDate(repoOwner, repoName) { module.exports = { async getPullRequests(label) { - const pullRequests = await getPullReviewsFromGithub(label); + const pullRequests = await _getPullReviewsFromGithub(label); const reviewsByPR = await Promise.all( - pullRequests.map(({number}) => getReviewsFromGithub(number)) + pullRequests.map(({number}) => _getReviewsFromGithub(number)) ); const data = zipWith(pullRequests, reviewsByPR, (pullRequest, reviews) => { @@ -204,7 +204,7 @@ module.exports = { }; }); - return createResponseForSlack(data, label); + return _createResponseForSlack(data, label); }, async getLatestReleaseTag(repoName = settings.github.repository) { @@ -227,7 +227,7 @@ module.exports = { const githubCICheckName = 'build-test-and-deploy'; const commitUrl = await getLastCommitUrl({ branchName, tagName }); const commitStatusUrl = commitUrl + '/check-runs'; - const octokit = createOctokit(); + const octokit = _createOctokit(); const { data } = await octokit.request(commitStatusUrl); const runs = data.check_runs; const ciRuns = runs.filter((run) => run.name === githubCICheckName); From 17fdc7707c588138466f99ebc00413140451d76f Mon Sep 17 00:00:00 2001 From: Vincent Hardouin Date: Wed, 12 May 2021 11:36:52 +0200 Subject: [PATCH 4/6] BSR: Remove redundant variable in _createResponseForSlack --- common/services/github.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/services/github.js b/common/services/github.js index 8a951db1..c9d53db1 100644 --- a/common/services/github.js +++ b/common/services/github.js @@ -81,13 +81,11 @@ function _createResponseForSlack(data, label) { }; }).sort(_sortWithInProgressLast); - const response = { + return { response_type: 'in_channel', text: 'PRs à review pour ' + label, attachments }; - - return response; } function _sortWithInProgressLast(prA, prB) { From 8bb243bd9e053345ab355847013aedb80542d955 Mon Sep 17 00:00:00 2001 From: Vincent Hardouin Date: Wed, 12 May 2021 11:37:50 +0200 Subject: [PATCH 5/6] BSR: Remove duplicate character in regex --- common/services/github.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/services/github.js b/common/services/github.js index c9d53db1..9b17e533 100644 --- a/common/services/github.js +++ b/common/services/github.js @@ -50,7 +50,7 @@ async function _getReviewsFromGithub(pull_number){ function _getEmojis(pullRequests) { const labelsEmojis = pullRequests.labels.map(label => { - const match = label.name.match(/^:[A-z,_,-]*:/); + const match = label.name.match(/^:[A-z,_-]*:/); return match ? match[0] : ''; }); return labelsEmojis.filter(Boolean).join(' '); From c5b17a114b210246967929449707022fca212900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Grill=C3=A8res?= Date: Wed, 12 May 2021 12:18:34 +0200 Subject: [PATCH 6/6] Wrap octokit.search.issuesAndPullRequests inside try catch block --- common/services/github.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/common/services/github.js b/common/services/github.js index 9b17e533..ab57b207 100644 --- a/common/services/github.js +++ b/common/services/github.js @@ -27,13 +27,19 @@ async function _getPullReviewsFromGithub(label){ label = label.replace(/ /g, '%20'); const octokit = _createOctokit(); - const { data } = await octokit.search.issuesAndPullRequests({ - q: `is:pr+is:open+archived:false+user:${owner}+label:${label}`, - sort: 'updated', - order: 'desc' - }); - return data.items; + try { + const { data } = await octokit.search.issuesAndPullRequests({ + q: `is:pr+is:open+archived:false+user:${owner}+label:${label}`, + sort: 'updated', + order: 'desc' + }); + + return data.items; + } catch (err) { + console.error(err); + throw err; + } } async function _getReviewsFromGithub(pull_number){