
Всем привет, меня зовут Алексей - я руководитель отдела интеграции EvaTeam. Это статья о том, как реализована настройка автоматизаций в EvaProject - российском аналоге Jira. В ней рассказывается о том, с чем пришлось столкнуться при переезде с Jira, и как мы реализовали функционал в нашей системе. Также прочитать эту статью вы можете в нашем блоге на Хабре.
Об автоматизации в Jira
Базовая автоматизация в Jira, прямо скажем, весьма скудная. Она подойдёт только небольшим и нетребовательным командам. По факту более менее полноценную автоматизацию можно получить только с использованием расширений и плагинов. Например, есть официальное расширение Jira Automation, которое позволяет добавлять свои правила. Но работа с ним тоже не очевидная и его болтает как матроса в лодке. Полноценно работает в премиум тарифе Cloud-версии. С недавних пор встроено в версию Data Centre. А для Server-тарифа устанавливается только как отдельное расширение с возможностью продлить его до февраля 2024 года.
Ещё есть плагины вроде ScriptRunner. Входит в топ самых популярных расширений в маркетплейсе и позволяет выстраивать процессы и автоматизации. Это действительно мощная вещь, которая может помочь организовать всё что происходит в компании. Но устанавливается отдельно и требует доплаты.
В целом у Jira богатая автоматизация за счет плагинов и расширений. Можно настроить всё что угодно: от смены исполнителя при изменении статуса по задаче, до включения кофемашины каждый день в 9 утра.
Поэтому возникают сложности у людей при переходе на российское ПО. Мало систем, которые позволят также гибко автоматизировать процессы, но и мы не из трусливых. Рассказываю о том, как работает этот функционал у нас в системе.
Автоматизация переходов
За основу работы автоматизации брались привычные настройки Jira, а также популярные плагины. В данный момент уже многое из этого реализовано в EvaProject, но ещё получаем много запросов, по специфичным настройкам, которые постоянно добавляем. Главное что сделано - заложены архитектурные возможности для дальнейшего развития в сторону замены Atlassian.
Для настройки логики бизнес-процессов используем классические 3 кита автоматизации:
- Условия перехода;
- Валидаторы;
- Действия.
Условия перехода
Условия перехода отрабатываются для визуального отображения в списке статусов. Они определяют именно видимость. Если условие для отображения перехода верное, то он будет отображаться в списке статусов. Например, можно настроить появление статуса "DONE" только тогда, когда в задаче оставили комментарий по ней.

Отображение статуса при выполненном условии "Оставлен комментарий"
Валидатор
Это дополнительные условия для статусов. Если условие валидатора не выполняется, при переходе на определённый статус возникает ошибка. Валидатор фиксирует почему возникла ошибка и выводит информативное окно с причиной её возникновения.

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

Настройка валидатора
Примеры использования валидатора.
Например, нам нужно добавить условие, чтобы нельзя было изменить статус, если текущее время больше дедлайна, или изменять статус вне рабочего времени (к примеру, брать в работу).
Действия
Это именно то, что произойдёт в системе после смены статуса. Действие не накладывает дополнительных условий на отображение и смены статуса, а выполняется после перехода из одного статуса заявки в другой.
Помимо предустановленных опций для настроек переходов также доступен более гибкий инструмент bzPython, который является аналогом плагина ScriptRunner в Jira.
bzPyton
Используя bzPyton можно реализовать абсолютно любую автоматизацию.

Пример добавления bzPython
bzPyton – внутренний бизнес-интерпретатор. Задача, в которой совершается определенный переход, передаётся в интерпретатор в переменной self, которая является объектом класса CmfTask и содержит всю информацию об этой задаче, то есть все поля и методы объекта CmfTask. Мы можем поменять статус, можем поменять будильник и любые другие поля, которые есть в модели CmfTask. Список доступных полей можно найти в официальной документации.
Для примера, сделаем чтобы ответственным для задачи стал пользователь, изменивший статус задачи. Добавляем новое действие для статуса, Вид Действия – bzPyton. В окно интерпретатора пишем:
self.responsible = g.current_user
В этом примере:
self.responsible – ответственный за текущую задачу;
g.current_user – специальная переменная, в которой содержится текущий пользователь, который совершает переход.
После окончания перехода, пользователь, совершивший этот переход, автоматически будет назначен ответственным по задаче.
Популярные шаблоны реализации автоматизации с помощью bzPyton можно посмотреть в официальной документации (https://docs.evateam.ru/docs/docs/DOC-000193#primery-bz-python)
Для переходов количество кода обычно невелико. Можно использовать более объемные скрипты в Триггерах или Cron.
И ещё один пример bzPython для валидатора. "Разрешить выполнять переход только тому пользователю, который является исполнителем по задаче":
if self.responsible != g.current_user:
return False
Триггеры
Триггеры могут выполняться, когда в системе происходят какие-либо события. К примеру, кто-то перенес задачу в канбане на следующую стадию, наступил новый рабочий или любое другое событие. На него мы можем назначить триггер.

Добавление события для триггера
События для триггеров:
- Комментирование;
- Создание;
- Удаление;
- Сохранение;
- Обновление.
Любое это событие может быть использовано вместе с моделями:
- Задача (CmfTask);
- Документ (CmfDocument);
- Список (CmfList);
- Сделка (CmfDeal).
Для создания нового триггера необходимо перейти в настройки, выбрать режим администратора, нажать «Автоматизация триггеры».
В поле «Название» впишите нужное имя триггера и нажмите «Добавить».
Нажмите «Изменить», откроется окно с параметрами триггера.

Настройка триггера
Есть уже предустановленные фильтры. Это "Фильтр по логическому типу объекта", "Фильтр по виду деятельности", "Фильтр по схеме бизнес-процессов". Для более гибкой настройки фильтра есть возможность использовать интерпретатор bzPyton.
Подробное описание по настройкам содержится в официальной документации (https://docs.evateam.ru/docs/docs/DOC-000238#avtomatizacziya-triggery)
Попробуем работу триггеров.
Задача для триггера: Автоматизация по созданию дочерней задачи с типом "Подзадача".
Для этого выбираем такие настройки:

Добавляем такой код:
task_code = self.name.value
relation_task = models.CmfTask.get(code=task_code, fields=['name', 'cf_custom_field_1'])
if relation_task:
# Меняем имя дочерней задачи
self.name = f'{task_code} - {relation_task.name}'
# Получаем кастомное поле из второй задачи
custom_1 = relation_task.cf_custom_field_1
# Меняем текст дочерней задачи
self.text = f'<p>Это текст первой строки. Кастом поле-{custom_1}</p>\n<p>Это текст второй строки</p>'
# Получаем нужный тип связи. В данном случае тип связи "Взаимная" - "Относится к"
relation_type = models.CmfRelationType.get(code='system.link')
# Создаем связь между второй задачей и новой созданной(дочерней)
new_rel = models.CmfRelationOption(out_link=self, in_link=relation_task, relation_type=relation_type)
new_rel.save()
А логика скрипта получается такой:
- Скрипт выполняется если в основной задаче нажать кнопку "Добавить дочернюю задачу" и прописать в названиеcode другой уже созданной задачи.
- По полю code будет найдена вторая задача. Из этой задачи будут получены поля name и cf_custom_field_1.
- Полученные поля из второй задачи будут прописаны в дочернюю.
- Между дочерней и второй задаче создается связь с типом system.link (Взаимная).
- В настройках триггера указано "Событие: Создание", "Фильтр по логическому типу объекта - Подзадача".
Cron
Автоматизировать работу системы можно не только по определённым действиям со стороны пользователей, но и по времени. Для этого есть специальный инструмент-планировщик. С помощью него, к примеру можно убирать выполненные задачи из списка задач, или настроить периодическую очистку архива.
Создать действие по времени можно также в настройках в режиме администратора. Для этого выбираем «Автоматизация Cron». Создаём новое правило и переходим к редактированию:

Окно настройки Cron
Выполняемые задачи для Cron не содержат переменную self. Вызываемые по расписанию объекты должны быть найдены в БД по определенному критерию. К примеру, по расписанию можно найти все задачи, у которых просрочен будильник, и изменить их статус. То есть с помощью Cron обычно производится автоматизация по большому списку задач, которые нужно раз в какой-то промежуток времени обработать.
Правила заполнения поля «Выражение Cron»
Время выполнения задается пятью колонками, разделёнными пробелами: минуты (0—59), часы (0—23), дни месяца(1—31), месяцы(1—12) и день недели(0—7). Значения могут быть в виде чисел, диапазонов или «». Примеры заполнения:
5 0 * * * – выполняется каждый день в 0 часов 5 минут;0-59 * * * * – выполняется ежеминутно;/5 * * * * – выполняется каждые пять минут;
5 4 * * sun – выполняется в 4:05 в воскресенье;
Рассмотрим пример. Нам нужно каждый день находить все задачи, у которых статус не менялся 7 дней, автоматически их закрывать и отправить уведомление владельцу задачи.
from datetime import timedelta
# Находим нужный workflow
wf = models.CmfWorkflow.get(code='WF-000005')
# Статус закрыто в нужном workflow
status_closed = models.CmfStatus.get(code='closed', workflow=wf)
seven_days_ago = g.now - timedelta(days=7)
# Находим задачи, у которых статус не менялся 7 дней
tasks = models.CmfTask.list(filter=['status_modified_at', '<=', seven_days_ago])
for task in tasks:
task.status = status_closed
task.save()
# Отправляем сообщение владельцу задачи
models.CmfNotify.place_notify(obj=task, person_id=task.cmf_owner.id.value,
msg=f'Заявка {task.code} отмечена как выполненная автоматически', text='Заголовок')
Помимо трёх вариантов автоматизации в EvaProject есть ещё Webhook и Git. Основные варианты автоматизации, благодаря bzPyton, имеют максимальную гибкость. Конкретные описания полей можно посмотреть в документации, либо обратиться в техподдержку.
Встроенный интерпретатор поддерживает подсветку синтаксиса, а также имеет подсказки по доступным командам. К примеру, если после self. нажать букву r, то мы увидим все возможные варианты. Все названия «говорящие», то есть по названию можно понять, что это за функция или переменная.
Конец
В следующей статье расскажу как мы помогали настраивать автоматизацию на примере одной крупной компании. Ну и это ещё минимальная часть от реализованного и планируемого функционала. Постоянно от клиентов получаем информацию о том как они "интересно" использовали Джиру и продолжаем добавлять фичи для автоматизаций.