Как мы обрабатываем жалобы пользователей с помощью JIRA (REST API)

  • Tutorial


В наше время мало какой веб-сервис или приложение обходится без функционала, где пользователи могут пожаловаться (уведомить, зарепортить) на различные виды контента, будь то грамматическая ошибка в тексте, ошибочные формулировки, неинтересный или не информативный контент (как упражнение, урок, статья, обучающий материал либо часть функционала). Возможность “сообщить о проблеме” является неотъемлемой частью вовлечения пользователей в продукт, реализации одной из форм сбора обратной связи и возможности улучшить свое приложение в целом.


Все жалобы пользователей должны где-то храниться, быть приоритезированы, удобно отслеживаться, и, более того, должны быть вовремя обработаны. Но не всегда есть возможность выделить достаточно ресурсов на разработку и поддержание подобной системы, ведь всегда найдется задача в бэклоге с более высоким приоритетом. Ниже я расскажу, как мы достаточно эффективно и быстро решили эту задачу в 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 токена:




  • Задаем имя токена -> нажимаем 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]
        }
    }
});

API Reference


Баг создан. Далее рассмотрим остальные методы необходимые для настройки полного цикла обработки жалоб и ведения их в 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];

API Reference


В случае если для практики баг найден, нужно обновить его статус, если проблема была решена и баг находился в статусе 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 Reference


Следующий шаг после перевода найденного бага в открытое состояние — обновление названия (а если нужно, то и описания либо приоритета).


Обновление названия бага через 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 Reference


Далее добавим детали самой жалобы в виде комментария к уже подготовленному багу.


Добавление комментария через API


Каждая жалоба пользователя хранится в виде комментария к багу-практике.


Код создания комментария:


await got.post({
    url: `${this.jiraApiHost}/issue/${practiceJiraTicket.key}/comment`,
    headers: {
        Authorization: authorization,
        'Content-Type': 'application/json'
    },
    responseType: 'json',
    json: comment // подробности о формировании объекта ниже
});

API Reference


Объект comment формируется в виде Atlassian Document Format.
На сайте так же есть Builder, который значительно упрощает генерацию объекта: просто форматируем текст под свои нужды в редакторе — при этом параллельно создается итоговый JSON объект.


Готово! Теперь можно принимать, хранить, обрабатывать, закидывать в спринты и удобно искать жалобы пользователей используя JIRA.


Как у нас выглядят жалобы в виде комментариев:



Итоговый вид нашего списка багов в JIRA (название содержит id, #N — число жалоб, % — верных ответов):



Дальше все зависит от вашей фантазии и требований. Например, можно:


  • реализовать асинхронную обработку жалоб, чтобы пользователь не ждал пока пройдет вся цепочка запросов к JIRA (у нас реализовано средствами AWS SNS)
  • добавить поле priority для багов и менять приоритет в зависимости от числа жалоб для более удобной фильтрации в борде
  • дополнительно информировать модераторов в Slack при появлении новой жалобы со ссылкой на созданный баг (slack/webhook пакет — очень прост в интеграции)
  • настроить JIRA Webhooks, чтобы при закрытии бага автоматически рассылать уведомления всем пользователям, которые жаловались на практику с благодарностью за участие в улучшении продукта
  • автоматически назначать баг на автора контента, на который поступила жалоба.

Всем спасибо за внимание! Надеюсь, статья была для вас полезной :)
С радостью отвечу на ваши вопросы!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

А в вашем продукте есть возможность «Сообщить о проблеме» для различных видов контента?

  • 11,1%Да, есть1
  • 44,4%У нас стандартный support / report a bug функционал4
  • 44,4%Какие проблемы?4
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 7

    0
    Всё думаю аналогично прикрутить гитхаб — чтобы прямо в UI программы писали отзывы, а оно сразу в бэклог.
    Но, группировку и классификации придумывать лень, поэтому всё откладываю.
      0
      Хорошая идея с использованием гитхаба, в любом случае это гораздо быстрее, чем писать свою систему.
      0
      Получение API токена:

      Логинимся в id.atlassian.com/manage/api-tokens


      … и вот так незаметно вы выкинули за борт то большинство пользователей, у которых Джира своя, on-premise, не связанная с Atlassian ID. Да, конечно, REST API там тоже есть, но там нет токенов в принципе и надо каждый раз логиниться по логину-паролю.

      Но вообще, кажется, самое главное в этой статье — блок-схема. Когда решили ограничить сверху число багов числом практик — это сразу на корню решило потенциальную проблему дублирования, решило проблему скептичного отношения к автоматическим багам. Но с другой стороны, добавило усилий для изначального triage — как быть, если в практике две совершенно разные проблемы? С текущим подходом они уйдут в один баг, который живой человек должен прочитать, понять, что вторая проблема не имеет отношения к первой и засабмитить ее отдельно (видимо)?
        0
        Да, конечно, REST API там тоже есть, но там нет токенов в принципе и надо каждый раз логиниться по логину-паролю.


        Спасибо за замечание. Что мешает так же создать отдельного пользователя для использования его емейла и пароля только для REST API? Используя описанный HTTP basic auth developer.atlassian.com/server/jira/platform/basic-authentication

        С текущим подходом они уйдут в один баг, который живой человек должен прочитать, понять, что вторая проблема не имеет отношения к первой и засабмитить ее отдельно (видимо)?


        В данном случае, если модератор видит, что проблема не в его зоне ответственности, то он решает те проблемы что может сам (например, грамматические ошибки — добавляя комментарий о решении), и потом переводит на нужного человека. При большом количестве таких случаев можно заводить баги в разных эпиках — свой эпик под каждый вид модератора (но у нас пока такой необходимости нет, все проблемы решаются одной командой).

          0
          Что мешает так же создать отдельного пользователя для использования его емейла и пароля только для REST API?


          Ну так я это и написал — ходить каждый раз по логину и паролю :) Естественно, персональные credentials в скрипты никто не зашивает, естественно, есть стопка faceless аккаунтов соответствующих.
        +1
        Чтобы не разбираться с форматами, можно взять готовую обертку для Jira. Например www.npmjs.com/package/jira-connector. Он поддерживает основные API. Из коробки не использует HTTP Keep-Alive и были проблемы с аттачами, но решения быстро гуглятся.
          0
          Хороший пакет. Спасибо за дополнение!

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое