- PVSM.RU - https://www.pvsm.ru -
Хотел бы рассказать о своем опыте настройки «YouTrack как HelpDesk» [1].
Перейдя по ссылке, что я указал выше, вы найдете более менее детальную инструкцию, как развернуть YouTrack с нуля и выполнить его первоначальную настройку. В случае же когда HelpDesk строится для обработки обращений в крупной компании, разработка своих Workflow неизбежна.
Я работаю в компании, которая предоставляет свои услуги банкам. До определенного времени мы использовали самописный 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. Поэтому написал банк — отправить автоответ, иначе не отправлять.
Кроме общения письмами, нам нужно было собирать информацию о состоянии задачи, кому принадлежит, сколько писем по задаче ушло. Для этих целей были созданы собственные поля задачи, такие как «Тип», «Критичность», «Количество писем банка», «Полигон», «Релиз», «Дедлайн», «Дата закрытия банком» и прочее.
Добрую половину из них заполняли при помощи 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 сам закрывает задачу, сам ставит нужные поля.
Уведомление о закрытии задачи:
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
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/upravlenie-proektami/79617
Ссылки в тексте:
[1] «YouTrack как HelpDesk»: http://habrahabr.ru/company/JetBrains/blog/222673/
[2] Источник: http://habrahabr.ru/post/247939/
Нажмите здесь для печати.