Практические советы

Как использовать триггеры для решения конкретных задач?

Ссылка на объект и поля объекта

Получить ссылку на объект, по событию которого сработал триггер можно по ключевому слову self

# Ссылка на объект, в котором возникло событие
self

# Обращение к системным полям задачи
#
# Список системных полей:
# id                      - id задачи
# parent                  - родительская задача
# subject                 - заголовок задачи
# description             - описание задачи

# type                    - тип задачи
# project                 - проект
# category                - категория задачи
# status                  - статус задачи
# version                 - версия/релиз/спринт

# priority                - приоритет задачи

# assigned_to             - исполнитель задачи
# author                  - автор задачи
# responsible             - 

# start_date              - планируемая дата начала задачи (для Ганта)
# due_date                - планируемая дата завершения задачи (для Ганта)
# duration                - планируемая длительность задачи в днях (для Ганта)
# schedule_manually       - режим русного планирования (булево да/нет) (для Ганта)
# ignore_non_working_days - пропускать нерабочие дни (булево да/нет) (для Ганта)

# done_ratio              - % выполнения заадчи

# estimated_hours         - предполагаемое время выыполнения задачи в часах
# derived_estimated_hours - фактическое время выполнения задачи
# remaining_hours         - предполагаемое оставшееся время выполнения задачи в часах
# derived_remaining_hours - фактическое оставшееся время выполнения задачи

# budget                  - 
# story_points            - оценка в сторипоинтах

self.id                                  # Доступ к полю объекта ID
self.status = Status.find :in_progress   # Доступ к полю объекта Status

# Доступ к кастомным полям
# К примеру есть кастомное поле:
# |  ID   |  Название        | Символ   |
# |------------------------------------
# |  99   |  Задача на паузе  | paused   |
# Тогда работать с ним можно так:

self.cf_99     = true         # 99 - это ID кастомного поля
self.cf_paused = false        # paused - это символьный идентификатор кастомного поля

Поиск задачи по номеру, изменение и сохранение её

В этом примере производится поиск задачи по ID, у найденной задачи устанавливается описание заничением даты создания задачи и производится сохранение задачи.

# Find Task by ID
t = Task.find_by! id: 80976

# Change value of custom field
t.description = t.created_at.strftime('%F')

# Save task
t.save!

Пример работы с датами и отрезками времени

В этом примере показано, как можно работать с датами и временем, форматированием даты/времени и вычислять отрезки времени.

self.description = (DateTime.now + 10.minutes).strftime('%FT%T%:z')
self.subject = "MyTriggeredTask [ Timeout at " + (DateTime.now + 10.minutes).strftime('%I:%M:%S') + " ]"

Добавить запись в журнал (деятельность)

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

# Создает комментарий от имени текущего пользователя, под которым выполнился триггер
self.add_journal(notes: "My comment1")

# Создает комментарий от имени пользователя, с ID=507
self.add_journal(notes: "My comment2", user: User.find_by!(id: 507))

# Создает комментарий от имени пользователя, с логином 'system_robot'
self.add_journal(notes: "My comment3", user: User.find_by!(login: 'system_robot'))

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

Если вы хотите зарегистрировать изменения задачи от лица определенного пользователя, то нужно вызвать add_journal без параметра notes. Этим вы сообщите системе, что в журнале автором изменений следует поставить указанного в add_journal пользователя.

if self.description.to_s.length < 100
	if self.subject !~ /\[ОШИБКА\]/   # Проверим чтобы не дописывать предупреждение повторно
        self.add_journal(user: User.find_by!(login: 'system_robot'))
		self.subject = "[ОШИБКА] " + self.subject
		self.description = "[ОШИБКА] Текст описания имеет длинну менее 100 символов. Исправьте его:\n" + self.description.to_s
	end
end

Обработка и отображение ошибок

Если вам потребуется в триггере сгенерировать ошибку и сообщить об этом пользователю, то вот совет.

begin
  self.custom_field_98 = (DateTime.now + 10.minutes).strftime('%FT%T%:z')
rescue => e
  show_error(e)           # Вывод сообщения об ошибке
  show_error(e.message)   # Вывод сообщения об ошибке. Тоже, что и в строке выше
  show_error(e.backtrace) # Вывод стека вызова
end

Сериализация данных

Иногда необходимо вывести данные объекта целиком для отладки. Лучшим решением для этого является сериализация объекта в JSON.

show_error(self.to_json)
# вывод \{"id":83965,"type_id":84,"project_id":9,"subject":"TriggeredTask",
# "description":null,"due_date":null,"category_id":null,"status_id":1,
# "assigned_to_id":null,"priority_id":8,"version_id":null,"author_id":507,
# "lock_version":17,"done_ratio":0,"estimated_hours":null,
# "created_at":"2023-11-22T01:42:59.449+07:00",
# "updated_at":"2023-11-22T02:00:46.305+07:00",
# "start_date":null,"responsible_id":null,"budget_id":null,"position":1,
# "story_points":null,"remaining_hours":null,"derived_estimated_hours":null,
# "schedule_manually":false,"parent_id":null,"duration":null,
# "ignore_non_working_days":false,"derived_remaining_hours":null\}

show_error(\{"cf_99_was" => self.cf_99_was, "cf_99" => self.cf_99\}.to_json)
# вывод: \{"cf_99_was":1.0,"cf_99":1.0\}

Значения полей исходного объекта

Иногда в теле триггера производятся изменения значений полей, но при этом вам. требуется получить значения полей объекта до изменения. Для этого можно воспользоваться следующим советом:

# Начальное значение поля cf_99 было "Первое значение"
self.cf_99 = "Второе значение"   # Присвоим полю новое значние
...
# Здесь вам потребовалось получить исходное значение неизмененного объекта
# Используйте для этого суффикс _was
show_error(self.cf_99_was)    # Вернет "Первое значение"
show_error(self.cf_99)        # Вернет "Второе значение"

Проверка на незаполненное значение

Неустановленное значение поля объекта легко проверить с помощью nil, так как этот способ работает независимо от типа поля.

if !self.cf_99_was nil    # строкая проверка на незаполненные данные для любых типов, включая multiple
  ...
end

if self.cf_99_was == nil # строгий nil
  ...
end

if self.cf_99_was.nil?   # строгий nil
  ...
end

Использование символьных идентификаторов для статусов

Символьные идентификаторы статусов позволяют писать более лаконичный и читаемый код.

# Вместо записи
self.status = Status.find_by! id: 22     # Здесь 22 это ID статуса "In progress"

# Вы можете использовать один из следующих вариантов
self.status = Status.find_by! symbol: 'in_progress'
self.status = Status.find :in_progress
self.status_sym = :in_progress

Имена переменных могут быть на кириллице

Для повышения читаемости кода триггеров вы можете использовать кириллические имена переменных.

КорневаяЗадача = Task.find_by! id: 83965
self.parent = КорневаяЗадача   # Установим текущей задаче родителя - задачу с ID=83965

Доступные объекты

Иногда в триггерах приходится обращаться к другим объектам системы. Здесь мы покажем какие объекты доступны для использования

# Project - модель проектов
# WorkPackage - модель задач
# Status - модель статусов
# Type - модель типов задач
# Workflow - модель бизнес-процессов
# CustomField - модель пользовательских полей
# CustomAction - модель пользовательских действий
# User - модель пользователей
# GroupUser - модель групп пользователей
# Category - модель категорий (мы не используем)
# Version - модель версий (используем для бэклогов и спринтов)
# Budget - модель бюджетов
# Enumeration - модель перечислений (используется для приоритетов)
# Journal - модель истории изменений объектов (версий в нотации 1С)
# Member - модель участников проектов
# Query - модель сохраняемых списков задач
# View - модель представлений
# Journal - модель записей журнала
# Journal::WorkPackageJournal - модель журнала версий задач

# Получаем первую запись журнала текущей задачи
JournalRecordFrom = Journal.where(journable_type: "WorkPackage", journable_id: self.id).first
# Получаем последнюю запись журнала текущей задачи
JournalRecordTo = Journal.where(journable_type: "WorkPackage", journable_id: self.id).last
self.description = "Задача была в работе не более " \
                 + ((JournalRecordTo.created_at - JournalRecordFrom.created_at)/86400).round(half: :up).to_s \
                 + " дня/дней\n" \
                 + self.description.to_s

Изменение пользователя

Внимание! Подобные действия являются нетипичными для использования в триггерах. Здесь мы приводим пример возможностей триггеров. Используйте, пожалуйста, данные возможности аккуратно. Представленный код триггера позволяет найти пользователя по какому-либо параметру и произвести изменение его данных.

user = User.find_by! login: 'ivan'    # Найти пользователя с логином ivan
user.activate                         # Активировать пользователя
user.failed_login_count = 0           # Сбросить счеткик неудачных попыток входа
user.password = user.password_confirmation = "YOUR NEW SAFE PASSWORD 1234!"  # Обновить пароль
user.save!                            # Сохранить пользователя

Отправка HTTP запросов и вебхуков

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

# Пример GET запроса
uri = URI('http://u-meteor.ru/hook')
res = Net::HTTP.get_response(uri)

# Пример POST запроса
uri = URI('http://u-meteor.ru/hook')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
req.body = self.to_json
res = http.request(req)

# В res.body содержится ответ сервера
puts res.body if res.is_a?(Net::HTTPSuccess)