diff --git a/README.rdoc b/README.rdoc
index f4ff6dc..9498e3d 100644
--- a/README.rdoc
+++ b/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 Custom workflows. 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 Create a custom workflow 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 @issue variable to access the issue properties and methods. You can also raise exceptions by raise WorkflowError, "Your message". 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 Create a custom workflow 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 raise WorkflowError, "Your message". 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 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
diff --git a/app/models/custom_workflow.rb b/app/models/custom_workflow.rb
index 8599399..6e53886 100644
--- a/app/models/custom_workflow.rb
+++ b/app/models/custom_workflow.rb
@@ -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
diff --git a/app/views/custom_workflows/_form.html.erb b/app/views/custom_workflows/_form.html.erb
index 82924b2..b0c85a2 100644
--- a/app/views/custom_workflows/_form.html.erb
+++ b/app/views/custom_workflows/_form.html.erb
@@ -2,8 +2,18 @@
diff --git a/assets/stylesheets/style.css b/assets/stylesheets/style.css
index 4ef37c0..e911648 100644
--- a/assets/stylesheets/style.css
+++ b/assets/stylesheets/style.css
@@ -1,11 +1,9 @@
#admin-menu a.custom-workflows {
- background-image: url(../../../images/ticket_go.png);
+ 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%;
-}
\ No newline at end of file
+#custom_workflow_description { width: 99%; }
+
+#custom_workflow_before_save, #custom_workflow_after_save { width: 98%; font-size: 11px; }
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 64fb0e6..99c8c4c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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.
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 2fcdbf2..791bf72 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -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.
diff --git a/db/migrate/20120831064944_create_example_workflow.rb b/db/migrate/20120831064944_create_example_workflow.rb
index 93d6362..d3a9ccf 100644
--- a/db/migrate/20120831064944_create_example_workflow.rb
+++ b/db/migrate/20120831064944_create_example_workflow.rb
@@ -1,3 +1,4 @@
+
class CreateExampleWorkflow < ActiveRecord::Migration
def self.up
CustomWorkflow.create(:name => "Duration/Done Ratio/Status correlation", :description => < < 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
diff --git a/db/migrate/20120908120613_fix_example_workflow.rb b/db/migrate/20120908120613_fix_example_workflow.rb
new file mode 100644
index 0000000..f757250
--- /dev/null
+++ b/db/migrate/20120908120613_fix_example_workflow.rb
@@ -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 = < 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