mirror of
https://github.com/anteo/redmine_custom_workflows.git
synced 2026-01-26 00:04:20 +00:00
Реализовать возможность ввода after_save скрипта для рабочего процесса (#8671)
This commit is contained in:
parent
6df2bafc72
commit
ae48e0be6c
52
README.rdoc
52
README.rdoc
@ -29,8 +29,9 @@ First, you need to define your own custom workflow(s). We already included one,
|
||||
|
||||
Go to the *Administration* section, then select <b>Custom workflows</b>. A list of defined workflows will appear. Here you can create new workflow, update, reorder and delete existing workflows. The order of workflows specifies the order in which workflow scripts will be executed.
|
||||
|
||||
Then click the <b>Create a custom workflow</b> button. Enter a short name, full description and script itself. The script entered will be executed before saving issues (on before_save callback),
|
||||
so you can use <b>@issue</b> variable to access the issue properties and methods. You can also raise exceptions by <b>raise WorkflowError, "Your message"</b>. If you change some properties of the issue during the script execution, it will be revalidated then and additional validation errors can appear.
|
||||
Then click the <b>Create a custom workflow</b> button. Enter a short name, full description and script itself. Below you will see two textareas. Fill one or both textareas by Ruby-language scripts that will be executed before and after saving the issue (on before_save and after_save callbacks respectively).
|
||||
|
||||
Both scripts are executed in the context of the issue. So access properties and methods of the issue directly (or through keyword "self"). You can also raise exceptions by <b>raise WorkflowError, "Your message"</b>. If you change some properties of the issue before saving it, it will be revalidated then and additional validation errors can appear.
|
||||
|
||||
== Enabling workflows for projects
|
||||
|
||||
@ -38,10 +39,57 @@ After you defined your custom workflow(s), you need to enable it. Open <b>Projec
|
||||
|
||||
Now go to the *Issues* and test it.
|
||||
|
||||
== Duration/Done Ratio/Status correlation example
|
||||
|
||||
Fill the "before save" script with:
|
||||
|
||||
if done_ratio_changed?
|
||||
if done_ratio==100 && status_id==2
|
||||
self.status_id=3
|
||||
elsif [1,3,4].include?(status_id) && done_ratio<100
|
||||
self.status_id=2
|
||||
end
|
||||
end
|
||||
|
||||
if status_id_changed?
|
||||
if status_id==2
|
||||
self.start_date ||= Time.now
|
||||
end
|
||||
if status_id==3
|
||||
self.done_ratio = 100
|
||||
self.start_date ||= created_on
|
||||
self.due_date ||= Time.now
|
||||
end
|
||||
end
|
||||
|
||||
== Example of creating subtask if the issue's status has changed.
|
||||
|
||||
Fill the "before save" script with:
|
||||
|
||||
@need_create = status_id_changed? && !new_record?
|
||||
|
||||
Fill the "after save" script with:
|
||||
|
||||
if @need_create
|
||||
issue = Issue.new(
|
||||
:author => User.current,
|
||||
:project => project,
|
||||
:tracker => tracker,
|
||||
:assigned_to => author,
|
||||
:parent_issue_id => id,
|
||||
:subject => "Subtask",
|
||||
:description => "Description")
|
||||
|
||||
issue.save!
|
||||
end
|
||||
|
||||
Do not forget to check whether issue is just created. Here we create the new issue and newly created issue will also be passed to this script on save. So without check, it will create another sub-issue. And etc. Thus it will fall into infinite recursion.
|
||||
|
||||
== Compatibility
|
||||
|
||||
This plug-in is compatible with Redmine 1.4.x, 2.0.x, 2.1.x
|
||||
|
||||
== Changelog
|
||||
|
||||
[0.0.2] Added ability to define after_save script along with before_save, improved logging, changed context of executing script to the issue.
|
||||
[0.0.1] Initial commit
|
||||
|
||||
@ -11,22 +11,24 @@ class CustomWorkflow < ActiveRecord::Base
|
||||
acts_as_list
|
||||
|
||||
default_scope :order => 'position ASC'
|
||||
validates_presence_of :script
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name, :case_sensitive => false
|
||||
validate :validate_syntax
|
||||
|
||||
def eval_script(context)
|
||||
context.each { |k, v| instance_variable_set ("@#{k}").to_sym, v }
|
||||
eval(script)
|
||||
end
|
||||
|
||||
def validate_syntax
|
||||
issue = Issue.new
|
||||
issue.send :instance_variable_set, :@issue, issue # compatibility with 0.0.1
|
||||
begin
|
||||
eval_script(:issue => Issue.new)
|
||||
issue.instance_eval(before_save)
|
||||
rescue WorkflowError => e
|
||||
rescue Exception => e
|
||||
errors.add :script, :invalid_script, :error => e
|
||||
errors.add :before_save, :invalid_script, :error => e
|
||||
end
|
||||
begin
|
||||
issue.instance_eval(after_save)
|
||||
rescue WorkflowError => e
|
||||
rescue Exception => e
|
||||
errors.add :after_save, :invalid_script, :error => e
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -2,8 +2,18 @@
|
||||
<div class="box tabular">
|
||||
<p><%= f.text_field :name, :required => true, :size => 50 %></p>
|
||||
<p><%= f.text_area :description, :cols => 80, :rows => 5 %></p>
|
||||
<p><%= f.text_area :script, :cols => 80, :rows => 20, :wrap => 'off', :required => true %>
|
||||
<em class="info"><%= l(:text_custom_workflow_script_note) %></em>
|
||||
<p><label><%= l(:label_workflow_scripts) %></label>
|
||||
<span class="splitcontentleft">
|
||||
<%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :no_label => true %>
|
||||
<em class="info"><%= l(:text_custom_workflow_before_save_note) %></em>
|
||||
</span>
|
||||
<span class="splitcontentright">
|
||||
<%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :no_label => true %>
|
||||
<em class="info"><%= l(:text_custom_workflow_after_save_note) %></em>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<em class="info"><%= l(:text_custom_workflow_general_note) %></em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,10 +2,8 @@
|
||||
background-image: url(../../../images/ticket_go.png);
|
||||
}
|
||||
|
||||
table.list.custom-workflows td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
table.list.custom-workflows td { vertical-align: middle; }
|
||||
|
||||
#custom_workflow_description, #custom_workflow_script {
|
||||
width: 99%;
|
||||
}
|
||||
#custom_workflow_description { width: 99%; }
|
||||
|
||||
#custom_workflow_before_save, #custom_workflow_after_save { width: 98%; font-size: 11px; }
|
||||
|
||||
@ -5,13 +5,14 @@ en:
|
||||
label_custom_workflow_plural: "Custom workflows"
|
||||
label_custom_workflow_new: "Create a custom workflow"
|
||||
|
||||
field_script: "Workflow script"
|
||||
label_workflow_scripts: "Workflow scripts"
|
||||
field_after_save: "Workflow script (after save)"
|
||||
field_before_save: "Workflow script (before save)"
|
||||
field_is_enabled: "Enabled"
|
||||
|
||||
field_custom_workflow:
|
||||
script: "Workflow script"
|
||||
|
||||
|
||||
activerecord:
|
||||
errors:
|
||||
messages:
|
||||
@ -20,4 +21,6 @@ en:
|
||||
new_status_invalid: "transition from '%{old_status}' to '%{new_status}' is prohibited"
|
||||
|
||||
text_select_project_custom_workflows: Select project custom workflows
|
||||
text_custom_workflow_script_note: Use the variable @issue for an access to the issue. To finish the process with an error, use raise WorkflowError, "Message to user"
|
||||
text_custom_workflow_before_save_note: This script will run BEFORE saving the issue. 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: This script will run AFTER saving the issue. 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.
|
||||
|
||||
@ -5,19 +5,22 @@ ru:
|
||||
label_custom_workflow_plural: "Пользовательские рабочие процессы"
|
||||
label_custom_workflow_new: "Новый процесс"
|
||||
|
||||
field_script: "Скрипт управления"
|
||||
label_workflow_scripts: "Сценарии"
|
||||
field_after_save: "Сценарий (после сохранения задачи)"
|
||||
field_before_save: "Сценарий (перед сохранением задачи)"
|
||||
field_is_enabled: "Разрешено"
|
||||
|
||||
field_custom_workflow:
|
||||
script: "Скрипт управления"
|
||||
|
||||
script: "Сценарий"
|
||||
|
||||
activerecord:
|
||||
errors:
|
||||
messages:
|
||||
invalid_script: "содержит ошибку: %{error}"
|
||||
custom_workflow_error: "Ошибка в рабочем процессе (обратитесь к администратору)"
|
||||
custom_workflow_error: "Ошибка в сценарии рабочего процесса (обратитесь к администратору)"
|
||||
new_status_invalid: "- переход от '%{old_status}' к '%{new_status}' невозможен"
|
||||
|
||||
text_select_project_custom_workflows: Выберите процессы для данного проекта
|
||||
text_custom_workflow_script_note: Используйте переменную @issue для доступа к задаче. Для завершения процесса с ошибкой используйте исключение WorkflowError.
|
||||
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.
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
class CreateExampleWorkflow < ActiveRecord::Migration
|
||||
def self.up
|
||||
CustomWorkflow.create(:name => "Duration/Done Ratio/Status correlation", :description => <<EOD, :script => <<EOS)
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
class AddAfterSaveToCustomWorkflows < ActiveRecord::Migration
|
||||
def self.up
|
||||
rename_column :custom_workflows, :script, :before_save
|
||||
change_column :custom_workflows, :before_save, :text, :null => false, :default => ""
|
||||
add_column :custom_workflows, :after_save, :text, :null => false, :default => ""
|
||||
end
|
||||
def self.down
|
||||
remove_column :custom_workflows, :after_save
|
||||
rename_column :custom_workflows, :before_save, :script
|
||||
end
|
||||
end
|
||||
30
db/migrate/20120908120613_fix_example_workflow.rb
Normal file
30
db/migrate/20120908120613_fix_example_workflow.rb
Normal file
@ -0,0 +1,30 @@
|
||||
class FixExampleWorkflow < ActiveRecord::Migration
|
||||
def self.up
|
||||
if workflow = CustomWorkflow.find_by_name("Duration/Done Ratio/Status correlation")
|
||||
workflow.before_save = <<EOS
|
||||
if done_ratio_changed?
|
||||
if done_ratio==100 && status_id==2
|
||||
self.status_id=3
|
||||
elsif [1,3,4].include?(status_id) && done_ratio<100
|
||||
self.status_id=2
|
||||
end
|
||||
end
|
||||
|
||||
if status_id_changed?
|
||||
if status_id==2
|
||||
self.start_date ||= Time.now
|
||||
end
|
||||
if status_id==3
|
||||
self.done_ratio = 100
|
||||
self.start_date ||= created_on
|
||||
self.due_date ||= Time.now
|
||||
end
|
||||
end
|
||||
EOS
|
||||
workflow.save
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
||||
@ -5,7 +5,8 @@ module RedmineCustomWorkflows
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
before_save :custom_workflow_eval
|
||||
before_save :before_save_custom_workflows
|
||||
after_save :after_save_custom_workflows
|
||||
validate :validate_status
|
||||
end
|
||||
end
|
||||
@ -22,22 +23,35 @@ module RedmineCustomWorkflows
|
||||
end
|
||||
end
|
||||
|
||||
def custom_workflow_eval
|
||||
def run_custom_workflows(on)
|
||||
return true unless project && project.module_enabled?(:custom_workflows_module)
|
||||
saved_attributes = attributes.dup
|
||||
@issue = self # compatibility with 0.0.1
|
||||
Rails.logger.info "= Running #{on} custom workflows for issue \"#{subject}\" (##{id})"
|
||||
project.custom_workflows.each do |workflow|
|
||||
begin
|
||||
workflow.eval_script(:issue => self)
|
||||
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.warn e
|
||||
Rails.logger.error "== Custom workflow exception: #{e.message}\n #{e.backtrace.join("\n ")}"
|
||||
errors.add :base, :custom_workflow_error
|
||||
return false
|
||||
end
|
||||
end
|
||||
saved_attributes == attributes || valid?
|
||||
Rails.logger.info "= Finished running #{on} custom workflows for issue \"#{subject}\" (##{id})."
|
||||
true
|
||||
end
|
||||
|
||||
def before_save_custom_workflows
|
||||
saved_attributes = attributes.dup
|
||||
run_custom_workflows(:before_save) && (saved_attributes == attributes || valid?)
|
||||
end
|
||||
|
||||
def after_save_custom_workflows
|
||||
run_custom_workflows(:after_save)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user