В наше время мало какой веб-сервис или приложение обходится без функционала, где пользователи могут пожаловаться (уведомить, зарепортить) на различные виды контента, будь то грамматическая ошибка в тексте, ошибочные формулировки, неинтересный или не информативный контент (как упражнение, урок, статья, обучающий материал либо часть функционала). Возможность сообщить о проблеме является неотъемлемой частью вовлечения пользователей в продукт, реализации одной из форм сбора обратной связи и возможности улучшить свое приложение в целом.
Все жалобы пользователей должны где-то храниться, быть приоритезированы, удобно отслеживаться, и, более того, должны быть вовремя обработаны. Но не всегда есть возможность выделить достаточно ресурсов на разработку и поддержание подобной системы, ведь всегда найдется задача в бэклоге с более высоким приоритетом. Ниже я расскажу, как мы достаточно эффективно и быстро решили эту задачу в Uxcel используя JIRA REST API.
Для чего нам Report a problem?
Добавим немного контекста для чего мы предоставляем функционал
жалоб и что нам нужно?
Uxcel веб-сервис для обучения UI/UX в игровой форме.
Обучающим элементом у нас является Практика в большинстве
случаев это 2 изображения, где одно верное, а другое нет. Что
позволяет натренировать глаз находить недочеты даже в визуально
идентичных элементах. Каждая практика помимо изображений
имеет подсказку (hint наводку на верный ответ) и описание
(description) с теорией, касающейся данной задачи.
Поскольку число практик исчисляется тысячами это
означает, что где-то может быть допущена грамматическая ошибка, а
где-то недостаточно понятное пользователю изображение, подсказка
или теория, поэтому очень важно учесть мнение каждого для
поддержания эталонного контента. Для всех этих случаев нужно дать
возможность Сообщить о проблеме прямо во время прохождения
практик, не отрываясь от процесса, максимально быстро и
удобно. Модераторам же нужно иметь возможность просматривать список
практик с жалобами, фильтровать, находить наиболее
популярные проблемные, отслеживать текущий статус и
закрывать жалобы, сохраняя историю.
Чтобы не изобретать велосипед со своими бордами, тикетами и backlog-ом, а также чтобы хранить все задачи команд в одной системе и даже в общих спринтах, было решено для этих целей использовать JIRA + REST API.
Организация тикетов в JIRA
Для каждой практики у которой есть хотя бы 1 жалоба создается BUG в JIRA в выделенном эпике Practices Reports. А сами жалобы хранятся в виде комментариев к соответствующим багам-практикам. В дополнение к этому, для разных видов практик добавляется Label (в нашем случае такие как: Course, Gym, UEye). Общая логика представлена на схеме ниже:
Таким образом, контент-команда выбирает наиболее приоритетные практики (в виде багов) для исправления в каждом спринте.
А теперь давайте взглянем на технические подробности реализации.
Интеграция с JIRA REST API
Первым делом нужно создать API token в JIRA, чтобы получить доступ к JIRA API. Для использования на продакшене рекомендую создать отдельного пользователя от имени которого и будут создаваться тикеты, иначе придется отбиваться от постоянных уведомлений, в то время как для отдельного пользователя их проще сразу все отключить.
Получение API токена:
- Логинимся в https://id.atlassian.com/manage/api-tokens
- Создаем API token
- Задаем имя токена -> нажимаем Create
Готово. Сохраняем куда-нибудь созданный токен, терять его нельзя.
Теперь можно выполнять API запросы к JIRA. В каждый запрос передается заголовок, содержащий емейл (пользователя для которого был создан токен) и сам токен их передаем посредством реализации HTTP basic authentication.
Пример кода (весь код на TypeScript для NodeJS):
private generateAuthHeader(): string { // конвертируем строку email:apiToken в Base64 const basicAuthValue = Buffer.from(`${this.jiraEmail}:${this.jiraApiToken}`).toString('base64'); return `Basic ${basicAuthValue}`;}
Примечание: для хранения ключей и паролей мы используем AWS Secrets Manager. Прямо в коде такие данные хранить не безопасно. Больше информации тут.
Создание бага через API
Осталось совсем немного подготовки. Для того чтобы создать баг, нам нужно знать его Issue ID в JIRA. Один из способов его узнать вызвать GET запрос на получение информации обо всех типах:
GET https://{id}.atlassian.net/rest/api/3/issuetype
Поскольку это разовая операция, то чтобы не писать код можно воспользоваться Postman:
Во вкладке Authorization выбираем Type: Basic Auth, вводим email и api token.
В ответе нас интересует эта часть:
{ "self": "https://{id}.atlassian.net/rest/api/3/issuetype/10004", "id": "10001", "description": "A problem or error.", "iconUrl": "https://${id}.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype", "name": "Bug", "untranslatedName": "Bug", "subtask": false, "avatarId": 10303}
После того как узнали Issue Id типа BUG (10001) нам нужно узнать Project Id, к которому баг будет принадлежать. Похожим образом можем получить список всех проектов и найти id нужного.
Для этого делаем GET запрос на
GET https://{id}.atlassian.net/rest/api/3/project/search
И последний подготовительный шаг: как я выше упоминал, мы храним баги в отдельном эпике (Jira Epic). Его id знать не обязательно, достаточно скопировать его Key (расположен перед названием эпика, либо в адресной строке, например UX-1).
Все готово к созданию первого бага через API.
Я использовал npm пакет Got для создания HTTP запросов для NodeJS.
await got.post({ url: `${this.jiraApiHost}/issue`, // jiraApiHost = https://{id}.atlassian.net/rest/api/3 headers: { Authorization: authorization, // созданный Basic Auth Header в методе generateAuthHeader 'Content-Type': 'application/json' }, responseType: 'json', json: { update: {}, fields: { issuetype: { id: this.jiraBugTypeId }, // полученный id типа BUG (пример - 10001) project: { id: this.jiraPracticeReportProjectId }, // id проекта (пример - 10005) parent: { key: this.jiraPracticeReportEpicKey }, // ключ Epic (пример - UX-1) summary: practiceTicketName, // имя практики формата - [practiceId] practiceName (#reports) labels: [practice.label] } }});
Баг создан. Далее рассмотрим остальные методы необходимые для настройки полного цикла обработки жалоб и ведения их в JIRA, такие как: Поиск, Обновление статуса, Обновление информации, Добавление комментария к багу.
Поиск бага через API
Поиск бага является первоначальной операцией в API обработки пользовательской жалобы. Необходимо убедиться, что для обжалованной практики не существует тикета, только в этом случае создаем его.
Пример кода:
// формируем JQL запрос, ищем по типу BUG в эпике где хранятся жалобы по id практики (которое есть в названии каждого бага)const jql = `issuetype = Bug AND project = CNT AND parent = ${this.jiraEpicKey} AND text ~ "${practiceId}" order by created DESC`; const response = await got.get({ url: `${this.jiraApiHost}/search?jql=${jql}`, headers: { Authorization: authorization }, responseType: 'json'});const practiceJiraTicket = response.body['issues'] && response.body['issues'][0];
В случае если для практики баг найден, нужно обновить его статус, если проблема была решена и баг находился в статусе CLOSED.
Обновление статуса бага через API
Чтобы обновить статус, воспользуемся Transitions. Но для этого нужно узнать Status ID для TODO / OPENED статуса (статус зависит от настроек JIRA).
Возвращаемся к Postman:
GET https://{id}.atlassian.net/rest/api/3/project/{projectIdOrKey}/statuses
По id проекта получаем все статусы, находим статус, который указывает на открытое состояние тикета и сохраняем его id.
Запрос на перевод бага в открытый статус:
await got.post({ url: `${this.jiraApiHost}/issue/${practiceJiraTicket.key}/transitions`, // где practiceJiraTicket - найденный объект бага headers: { Authorization: authorization, 'Content-Type': 'application/json' }, responseType: 'json', json: { transition: { id: this.jiraToDoStatusId // id статуса полученного выше (пример - 10006) } }});
Следующий шаг после перевода найденного бага в открытое состояние обновление названия (а если нужно, то и описания либо приоритета).
Обновление названия бага через API
Названия багов должны отражать текущее состояние проблемы, помимо id и названия практики в названии бага можно хранить и статистику для более удобного поиска и просмотра открытых жалоб на борде. Под статистикой в нашем случае подразумевается количество жалоб на данную практику и процент верных ответов. Так, если у практики число верных ответов достаточно низкое либо же число жалоб больше n можно повышать приоритет.
Код вызова API для обновления бага:
await got.put({ url: `${this.jiraApiHost}/issue/${practiceJiraTicket.key}`, headers: { Authorization: authorization, 'Content-Type': 'application/json' }, responseType: 'json', json: { update: { summary: [{ set: newPracticeTicketName }] } }});
Далее добавим детали самой жалобы в виде комментария к уже подготовленному багу.
Добавление комментария через API
Каждая жалоба пользователя хранится в виде комментария к багу-практике.
Код создания комментария:
await got.post({ url: `${this.jiraApiHost}/issue/${practiceJiraTicket.key}/comment`, headers: { Authorization: authorization, 'Content-Type': 'application/json' }, responseType: 'json', json: comment // подробности о формировании объекта ниже});
Объект comment формируется в виде Atlassian Document Format.
На сайте так же есть Builder, который
значительно упрощает генерацию объекта: просто форматируем текст
под свои нужды в редакторе при этом параллельно создается итоговый
JSON объект.
Готово! Теперь можно принимать, хранить, обрабатывать, закидывать в спринты и удобно искать жалобы пользователей используя JIRA.
Как у нас выглядят жалобы в виде комментариев:
Итоговый вид нашего списка багов в JIRA (название содержит id, #N число жалоб, % верных ответов):
Дальше все зависит от вашей фантазии и требований. Например, можно:
- реализовать асинхронную обработку жалоб, чтобы пользователь не ждал пока пройдет вся цепочка запросов к JIRA (у нас реализовано средствами AWS SNS)
- добавить поле priority для багов и менять приоритет в зависимости от числа жалоб для более удобной фильтрации в борде
- дополнительно информировать модераторов в Slack при появлении новой жалобы со ссылкой на созданный баг (slack/webhook пакет очень прост в интеграции)
- настроить JIRA Webhooks, чтобы при закрытии бага автоматически рассылать уведомления всем пользователям, которые жаловались на практику с благодарностью за участие в улучшении продукта
- автоматически назначать баг на автора контента, на который поступила жалоба.
Всем спасибо за внимание! Надеюсь, статья была для вас полезной
:)
С радостью отвечу на ваши вопросы!