Создание плагинов для Redmine

в 5:58, , рубрики: hooks, plugins, redmine, ruby on rails, метки: , , ,

Процесс создания плагинов для Redmine очень слабо документирован. Задача данной статьи отчасти восполнить этот пробел, рассказав про успешный опыт создания небольшого, но полезного плагина. Важное примечание. Redmine написан на Ruby on Rails, вам нужно быть к этому готовым, если вы собрались делать свой плагин :)

Исходная задача

Наша компания использует Redmine в качестве основного таск-трекера. Не буду рассматривать его плюсы и минусы, остановлюсь лишь на количестве писем, которые он шлет при любом изменении задач. В этом «шуме» иногда теряются новые задачи или важные комментарии.

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

Создание плагинов для Redmine

Особенно напрягали уведомления на смену статуса задачи на «Задача закрыта». Мне, как разработчику, они не важны. Если по задаче есть доработки, то задаче снова присваивается статус «Новая», пишется какой-то комментарий, а я получаю уведомление и приступаю к ее решению.

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

Итак, задача: создать плагин, добавляющий в аккаунт пользователя настройку «Не извещать меня, когда задача закрыта».

Описание решения

Создание шаблона под плагин я опущу, это подробно описано в соответствующем разделе на redmine.org.

Добавим галочку, по аналогии с «Не извещать об изменениях, которые я сделал сам». Для этого просто скопируем шаблон appviewsusers_mail_notifications.html.erb в свой плагин и допишем требуемый html код.

Это не сильно правильно, но стандартный хук на форму пользователя :view_users_form, выводит чекбокс не в том месте, где хотелось. Redmine с каждой новой версией поддерживает все больше и больше хуков, но их всегда будет недостаточно и данный случай не исключение.

Далее «пропатчим» 3 метода, это создание и редактирование пользователя админом и редактирование на странице аккаунта. На эти методы тоже нет хуков, поэтому на помощь пришли «хуки из коробки»: alias_method и alias_method_chain. Логика их работы понятна из названия, связать методы и связать методы в цепочку.

За страницу аккаунта отвечает контроллер my_controller, метод account. Пропатчим его, создав файл в поддиректории плагина libpatches my_controller_patch.rb

module Patches
 module MyControllerPatch

   def self.included(base)
     # наследуем родные методы
     base.extend(ClassMethods)
     # расширяем класс своими методами
     base.send(:include, InstanceMethods)
     base.class_eval do
       unloadable
       # соединяем методы в цепочку
       alias_method_chain :account, :ext
     end
   end

   module ClassMethods
   end

   module InstanceMethods

     # новый метод с расширением ext
     def account_with_ext
       if request.post?
         # определяем отмечен ли чекбокс и добавляем значение в конфиг пользователя
         User.current.pref[:no_self_notified_closed] = (params[:no_self_notified_closed] == '1')
       end
       # выполняем базовый метод account
       account_without_ext
     end

   end

 end
end

Создание и редактирование админом, аналогично.

Далее нужно считывать выставленное значение при формировании списка пользователей на рассылку уведомления, за это отвечает модель issue, метод recipients и watcher_recipients, копируем их в libpatches issue_patch.rb и изменяем под себя.

module Patches
 module IssuePatch

   def self.included(base)
     …
     base.class_eval do
       unloadable
       # переопределяем базовые методы своими
       alias_method :recipients, :recipients_ext
       alias_method :watcher_recipients, :watcher_recipients_ext
     end
   end
   …
   module InstanceMethods

     # автор задачи и исполнители
     def recipients_ext
       # получаем иформацию о текущем статусе задачи
       @status = IssueStatus.find_by_id(self.status_id)
       notified = project.notified_users
       # все что требует это добавить свою дополнительную проверку (метод allow_notify_closed)
       notified << author if author && author.active? && author.notify_about?(self) && allow_notify_closed?(author)
       if assigned_to
         if assigned_to.is_a?(Group)
           notified += assigned_to.users.select { |u| u.active? && u.notify_about?(self) && allow_notify_closed?(u) }
         else
           notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self) && allow_notify_closed?(assigned_to)
         end
       end
       notified.uniq!
       notified.reject! { |user| !visible?(user) }
       notified.collect(&:mail)
     end

     # наблюдатели
     def watcher_recipients_ext
       notified = watcher_users.active
       notified.reject! { |user| user.mail_notification == 'none' || allow_notify_closed?(user) === false }

       if respond_to?(:visible?)
         notified.reject! { |user| !visible?(user) }
       end
       notified.collect(&:mail).compact
     end

     private

     # добавим свой метод, для проверки значения нашего чекбокса и свойства у статуса
     def allow_notify_closed?(user)
       (user.pref[:no_self_notified_closed] && @status.is_closed?) ? false : true
     end

   end

 end
end

И осталось самое главное, подключить наши патчи. В корне плагина файл init.rb, дописываем:

# dispatcher позволяем расширить базовые классы нашими патчами
require 'dispatcher'

Dispatcher.to_prepare do
 MyController.send(:include, Patches::MyControllerPatch)
 UsersController.send(:include, Patches::UsersControllerPatch)
 Issue.send(:include, Patches::IssuePatch)
end

Устанавливаем плагин в папку /vendor/plugins/ и перезапускаем redmine. Все, теперь можно управлять уведомлениями и не получать ненужные письма. Таким образом, вы можете изменить поведения любого метода в Redmine и написать свои плагины, автоматизирующие и облегчающие работу.

Ссылка на исходники https://github.com/parshukovvv/redmine_notice

Автор: parshukovvv

Поделиться

* - обязательные к заполнению поля