diff --git a/README.md b/README.md index 38c3379..9f2fdbb 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ A GitHub Action that verifies your pull request contains a reference to a Clubho Add `.github/workflows/lint.yaml` with the following: ```yaml -name: PR Lint +name: Clubhouse on: pull_request: types: [opened, edited, reopened, synchronize] jobs: ch_lint_pr: - name: Clubhouse + name: Check for story ID runs-on: ubuntu-latest steps: - uses: movableink/pr-clubhouse-lint-action@release diff --git a/index.js b/index.js index 4338a60..fc9bd28 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,15 @@ +// When using a release branch, we have node_modules vendored. But on other +// branches we don't, so when we're testing we want to optionally just run npm +// to install dependencies +const fs = require('fs'); +if (!fs.existsSync(`${__dirname}/node_modules`)) { + const child_process = require('child_process'); + child_process.execSync('npm install', { stdio: [0, 1, 2], cwd: __dirname }); +} + const github = require('@actions/github'); const action = require('./lib/action'); -action(github.context); +const octokit = new github.GitHub(process.env.GITHUB_TOKEN); + +action(github.context, octokit); diff --git a/lib/action.js b/lib/action.js index 7978f4c..9f9985a 100644 --- a/lib/action.js +++ b/lib/action.js @@ -1,9 +1,13 @@ const core = require('@actions/core'); -module.exports = function(context) { - // Matches [ch49555] and ch49555/branch-name - const clubhouseRegex = /(\[ch\d+\])|(ch\d+\/)/; +// Matches [ch49555] and ch49555/branch-name +const clubhouseRegex = /(\[ch\d+\])|(ch\d+\/)/; +// We need to be able to identify old checks to neutralize them, +// unfortunately the only way is to name them with one of these: +const jobNames = ['Clubhouse', 'Check for story ID']; + +module.exports = async function(context, api) { const { repository, pull_request } = context.payload; const repoInfo = { @@ -25,7 +29,7 @@ module.exports = function(context) { const passed = toSearch.some(line => { const linePassed = !!line.match(clubhouseRegex); - core.warning(`Searching ${line}...${linePassed}`); + console.log(`Searching ${line}...${linePassed}`); return linePassed; }); @@ -34,4 +38,24 @@ module.exports = function(context) { if (!passed) { core.setFailed('PR Linting Failed'); } + + if (process.env.GITHUB_TOKEN) { + // If there are any previously failed CH checks, set them to neutral + // since we want this check to override those. + const checkList = await api.checks.listForRef(repoInfo); + const { check_runs } = checkList.data; + + const clubhouseChecks = check_runs.filter(r => jobNames.includes(r.name)); + const completedChecks = clubhouseChecks.filter(r => r.status === 'completed'); + + for (let check of completedChecks) { + console.log(`Updating ${check.id} check to neutral status`); + + await api.checks.update({ + ...repoInfo, + check_run_id: check.id, + conclusion: 'neutral' + }); + } + } }; diff --git a/lib/action.test.js b/lib/action.test.js index ed85c88..09f1930 100644 --- a/lib/action.test.js +++ b/lib/action.test.js @@ -3,7 +3,10 @@ jest.mock('@actions/core'); const core = require('@actions/core'); const action = require('./action'); +process.env.GITHUB_TOKEN = 'fake_token'; + describe('pr-lint-action', () => { + let api = {}; let context = {}; const bad_title_and_branch = { @@ -47,39 +50,112 @@ describe('pr-lint-action', () => { core.setFailed = jest.fn(); }); - it('fails if missing from title and branch and body', async () => { - context.payload = pullRequestOpenedFixture(bad_title_and_branch); + describe('checking status', () => { + beforeEach(() => { + api.checks = { + listForRef: () => ({ data: { check_runs: [] } }), + update: () => {} + }; + }); - await action(context); - expect(core.setFailed).toHaveBeenCalledWith('PR Linting Failed'); - expect.assertions(1); - }); + it('fails if missing from title and branch and body', async () => { + context.payload = pullRequestOpenedFixture(bad_title_and_branch); - it('passes if branch matches', async () => { - context.payload = pullRequestOpenedFixture(bad_title_and_good_branch); + await action(context, api); + expect(core.setFailed).toHaveBeenCalledWith('PR Linting Failed'); + expect.assertions(1); + }); - await action(context); - expect(console.log).toHaveBeenCalledWith('Passed clubhouse number check: true'); - expect(core.setFailed).not.toHaveBeenCalled(); - expect.assertions(2); - }); + it('passes if branch matches', async () => { + context.payload = pullRequestOpenedFixture(bad_title_and_good_branch); + + await action(context, api); + expect(console.log).toHaveBeenCalledWith('Passed clubhouse number check: true'); + expect(core.setFailed).not.toHaveBeenCalled(); + expect.assertions(2); + }); + + it('passes if title matches', async () => { + context.payload = pullRequestOpenedFixture(good_title_and_branch); - it('passes if title matches', async () => { - context.payload = pullRequestOpenedFixture(good_title_and_branch); + await action(context, api); + expect(console.log).toHaveBeenCalledWith('Passed clubhouse number check: true'); + expect(core.setFailed).not.toHaveBeenCalled(); + expect.assertions(2); + }); - await action(context); - expect(console.log).toHaveBeenCalledWith('Passed clubhouse number check: true'); - expect(core.setFailed).not.toHaveBeenCalled(); - expect.assertions(2); + it('passes if body matches', async () => { + context.payload = pullRequestOpenedFixture(good_body); + + await action(context, api); + expect(console.log).toHaveBeenCalledWith('Passed clubhouse number check: true'); + expect(core.setFailed).not.toHaveBeenCalled(); + expect.assertions(2); + }); }); - it('passes if body matches', async () => { - context.payload = pullRequestOpenedFixture(good_body); + describe('clearing old checks', () => { + beforeEach(() => { + api.checks = { + listForRef: () => ({ + data: { + check_runs: [ + { + // this is us, ignore + id: 1, + name: 'Clubhouse', + status: 'in_progress', + conclusion: 'neutral' + }, + { + id: 2, + name: 'Clubhouse', + status: 'completed', + conclusion: 'failure' + }, + { + id: 3, + name: 'Clubhouse', + status: 'completed', + conclusion: 'failure' + }, + { + // this is travis, ignore + id: 4, + name: 'Travis', + status: 'completed', + conclusion: 'failure' + } + ] + } + }), + update: jest.fn() + }; + }); + + it('updates the previous failed check to neutral', async () => { + context.payload = pullRequestOpenedFixture(good_body); + + await action(context, api); + + expect(api.checks.update).toHaveBeenCalledTimes(2); + + expect(api.checks.update).toHaveBeenNthCalledWith(1, { + check_run_id: 2, + owner: 'movableink', + repo: 'pr-clubhouse-lint-action-test', + ref: 'fix_things', + conclusion: 'neutral' + }); - await action(context); - expect(console.log).toHaveBeenCalledWith('Passed clubhouse number check: true'); - expect(core.setFailed).not.toHaveBeenCalled(); - expect.assertions(2); + expect(api.checks.update).toHaveBeenNthCalledWith(2, { + check_run_id: 3, + owner: 'movableink', + repo: 'pr-clubhouse-lint-action-test', + ref: 'fix_things', + conclusion: 'neutral' + }); + }); }); }); diff --git a/package-lock.json b/package-lock.json index 3ec384e..7162e66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2434,9 +2434,9 @@ "dev": true }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.3.tgz", + "integrity": "sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -5114,13 +5114,13 @@ "dev": true }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.2.tgz", + "integrity": "sha512-+gh/xFte41GPrgSMJ/oJVq15zYmqr74pY9VoM69UzMzq9NFk4YDylclb1/bhEzZSaUQjbW5RvniHeq1cdtRYjw==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.0", + "commander": "2.20.0", "source-map": "~0.6.1" } },