|
2 | 2 |
|
3 | 3 | const LRU = require('lru-cache')
|
4 | 4 | const retry = require('async').retry
|
| 5 | +const Aigle = require('aigle') |
| 6 | +const request = require('request') |
5 | 7 |
|
6 | 8 | const githubClient = require('./github-client')
|
| 9 | +const { createPrComment } = require('./github-comment') |
7 | 10 | const resolveLabels = require('./node-labels').resolveLabels
|
| 11 | +const { Owners } = require('./node-owners') |
8 | 12 | const existingLabelsCache = new LRU({ max: 1, maxAge: 1000 * 60 * 60 })
|
9 | 13 |
|
10 | 14 | const fiveSeconds = 5 * 1000
|
@@ -185,10 +189,115 @@ function stringsInCommon (arr1, arr2) {
|
185 | 189 | return arr1.filter((str) => loweredArr2.indexOf(str.toLowerCase()) !== -1)
|
186 | 190 | }
|
187 | 191 |
|
| 192 | +async function deferredResolveOwnersThenPingPr (options) { |
| 193 | + const timeoutMillis = (options.timeoutInSec || 0) * 1000 |
| 194 | + await sleep(timeoutMillis) |
| 195 | + return resolveOwnersThenPingPr(options) |
| 196 | +} |
| 197 | + |
| 198 | +function getCodeOwnersUrl (owner, repo, defaultBranch) { |
| 199 | + const base = 'raw.githubusercontent.com' |
| 200 | + const filepath = '.github/CODEOWNERS' |
| 201 | + return `https://${base}/${owner}/${repo}/${defaultBranch}/${filepath}` |
| 202 | +} |
| 203 | + |
| 204 | +async function getFiles ({ owner, repo, number, logger }) { |
| 205 | + try { |
| 206 | + const response = await githubClient.pullRequests.getFiles({ |
| 207 | + owner, |
| 208 | + repo, |
| 209 | + number |
| 210 | + }) |
| 211 | + return response.data.map(({ filename }) => filename) |
| 212 | + } catch (err) { |
| 213 | + logger.error(err, 'Error retrieving files from GitHub') |
| 214 | + throw err |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +async function getDefaultBranch ({ owner, repo, logger }) { |
| 219 | + try { |
| 220 | + const data = (await githubClient.repos.get({ owner, repo })).data || { } |
| 221 | + |
| 222 | + if (!data['default_branch']) { |
| 223 | + logger.error(null, 'Couldn\' determine default branch') |
| 224 | + throw new Error('unknown default branch') |
| 225 | + } |
| 226 | + |
| 227 | + return data.default_branch |
| 228 | + } catch (err) { |
| 229 | + logger.error(err, 'Error retrieving repository data') |
| 230 | + throw err |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +function getCodeOwnersFile (url, { logger }) { |
| 235 | + return new Promise((resolve, reject) => { |
| 236 | + request(url, (err, res, body) => { |
| 237 | + if (err || res.statusCode !== 200) { |
| 238 | + logger.error(err, 'Error retrieving OWNERS') |
| 239 | + return reject(err) |
| 240 | + } |
| 241 | + return resolve(body) |
| 242 | + }) |
| 243 | + }) |
| 244 | +} |
| 245 | + |
| 246 | +async function resolveOwnersThenPingPr (options) { |
| 247 | + const { owner, repo } = options |
| 248 | + const times = options.retries || 5 |
| 249 | + const interval = options.retryInterval || fiveSeconds |
| 250 | + const retry = fn => Aigle.retry({ times, interval }, fn) |
| 251 | + |
| 252 | + options.logger.debug('getting file paths') |
| 253 | + options.number = options.prId |
| 254 | + const filepathsChanged = await retry(() => getFiles(options)) |
| 255 | + |
| 256 | + options.logger.debug('getting default branch') |
| 257 | + const defaultBranch = await retry(() => getDefaultBranch(options)) |
| 258 | + |
| 259 | + const url = getCodeOwnersUrl(owner, repo, defaultBranch) |
| 260 | + options.logger.debug(`Fetching OWNERS on ${url}`) |
| 261 | + |
| 262 | + const file = await retry(() => getCodeOwnersFile(url, options)) |
| 263 | + |
| 264 | + options.logger.debug('parsing codeowners file') |
| 265 | + const owners = Owners.fromFile(file) |
| 266 | + const selectedOwners = owners.getOwnersForPaths(filepathsChanged) |
| 267 | + |
| 268 | + options.logger.debug('pinging codeowners file') |
| 269 | + if (selectedOwners.length > 0) { |
| 270 | + await pingOwners(options, selectedOwners) |
| 271 | + } |
| 272 | +} |
| 273 | + |
| 274 | +function getCommentForOwners (owners) { |
| 275 | + return `Review requested:\n\n${owners.map(i => `- [ ] ${i}`).join('\n')}` |
| 276 | +} |
| 277 | + |
| 278 | +async function pingOwners (options, owners) { |
| 279 | + try { |
| 280 | + await createPrComment({ |
| 281 | + owner: options.owner, |
| 282 | + repo: options.repo, |
| 283 | + number: options.prId, |
| 284 | + logger: options.logger |
| 285 | + }, getCommentForOwners(owners)) |
| 286 | + } catch (err) { |
| 287 | + options.logger.error(err, 'Error while pinging owners') |
| 288 | + throw err |
| 289 | + } |
| 290 | + options.logger.debug('Pinged owners: ' + owners) |
| 291 | +} |
| 292 | + |
188 | 293 | exports.getBotPrLabels = getBotPrLabels
|
189 | 294 | exports.removeLabelFromPR = removeLabelFromPR
|
190 | 295 | exports.fetchExistingThenUpdatePr = fetchExistingThenUpdatePr
|
191 | 296 | exports.resolveLabelsThenUpdatePr = deferredResolveLabelsThenUpdatePr
|
| 297 | +exports.resolveOwnersThenPingPr = deferredResolveOwnersThenPingPr |
192 | 298 |
|
193 | 299 | // exposed for testability
|
194 | 300 | exports._fetchExistingLabels = fetchExistingLabels
|
| 301 | +exports._testExports = { |
| 302 | + pingOwners, getCodeOwnersFile, getCodeOwnersUrl, getDefaultBranch, getFiles, getCommentForOwners |
| 303 | +} |
0 commit comments