Skip to content

Commit

Permalink
Skeleton of resolving and applying labels copy-pasted from github-bot (
Browse files Browse the repository at this point in the history
…#1)

With a few tweaks related to:

- Logging: using `@actions/core` methods like `core.debug()` instead of
		   `bunyan` like the github-bot has been using
- GitHub Client: using `@actions/github` which is more or less exactly
			     what the github-bot also used, namely the
			     `oktokit/rest.js` package, it has just been pieced
			     together to be more convenient for GitHub Actions code

Other than that, it's copy-pasting of what was found in the github-bot
project:

- `./lib/node-repo.js`
- `./lib/node-labels.js` (renamed to `resolve-labels.js`)

..removing code that was not related to resolving & applying labels, along
with the related tests.

Currently this is *not* to be considered a GitHub Action, it's only the
said files from github-bot, adjusted slightly to the `@actions/*`
packages as a precursor to adding GitHub Action artifacts like
`action.yml` and so on.

Copied from this specific point in time: [nodejs/github-bot 737094](https://github.com/nodejs/github-bot/tree/737094cf07224be73eafa279356fd44a1cc55c87)
  • Loading branch information
phillipj authored Mar 3, 2021
1 parent 143cc46 commit 4164d9e
Show file tree
Hide file tree
Showing 14 changed files with 6,730 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Test

on: [push, pull_request]

jobs:
tests:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [14.x, 15.x]

steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2.1.2
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
npm-debug.log

.DS_Store
.env
.vscode
.nyc_output
8 changes: 8 additions & 0 deletions lib/github-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict'

const github = require('@actions/github')
const core = require('@actions/core')

const token = core.getInput('repo-token', { required: true })

module.exports = github.getOctokit(token)
133 changes: 133 additions & 0 deletions lib/node-repo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
'use strict'

/* eslint-disable camelcase */

const core = require('@actions/core')
const LRU = require('lru-cache')
const Aigle = require('aigle')

const githubClient = require('./github-client')
const resolveLabels = require('./resolve-labels')
const existingLabelsCache = new LRU({ max: 1, maxAge: 1000 * 60 * 60 })

const fiveSeconds = 5 * 1000

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

async function deferredResolveLabelsThenUpdatePr (options) {
const timeoutMillis = (options.timeoutInSec || 0) * 1000
await sleep(timeoutMillis)
return resolveLabelsThenUpdatePr(options)
}

async function resolveLabelsThenUpdatePr (options) {
const times = options.retries || 5
const interval = options.retryInterval || fiveSeconds
const retry = fn => Aigle.retry({ times, interval }, fn)

const filepathsChanged = await retry(() => listFiles({
owner: options.owner,
repo: options.repo,
pull_number: options.prId
}))
core.debug('Fetching PR files for labelling')

const resolvedLabels = resolveLabels(filepathsChanged, options.baseBranch)

return fetchExistingThenUpdatePr(options, resolvedLabels)
}

async function fetchExistingThenUpdatePr (options, labels) {
try {
const existingLabels = await fetchExistingLabels(options)
const labelsToAdd = stringsInCommon(existingLabels, labels)
core.debug('Resolved labels: ' + labels)
core.debug('Resolved labels to add: ' + labelsToAdd)
core.debug('Resolved existing labels: ' + existingLabels)

return updatePrWithLabels(options, labelsToAdd)
} catch (err) {
core.error('Error retrieving existing repo labels: ' + err)

return updatePrWithLabels(options, labels)
}
}

async function updatePrWithLabels (options, labels) {
// no need to request github if we didn't resolve any labels
if (!labels.length) {
return
}

core.debug('Trying to add labels: ' + labels)

try {
await githubClient.issues.addLabels({
owner: options.owner,
repo: options.repo,
issue_number: options.prId,
labels: labels
})

core.info('Added labels: ' + labels)
} catch (err) {
core.error('Error while adding labels: ' + err)
}
}

async function fetchExistingLabels (options) {
const cacheKey = `${options.owner}:${options.repo}`

if (existingLabelsCache.has(cacheKey)) {
return existingLabelsCache.get(cacheKey)
}

const labelsResult = await fetchLabelPages(options, 1)
const existingLabels = labelsResult.data || labelsResult || []
const existingLabelNames = existingLabels.map((label) => label.name)

// cache labels so we don't have to fetch these *all the time*
existingLabelsCache.set(cacheKey, existingLabelNames)
core.debug('Filled existing repo labels cache: ' + existingLabelNames)

return existingLabelNames
}

async function fetchLabelPages (options) {
// the github client API is somewhat misleading,
// this fetches *all* repo labels not just for an issue
const listLabelsOptions = await githubClient.issues.listLabelsForRepo.endpoint.merge({
owner: options.owner,
repo: options.repo,
per_page: 100
})

return await githubClient.paginate(listLabelsOptions)
}

function stringsInCommon (arr1, arr2) {
const loweredArr2 = arr2.map((str) => str.toLowerCase())
// we want the original string cases in arr1, therefore we don't lowercase them
// before comparing them cause that would wrongly make "V8" -> "v8"
return arr1.filter((str) => loweredArr2.indexOf(str.toLowerCase()) !== -1)
}

async function listFiles ({ owner, repo, pull_number }) {
try {
const response = await githubClient.pulls.listFiles({
owner,
repo,
pull_number
})
return response.data.map(({ filename }) => filename)
} catch (err) {
core.error('Error retrieving files from GitHub: ' + err)
throw err
}
}

exports.fetchExistingThenUpdatePr = fetchExistingThenUpdatePr
exports.resolveLabelsThenUpdatePr = deferredResolveLabelsThenUpdatePr

// exposed for testability
exports._fetchExistingLabels = fetchExistingLabels
Loading

0 comments on commit 4164d9e

Please sign in to comment.