Простой jira-бот для вашего слака на NodeJS

или любой другой базовый бот на ноде

Katya Pavlenko
4 min readJul 10, 2019

Моей компании в слаке очень не хватало бота, который бы на упоминание задачи присылал информацию о ней, чтобы не ходить в жиру лишний раз и быстро ориентироваться сразу в чате.

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.

так это выглядит на heroku

Еще мой коллега Саша написал решение для докера(уже в репозитории), однако там как раз требуется пушить нежные креды. Жду ваш PR с улучшением этого момента ;)

--

--

Katya Pavlenko
Katya Pavlenko

Written by Katya Pavlenko

frontend developer, love instant noodles and super simple explanations of complex things Github: https://github.com/cakeinpanic

No responses yet