#15: Added observables User, Group, Group Users

This commit is contained in:
Anton Argirov 2015-06-23 13:47:45 +06:00
parent d4999fba06
commit 209329b319
17 changed files with 374 additions and 96 deletions

View File

@ -54,7 +54,7 @@ class CustomWorkflowsController < ApplicationController
def create
@workflow = CustomWorkflow.new(params[:custom_workflow])
respond_to do |format|
if @workflow.save
if params.has_key?(:commit) && @workflow.save
flash[:notice] = l(:notice_successful_create)
cookies[:custom_workflows_author] = @workflow.author
format.html { redirect_to(custom_workflows_path) }
@ -74,7 +74,8 @@ class CustomWorkflowsController < ApplicationController
def update
respond_to do |format|
if @workflow.update_attributes(params[:custom_workflow])
@workflow.assign_attributes(params[:custom_workflow])
if params.has_key?(:commit) && @workflow.save
flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(custom_workflows_path) }
else

View File

@ -8,6 +8,9 @@ class WorkflowError < StandardError
end
class CustomWorkflow < ActiveRecord::Base
OBSERVABLES = [:issue, :user, :group, :group_users, :shared]
PROJECT_OBSERVABLES = [:issue]
COLLECTION_OBSERVABLES = [:group_users]
attr_protected :id
has_and_belongs_to_many :projects
@ -19,37 +22,104 @@ class CustomWorkflow < ActiveRecord::Base
validate :validate_syntax
default_scope { order(:position => :asc) }
scope :for_all, lambda { where(:is_for_all => true) }
scope :active, lambda { where(:active => true) }
scope :for_project, (lambda do |project|
where("is_for_all OR EXISTS (SELECT * FROM #{reflect_on_association(:projects).join_table} WHERE project_id=? AND custom_workflow_id=id)", project.id)
end)
scope :observing, lambda { |observable| where(:observable => observable) }
class << self
def import_from_xml(xml)
attributes = Hash.from_xml(xml).values.first
attributes.delete('exported_at')
attributes.delete('plugin_version')
attributes.delete('ruby_version')
attributes.delete('rails_version')
CustomWorkflow.new(attributes)
end
def run_shared_code(object)
workflows = CustomWorkflow.observing(:shared).active
Rails.logger.info "= Running shared code for #{object.class} \"#{object}\" (\##{object.id})"
workflows.each do |workflow|
return false unless workflow.run(object, :shared_code)
end
Rails.logger.info "= Finished running shared code for #{object.class} \"#{object}\" (\##{object.id})"
true
end
def run_custom_workflows(observable, object, event)
workflows = CustomWorkflow.active.observing(observable)
if object.respond_to? :project
return true unless object.project
workflows = workflows.for_project(object.project)
end
return true unless workflows.any?
Rails.logger.info "= Running #{event} custom workflows for #{object.class} \"#{object}\" (\##{object.id})"
workflows.each do |workflow|
return false unless workflow.run(object, event)
end
Rails.logger.info "= Finished running #{event} custom workflows for #{object.class} \"#{object}\" (\##{object.id})"
true
end
end
def validate_syntax
issue = Issue.new
issue.send :instance_variable_set, :@issue, issue # compatibility with 0.0.1
begin
issue.instance_eval(before_save) if respond_to?(:before_save) && before_save
def run(object, event)
Rails.logger.info "== Running #{event} custom workflow \"#{name}\""
object.instance_eval(read_attribute(event))
true
rescue WorkflowError => e
Rails.logger.info "== User workflow error: #{e.message}"
object.errors.add :base, e.error
false
rescue Exception => e
errors.add :before_save, :invalid_script, :error => e
Rails.logger.error "== Custom workflow exception: #{e.message}\n #{e.backtrace.join("\n ")}"
object.errors.add :base, :custom_workflow_error
false
end
begin
issue.instance_eval(after_save) if respond_to?(:after_save) && after_save
def has_projects_association?
PROJECT_OBSERVABLES.include? observable.to_sym
end
def validate_syntax_for(object, event)
object.instance_eval(read_attribute(event)) if respond_to?(event) && read_attribute(event)
rescue WorkflowError => e
rescue Exception => e
errors.add :after_save, :invalid_script, :error => e
errors.add event, :invalid_script, :error => e
end
def validate_syntax
case observable
when 'shared'
CustomWorkflow.run_shared_code(self)
validate_syntax_for(self, :shared_code)
when 'user', 'group', 'issue'
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)
end
end
def export_as_xml
to_xml :only => [:author, :name, :description, :before_save, :after_save, :created_at] do |xml|
only = [:author, :name, :description, :before_save, :after_save, :shared_code, :observable,
:before_add, :after_add, :before_remove, :after_remove, :created_at]
only = only.select { |p| self[p] }
to_xml :only => only do |xml|
xml.tag! 'exported-at', Time.current.xmlschema
xml.tag! 'plugin-version', Redmine::Plugin.find(:redmine_custom_workflows).version
xml.tag! 'ruby-version', "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
xml.tag! 'rails-version', Rails::VERSION::STRING
end
end

View File

@ -5,12 +5,19 @@
<p><%= f.text_field :author, :size => 50, :label => :field_custom_workflow_author %>
<em class="info"><%= l(:text_custom_workflow_author) %></em>
</p>
<p><%= f.select :observable,
CustomWorkflow::OBSERVABLES.collect {|o| [l("custom_workflow_observable_#{o}"), o]}, {},
:onchange => 'this.form.submit()',
:disabled => !@workflow.new_record? %></p>
<p><%= f.text_area :description, :cols => 40, :rows => 5 %></p>
<% if @workflow.has_projects_association? %>
<p><%= f.check_box :is_for_all, :onclick => "checkAndDisable('custom_workflow_enabled_projects', this.checked);", :label => :field_enabled_for_all_projects %></p>
<% end %>
<p><%= f.check_box :active, :label => :field_custom_workflow_active %></p>
</div>
</div>
<% if @workflow.has_projects_association? %>
<div class="splitcontentright">
<fieldset class="box" id="custom_workflow_enabled_projects">
<legend><%= l(:label_enabled_projects) %></legend>
@ -22,31 +29,65 @@
<p><%= check_all_links 'custom_workflow_enabled_projects' %></p>
</fieldset>
</div>
<% end %>
</div>
<div style="clear: left;"></div>
<fieldset class="box">
<legend><%= l(:label_workflow_scripts) %></legend>
<% case @workflow.observable %>
<% when 'shared' %>
<%= f.text_area :shared_code, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
<% when 'group_users' %>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off' %>
<%= f.text_area :before_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
<div class="splitcontentright">
<%= f.text_area :after_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
</div>
<div style="clear: left;"></div>
<div class="splitcontent">
<div class="splitcontentleft">
<%= f.text_area :before_remove, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
</div>
<div class="splitcontentright">
<%= 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' %>
<%= 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_general_note) %></em>
<em class="info"><%= l("text_custom_workflow_#{@workflow.observable}_code_note") %></em>
</p>
</fieldset>
<script type="text/javascript">
jQuery('#custom_workflow_before_save').taboverride(2, true);
jQuery('#custom_workflow_after_save').taboverride(2, true);
jQuery('.custom_workflow_script').taboverride(2, true);
function checkAndDisable(id, checked) {
if (checked) {
jQuery('#' + id).find('input[type=checkbox]').attr('checked', true).attr('disabled', true);

View File

@ -11,12 +11,13 @@
<table class="custom-workflows list">
<thead>
<tr>
<th><%=l(:field_name)%></th>
<th><%=l(:field_description)%></th>
<th><%=l(:field_author)%></th>
<th><%=l(:label_project_plural)%></th>
<th style="width:15%;"><%= l(:button_sort) %></th>
<th style="width:10%;"></th>
<th><%= l(:field_name) %></th>
<th><%= l(:field_description) %></th>
<th><%= l(:field_observable) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:label_project_plural) %></th>
<th><%= l(:button_sort) %></th>
<th></th>
</tr>
</thead>
<tbody>
@ -24,9 +25,12 @@
<tr class="<%= cycle("odd", "even") %> <%= 'disabled' unless workflow.active? %>">
<td class="name"><%= link_to(workflow.name, edit_custom_workflow_path(workflow)) %></td>
<td class="description"><%= textilizable(workflow.description) %></td>
<td class="observable"><%= l("custom_workflow_observable_#{workflow.observable}") %></td>
<td class="author"><%= mail_to workflow.author if workflow.author.present? %></td>
<td>
<% if workflow.is_for_all? %>
<% if not workflow.has_projects_association? %>
-
<% elsif workflow.is_for_all? %>
<%= l(:field_enabled_for_all_projects) %>
<% elsif workflow.projects.empty? %>
<%= l(:text_no_enabled_projects) %>
@ -34,7 +38,7 @@
<%= workflow.projects.map(&:name).join(", ") %>
<% end %>
</td>
<td align="center"><%= reorder_links("custom_workflow", {:action => 'update', :id => workflow}) %></td>
<td align="center"><%= 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

@ -3,9 +3,9 @@
<%= hidden_field_tag 'project[custom_workflow_ids][]', '' %>
<fieldset>
<legend><%= l(:text_select_project_custom_workflows) %></legend>
<% if CustomWorkflow.exists? %>
<% if CustomWorkflow.observing(CustomWorkflow::PROJECT_OBSERVABLES).exists? %>
<dl>
<% CustomWorkflow.all.each do |w| %>
<% CustomWorkflow.observing(CustomWorkflow::PROJECT_OBSERVABLES).each do |w| %>
<dt class="<%= 'disabled' unless w.active? %>">
<label>
<%= check_box_tag 'project[custom_workflow_ids][]', w.id, @project.custom_workflow_ids.include?(w.id) || w.is_for_all?, :disabled => w.is_for_all? %> <%= w.name %>

View File

@ -12,6 +12,13 @@ table.list.custom-workflows td {
table.list.custom-workflows td.description {
text-align: left;
width: 40%;
}
table.list.custom-workflows td.buttons {
white-space: normal;
width: 10%;
text-align: left;
}
table.list.custom-workflows tr.disabled {
@ -22,7 +29,7 @@ table.list.custom-workflows tr.disabled {
width: 98%;
}
#custom_workflow_before_save, #custom_workflow_after_save {
.custom_workflow_script {
width: 98%;
font-size: 11px;
}
@ -32,7 +39,18 @@ table.list.custom-workflows tr.disabled {
overflow-y: auto;
}
.icon-export { background-image: url(../images/export.png); }
.icon-import { background-image: url(../images/import.png); }
.icon-active { background-image: url(../images/active.png); }
.icon-inactive { background-image: url(../images/inactive.png); }
.icon-export {
background-image: url(../images/export.png);
}
.icon-import {
background-image: url(../images/import.png);
}
.icon-active {
background-image: url(../images/active.png);
}
.icon-inactive {
background-image: url(../images/inactive.png);
}

View File

@ -14,8 +14,14 @@ en:
button_custom_workflow_activate: "Activate"
button_custom_workflow_deactivate: "Deactivate"
field_after_save: "Workflow script executable after saving the issue"
field_before_save: "Workflow script executable before saving the issue"
field_after_save: "Workflow script executable after saving observable object"
field_before_save: "Workflow script executable before saving 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"
field_before_remove: "Workflow script executable before removing observable object from collection"
field_shared_code: "Shared code"
field_observable: "Observable object"
field_is_enabled: "Enabled"
field_enabled_for_all_projects: "Enabled for all projects"
field_custom_workflow_author: "Author's e-mail"
@ -38,8 +44,18 @@ en:
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_general_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: 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_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_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_no_enabled_projects: No projects
text_custom_workflow_author: Will be included in exported XML
text_custom_workflow_disabled: disabled by admin
text_custom_workflow_is_for_all: enabled for all projects
custom_workflow_observable_shared: "<shared code>"
custom_workflow_observable_issue: "Issue"
custom_workflow_observable_group: "Group"
custom_workflow_observable_user: "User"
custom_workflow_observable_group_users: "Group Users"

View File

@ -14,8 +14,14 @@ pt-br:
button_custom_workflow_activate: "Ativar"
button_custom_workflow_deactivate: "Desativar"
field_after_save: "Script do fluxo de trabalho executado depois de salvar a Tarefa"
field_before_save: "Script do fluxo de trabalho executado antes de salvar a Tarefa"
field_after_save: "Workflow script executable after saving observable object"
field_before_save: "Workflow script executable before saving 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"
field_before_remove: "Workflow script executable before removing observable object from collection"
field_shared_code: "Shared code"
field_observable: "Observable object"
field_is_enabled: "Ativado"
field_enabled_for_all_projects: "Ativado para todos os projetos"
field_custom_workflow_author: "E-mail do autor"
@ -38,8 +44,18 @@ pt-br:
text_select_project_custom_workflows: Selecione o projeto para os fluxos de trabalho
text_custom_workflow_before_save_note: Você pode alterar as propriedades das tarefas aqui. Não crie ou atualize tarefas relacionadas neste script. Para terminar com o erro, utilize a exceção WorkflowError, "Mensagem para o usuário".
text_custom_workflow_after_save_note: Você pode atualizar ou criar tarefas relacionadas aqui. Note que esse script também será executado para as tarefas criadas recentemente. Então, faça as verificações adequadas para evitar que entre em recursividade infinita.
text_custom_workflow_general_note: Ambos os scripts são executados no contexto da tarefa pelas chamadas before_save com retornos para a chamada after_save. Portanto, use métodos e propriedades da tarefa diretamente (ou através de "self"). Variáveis de instância (@variable) também são permitidos e podem ser utilizados, se necessário.
text_custom_workflow_issue_code_note: Ambos os scripts são executados no contexto da tarefa pelas chamadas before_save com retornos para a chamada after_save. Portanto, use métodos e propriedades da tarefa diretamente (ou através de "self"). Variáveis de instância (@variable) também são permitidos e podem ser utilizados, se necessário.
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_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_no_enabled_projects: Não há projeto
text_custom_workflow_author: Será incluído no XML exportado
text_custom_workflow_disabled: desabilitado por admin
text_custom_workflow_is_for_all: Ativado para todos os projetos
custom_workflow_observable_shared: "<shared code>"
custom_workflow_observable_issue: "Issue"
custom_workflow_observable_group: "Group"
custom_workflow_observable_user: "User"
custom_workflow_observable_group_users: "Group Users"

View File

@ -14,8 +14,14 @@ ru:
button_custom_workflow_activate: "Активировать"
button_custom_workflow_deactivate: "Деактивировать"
field_after_save: "Сценарий выполняемый после сохранения задачи"
field_before_save: "Сценарий выполняемый перед сохранением задачи"
field_after_save: "Сценарий выполняемый после сохранения наблюдаемого объекта"
field_before_save: "Сценарий выполняемый перед сохранением наблюдаемого объекта"
field_after_add: "Сценарий выполняемый после добавления наблюдаемого объекта в коллекцию"
field_before_add: "Сценарий выполняемый перед добавлением наблюдаемого объекта в коллекцию"
field_after_remove: "Сценарий выполняемый после удаления наблюдаемого объекта из коллекции"
field_before_remove: "Сценарий выполняемый перед удалением наблюдаемого объекта из коллекции"
field_shared_code: "Общий код"
field_observable: "Наблюдаемый объект"
field_is_enabled: "Разрешено"
field_enabled_for_all_projects: "Разрешен для всех проектов"
field_custom_workflow_author: "E-Mail адрес автора"
@ -38,8 +44,18 @@ ru:
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_general_note: Оба сценария исполняются в контексте задачи, как и обычные обратные вызовы before_save и after_save. Поэтому используйте методы и свойства задачи напрямую или через ключевое слово self.
text_custom_workflow_issue_code_note: Оба сценария исполняются в контексте задачи, как и обычные обратные вызовы before_save и after_save. Поэтому используйте методы и свойства задачи напрямую или через ключевое слово 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_no_enabled_projects: Нет проектов
text_custom_workflow_author: Будет использован в XML файле при экспорте
text_custom_workflow_disabled: отключен администратором
text_custom_workflow_is_for_all: разрешен для всех проектов
custom_workflow_observable_shared: "<общий код>"
custom_workflow_observable_issue: "Задача"
custom_workflow_observable_group: "Группа"
custom_workflow_observable_user: "Пользователь"
custom_workflow_observable_group_users: "Пользователи группы"

View File

@ -0,0 +1,5 @@
class AddObservableFieldToCustomWorkflows < ActiveRecord::Migration
def change
add_column :custom_workflows, :observable, :string, :null => false, :default => 'issue'
end
end

View File

@ -0,0 +1,9 @@
class AddAdditionalScriptFieldsToCustomWorkflows < ActiveRecord::Migration
def change
add_column :custom_workflows, :shared_code, :text, :null => true
add_column :custom_workflows, :before_add, :text, :null => true
add_column :custom_workflows, :after_add, :text, :null => true
add_column :custom_workflows, :before_remove, :text, :null => true
add_column :custom_workflows, :after_remove, :text, :null => true
end
end

View File

@ -11,6 +11,12 @@ Rails.application.config.to_prepare do
unless Issue.include?(RedmineCustomWorkflows::IssuePatch)
Issue.send(:include, RedmineCustomWorkflows::IssuePatch)
end
unless User.include?(RedmineCustomWorkflows::UserPatch)
User.send(:include, RedmineCustomWorkflows::UserPatch)
end
unless Group.include?(RedmineCustomWorkflows::GroupPatch)
Group.send(:include, RedmineCustomWorkflows::GroupPatch)
end
unless ActionView::Base.include?(RedmineCustomWorkflows::Helper)
ActionView::Base.send(:include, RedmineCustomWorkflows::Helper)
end

View File

@ -0,0 +1,39 @@
module RedmineCustomWorkflows
module GroupPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
callback = lambda do |event, group, user|
group.instance_variable_set(:@group, group)
group.instance_variable_set(:@user, user)
CustomWorkflow.run_shared_code(group) if event.to_s.starts_with? 'before_'
CustomWorkflow.run_custom_workflows(:group_users, group, event)
end
before_add_for_users << callback
before_remove_for_users << callback
after_add_for_users << callback
after_remove_for_users << callback
end
end
module InstanceMethods
def before_save_custom_workflows
@group = self
@saved_attributes = attributes.dup
CustomWorkflow.run_shared_code(self)
result = CustomWorkflow.run_custom_workflows(:group, self, :before_save) && (@saved_attributes == attributes || valid?)
@saved_attributes = nil
result
end
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:group, self, :after_save)
end
end
end
end

View File

@ -23,39 +23,17 @@ module RedmineCustomWorkflows
:new_status => status_new && status_new.name
end
def run_custom_workflows(on)
return true unless project
workflows = project.enabled_custom_workflows
return true unless workflows.any?
@issue = self # compatibility with 0.0.1
Rails.logger.info "= Running #{on} custom workflows for issue \"#{subject}\" (##{id})"
workflows.each do |workflow|
begin
Rails.logger.info "== Running #{on} custom workflow \"#{workflow.name}\""
instance_eval(workflow.read_attribute(on))
rescue WorkflowError => e
Rails.logger.info "== User workflow error: #{e.message}"
errors.add :base, e.error
return false
rescue Exception => e
Rails.logger.error "== Custom workflow exception: #{e.message}\n #{e.backtrace.join("\n ")}"
errors.add :base, :custom_workflow_error
return false
end
end
Rails.logger.info "= Finished running #{on} custom workflows for issue \"#{subject}\" (##{id})."
true
end
def before_save_custom_workflows
@issue = self
@saved_attributes = attributes.dup
result = run_custom_workflows(:before_save) && (@saved_attributes == attributes || valid?)
CustomWorkflow.run_shared_code(self)
result = CustomWorkflow.run_custom_workflows(:issue, self, :before_save) && (@saved_attributes == attributes || valid?)
@saved_attributes = nil
result
end
def after_save_custom_workflows
run_custom_workflows(:after_save)
CustomWorkflow.run_custom_workflows(:issue, self, :after_save)
end
end
end

View File

@ -0,0 +1,35 @@
module RedmineCustomWorkflows
module MailerPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
end
end
module InstanceMethods
def custom_email(headers={})
user = headers.delete :user
headers[:to] = user.mail if user
text_body = headers.delete :text_body
html_body = headers.delete :html_body
template_name = headers.delete :template_name
template_params = headers.delete :template_params || {}
if text_body || html_body
mail headers do |format|
format.text { render :text => text_body } if text_body
format.html { render :html => html_body } if html_body
end
elsif template_name
template_params.each { |k,v| instance_variable_set("@#{k}", v) }
mail headers do |format|
format.text { render template_name }
format.html { render template_name } unless Setting.plain_text_mail?
end
else
raise 'Nor :text_body, :html_body or :template_name specified'
end
end
end
end
end

View File

@ -12,9 +12,6 @@ module RedmineCustomWorkflows
end
module InstanceMethods
def enabled_custom_workflows
(CustomWorkflow.for_all + custom_workflows).select { |w| w.active? }.uniq.sort
end
end
end
end

View File

@ -0,0 +1,27 @@
module RedmineCustomWorkflows
module UserPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
before_save :before_save_custom_workflows
after_save :after_save_custom_workflows
end
end
module InstanceMethods
def before_save_custom_workflows
@user = self
@saved_attributes = attributes.dup
CustomWorkflow.run_shared_code(self)
result = CustomWorkflow.run_custom_workflows(:user, self, :before_save) && (@saved_attributes == attributes || valid?)
@saved_attributes = nil
result
end
def after_save_custom_workflows
CustomWorkflow.run_custom_workflows(:user, self, :after_save)
end
end
end
end