* Hey!

* Remove event option (its broken)

* Change up logging

* Adds pull_request opened test

* Log error

* Import logic

* This should be async

* Execute a module action

* Log moveResult

* Throw if failures exist

* Swapping API libs

* Well there's that

* Adds a bit of functionality

* 💄 eventModule handler

* Add payload log

* Change icon, fix ref type exclusion

* Fix typo

* Fixes body resolution

* Adds PR movement automations

* Nvm don't need this.
This commit is contained in:
James 2019-07-11 11:14:43 -06:00 committed by GitHub
parent a15359d149
commit 9a63f16fbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 799 additions and 760 deletions

View File

@ -3,7 +3,7 @@ FROM node:slim
LABEL "com.github.actions.name"="Zenhub Automations"
LABEL "com.github.actions.description"="Automate zenhub with ease"
# Here are all of the available icons: https://feathericons.com/
LABEL "com.github.actions.icon"="move"
LABEL "com.github.actions.icon"="power"
# And all of the available colors: https://developer.github.com/actions/creating-github-actions/creating-a-docker-container/#label
LABEL "com.github.actions.color"="gray-dark"

View File

@ -1,6 +1,33 @@
const { Toolkit } = require('actions-toolkit')
const { Toolkit } = require('actions-toolkit'),
{ join } = require('path');
const tools = new Toolkit();
// Run your GitHub Action!
Toolkit.run(async tools => {
tools.exit.success('We did it!')
})
const handlerRef = `${tools.context.event}.js`;
tools.log.info(`Trying to load handler: "${handlerRef}"...`);
try {
var eventModule = require(`./lib/handlers/${handlerRef}`);
} catch (e) {
console.log(e)
return tools.exit.neutral('Failed to load module for event. No action necessary.');
}
const moduleAction = eventModule[tools.context.payload.action] || eventModule[tools.context.payload.ref_type];
console.log(tools.context.payload);
if (!moduleAction) {
return tools.exit.neutral('Failed to find sub handler. No action necessary.');
}
try {
await moduleAction(tools);
} catch (e) {
return tools.exit.failure(`Failed to run event handler: ${e}`);
}
tools.exit.success('Executed event handler.');
});

View File

@ -1,23 +0,0 @@
const { Toolkit } = require('actions-toolkit')
describe('Zenhub Automations', () => {
let action, tools
// Mock Toolkit.run to define `action` so we can call it
Toolkit.run = jest.fn((actionFn) => { action = actionFn })
// Load up our entrypoint file
require('.')
beforeEach(() => {
// Create a new Toolkit instance
tools = new Toolkit()
// Mock methods on it!
tools.exit.success = jest.fn()
})
it('exits successfully', () => {
action(tools)
expect(tools.exit.success).toHaveBeenCalled()
expect(tools.exit.success).toHaveBeenCalledWith('We did it!')
})
})

56
lib/handlers/create.js Normal file
View File

@ -0,0 +1,56 @@
const zh = require('../zh-client'),
resolveIssueNumbers = require('../resolve-issue-number'),
{ INPROG_COLUMN } = process.env;
exports.branch = async function handleCreatedBranch (tools) {
tools.log.info('Handling created branch...');
const failures = [],
{ payload } = tools.context;
let issueNumbers = resolveIssueNumbers(payload.ref);
if (!issueNumbers || issueNumbers.length < 1) {
return tools.exit.neutral('No issues detected to act upon.');
}
for (let i = 0; i < issueNumbers.length; i++) {
let issueNo = issueNumbers[i];
// Assign sender to issue(s)
try {
tools.log.info(`Adding assignees for #${issueNo}...`);
await tools.github.issues.addAssignees({
owner: payload.repository.owner.login,
repo: payload.repository.name,
issue_number: issueNo,
assignees: [ payload.sender.login ]
});
tools.log.info(`Added assignees for #${issueNo}.`);
} catch (e) {
failures.push(`Failed to added assignees for #${issueNo}: ${e}`);
}
// Move issue(s) to INPROG_COLUMN
if (INPROG_COLUMN) {
try {
tools.log.info(`Moving issue #${issueNo} to in progress...`);
await zh.issues.moveIssueBetweenPipelines(payload.repository.id, issueNo, {
pipeline_id: INPROG_COLUMN,
position: 'top'
});
tools.log.info(`Moved #${issueNo} to in progress.`);
} catch (e) {
failures.push(`Failed to move #${issueNo} to in progress: ${e}`);
}
}
}
if (failures.length) {
throw new Error(`Failed to execute some actions: ${failures.map(x => x.message || x).join(', ')}`);
}
};

View File

@ -0,0 +1,50 @@
const zh = require('../zh-client'),
resolveIssueNumbers = require('../resolve-issue-number'),
{
PR_COLUMN,
REVIEW_COLUMN
} = process.env;
exports.opened = async function handleOpenedPR (tools) {
tools.log.info('Handling opened PR...');
const failures = [],
{ payload } = tools.context;
// Move PR to PR issue column specified by ENV var
if (PR_COLUMN) {
tools.log.info('ZH :: Moving opened PR...');
try {
await zh.issues.moveIssueBetweenPipelines(payload.repository.id, payload.number, {
pipeline_id: PR_COLUMN,
position: 'top'
});
} catch (e) {
failures.push(e);
}
} else {
tools.log.info('ZH :: Skipped moving opened PR because `PR_COLUMN` is undefined.');
}
// Move PR closes to REVIEW_COLUMN
if (REVIEW_COLUMN) {
let issueNumbers = resolveIssueNumbers(payload.pull_request.head.ref, payload.pull_request.body);
for (let i = 0; i < issueNumbers.length; i++) {
let issueNo = issueNumbers[i];
try {
await zh.issues.moveIssueBetweenPipelines(payload.repository.id, issueNo, {
pipeline_id: REVIEW_COLUMN,
position: 'top'
});
} catch (e) {
failures.push(`Failed to move issue ${issueNo}: ${e}`);
}
}
}
if (failures.length) {
throw new Error(`Failed to execute some actions: ${failures.map(x => x.message || x).join(', ')}`);
}
};

View File

@ -0,0 +1,32 @@
const { CLOSING_VERBS } = process.env;
const closingVerbs = CLOSING_VERBS ?
// converts CLOSING_VERBS=test,abc to [ 'test', 'abc' ]
CLOSING_VERBS.split(',') :
// default
[ 'closes', 'fixes', 'fixed', 'resolves', 'resolved', 'gotem' ];
const descMatching = new RegExp(`(?:${closingVerbs.join('|')}) +([\\d, #]+)`, 'i');
module.exports = function extractIssueNumbers (...str) {
return str.reduce((issueNumbers, s) => {
// branch mode
if (s.indexOf(' ') < 0) {
let brMatch = /#(\d+)/.exec(str);
if (brMatch && brMatch[1]) {
issueNumbers.push(brMatch[1]);
return issueNumbers;
}
}
// plain mode
let match = descMatching.exec(str);
if (!match || !match[1]) {
return issueNumbers;
}
return [ ...issueNumbers, ...match[1].replace(/\s|#/g, '').split(',') ];
}, []);
};

View File

@ -0,0 +1,59 @@
const chai = require('chai'),
expect = chai.expect;
const resolveNumbers = require('./resolve-issue-number');
describe('Unit :: Issue Number Resolution', () => {
it('should be a function', () => {
expect(resolveNumbers).to.be.a('function');
});
it('should resolve a branch issue', () => {
const cases = {
'feature/#123-tester': [ '123' ],
'enhancement/#22234': [ '22234' ],
'bug/#888-someiss': [ '888' ],
'#122': [ '122' ]
};
Object.keys(cases).forEach(branchName => {
let result = resolveNumbers(branchName),
expected = cases[branchName];
expect(result).to.have.lengthOf(expected.length);
expect(result).to.have.members(expected);
});
});
it('should resolve a description issue', () => {
const baseCases = {
'[verb] #123': [ '123' ],
'[verb] #123, #444': [ '123', '444' ],
'heyo [verb] #123': [ '123' ],
'heyo [verb] #123, #333, #456': [ '123', '333', '456' ]
};
const verbs = [ 'Closes', 'closes', 'fixes', 'fixed', 'resolves', 'resolved', 'gotem' ];
const cases = Object.keys(baseCases).reduce((c, desc) => {
verbs.forEach(verb => {
c[desc.replace('[verb]', verb)] = baseCases[desc];
})
return c;
}, {});
Object.keys(cases).forEach(desc => {
let result = resolveNumbers(desc),
expected = cases[desc];
expect(result).to.have.lengthOf(expected.length);
expect(result).to.have.members(expected);
});
});
it('accept multiple args for resolution', () => {
let multiArgResult = resolveNumbers('feature/#123-test', 'also closes #455');
expect(multiArgResult).to.have.members([ '123', '455' ]);
});
});

9
lib/zh-client.js Normal file
View File

@ -0,0 +1,9 @@
const ZenhubAPI = require('zenhub-client'),
{ promisifyAll } = require('bluebird'),
{ ZENHUB_API_KEY } = process.env;
if (!ZENHUB_API_KEY) {
throw new Error('"ZENHUB_API_KEY" was not found, please set it in environment secrets.');
}
module.exports = new ZenhubAPI(ZENHUB_API_KEY);

1286
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,12 +4,15 @@
"main": "index.js",
"scripts": {
"start": "node ./index.js",
"test": "jest"
"test": "NODE_ENV=test mocha -R spec **/*.test.js --exit"
},
"dependencies": {
"actions-toolkit": "^2.1.0"
"actions-toolkit": "^2.1.0",
"bluebird": "^3.5.5",
"zenhub-client": "^1.0.0"
},
"devDependencies": {
"jest": "^24.5.0"
"chai": "^4.2.0",
"mocha": "^6.1.4"
}
}