Как использовать триггеры для решения конкретных задач?
Ссылка на объект и поля объекта
Получить ссылку на объект, по событию которого сработал триггер можно по ключевому слову 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.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.
Иногда в теле триггера производятся изменения значений полей, но при этом вам. требуется получить значения полей объекта до изменения. Для этого можно воспользоваться следующим советом:
# Начальное значение поля 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
Имена переменных могут быть на кириллице
Для повышения читаемости кода триггеров вы можете использовать кириллические имена переменных.
Иногда в триггерах приходится обращаться к другим объектам системы. Здесь мы покажем какие объекты доступны для использования
# 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)