MVP (#5)
* 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:
parent
a15359d149
commit
9a63f16fbb
|
@ -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"
|
||||
|
||||
|
|
35
index.js
35
index.js
|
@ -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.');
|
||||
});
|
||||
|
|
|
@ -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!')
|
||||
})
|
||||
})
|
|
@ -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(', ')}`);
|
||||
}
|
||||
};
|
|
@ -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(', ')}`);
|
||||
}
|
||||
};
|
|
@ -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(',') ];
|
||||
}, []);
|
||||
};
|
|
@ -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' ]);
|
||||
});
|
||||
});
|
|
@ -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);
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue