-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Skeleton of resolving and applying labels copy-pasted from github-bot (…
…#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
Showing
14 changed files
with
6,730 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.