New observable objects added (Project, Wiki Content, Attachment, Issue Attachments, Project Attachments, Wiki Page Attachments)

Ability to hook before_destroy and after_destroy events
This commit is contained in:
Anton Argirov 2015-11-22 16:44:16 +06:00
parent b9de089279
commit eab90b312f
16 changed files with 391 additions and 94 deletions

View File

@ -118,6 +118,8 @@ This plug-in is compatible with Redmine 2.x.x, 3.x.x
== Changelog
[0.1.5] * New observable objects added (Project, Wiki Content, Attachment, Issue Attachments, Project Attachments, Wiki Page Attachments)
* Ability to hook before_destroy and after_destroy events
[0.1.4] * Ability to exit current workflow with `return` or `return true` and cancel workflow's execution chain with `return false`
* Non-active workflows are now not checked for syntax. Now you can import non-valid (for your Redmine instance for example) workflow, make changes to it and then activate.
[0.1.3] Compatibility with Redmine 2.x.x returned, support of Redmine 1.x.x cancelled

View File

@ -8,9 +8,11 @@ class WorkflowError < StandardError
end
class CustomWorkflow < ActiveRecord::Base
OBSERVABLES = [:issue, :user, :group, :group_users, :shared]
PROJECT_OBSERVABLES = [:issue]
COLLECTION_OBSERVABLES = [:group_users]
OBSERVABLES = [:issue, :issue_attachments, :user, :attachment, :group, :group_users, :project, :project_attachments,
:wiki_content, :wiki_page_attachments, :shared]
PROJECT_OBSERVABLES = [:issue, :issue_attachments, :project, :project_attachments, :wiki_content, :wiki_page_attachments]
COLLECTION_OBSERVABLES = [:group_users, :issue_attachments, :project_attachments, :wiki_page_attachments]
SINGLE_OBSERVABLES = [:issue, :user, :group, :attachment, :project, :wiki_content]
attr_protected :id
has_and_belongs_to_many :projects
@ -19,7 +21,7 @@ class CustomWorkflow < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name, :case_sensitive => false
validates_format_of :author, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
validate :validate_syntax
validate :validate_syntax, :validate_scripts_presence, :if => Proc.new {|workflow| workflow.respond_to?(:observable) and workflow.active?}
if Rails::VERSION::MAJOR >= 4
default_scope { order(:position => :asc) }
@ -53,7 +55,7 @@ class CustomWorkflow < ActiveRecord::Base
workflows = CustomWorkflow.observing(:shared).active
log_message '= Running shared code', object
workflows.each do |workflow|
if workflow.run(object, :shared_code) == false
unless workflow.run(object, :shared_code)
log_message '= Abort running shared code', object
return false
end
@ -64,14 +66,14 @@ class CustomWorkflow < ActiveRecord::Base
def run_custom_workflows(observable, object, event)
workflows = CustomWorkflow.active.observing(observable)
if object.respond_to? :project
if PROJECT_OBSERVABLES.include? observable
return true unless object.project
workflows = workflows.for_project(object.project)
end
return true unless workflows.any?
log_message "= Running #{event} custom workflows", object
workflows.each do |workflow|
if workflow.run(object, event) == false
unless workflow.run(object, event)
log_message "= Abort running #{event} custom workflows", object
return false
end
@ -82,6 +84,7 @@ class CustomWorkflow < ActiveRecord::Base
end
def run(object, event)
return true unless attribute_present?(event)
Rails.logger.info "== Running #{event} custom workflow \"#{name}\""
object.instance_eval(read_attribute(event))
true
@ -106,32 +109,60 @@ class CustomWorkflow < ActiveRecord::Base
errors.add event, :invalid_script, :error => e
end
def validate_scripts_presence
case observable.to_sym
when :shared
fields = [shared_code]
when *SINGLE_OBSERVABLES
fields = [before_save, after_save, before_destroy, after_destroy]
when *COLLECTION_OBSERVABLES
fields = [before_add, after_add, before_remove, after_remove]
else
fields = []
end
unless fields.any? {|field| field.present?}
errors.add :base, :scripts_absent
end
end
def validate_syntax
return unless respond_to?(:observable) && active?
case observable
when 'shared'
CustomWorkflow.run_shared_code(self)
validate_syntax_for(self, :shared_code)
when 'user', 'group', 'issue'
case observable.to_sym
when :shared
CustomWorkflow.run_shared_code self
validate_syntax_for self, :shared_code
when *SINGLE_OBSERVABLES
object = observable.camelize.constantize.new
object.send :instance_variable_set, "@#{observable}", object # compatibility with 0.0.1
CustomWorkflow.run_shared_code(object)
validate_syntax_for(object, :before_save)
validate_syntax_for(object, :after_save)
when 'group_users'
@user = User.new
@group = Group.new
CustomWorkflow.run_shared_code(self)
validate_syntax_for(self, :before_add)
validate_syntax_for(self, :before_remove)
validate_syntax_for(self, :after_add)
validate_syntax_for(self, :after_remove)
CustomWorkflow.run_shared_code object
[:before_save, :after_save, :before_destroy, :after_destroy].each {|field| validate_syntax_for object, field}
when *COLLECTION_OBSERVABLES
object = nil
case observable.to_sym
when :group_users
object = Group.new
object.send :instance_variable_set, :@user, User.new
object.send :instance_variable_set, :@group, object
when :issue_attachments
object = Issue.new
object.send :instance_variable_set, :@attachment, Attachment.new
object.send :instance_variable_set, :@issue, object
when :project_attachments
object = Project.new
object.send :instance_variable_set, :@attachment, Attachment.new
object.send :instance_variable_set, :@project, object
when :wiki_page_attachments
object = WikiPage.new
object.send :instance_variable_set, :@attachment, Attachment.new
object.send :instance_variable_set, :@page, object
end
CustomWorkflow.run_shared_code self
[:before_add, :after_add, :before_remove, :after_remove].each {|field| validate_syntax_for object, field}
end
end
def export_as_xml
only = [:author, :name, :description, :before_save, :after_save, :shared_code, :observable,
:before_add, :after_add, :before_remove, :after_remove, :created_at]
:before_add, :after_add, :before_remove, :after_remove, :before_destroy, :after_destroy, :created_at]
only = only.select { |p| self[p] }
to_xml :only => only do |xml|
xml.tag! 'exported-at', Time.current.xmlschema

View File

@ -36,10 +36,18 @@
<fieldset class="box">
<legend><%= l(:label_workflow_scripts) %></legend>
<% case @workflow.observable %>
<% when 'shared' %>
<% observable = @workflow.observable.to_sym %>
<p>
<em class="info"><%= l("text_custom_workflow_#{observable}_code_note") %></em>
</p>
<% case observable %>
<% when :shared %>
<%= f.text_area :shared_code, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
<% when 'group_users' %>
<% when *CustomWorkflow::COLLECTION_OBSERVABLES %>
<%= render :layout => 'layouts/collapsible',
:locals => {
:collapsed => (not (@workflow.before_add.present? or @workflow.after_add.present? or @workflow.errors[:base].present?)),
:label => l(:label_add_workflows)} do %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
@ -48,7 +56,11 @@
<%= f.text_area :after_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
</div>
<div style="clear: left;"></div>
<% end %>
<%= render :layout => 'layouts/collapsible',
:locals => {
:collapsed => (not (@workflow.before_remove.present? or @workflow.after_remove.present?)),
:label => l(:label_remove_workflows)} do %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_remove, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
@ -57,33 +69,41 @@
<%= f.text_area :after_remove, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
</div>
<div style="clear: left;"></div>
<% when 'user', 'group' %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
<div class="splitcontentright">
<%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
</div>
<div style="clear: left;"></div>
<% when 'issue' %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
<em class="info"><%= l(:text_custom_workflow_before_save_note) %></em>
</div>
<div class="splitcontentright">
<%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
<em class="info"><%= l(:text_custom_workflow_after_save_note) %></em>
</div>
</div>
<div style="clear: left;"></div>
<% end %>
<p>
<em class="info"><%= l("text_custom_workflow_#{@workflow.observable}_code_note") %></em>
</p>
<% when *CustomWorkflow::SINGLE_OBSERVABLES %>
<%= render :layout => 'layouts/collapsible',
:locals => {
:collapsed => (not (@workflow.before_save.present? or @workflow.after_save.present? or @workflow.errors[:base].present?)),
:label => l(:label_save_workflows)} do %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
<% if observable == :issue %>
<em class="info"><%= l(:text_custom_workflow_before_save_note) %></em>
<% end %>
</div>
<div class="splitcontentright">
<%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
<% if observable == :issue %>
<em class="info"><%= l(:text_custom_workflow_after_save_note) %></em>
<% end %>
</div>
</div>
<% end %>
<%= render :layout => 'layouts/collapsible',
:locals => {
:collapsed => (not (@workflow.before_destroy.present? or @workflow.before_destroy.present?)),
:label => l(:label_destroy_workflows)} do %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_destroy, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
<div class="splitcontentright">
<%= f.text_area :after_destroy, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
</div>
<% end %>
<% end %>
</fieldset>
<script type="text/javascript">

View File

@ -38,7 +38,7 @@
<%= workflow.projects.map(&:name).join(", ") %>
<% end %>
</td>
<td align="center"><%= reorder_links("custom_workflow", {:action => 'update', :id => workflow, :commit => true}) %></td>
<td align="center" nowrap><%= reorder_links("custom_workflow", {:action => 'update', :id => workflow, :commit => true}) %></td>
<td class="buttons">
<% if workflow.active? %>
<%= link_to(l(:button_custom_workflow_deactivate), custom_workflow_status_path(workflow, :active => false), :class => 'icon icon-inactive', :method => :post) %>

View File

@ -0,0 +1,6 @@
<fieldset class="collapsible <%= collapsed ? 'collapsed' : '' %>">
<legend onclick="toggleFieldset(this);"><%= label %></legend>
<div style="<%= collapsed ? 'display: none' : '' %>">
<%= yield %>
</div>
</fieldset>

View File

@ -9,6 +9,10 @@ en:
label_enabled_projects: "Enabled for project(s)"
label_custom_workflow_export: "Export"
label_custom_workflow_import: "Import workflow"
label_save_workflows: "Saving observable objects"
label_destroy_workflows: "Destroying observable objects"
label_add_workflows: "Adding observable objects to collection"
label_remove_workflows: "Removing observable objects from collection"
button_import: "Import"
button_custom_workflow_activate: "Activate"
@ -16,6 +20,8 @@ en:
field_after_save: "Workflow script executable after saving observable object"
field_before_save: "Workflow script executable before saving observable object"
field_after_destroy: "Workflow script executable after destroying observable object"
field_before_destroy: "Workflow script executable before destroying observable object"
field_after_add: "Workflow script executable after adding observable object to collection"
field_before_add: "Workflow script executable before adding observable object to collection"
field_after_remove: "Workflow script executable after removing observable object from collection"
@ -40,15 +46,23 @@ en:
invalid_script: "contains error: %{error}"
custom_workflow_error: "Custom workflow error (please contact administrator)"
new_status_invalid: "transition from '%{old_status}' to '%{new_status}' is prohibited"
scripts_absent: "At least one script should be defined"
text_select_project_custom_workflows: Select project custom workflows
text_custom_workflow_before_save_note: You can change properties of the issues here. Do not create or update related issues in this script. To finish with error, use raise WorkflowError, "Message to user".
text_custom_workflow_after_save_note: You can update or create related issues here. Note that this script will be also executed for the newly created issues. So make appropriate checks to prevent infinite recursion.
text_custom_workflow_issue_code_note: Both scripts are executed in the context of the issue like ordinary before_save and after_save callbacks. So use methods and properties of the issue directly (or through "self"). Instance variables (@variable) are also allowed and may be used if needed.
text_custom_workflow_issue_code_note: Scripts are executed in the context of Issue object like ordinary before_save and after_save callbacks. So use methods and properties of the issue directly (or through "self"). Instance variables (@variable) are also allowed and may be used if needed.
text_custom_workflow_shared_code_note: This code will run before any other workflow and may contain shared code, e.g. functions and classes needed by other workflows
text_custom_workflow_user_code_note: Both scripts are executed in the context of the user object when user object changes. Use methods and properties of the user directly (or through "self")
text_custom_workflow_group_code_note: Both scripts are executed in the context of the group object when group object changes. Use methods and properties of the group directly (or through "self")
text_custom_workflow_user_code_note: Scripts are executed in the context of User object when user object changes (destroys). Use methods and properties of the user directly (or through "self")
text_custom_workflow_group_code_note: Scripts are executed in the context of Group object when group object changes (destroys). Use methods and properties of the group directly (or through "self")
text_custom_workflow_group_users_code_note: These scripts are executed when user being added to group/removed from group. Use variables @user and @group to access appropriate objects in your scripts.
text_custom_workflow_attachment_code_note: Scripts are executed in the context of Attachment object when attachment object changes (destroys). Use methods and properties of the attachment object directly (or through "self"). Note that these scripts will affect all attachment types (issue, document, wiki pages and etc), so you should check 'container_type' field additionally in your script or select specific "... Attachments" observable object.
text_custom_workflow_issue_attachments_code_note: These scripts are executed when attachment being added to issue/removed from issue. Use variables @issue and @attachment to access appropriate objects in your scripts.
text_custom_workflow_project_code_note: Scripts are executed in the context of Project object when project object changes (destroys). Use methods and properties of the project directly (or through "self")
text_custom_workflow_project_attachments_code_note: These scripts are executed when a file being added to project/removed from project. Use variables @project and @attachment to access appropriate objects in your scripts.
text_custom_workflow_wiki_content_code_note: Scripts are executed in the context of Wiki Content object when project object changes (destroys). Use methods and properties of the project directly (or through "self")
text_custom_workflow_wiki_page_attachments_code_note: These scripts are executed when a file being added to wiki page/removed from wiki page. Use variables @page and @attachment to access appropriate objects in your scripts.
text_no_enabled_projects: No projects
text_custom_workflow_author: Will be included in exported XML
text_custom_workflow_disabled: disabled by admin
@ -56,6 +70,12 @@ en:
custom_workflow_observable_shared: "<shared code>"
custom_workflow_observable_issue: "Issue"
custom_workflow_observable_issue_attachments: "Issue Attachments"
custom_workflow_observable_group: "Group"
custom_workflow_observable_user: "User"
custom_workflow_observable_attachment: "Attachment"
custom_workflow_observable_project: "Project"
custom_workflow_observable_project_attachments: "Project Attachments / Files"
custom_workflow_observable_wiki_content: "Wiki Content"
custom_workflow_observable_wiki_page_attachments: "Wiki Page Attachments"
custom_workflow_observable_group_users: "Group Users"

View File

@ -9,6 +9,10 @@ ru:
label_enabled_projects: "Разрешен в проектах"
label_custom_workflow_export: "Экспорт"
label_custom_workflow_import: "Импорт процесса"
label_save_workflows: "Сохранение наблюдаемых объектов"
label_destroy_workflows: "Уничтожение наблюдаемых объектов"
label_add_workflows: "Добавление наблюдаемых объектов в коллекцию"
label_remove_workflows: "Удаление наблюдаемых объектов из коллекции"
button_import: "Импортировать"
button_custom_workflow_activate: "Активировать"
@ -16,6 +20,8 @@ ru:
field_after_save: "Сценарий выполняемый после сохранения наблюдаемого объекта"
field_before_save: "Сценарий выполняемый перед сохранением наблюдаемого объекта"
field_after_destroy: "Сценарий выполняемый после уничтожения наблюдаемого объекта"
field_before_destroy: "Сценарий выполняемый перед уничтожением наблюдаемого объекта"
field_after_add: "Сценарий выполняемый после добавления наблюдаемого объекта в коллекцию"
field_before_add: "Сценарий выполняемый перед добавлением наблюдаемого объекта в коллекцию"
field_after_remove: "Сценарий выполняемый после удаления наблюдаемого объекта из коллекции"
@ -40,15 +46,23 @@ ru:
invalid_script: "содержит ошибку: %{error}"
custom_workflow_error: "Ошибка в сценарии рабочего процесса (обратитесь к администратору)"
new_status_invalid: "- переход от '%{old_status}' к '%{new_status}' невозможен"
scripts_absent: "Хотя бы один скрипт должен быть определен"
text_select_project_custom_workflows: Выберите процессы для данного проекта
text_custom_workflow_before_save_note: Здесь вы можете изменять свойства задачи. Не создавайте и не обновляйте связанные задачи в этом сценарии. Чтобы завершить сценарий с произвольной ошибкой, используйте raise WorkflowError, "Message to user".
text_custom_workflow_after_save_note: Вы можете обновлять и создавать задачи (в том числе и связанные задачи) здесь. Обратите внимание, что данный сценарий будет также выполняться и для вновь создаваемых задач. Поэтому используйте дополнительные проверки, чтобы избежать бесконечной рекурсии.
text_custom_workflow_issue_code_note: Оба сценария исполняются в контексте задачи, как и обычные обратные вызовы before_save и after_save. Поэтому используйте методы и свойства задачи напрямую или через ключевое слово self.
text_custom_workflow_issue_code_note: Эти сценарии исполняются в контексте задачи, как и обычные обратные вызовы before_save и after_save. Поэтому используйте методы и свойства задачи (Issue) напрямую или через ключевое слово self.
text_custom_workflow_shared_code_note: Этот код будет исполняться перед любым другим процессом и может содержать общий код, например, функции и классы, необходимые для работы других процессов.
text_custom_workflow_user_code_note: Оба сценария исполняются в контексте объекта пользователя когда объект пользователя изменяется. Используйте методы и свойства объекта пользователя напрямую или через ключевое слово self.
text_custom_workflow_group_code_note: Оба сценария исполняются в контексте объекта группы когда объект группы изменяется. Используйте методы и свойства объекта группы напрямую или через ключевое слово self.
text_custom_workflow_group_users_code_note: Эти скрипты выполняются когда пользователь добавляется в группу/удаляется из группы. Используйте переменные @user и @group для доступа к соответствующим объектам из Ваших сценариев.
text_custom_workflow_user_code_note: Эти сценарии исполняются в контексте объекта пользователя когда объект пользователя изменяется (удаляется). Используйте методы и свойства объекта пользователя (User) напрямую или через ключевое слово self.
text_custom_workflow_group_code_note: Эти сценарии исполняются в контексте объекта группы когда объект группы изменяется (удаляется). Используйте методы и свойства объекта группы (Group) напрямую или через ключевое слово self.
text_custom_workflow_group_users_code_note: Эти сценарии выполняются когда пользователь добавляется в группу/удаляется из группы. Используйте переменные @user и @group для доступа к соответствующим объектам из Ваших сценариев.
text_custom_workflow_attachment_code_note: Эти сценарии исполняются в контексте объекта вложения когда объект вложения изменяется (удаляется). Используйте методы и свойства объекта вложения (Attachment) напрямую или через ключевое слово self. Обратите внимание на то, что данные сценарии выполняются при сохранении (удалении) вложения любого типа (задача, документ, страница Wiki и т.д.), поэтому следует дополнительно проверять в коде поле container_type либо в качестве наблюдаемого объекта выбрать конкретный тип вложения.
text_custom_workflow_issue_attachments_code_note: Эти сценарии выполняются когда вложение прикладывается к задаче/удаляется из задачи. Используйте переменные @issue и @attachment для доступа к соответствующим объектам из Ваших сценариев.
text_custom_workflow_project_code_note: Эти сценарии исполняются в контексте объекта проекта когда объект проекта изменяется (удаляется). Используйте методы и свойства объекта группы (Project) напрямую или через ключевое слово self.
text_custom_workflow_project_attachments_code_note: Эти сценарии выполняются когда файл загружается в проект/удаляется из проекта. Используйте переменные @project и @attachment для доступа к соответствующим объектам из Ваших сценариев.
text_custom_workflow_wiki_content_code_note: Эти сценарии исполняются в контексте объекта Wiki содержания когда объект Wiki содержания изменяется (удаляется). Используйте методы и свойства объекта содержания Wiki (WikiContent) напрямую или через ключевое слово self.
text_custom_workflow_wiki_page_attachments_code_note: Эти сценарии выполняются когда файл загружается на Wiki страницу/удаляется с Wiki страницы. Используйте переменные @page и @attachment для доступа к соответствующим объектам из Ваших сценариев.
text_no_enabled_projects: Нет проектов
text_custom_workflow_author: Будет использован в XML файле при экспорте
text_custom_workflow_disabled: отключен администратором
@ -56,6 +70,12 @@ ru:
custom_workflow_observable_shared: "<общий код>"
custom_workflow_observable_issue: "Задача"
custom_workflow_observable_issue_attachments: "Вложения задач"
custom_workflow_observable_group: "Группа"
custom_workflow_observable_user: "Пользователь"
custom_workflow_observable_attachment: "Вложение"
custom_workflow_observable_project: "Проект"
custom_workflow_observable_project_attachments: "Вложения проекта / Файлы"
custom_workflow_observable_wiki_content: "Содержание Wiki"
custom_workflow_observable_wiki_page_attachments: "Вложения страниц Wiki"
custom_workflow_observable_group_users: "Пользователи группы"

View File

@ -0,0 +1,6 @@
class AddBeforeAndAfterDestroyToCustomWorkflows < ActiveRecord::Migration
def change
add_column :custom_workflows, :before_destroy, :text, :null => true
add_column :custom_workflows, :after_destroy, :text, :null => true
end
end

11
init.rb
View File

@ -8,6 +8,9 @@ Rails.application.config.to_prepare do
unless ProjectsHelper.include?(RedmineCustomWorkflows::ProjectsHelperPatch)
ProjectsHelper.send(:include, RedmineCustomWorkflows::ProjectsHelperPatch)
end
unless Attachment.include?(RedmineCustomWorkflows::AttachmentPatch)
Attachment.send(:include, RedmineCustomWorkflows::AttachmentPatch)
end
unless Issue.include?(RedmineCustomWorkflows::IssuePatch)
Issue.send(:include, RedmineCustomWorkflows::IssuePatch)
end
@ -17,6 +20,12 @@ Rails.application.config.to_prepare do
unless Group.include?(RedmineCustomWorkflows::GroupPatch)
Group.send(:include, RedmineCustomWorkflows::GroupPatch)
end
unless WikiContent.include?(RedmineCustomWorkflows::WikiContentPatch)
WikiContent.send(:include, RedmineCustomWorkflows::WikiContentPatch)
end
unless WikiPage.include?(RedmineCustomWorkflows::WikiPagePatch)
WikiPage.send(:include, RedmineCustomWorkflows::WikiPagePatch)
end
unless Mailer.include?(RedmineCustomWorkflows::MailerPatch)
Mailer.send(:include, RedmineCustomWorkflows::MailerPatch)
end
@ -29,7 +38,7 @@ Redmine::Plugin.register :redmine_custom_workflows do
name 'Redmine Custom Workflow plugin'
author 'Anton Argirov'
description 'Allows to create custom workflows for issues, defined in the plain Ruby language'
version '0.1.4'
version '0.1.5'
url 'http://www.redmine.org/plugins/custom-workflows'
menu :admin_menu, :custom_workflows, {:controller => 'custom_workflows', :action => 'index'}, :caption => :label_custom_workflow_plural

View File

@ -0,0 +1,38 @@
module RedmineCustomWorkflows
module AttachmentPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
before_destroy :before_destroy_custom_workflows
after_destroy :after_destroy_custom_workflows
end
end
module InstanceMethods
def before_save_custom_workflows
@attachment = self
@saved_attributes = attributes.dup
CustomWorkflow.run_shared_code(self)
CustomWorkflow.run_custom_workflows(:attachment, self, :before_save)
errors.empty? && (@saved_attributes == attributes || valid?)
ensure
@saved_attributes = nil
end
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:attachment, self, :after_save)
end
def before_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:attachment, self, :before_destroy)
end
def after_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:attachment, self, :after_destroy)
end
end
end
end

View File

@ -6,6 +6,8 @@ module RedmineCustomWorkflows
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
before_destroy :before_destroy_custom_workflows
after_destroy :after_destroy_custom_workflows
def self.users_callback(event, group, user)
group.instance_variable_set(:@group, group)
@ -13,17 +15,12 @@ module RedmineCustomWorkflows
CustomWorkflow.run_shared_code(group) if event.to_s.starts_with? 'before_'
CustomWorkflow.run_custom_workflows(:group_users, group, event)
end
if Rails::VERSION::MAJOR >= 4
callback_lambda = lambda { |event, group, user| Group.users_callback(event, group, user) }
before_add_for_users << callback_lambda
before_remove_for_users << callback_lambda
after_add_for_users << callback_lambda
after_remove_for_users << callback_lambda
[:before_add, :before_remove, :after_add, :after_remove].each do |observable|
send("#{observable}_for_users") << if Rails::VERSION::MAJOR >= 4
lambda { |event, group, user| Group.users_callback(event, group, user) }
else
before_add_for_users << lambda { |group, user| Group.users_callback(:before_add, group, user) }
before_remove_for_users << lambda { |group, user| Group.users_callback(:before_remove, group, user) }
after_add_for_users << lambda { |group, user| Group.users_callback(:after_add, group, user) }
after_remove_for_users << lambda { |group, user| Group.users_callback(:after_remove, group, user) }
lambda { |group, user| Group.users_callback(observable, group, user) }
end
end
end
end
@ -42,6 +39,14 @@ module RedmineCustomWorkflows
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:group, self, :after_save)
end
def before_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:group, self, :before_destroy)
end
def after_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:group, self, :after_destroy)
end
end
end
end

View File

@ -6,7 +6,24 @@ module RedmineCustomWorkflows
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
before_destroy :before_destroy_custom_workflows
after_destroy :after_destroy_custom_workflows
validate :validate_status
def self.attachments_callback(event, issue, attachment)
issue.instance_variable_set(:@issue, issue)
issue.instance_variable_set(:@attachment, attachment)
CustomWorkflow.run_shared_code(issue) if event.to_s.starts_with? 'before_'
CustomWorkflow.run_custom_workflows(:issue_attachments, issue, event)
end
[:before_add, :before_remove, :after_add, :after_remove].each do |observable|
send("#{observable}_for_attachments") << if Rails::VERSION::MAJOR >= 4
lambda { |event, issue, attachment| Issue.attachments_callback(event, issue, attachment) }
else
lambda { |issue, attachment| Issue.attachments_callback(observable, issue, attachment) }
end
end
end
end
@ -36,6 +53,14 @@ module RedmineCustomWorkflows
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:issue, self, :after_save)
end
def before_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:issue, self, :before_destroy)
end
def after_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:issue, self, :after_destroy)
end
end
end
end

View File

@ -5,13 +5,53 @@ module RedmineCustomWorkflows
base.send :include, InstanceMethods
base.class_eval do
has_and_belongs_to_many :custom_workflows
#accepts_nested_attributes_for :custom_workflow, :update_only => true
safe_attributes :custom_workflow_ids, :if =>
lambda { |project, user| project.new_record? || user.allowed_to?(:manage_project_workflow, project) }
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
before_destroy :before_destroy_custom_workflows
after_destroy :after_destroy_custom_workflows
def self.attachments_callback(event, project, attachment)
project.instance_variable_set(:@project, project)
project.instance_variable_set(:@attachment, attachment)
CustomWorkflow.run_shared_code(project) if event.to_s.starts_with? 'before_'
CustomWorkflow.run_custom_workflows(:project_attachments, project, event)
end
[:before_add, :before_remove, :after_add, :after_remove].each do |observable|
send("#{observable}_for_attachments") << if Rails::VERSION::MAJOR >= 4
lambda { |event, project, attachment| Project.attachments_callback(event, project, attachment) }
else
lambda { |project, attachment| Project.attachments_callback(observable, project, attachment) }
end
end
end
end
module InstanceMethods
def before_save_custom_workflows
@project = self
@saved_attributes = attributes.dup
CustomWorkflow.run_shared_code(self)
CustomWorkflow.run_custom_workflows(:project, self, :before_save)
errors.empty? && (@saved_attributes == attributes || valid?)
ensure
@saved_attributes = nil
end
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:project, self, :after_save)
end
def before_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:project, self, :before_destroy)
end
def after_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:project, self, :after_destroy)
end
end
end
end

View File

@ -6,6 +6,8 @@ module RedmineCustomWorkflows
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
before_destroy :before_destroy_custom_workflows
after_destroy :after_destroy_custom_workflows
end
end
@ -23,6 +25,14 @@ module RedmineCustomWorkflows
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:user, self, :after_save)
end
def before_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:user, self, :before_destroy)
end
def after_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:user, self, :after_destroy)
end
end
end
end

View File

@ -0,0 +1,38 @@
module RedmineCustomWorkflows
module WikiContentPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
before_destroy :before_destroy_custom_workflows
after_destroy :after_destroy_custom_workflows
end
end
module InstanceMethods
def before_save_custom_workflows
@content = self
@saved_attributes = attributes.dup
CustomWorkflow.run_shared_code(self)
CustomWorkflow.run_custom_workflows(:wiki_content, self, :before_save)
errors.empty? && (@saved_attributes == attributes || valid?)
ensure
@saved_attributes = nil
end
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:wiki_content, self, :after_save)
end
def before_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:wiki_content, self, :before_destroy)
end
def after_destroy_custom_workflows
CustomWorkflow.run_custom_workflows(:wiki_content, self, :after_destroy)
end
end
end
end

View File

@ -0,0 +1,27 @@
module RedmineCustomWorkflows
module WikiPagePatch
def self.included(base)
base.send :include, InstanceMethods
base.class_eval do
def self.attachments_callback(event, page, attachment)
page.instance_variable_set(:@page, page)
page.instance_variable_set(:@attachment, attachment)
CustomWorkflow.run_shared_code(page) if event.to_s.starts_with? 'before_'
CustomWorkflow.run_custom_workflows(:wiki_page_attachments, page, event)
end
[:before_add, :before_remove, :after_add, :after_remove].each do |observable|
send("#{observable}_for_attachments") << if Rails::VERSION::MAJOR >= 4
lambda { |event, page, attachment| WikiPage.attachments_callback(event, page, attachment) }
else
lambda { |page, attachment| WikiPage.attachments_callback(observable, page, attachment) }
end
end
end
end
module InstanceMethods
end
end
end