Кастомизация рабочих процессов в YouTrack. Мой опыт тонкой настройки YouTrack как HelpDesk
Хотел бы рассказать о своем опыте настройки «YouTrack как HelpDesk».
Перейдя по ссылке, что я указал выше, вы найдете более менее детальную инструкцию, как развернуть YouTrack с нуля и выполнить его первоначальную настройку. В случае же когда HelpDesk строится для обработки обращений в крупной компании, разработка своих Workflow неизбежна.
1. Регистрируем обращение, формируем автоответы
Я работаю в компании, которая предоставляет свои услуги банкам. До определенного времени мы использовали самописный HelpDesk и он был убогим. В определенное время встал вопрос о его замене. При этом для сотрудников банков этот переезд должен был быть незаметен/заметен минимально.
Первое, с чем пришлось столкнуться мне, регистрация новых обращений и отправка автоответов на них.
С регистрацией все просто, добавляем SMTP, в параметрах интеграции с электронной почтой добавляем ящик и правило фильтрации. С автоответом поинтереснее. Изначально у меня было правило «Send notifications to unregistered users» в процессе notifyUnregisteredUsers. Суть его сводилась к правилу «Когда добавлен комментарий — отправить ». Однако это правило подходит нам только для отправки ответа в банк, когда мы его написали сами в комментарии.
В качестве решения было написано такое правило:
rule notification about created issue
when becomesReported() {
.....
if (Customer email != null) {
sendMail(Customer email, "Autoreply:" + " [" + getId() + "] " + issue.summary, "Ваше письмо получено службой технической поддержки компании "ИМЯ РЕК", соответствующая задача зарегистрирована под номером: [" + getId() + "]<br>Время регистрации: " + now.format(mediumDateTime) + "<br>В ближайшее время Ваше обращение будет обработано. <br><br>С уважением, .... и т.д.")
}
}
Условие when becomesReported() срабатывает всегда ДО момента публикации нового issue. Так решена задача автоответа.
Следующим стал вопрос, как отправить автоответ при добавлении комментария банка (т.е. ответа на ответ суппортера). В качестве решения подправил Send notifications to unregistered users:
when comments.added.isNotEmpty {
......
// Если Last comment author не null, это ответ банка
if (Last comment author != null) {
sendMail(Last comment author, "Autoreply:" + " [" + getId() + "] " + issue.summary, "Ваше письмо получено cлужбой технической поддержки компании "ИМЯ РЕК" и добавлено к ранее зарегистрированной задаче под номером: [" + getId() + "]<br>Время регистрации: " + now.format(mediumDateTime) + "<br>В ближайшее время Ваше обращение будет обработано. <br><br>С уважением, cлужба .....");
}
.......
Last comment author = null;
}
Last comment author поле техническое, оно необходимо для того, чтобы понять кто писал банк или суппортер. В почтовом правиле в пост-обработке мы указали «Last comment author ${from}» что означает «Установить значение поля Last comment author как e-mail отправителя письма».
А в конце правила мы его обязательно ставим как null. Поэтому написал банк — отправить автоответ, иначе не отправлять.
2. Заполнение кастомных полей
Кроме общения письмами, нам нужно было собирать информацию о состоянии задачи, кому принадлежит, сколько писем по задаче ушло. Для этих целей были созданы собственные поля задачи, такие как «Тип», «Критичность», «Количество писем банка», «Полигон», «Релиз», «Дедлайн», «Дата закрытия банком» и прочее.
Добрую половину из них заполняли при помощи Workflow. Ниже пример заполнения дедлайна и определения банка по домену почты:
when becomesReported() {
.....
// на ответ банку сутки без учета выходных
issue.deadline = now + 24 hours;
if (issue.deadline.format(#e) == 6) {
issue.deadline = issue.deadline + 48 hours;
}
if (issue.deadline.format(#e) == 7) {
issue.deadline = issue.deadline + 24 hours;
}
.....
// Определим и сохраним Банк
var bank = "";
var ce = "";
ce = issue.Customer email + "~";
bank = ce.substringBetween("@", "~");
if (issue.Банк == null) {
if (bank == "somebank.domain.com") {
issue.Банк = {Какой-то банк};
}
.......
}
}
Немного поясню код. issue.{что-то} — это кастомное поле задачи. Оно отображается в проекте. А еще можно опускать «issue.» таким образом, issue.Customer email и Customer email это одно и то же. А вот var bank = ""; просто локальная переменная.
Вот правило, которое закрывает задачу самостоятельно:
rule CloseFromBankRequest
when issue.hasTag("Закрывается банком") {
if (issue.hasTag("Закрывается банком")) {
debug("Нашли тег у задачи" + issue.getId());
if (issue.Type == {Bug}) {
// Если это инцидент, проверим набор полей на заполнение и пустые заполним дефолтом
if (issue.desc == null) {
issue.desc = issue.summary;
}
if (issue.solution == null) {
issue.solution = "Закрыта банком самостоятельно";
}
issue.ballis = {Банк};
issue.polygon = {Продуктивный};
issue.causer = {Банк};
if (issue.Банк == null) {
issue.Банк = {Клиент банка};
}
issue.Timer = {Закрыта};
} else {
if (issue.desc == null) {
issue.desc = issue.description;
}
if (issue.Банк == null) {
issue.Банк = {Клиент банка};
}
issue.ballis = {Банк};
issue.Timer = {Закрыта};
}
}
}
when issue.hasTag — когда добавили тег.
У меня это правило используется так. Реализован свой web-интерфейс, в котором через api передается список задач банка. Нажав на кнопку «Закрыть» опять же через api YouTrack ставится тег. По этому тегу Workflow сам закрывает задачу, сам ставит нужные поля.
3. Еще пару примеров
Уведомление о закрытии задачи:
rule VoteCloseIssues
when issue.Состояние.changed {
if (issue.Состояние == {Fixed} && (issue.Type == {Bug} || issue.Type == {Task})) {
var preview = "Добрый день. <br><br> Уведомляем Вас, что ранее зарегистрированная задача [" + getId() + "] закрыта. <br>Вы можете повторно открыть задачу, перейдя по ссылке <a href="mailto:support@bifit.ua?subject=[" + getId() + "] " + summary + "&body=Укажите комментарий">переоткрыть</a> или ответив на это письмо. <br><br>Как бы Вы оценили уровень полученной поддержки? <br><a href="http://{link_to_web_interface}/report/Issues.php?api=vote&id=" + getId() + "&mark=good">Хорошо, я доволен</a><br><a href="http://{link_to_web_interface}/report/Issues.php?api=vote&id=" + getId() + "&mark=bad">Плохо, я недоволен</a><br><br>Ниже история переписки:<br><br>";
var i = 0;
var author_comment = "";
var subj_comment = "Уведомление о закрытии задачи [" + getId() + "]";
var text_comment = "";
var date_comment = now.format(mediumDateTime);
var full_text = "";
while (issue.comments[i].text != null || i < 10) {
author_comment = issue.comments[i].author.fullName;
date_comment = issue.comments[i].created.format(mediumDateTime);
text_comment = wikify(issue.comments[i].text);
// Уберем внутренние комменты
if (issue.comments[i].permittedGroup == null && issue.comments[i].text != null) {
full_text = "<b>" + author_comment + ":</b><br>Дата сообщения: " + date_comment + "<br><blockquote>" + text_comment + "</blockquote><br><hr><br><br>" + full_text;
}
i = i + 1;
}
full_text = full_text + "<b>Банк" + ":</b><br>Дата сообщения: " + issue.created.format(mediumDateTime) + "<br><blockquote>" + wikify(issue.description) + "</blockquote><br><hr><br><br>";
full_text = preview + full_text;
sendMail(Customer email, subj_comment, full_text);
}
}
Тут немного объясню.
when issue.Состояние.changed
Когда изменили значение поля «Состояние».
if (issue.Состояние == {Fixed} && (issue.Type == {Bug} || issue.Type == {Task}))
Если состояние «Закрыт» и тип задачи «Баг» или «Консультация».
preview
Начало текста письма. Тут уведомление о закрытии задачи, ссылки на оценку (все тот же web-интерфейс и api).
while (issue.comments[i].text != null || i < 10)
Пройдемся по 10-ти последним комментам и добавим автора, текст коммента в full_text (это история переписки)
full_text = full_text + "<b>Банк" + ":</b><br>Дата сообщения: " + issue.created.format(mediumDateTime) + "<br><blockquote>" + wikify(issue.description) + "</blockquote><br><hr><br><br>";
В истории самым последним комментарием будет первое обращение банка wikify(issue.description)
sendMail(Customer email, subj_comment, full_text);
Отправим все это в банк.
Заполним подсистему автоматически, если выбран конкретный тип модуля интеграции:
rule set_subsystem_if_change_gateway
when issue.Модуль интеграции.changed {
if (issue.Subsystem != {Модуль интеграции} && issue.Модуль интеграции != null) {
issue.Subsystem = {Модуль интеграции};
}
}
Проверяем, не вышли ли мы за дедлайн (задачи, запускаемые по времени):
schedule rule check deadline
every minute [issue.Timer != {Закрыта} || issue.Состояние == {Fixed}] {
if (issue.deadline == null || issue.Состояние == {Fixed} || issue.ballis == {Банк}) {
// Если нет дедлайна или задача закрыта или задача на стороне банка и есть тег просрочена, убираем
if (issue.hasTag("Просрочена")) {
issue.applyCommand("убрать тег Просрочена");
}
// иначе, если вышел дедлайн
} else if (issue.deadline <= now) {
// если есть тег отложена и просрочена, убираем "просрочена" и признак просрочена тоже
if (issue.hasTag("Отложена")) {
if (issue.hasTag("Просрочена")) {
issue.applyCommand("убрать тег Просрочена");
if (issue.isoverdue == {Да}) {
issue.isoverdue = null;
}
}
// иначе (не отложена задача)
} else {
if (!issue.hasTag("Просрочена")) {
issue.applyCommand("добавить тег Просрочена");
if (issue.isoverdue == null) {
issue.isoverdue = {Да};
}
}
}
}
}
Если мы забыли остановить задачу и ушли домой, YouTrack это сделает за вас:
schedule rule pause all issues in overtime
daily at 18:15:00 [issue.Timer == {В работе}] {
issue.Timer = {Пауза};
}
Ну, вот и все. Надеюсь моя публикация когда-нибудь поможет.
Вопросы/замечания пишите в комменты или личные сообщения.
Автор: lavelas