Если в строке поиска на Хабре ввести “Jira Badoo”, результаты займут не одну страницу: мы упоминаем её почти везде, потому что она играет важную роль в наших процессах. Причём каждый из нас хочет от нее немножко разного.



Разработчик, которому пришла задача на ревью, ожидает что в задаче указана ветка, есть ссылки на дифф и лог изменений. Разработчик, который писал код, ожидает увидеть в Jira комментарии по итогам ревью. Тестировщик, который получает задачу после них, хочет видеть результаты тестов и иметь возможность запустить необходимые сборки, не переходя в другие интерфейсы. Продакт-менеджеры вообще хотят создавать десять задач по разработке одновременно, нажав на одну кнопку.

И всё это сегодня доступно и происходит автоматически. Большую часть магии мы реализовали на PHP с помощью постоянно развивающегося API Jira и с использованием её webhook’а. И сегодня мы хотим поделиться с сообществом нашей версией клиента для этого API.

Сначала мы хотели просто рассказать об идеях и подходе, который мы используем, а потом решили, что к такой статье решительно не хватает кода для наглядности. Так появилась open-source-версия Badoo Jira PHP Client. Огромное спасибо ShaggyRatte за то, что помог с ее описанием. И добро пожаловать под кат!

Больше подробностей и контекста
Если вам нужно больше подробностей относительно того, что мы делаем с Jira, их можно найти в других наших статьях:

habr.com/ru/company/badoo/blog/169417
habr.com/ru/company/badoo/blog/433540
habr.com/ru/company/badoo/blog/424655

Что он умеет?


По сути, Badoo Jira PHP Client — это набор готовых классов-обёрток для ответов Jira API, большинство из которых умеют вести себя как ActiveRecord: знают, как получить о себе данные и как их обновить на сервере, поддерживают ленивую инициализацию и кеширование данных на уровне кода. Обёрнуты все сущности Jira, с которыми приходится работать постоянно: Issue, Status, Priority, Changelog, User, Version, Component и т. п. (почти всё, что вы видите в интерфейсе).

Кроме того, Badoo Jira PHP Client предоставляет единую иерархию классов для всех кастомных полей Jira и способен генерировать определения классов для каждого кастомного поля, которое вам потребуется.

$Issue = new \Badoo\Jira\Issue('SMPL-1');

$Issue->addComment('Sample comment text');
$Issue->attachFile('kitten.jpeg', 'pretty!', 'image/jpeg');

if ($Issue->getStatus()->getName() === 'Open') {
   $Issue->step('In Progress');
}

$DeveloperField = new \Example\CustomFields\Developer($Issue);
$DeveloperField->setValue('username')->save();

$User = \Badoo\Jira\User::get('username');
$User->watchIssue('SMPL-1');
$User->assign('SMPL-2');

Благодаря этому взаимодействие с API из PHP становится проще и удобнее, а документация к вашей Jira перемещается прямо в код, что позволяет использовать автодополнение в IDE для многих стандартных действий.

Но обо всём по порядку.

Особенности API, с которыми мы столкнулись


Когда мы начали активно использовать API Jira, он был доступен только по протоколу SOAP. Его REST-версия появилась позже, и мы были в числе её первых пользователей. В то время публично доступных REST-клиентов, написанных на PHP, было очень мало. Ещё сложнее было найти что-то, что можно было бы легко интегрировать в нашу кодовую базу, постепенно переехав с SOAP на REST. Так что выбора у нас не было: мы решили продолжить развитие собственного клиента.

Так мы и жили, перетаскивая всякие хаки и костылики из SOAP-клиента и обрастая новыми из-за особенностей REST. В результате у нас выросло несколько весьма жирных классов с кучей дублированного кода и назрела необходимость этот бардак разгрести.

Самым больным местом для нас всегда были кастомные поля: их у нас больше 300 (на момент написания статьи — 338), и это число потихоньку растёт.

Странные сообщения об ошибках


За долгую историю взаимодействия с API мы много разного видели. Большинство сообщений об ошибках адекватные, но попадаются такие, для понимания которых приходится сильно напрягать мозг.

Например, если Jira вдруг распознает в вашем пользователе робота, она начнёт показывать ему капчу. В этом случае при обращениях к API она бессовестно игнорирует заголовок Accept-Encoding: application/json и выдаёт вам HTML. Естественно, что клиент, который ждёт JSON, к такому “здрасьте” может оказаться не готов.

А вот пример работы с кастомным полем:



Когда вы пишете код и «вот прямо сейчас» его тестируете, понять, что customfield_12664 — это Developers, очень легко. А если такая ошибка вылезает где-нибудь на production (например, из-за того, что кто-то поменял конфигурацию и изменил список допустимых значений Select-поля), часто единственный способ идентифицировать поле — залезть в Jira и узнать название оттуда. Причём в её интерфейсе ID не отображаются — только имена.

Получается, что, когда вы хотите узнать название поля, которое привело к ошибке, и исправить его конфигурацию, сделать это можно либо через запрос к API, либо с помощью каких-то других неочевидных методов: покопавшись в исходном коде страницы, открыв настройки произвольного поля и исправив URL в адресной строке и т. п. Удобным этот процесс не назовёшь, и каждый раз он занимает слишком много времени для столь простой задачи.

Но непонятными именами полей проблемы не ограничиваются. Вот как выглядит взаимодействие с API, если вы ошиблись в структуре данных для обновления поля:




Много разных форматов данных


А ещё не стоит забывать о том, что для обновления полей разных типов требуются разные структуры данных.

$Jira->issue()->edit(
    'SMPL-1',
    [
        'customfield_10200' => ['name' => 'denkoren'],
        'customfield_10300' => ['value' => 'PHP'],
        'customfield_10400' => [['value' => 'Android'], ['value' => 'iOS']],
        'security' => ['id' => 100500],
        'description' => 'Just text',
    ],
);

Ответы API для них, разумеется, тоже разные.

Держать это всё в голове получается, только если вы постоянно работаете с API Jira и не отвлекаетесь надолго на решение других задач. Иначе эти особенности вылетают из памяти за пару недель. Кроме того, вам необходимо помнить тип нужного поля, чтобы «скормить» ему правильную структуру. Когда у вас сотни кастомных полей, часто приходится либо искать в коде, где же оно ещё использовалось, либо лезть в админку Jira.

До того как мы написали наш клиент, Stack Overflow и Atlassian Community в вопросах обновления кастомных полей были моими лучшими друзьями. Сейчас эта информация гуглится легко и быстро, но мы переходили на REST API, когда он был ещё достаточно новым и активно дорабатывался: на поиск подходящего cURL-запроса в гугле можно было потратить минут десять, а потом эту кучу скобок надо было распарсить глазами и преобразовать в правильную структуру для PHP, что часто получалось не с первой попытки.

В общем, взаимодействие с кастомными полями — тот процесс, реорганизация которого требовалась нам в первую очередь.

Из чего состоит клиент?


Классы для работы с кастомными полями


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

В итоге мы создали единую иерархию классов для всех кастомных полей. Получилось три слоя:

  1. Общий абстрактный родитель для всех: \Badoo\Jira\CustomFields\CustomField.
  2. По абстрактному классу для каждого типа полей: SelectField, UserField, TextField и т. д.
  3. По классу для каждого конкретного поля: например, Developer или Reviewer.

Эти классы можно писать самостоятельно, а можно создавать автоматически с помощью специального скрипта-генератора (к нему мы ещё вернёмся).


Благодаря такой структуре, для того чтобы научить код обновлять значение вашего кастомного поля типа Select List (multiple choice), достаточно создать PHP-класс, унаследованный от SelectField. По сути, каждое кастомное поле Jira превращается в обычный ActiveRecord в PHP-коде.

namespace \Example\CustomFields;

class Developer extends \Badoo\Jira\CustomFields\SingleUserField
{
	const ID = 'customfield_10200';
	const NAME = 'Developer';
}

// Всё, класс готов, можно пользоваться!


В этом же классе мы храним информацию о поле: по умолчанию это ID, имя поля и список доступных значений, если он ограничен (например, для Checkbox и Select).

Примеры поля в интерфейсе Jira и соответствующего ему класса


class IssueFor extends \Badoo\Jira\CustomFields\SingleSelectField
{
   const ID    = 'customfield_10662';
   const NAME  = 'Issue for';

   /* Available field values. */
   const VALUE_BI = 'BI';
   const VALUE_C_C = 'C\C++';
   const VALUE_HTML_CSS = 'HTML\CSS';
   const VALUE_JS = 'JS';
   const VALUE_OTHER = 'Other';
   const VALUE_PHP = 'PHP';
   const VALUE_TRANSLATION = 'Translation';

   const VALUES = [
       self::VALUE_BI,
       self::VALUE_C_C,
       self::VALUE_HTML_CSS,
       self::VALUE_JS,
       self::VALUE_OTHER,
       self::VALUE_PHP,
       self::VALUE_TRANSLATION,
   ];

   public function getItemsList() : array
   {
       return static::VALUES;
   }
}


Получается этакая документация именно к вашей Jira, расположенная прямо в PHP-коде. Когда она настолько близко, это очень удобно и существенно ускоряет разработку, уменьшая при этом количество ошибок.

Кроме того, сообщения об ошибках становятся более понятными: вместо ничего не говорящего ‘customfield_12664’ вылетает, например, что-то такое:

Uncaught Badoo\Jira\Exception\CustomField: User 'asdkljfh' not found in Jira. Can't change 'Developer' field value.

Классы для работы с системными объектами


В Jira много данных со сложной структурой: например, системные поля Status и Security, линки между задачами, пользователи, версии, вложения (файлы).

Их мы тоже обернули в классы:

// статус задачи
$Status = $Issue->getStatus();

$Status->getName();
$Status->getId();

// changelog задачи
$History = \Badoo\Jira\Issue\History::forIssue('SMPL-1');
$seconds_in_status = $History->getTimeInStatus('Open');

// пользователь Jira
$User = new \Badoo\Jira\User('sampleuser');
$User->assign('SMPL-1');

Такие обёртки дают вашей IDE возможность подсказывать, какие данные доступны и позволяют строго формализовать интерфейсы функций в вашем коде. Мы активно используем type declarations, почти всегда это позволяет видеть ошибку ещё во время написания кода благодаря подсветке IDE. А если ошибку всё-таки пропустили, она вылезет ровно в том месте, где впервые появилась, а не там, где в конце концов уронила ваш код.

Ещё есть статические методы, позволяющие быстро получить объект по какому-то критерию:

$users = \Badoo\Jira\User::search('<pattern>'); // поиск пользователя по login, email или display name

$Version = \Badoo\Jira\Version::byName('<project>', '<version name>'); // поиск версии по имени в конкретном проекте
$components = \Badoo\Jira\Component::forProject('<project>'); // полный список компонентов в проекте

Эти методы подчиняются общим правилам, чтобы их было легко найти:
  • ::search(), если вам нужно найти объекты по нескольким полям: \Badoo\Jira\Issue::search() ищет задачи с помощью JQL, где вы можете указать много критериев поиска, а \Badoo\Jira\User::search() ищет пользователя одновременно по ‘name’ (логину), ‘email’ и ‘displayName’ (тому имени, которое отрисовывается в вебе);
  • ::by*(), если нужно получить объект не по ID, а по какому-то другому критерию: \Badoo\Jira\User::byEmail() ищет пользователя по его email-адресу;
  • ::for*() ищет все объекты, связанные с чем-то: \Badoo\Jira\Version::forProject
    отдаёт все версии из конкретного проекта;
  • ::fromStdClass() создаст объект из сырых данных, имеющих подходящую структуру, но полученных не из API, а, например, из webhook’а: в теле POST-запроса Jira отправляет JSON с разной информацией о событии, в том числе с телом задачи, включающем все поля. На основе этих данных можно создать объект \Badoo\Jira\Issue и пользоваться им как обычно.

Класс \Badoo\Jira\Issue


Мне кажется, следующий скриншот PhpStorm достаточно красноречив сам по себе:



По сути, объект \Badoo\Jira\Issue связывает всё, что было описано выше, в единую систему. Он хранит в себе всю информацию о задаче, в нём есть методы для быстрого доступа к наиболее часто используемым данным, перевода задачи между статусами и т. д.

Для создания объекта в простейшем случае достаточно знать только ключик задачи.

Создаём объект, имея в кармане только ключ задачи
$Issue = new \Badoo\Jira\Issue('SMPL-1');


Можно также использовать любой фрагментарный набор данных. Например, информация о линке между задачами, прилетающая из API, содержит только несколько полей: id, summary, status, priority и issuetype. \Badoo\Jira\Issue позволяет собрать объект из этих данных так, чтобы отдавать их сразу, а для всего остального обращаться к API.

Создаём объект, кешируя значения для некоторых полей
        $IssueFromLink = \Badoo\Jira\Issue::fromStdClass(
            $LinkInfo,
            [
                'id',
                'key',
                'summary',
                'status',
                'priority',
                'issuetype',
            ]
        );


Это достигается за счёт ленивой инициализации и кеширования данных в коде. Такой подход особенно удобен тем, что вы можете обмениваться в своём коде только объектами \Badoo\Jira\Issue вне зависимости от того, с каким набором полей они были созданы.

Получаем недостающие данные о задаче
$IssueFromLink->getSummary(); // никакого запроса к API, возвращает данные сразу
$IssueFromLink->getDescription(); // запрашивает API и возвращает свежий description


Как мы ходим в API
В API Jira присутствует возможность получить для задачи не все поля, а только необходимые в данный момент: например, только key и summary. Однако мы намеренно не ходим в Jira только за одним полем в getter. В примере выше getDescription() обновит информацию сразу обо всех полях. Поскольку \Badoo\Jira\Issue не имеет ни малейшего представления о том, что ещё вам потребуется дальше, выгоднее получить из API сразу всё, раз уж мы всё равно туда пошли. Да, выполнение запроса «получить только description» и запроса «получить все поля по умолчанию» для пары сотен тикетов занимает разное время, но для одного эта разница уже не так заметна.

//Time for single field: 0.40271635055542 (second)
//Time for all default fields: 0.84159119129181 (second)

Из цифр видно, что при получении всего трёх полей (по одному в запросе) выгоднее получить сразу все, а не ходить в API за каждым. Результат этого измерения, на самом деле, зависит от конфигурации Jira и сервера, на котором она работает. От задачи к задаче и от измерения к измерению цифры меняются и Time for all default fields получается стабильно меньше трёх Time for single field, а часто даже меньше двух.

Однако при работе с большим количеством задач разница может измеряться секундами. Поэтому, когда вы знаете, что вам нужны только key и description для 500 тикетов, возможность получить их одним эффективным запросом остаётся в методах \Badoo\Jira\Issue::search() и \Badoo\Jira\Issue::byKeys().


\Badoo\Jira\Issue — вообще про задачи в какой-то абстрактной Jira. Но ваша (как и наша) Jira не абстрактная — в ней есть совершенно конкретный набор кастомных полей и свои workflow. Какие-то из полей и переходов вы используете чертовски часто, так что каждый раз ходить за ними длинными путями не очень удобно. Поэтому \Badoo\Jira\Issue можно легко расширять своими собственными методами, специфичными для конкретной конфигурации Jira.

Пример расширения класса методом для быстрого получения кастомного поля
namespace \Deploy;

class Issue extends \Badoo\Jira\Issue {
    // …

    /**
     * Get ‘Developer’ custom field as object
     */
    function getDeveloperField() : \Deploy\CustomFields\Developer
    {
        return $this->getCustomField(\Deploy\CustomFields\Developer::class);
    }

    // ...
}



Issue Create Request


Создание задачи в Jira — довольно сложная процедура. Когда вы делаете это через веб-интерфейс, вам показывается специальный скрин (Create Screen) с набором полей. Какие-то из них вы можете заполнить просто потому, что хотите, а какие-то обозначены как обязательные для заполнения. При этом Create Screen может быть свой для каждого проекта и даже для разных типов задач в одном проекте. Так что существуют всякие ограничения на значения полей и на саму возможность задать значение поля в процессе создания задачи.

Самое неприятное для разработчиков в этой ситуации то, что эти ограничения распространяются и на API. В последнем есть специальный запрос (create-meta доступен в REST API начиная с версии 5.0), с помощью которого можно получить список настроек полей, доступных при создании задачи. Однако разработчик, которому нужно «вот прям щас сделать простенькую штуку», заморачиваться с этим, скорее всего, не будет.

В итоге у нас происходило так: поскольку запрос на создание задачи может быть достаточно большим, мы часто добавляли в него данные постепенно, а ошибку получали уже при попытке отправить всё в Jira. После этого приходилось искать в коде все места, где в запросе что-то менялось, и долго и нудно пытаться понять, что именно пошло не так.

Поэтому мы и сделали \Badoo\Jira\Issue\CreateRequest. Он позволяет увидеть ошибку раньше, прямо в том месте, где вы пытаетесь сделать что-то не то: дать полю какое-то кривое значение или изменить поле, которое недоступно. Например, если вы попытаетесь указать задаче компонент, которого не существует в проекте, exception вылетит в том месте, где вы это сделали, а не там, где в конечном итоге отправили запрос в API.

Примерно так выглядит flow работы с CreateRequest
$Request = new \Badoo\Jira\Issue\CreateRequest('DD', 'Task', $Client);
$Request
   ->setSummary('summary')
   ->setDescription('description')
   ->setFieldValue('For QA', 'custom field with some comments for QA who will check the issue');

$Request->send();


Работа с API напрямую


Набор классов, который описан выше, закрывает большинство потребностей. Однако мы прекрасно понимаем, что большинство — это далеко не все. Поэтому у нас также есть небольшой клиент для работы с API напрямую — \Badoo\Jira\REST\Client.

Пример использования клиента
$Jira = \Badoo\Jira\REST\Client::instance();
$Jira->setJiraUrl('https://jira.example.com/');
$Jira->setAuth('user', 'password')

$IssueInfo = $Jira->issue()->get('SMPL-1');


Генератор классов для кастомных полей


Чтобы работа с кастомными полями была удобной, для каждого поля необходимо иметь свой собственный класс в коде. У себя мы создавали их вручную по мере необходимости, но перед публикацией клиента решили, что этот подход может быть не очень удобен новым пользователям. Поэтому мы сделали специальный генератор, который умеет ходить в Jira API за списком кастомных полей и создавать для известных ему типов полей классы по шаблонам.

Мы верим, что для большинства задач достаточно использовать CLI-скрипт bin/generate из нашего репозитория. Его можно попросить рассказать о себе через опцию --help/-h:

./bin/generate --help

В простейшем случае для генерации достаточно указать URL вашей Jira, пользователя, его пароль, namespace для классов и директорию, куда класть код:

./bin/generate -u user -p password --jira-url https://jira.mlan --target-dir custom-fields --namespace CustomFields

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

Заключение


Нам нравится то, что у нас получилось. С этой концепцией — свои классы для кастомных полей, обёртки для статусов, версий, пользователей и т. п. — мы живём уже больше года и чувствуем себя прекрасно. Перед публикацией кода мы даже расширили функционал и дописали прекрасные штуки, до которых долгое время не доходили руки, чтобы пользоваться клиентом было ещё удобнее: например, добавили возможность обновлять в Issue несколько полей за один запрос и написали генератор классов для кастомных полей.

На наш взгляд, получилась годная штука, которую однозначно стоит пощупать, чтобы понять, подходит ли она под ваши задачи и требования. Под наши — точно подошла.

Еще раз ссылка: github.com/badoo/jira-client.

Спасибо, что дочитали до конца. Надеемся, этот код теперь будет приносить пользу и экономить время не только нам.

Комментарии (12)


  1. Batekk
    25.11.2019 17:15
    +1

    Хорошо ложка к обеду, осталось написать статью на Хабр с плюсов и минусов
    github.com/lesstif/php-jira-rest-client


    1. DenKoren Автор
      25.11.2019 17:27

      Я натыкался на него перед публикацией, да. Использовать не довелось, так что могу провести только поверхностную оценку. Для нормального сравнения всё-таки стоит попользоваться обоими клиентами в реальных условиях.

      Насколько я вижу из документации к клиенту — lesstif/php-jira-rest-client это только та часть нашего клиента, которая в \Badoo\Jira\REST\Client. Хотя lesstif/php-jira-rest-client покрывает API JIRA интерейсами лучше нас. Например, для того, чтобы создать новый проект через API, у нас придётся пользоваться \Badoo\Jira\REST\ClientRaw, а в клиенте lesstif есть для этого удобная обвязка.

      Но помимо самого клиента к API мы предоставляем набор ActiveRecord классов, которые умеют прозрачно догружать данные из API при необходимости и обновлять их в JIRA. + каждое кастомное поле в JIRA можно представить отдельным классом и создавать такие классы с помощью генератора PHP-кода, так же поставляемого с клиентом.

      Повторюсь, это поверхностная оценка, т.к. я не использовал lesstif/php-jira-rest-client, но пока мне кажется, что опубликованный нами клиент — это часть lesstif/php-jira-rest-client + куча дополнительных обвязок, которых в lesstif нету и которые делают использование API сильно удобнее.


  1. rustalenin
    25.11.2019 17:16
    +1

    Спасибо, задумались о том, чтобы активно начать использовать вашу либу.

    Скажите, а как в итоге справились с авторизацией, у нас периодически наши костыли перестают работать, потому что JIRA начинает требовать введение капчи, есть какой-то рецепт?

    И ещё, вы плагины свои для JIRA не пишите?


    1. DenKoren Автор
      25.11.2019 17:36

      Лично мне плагины писать не довелось, хотя возможно кто-то этим в компании и занимался, так что не готов однозначно ответить. В большинстве случаев нам хватает Adaptavist ScriptRunner'а (он очень много плюшек даёт), WebHook'ов, настроек переходов Workflow и кастомных полей.

      Про капчу — мы сами от этого периодически страдаем, так что я нормального способа не знаю :(
      К сожалению JIRA не даёт возможность сказать про пользователя «да, это робот, я знаю, пусти!». Последний раз когда я проверял информацию про настройки капчи — её можно было либо выключить совсем для всех, либо для всех использовать. Мы предпочли её оставить.

      Так что на капчу мы наступаем при каждом рестарте nginx, который живёт над нашей JIRA: соединения между nginx и JIRA рвутся и та вспоминает что AIDA — робот.

      Единственное решение, которое мы для этого нашли — адекватно ругаться в exception о том, что мы схлопотали капчу:

      throw new \Badoo\Jira\REST\Exception\Authorization(
              "Access to the API method is forbidden. You either have not enough privileges or the capcha shown to your user"
      );
      

      Это позволяет быстрее понять, почему именно отваливаются скрипты, зайти в JIRA и ввести капчу ручками, чтобы разблокировать пользователя.

      P.S.: если вы ходите в JIRA напрямую, то возможно добавив nginx между вашим клиентом и JIRA вы уменьшите количество показываний капчи. Судя по тому что мы наблюдаем, JIRA не трогает роботов, если они ходят через уже существующие соединения. Так что nginx с keepalive-соединениями к upstream'ам могжет сильно облегчить боль. По сути, мимо nginx'а-то мы никогда и не ходили, так что возможно причина не в этом. Но то, что мы ловим капчу при каждом рестарте nginx'а заставляет меня думать что всё именно так работает.


      1. rustalenin
        25.11.2019 18:05

        Ясно, жалко, видимо пока мы все продолжим страдать с этой капчей, как вариант, сделаем отправку уведомления в Slack при получение такой ошибки, чтобы фиксить проблему оперативней.

        За совет с ScriptRunner'ом тоже спасибо.


        1. avengerweb
          26.11.2019 00:54

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


          1. rustalenin
            26.11.2019 11:13

            Если вы знакомы с Java, со стеком JIRA, то да, пишется за час. А если на Java никогда не писал, сейчас работаешь PM'ом и в последний раз коммерческий код писал полтора года назад на PHP…


            1. DenKoren Автор
              26.11.2019 11:46

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

              По рабочим задачам мне приходилось писать JAVA-код для работы с задачами JIRA из ScriptRunner'а. До этого я с JAVA в роли разработчика не сталкивался. Всё в комплексе — знакомство и с JAVA и с JIRA JAVA SDK — это так себе удовольствие для человека «со стороны»: документация, на мой взгляд, скудная, при этом структура JIRA JAVA SDK ещё хуже чем её REST — для выполнения простых действий приходится делать кучу сложных телодвижений: фабрики всякие, контроллеры и пр. При этом есть большое количество deprecated-методов и те подходы что были каноническими 3-4 года назад сейчас уже «фу-фу» и «не надо так!».
              На то, чтобы обновить значение кастомного поля в задаче и подвинуть её статус, мне пришлось гуглить и экспериментировать минут 30.
              Разработка ещё осложняется тем, что быстро из коробки получить нормальную локальную среду с autocomplete'ом и подсказками по коду без знания «что качать», «откуда взять библиотеку классов JIRA» и «куда её в этой среде подпихнуть чтобы она подхватилась» не получается. И в итоге ты как слепой котенок — рыскаешь рецепты в сети, пробуешь, спотыкаешься и понимаешь, что рецепт уже не актуален т.к. методы deprecated или выпилены и идёшь на новый круг.

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

              Процесс погружения в JAVA/JIRA достаточно трудоёмкий, и если само написание плагина действительно может занимать час-два, то вместе со знакомством с языком, с доступной библиотекой классов, с инструментами, с процессом отладки и подготовкой окружения это может растянуться на неделю.


              1. rustalenin
                26.11.2019 11:54

                Ну вот да, у нас сейчас есть целый пул не стандартных задач по JIRA которые не сделать только через её настройки. И пока путь:

                — Выстрелить WebHook'ом в наш сервис по работе с JIRA
                — Обработать запрос
                — Сделать необходимые изменения используя JIRA REST API

                Кажется значительно проще, чем напрягать коллег пишущих на Java сделать вводную в язык, потом неделю разворачивать локальное окружение, потом напрягать DevOps'ов делать полноценный DevStage с CI/CD (вместо просто поднятия копии из бэкапа)…


                1. DenKoren Автор
                  26.11.2019 12:03

                  Мы в Badoo ровно по этому пути и пошли.
                  99% задач этот flow покрывает, но увы, не все. Некоторые узкоспециальные вещи через REST API сделать нельзя. Поэтому лезть внутрь нам, хоть и крайне редко, но всё-таки приходится.
                  Будьте к этому морально готовы :)

                  У нас для экспериментов с JIRA есть staging-версия, которая раз в сутки синхронизируется с продом.
                  Эта staging-версия используется админами для накатывания обновлений, чтобы случайно не ушатать прод при переходе на новую версию JIRA или обновлении плагина. Её же мы (релизеры) используем для обкатки наших скриптов: вебхуков, запросов к API, правок workflow и пр.

                  Синхронизация с продом удобна тем, что у тебя для экспериментов всегда есть актуальное состояние JIRA и ты можешь безопасно поиграть с «настоящими» проектами и тикетами, не рискуя при этом поломать что-то работающим сейчас коллегам.


                  1. avengerweb
                    26.11.2019 22:53

                    Не так страшен черт, как его малюют. Для быстрого старта вам нужен JDK и SDK атлежена (если у вас линукс то это 5 минут, если windows то минут 15). Документация у них и вправду хромает, (JIRA еще ничего, а вот bitbucket) но вот SDK у них самодостаточный, две команды:

                    atlas-create-jira-plugin
                    atlas-debug
                    

                    И у вас поднят чистый инстанс в дебаге, чего достаточно для разработки 80% плагинов на старте. Плюс там спринг, у нас джун с явой на уровне университета справлялся с первой простой задачей за пару дней.

                    В компании в которой я работаю, использовался описаный выше подход довольного долго, в какой то момент размазанная логика просто убивает перформанс команды разработки, вместо написание бизнес-логики, разработчики начинали искать всякие обходные пути через REST, скриптраннер и тп, вместо простого плагина. В итоге мы взяли и за полгода выкинули всю php`шную обвязку, за исключением внешних сервисов.

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


          1. DenKoren Автор
            26.11.2019 11:56

            Спасибо за совет.
            Похоже, это хорошее решение задачи, над которым лично я даже не задумывался. Мне почему-то не пришло в голову, что авторизацией в JIRA можно управлять через плагины и аналог того, что в Cloud версии JIRA называется токенами авторизации (которые используются вместо паролей для доступа к API) можно самому собрать на коленке.

            Мы, правда, не очень искали решение проблемы, т.к. из-за наличия «вечно живого» nginx над JIRA проблема с капчей у нас всплывала раз в пару месяцев, а иногда не проявлялась и по полгода. Так что капча — это, конечно, грабли, но били они не настолько больно, чтобы заморачиваться со сложным решением.