mirror of
https://github.com/anteo/redmine_custom_workflows.git
synced 2026-01-26 00:04:20 +00:00
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:
parent
b9de089279
commit
eab90b312f
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -36,54 +36,74 @@
|
||||
|
||||
<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_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', :class => 'custom_workflow_script' %>
|
||||
<em class="info"><%= l(:text_custom_workflow_after_save_note) %></em>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: left;"></div>
|
||||
<% end %>
|
||||
<% observable = @workflow.observable.to_sym %>
|
||||
<p>
|
||||
<em class="info"><%= l("text_custom_workflow_#{@workflow.observable}_code_note") %></em>
|
||||
<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 *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' %>
|
||||
</div>
|
||||
<div class="splitcontentright">
|
||||
<%= f.text_area :after_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
|
||||
</div>
|
||||
</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' %>
|
||||
</div>
|
||||
<div class="splitcontentright">
|
||||
<%= f.text_area :after_remove, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% 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">
|
||||
|
||||
@ -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) %>
|
||||
|
||||
6
app/views/layouts/_collapsible.html.erb
Normal file
6
app/views/layouts/_collapsible.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<fieldset class="collapsible <%= collapsed ? 'collapsed' : '' %>">
|
||||
<legend onclick="toggleFieldset(this);"><%= label %></legend>
|
||||
<div style="<%= collapsed ? 'display: none' : '' %>">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -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"
|
||||
@ -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: "Пользователи группы"
|
||||
@ -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
11
init.rb
@ -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
|
||||
|
||||
38
lib/redmine_custom_workflows/attachment_patch.rb
Normal file
38
lib/redmine_custom_workflows/attachment_patch.rb
Normal 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
|
||||
@ -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
|
||||
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) }
|
||||
[: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
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
38
lib/redmine_custom_workflows/wiki_content_patch.rb
Normal file
38
lib/redmine_custom_workflows/wiki_content_patch.rb
Normal 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
|
||||
27
lib/redmine_custom_workflows/wiki_page_patch.rb
Normal file
27
lib/redmine_custom_workflows/wiki_page_patch.rb
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user