Простой jira-бот для вашего слака на NodeJS
или любой другой базовый бот на ноде
Моей компании в слаке очень не хватало бота, который бы на упоминание задачи присылал информацию о ней, чтобы не ходить в жиру лишний раз и быстро ориентироваться сразу в чате.
TLDR: Работающее решение на nodejs, устанавливайте, пользуйтесь, присылайте PR!
Такие боты уже существуют как пакетное решение(быстро нагугленный вариант), но они платные, тяжеловесные и/или требуют выдавать наружу креды от жиры(что делать крайне небезопасно)
На гитхабе нашелся вот такой, но он
- жестко фиксирует проекты(префиксы задач), для которых работает
- не умеет писать в тред
И еще вот такой, но он
- написан на go и его сложно расширять при необходимости(так как я и мои коллеги не знаем go)
- имеет уж слишком мало параметров для конфигурации
В этой ситуации, конечно же, выходом было написать еще один бот!(это противоречит концепции opensource, но бодаться с авторами старых пакетов, чтобы сделать код более читаемым, удобным для поддержки и гибким конкретно под наши пожелания, не было времени и сил)
Я написала маленькое новое решение на nodejs и на его примере объясню, как легко писать любых ботов для слака на ноде!
Для начала нужно создать его:
Принцип работы простейшего бота
Бот добавляется в чат(он не видит личную переписку между остальными людьми в пространстве) и парсит каждое сообщение на предмет наличия там конструкции типа PROJECT-554, и, найдя его, дергает из API Jira задачу с таким ID, информацию о которой отправляет обратно в чат.
Как сформировать строки, вхождение которых искать?
Компания большая, проектов(а, значит, и разнообразных префиксов) в JIRA очень много, большинство из них даже не доступны всем пользователям. Периодически появляются новые проекты. Если хранить список префиксов в конфиге, то на каждый чих придется его править, а это неудобно, небезопасно и долго.
Мы придумали завести специального jira-пользователя исключительно под цели бота. И давать ему права на чтение во всех проектах, которые нужны. При старте приложения достаем rest-запросом из JIRA список всех доустпынх проектов и формируем из префиксов регексп. Если появляется необходимость добавить новый проект, выдаем пользователю дополнильные права и просто перезапускаем бот, не трогая конфиг. Перезапустить бота — задача несложная.
// создаем регексп из массива префиксов
updatePattern() {
this.pattern = this.projects.length
? `(${this.projects.join(‘|’)})-[1–9][0–9]*`
: null;
}
Как слушать каждое сообщение в чате?
Создаем экземпляры RTMClient и WebClient(это из Slack API, один для того, чтобы слушать события, второй — чтобы отправлять сообщения). И просто подписываемся на событие message у RTMClient. Туда будет приходить все, что написано в канале, в который приглашен бот, или же прямо в личку самому боту.
constructor(){
this.rtm = new RTMClient(slack.token, {logLevel: LogLevel.INFO});
this.web = new WebClient(slack.token, {logLevel: LogLevel.INFO}); this.rtm.on('message', data => { this.handleMessage(data);});
}
Проверяем, что сообщение достойного поиска упоминания задачи :)
const isMessageParsable = message.type === 'message'
&& message.text != null
&& message.subtype !== 'bot_message';
Ищем в тексте сообщения все вхождения созданного ранее регекспа(и удаляем дубли найденных задач при помощи lodash)
const regExp = new RegExp(this.pattern, 'g');
return _.uniq(message.text.match(regExp));
Через Promise.all(с обязательным экранированием ошибки в каждом запросе) получаем информацию обо всех упомянутых в сообщении задачах, вытаскиваем из них информацию, которую хотим выводить в боте. У нас в jira было кастомное поле implementer, опытным путем я вытащила его id.
Lodash тут(да и везде в примерах) используется, чтобы код просто выглядел короче. Использовать его совершенно не необходимость.
this.jiraApi.findIssue(issueId, function(error, issue) {
if (error) {
reject(error);
return;
}
const fixVersions = _.get(issue.fields, 'fixVersions', []).map(({name}) => name);resolve({
key: issueId,
status: _.get(issue.fields, 'status.name'),
implementer: _.get(issue.fields, 'customfield_10502.name',
summary: issue.fields.summary,
fixVersions
});
});
составляем из них красивое сообщение с версткой.
generateMessage({key, summary, implementer, status, fixVersions}) {
const msg = [];
msg.push(`> *${this.generateLink(key, `${key} ${summary}`)}*`);
msg.push(`> *Implementer* ${implementer}`);
msg.push(`> *Fix versions:* ${fixVersions.join(', ')}`);
msg.push(`> *Status* ${status}`);
return msg.join('\n');
}
И отправляем его в чат(или в тред к сообщению, смотря что в настройках). message
здесь — оригинальное сообщение, которое мы и парсили. this.web
уже был создан выше, вместе с this.rtm
. jira.useThread
— конфиг, переданный при старте бота
const thread = jira.useThread ? message.ts : null;postMessage(messageToSend, message.channel, message.thread_ts || thread);function postMessage(text, channel, thread_ts) {
this.web.chat
.postMessage({
text,
channel,
thread_ts
})
}
Вот и все! Бот готов к запуску локально и на сервере.
Как запустить бота?
Локально — просто заполнить файл с кредами и конфигами и выполнить npm start
. А на сервере? Самый просто способ — просто запускать на CI
npm i && npm start:ci
настроив CI так, чтобы он кидал в node.env все необходимые переменные. Это можно сделать в любом CI, и это избавит нас от необходимости пушить переменные в git.
Еще мой коллега Саша написал решение для докера(уже в репозитории), однако там как раз требуется пушить нежные креды. Жду ваш PR с улучшением этого момента ;)