Skip to content

Instantly share code, notes, and snippets.

@joegaudet
Last active January 26, 2020 20:04
Show Gist options
  • Save joegaudet/18b4020082e8b7ef0861c2ee30e76de7 to your computer and use it in GitHub Desktop.
Save joegaudet/18b4020082e8b7ef0861c2ee30e76de7 to your computer and use it in GitHub Desktop.
{
"name": "foodee-slack-jira",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"async-redis": "^1.1.7",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"jira-connector": "^2.16.1",
"redis": "^2.8.0",
"slack": "^11.0.2"
}
}
web: node index.js
// Infra
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
const port = process.env.PORT || 4000;
const redis = require('async-redis');
const client = redis.createClient({url: process.env['REDIS_URL']});
// Slack
const token = '';
const Slack = require('slack');
const bot = new Slack({token});
// Jira
let jira;
const HOST = "foodee.atlassian.net";
const PROJECT = 'DEV';
const BUG_REPORTS_TESTING = '';
const BUG_REPORTS = '';
try {
const JiraClient = require("jira-connector");
jira = new JiraClient({
host: HOST,
basic_auth: {
email: "",
api_token: ""
}
});
} catch (e) {
throw 'Could not connect to jira'
}
const BUG_FORM = {
title: 'Submit a bug',
submit_label: 'Submit',
callback_id: 'file_bug',
elements: [
{
type: 'text',
label: 'Summary',
name: 'summary'
},
{
type: 'select',
label: 'Severity',
name: 'priority',
value: 'Low',
placeholder: 'How severe is this issue',
options: [
{
label: 'Critical',
value: 'Highest'
},
{
label: 'High',
value: 'High'
},
{
label: 'Medium',
value: 'Medium'
},
{
label: 'Low',
value: 'Low'
},
{
label: 'Lowest',
value: 'Lowest'
},
]
},
{
type: 'textarea',
label: 'Description',
name: 'description',
hint: 'Describe the issue in as much detail as possible'
},
{
type: 'text',
label: 'URL',
optional: true,
name: 'customfield_11215',
subtype: 'url'
},
{
type: 'text',
label: 'Order Number',
optional: true,
name: 'customfield_11216',
subtype: 'number'
},
{
type: 'select',
label: 'Affected User Type',
name: 'customfield_11217',
value: 'Client',
options: [
{
label: 'Client',
value: 'Client'
},
{
label: 'Restaurant',
value: 'Restaurant'
},
{
label: 'Internal',
value: 'Internal'
}
]
},
{
type: 'text',
label: 'Affected User ID',
hint: 'User/Client/Restaurant ID if applicable',
name: 'customfield_11218',
optional: true,
},
{
type: 'select',
label: 'Browser/Device',
name: 'customfield_11219',
options: [
{
label: 'StarFox - Chrome',
value: 'StarFox - Chrome'
},
{
label: 'SwiftFox - Chrome',
value: 'SwiftFox - Chrome'
},
{
label: 'SwiftFox - Internet Explorer',
value: 'SwiftFox - Internet Explorer'
},
{
label: 'SwiftFox - Safari',
value: 'SwiftFox - Safari'
},
{
label: 'SwiftFox - Phone (iOS/Android)',
value: 'SwiftFox - Phone (iOS/Android)',
},
{
label: 'FleetFox - iOS',
value: 'FleetFox - iOS',
},
{
label: 'FleetFox - Android',
value: 'FleetFox - Android',
},
{
label: 'Unknown',
value: 'Unknown',
}
]
}
],
labelFor(name) {
return this.elements.find(_ => _.name === name).label;
}
};
async function put(key, object) {
return await client.set(key, JSON.stringify(object));
}
async function get(key) {
const val = await client.get(key);
return val ? JSON.parse(val) : null;
}
app.post('/slack/commands/report-bug', async (req, res) => {
console.log('GET /slack/commands/report-bug');
const {trigger_id} = req.body;
try {
console.log('Triggering bug report form');
await bot.dialog.open({
trigger_id: trigger_id,
dialog: BUG_FORM
});
} catch (e) {
console.log(e)
}
res.end();
});
app.post('/slack/actions', async (req, res) => {
console.log('POST /slack/actions');
const {callback_id, submission, user} = JSON.parse(req.body.payload);
if (callback_id === 'file_bug') {
console.log(submission);
const text = Object
.entries(submission)
.filter(([k, v]) => v !== null)
.map(([k, v]) => `> *${BUG_FORM.labelFor(k)}*: ${v}`)
.join('\n');
console.log('Posting to bug room');
const {channel, ts} = await bot.chat.postMessage({
channel: BUG_REPORTS,
text,
link_names: 1,
mrkdwn: true
});
const {permalink} = await bot.chat.getPermalink({channel, message_ts: ts});
const userInfo = await bot.users.info({user: user.id});
const userName = userInfo.user.real_name;
// TODO move this coercion to the DIALOG Constant
submission.priority = {name: submission.priority};
submission.customfield_11216 = parseInt(submission.customfield_11216, 10);
submission.customfield_11218 = parseInt(submission.customfield_11218, 10);
submission.customfield_11217 = {value: submission.customfield_11217};
submission.customfield_11219 = {value: submission.customfield_11219};
console.log('Creating Jira Issue');
const {key} = await jira.issue.createIssue({
fields: Object.assign(submission, {
project: {key: PROJECT},
issuetype: {
"name": "Bug"
},
customfield_11223: userName,
customfield_11224: permalink
})
});
console.log('Posting to thread');
await bot.chat.postMessage({
channel,
text: `Thanks for reporting <@${user.id}> I've created issue <https://${HOST}/browse/${key}|${key}>. Please upload any screenshots you might find helpful.`,
thread_ts: ts,
link_names: 1
}
);
await put(key, {
channel,
thread_ts: ts,
status: 'TODO',
priority: submission.priority.name
});
}
res.send({});
});
app.get('/slack', (req, res) => {
console.log('GET /slack');
res.send({});
});
app.post('/ping', (req, res) => {
console.log('POST /ping');
res.send({message: 'pong'});
});
app.get('/', (req, res) => {
console.log('GET /');
res.send({message: 'Hello from foodee bot'});
});
app.post('/jira/events', async (req, res) => {
console.log('POST /jira/events');
if (req.body.webhookEvent === 'jira:issue_updated') {
const key = req.body.issue.key;
const storedIssue = (await get(key)) || {};
const {channel, thread_ts} = storedIssue;
const {assignee, priority, status} = req.body.issue.fields;
if (assignee !== null && assignee.emailAddress !== storedIssue.assignee) {
storedIssue.assignee = assignee.emailAddress;
if (storedIssue.assignee) {
const {user: {id}} = await bot.users.lookupByEmail({email: storedIssue.assignee});
if (channel && thread_ts) {
await bot.chat.postMessage({
channel,
text: `<https://${HOST}/browse/${key}|${key}> assigned to <@${id}>`,
thread_ts,
link_names: 1
}
);
try {
console.log(`Adding :investigating-issue: to ${thread_ts}`);
await bot.reactions.add({
channel,
name: 'investigating-issue',
timestamp: thread_ts
}
);
} catch (e) {
console.log(e);
}
}
}
}
if (priority.name !== storedIssue.priority) {
storedIssue.priority = priority.name;
if (channel && thread_ts) {
await bot.chat.postMessage({
channel,
text: `<https://${HOST}/browse/${key}|${key}> priority changed to: ${priority.name}`,
thread_ts,
link_names: 1
}
);
}
}
if (status.name !== storedIssue.status) {
storedIssue.status = status.name;
if (channel && thread_ts) {
await bot.chat.postMessage({
channel,
text: `<https://${HOST}/browse/${key}|${key}> status changed to: ${status.name}`,
thread_ts,
link_names: 1
}
);
if (status.name === 'DONE') {
try {
await bot.reactions.add({
channel,
name: 'white_check_mark',
timestamp: thread_ts
}
);
} catch (e) {
console.log(e);
}
}
}
}
put(key, storedIssue)
}
res.send({});
}
);
app.listen(port, () => console.log(`Lady Bug Man listening on port ${port}!`));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment