From ae48e0be6cb1944d60c62865005fc1d6861ef689 Mon Sep 17 00:00:00 2001 From: Anton Argirov Date: Sat, 8 Sep 2012 19:15:34 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B2=D0=B2=D0=BE=D0=B4=D0=B0?= =?UTF-8?q?=20after=5Fsave=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B5=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=B0=20(#8?= =?UTF-8?q?671)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.rdoc | 52 ++++++++++++++++++- app/models/custom_workflow.rb | 18 ++++--- app/views/custom_workflows/_form.html.erb | 14 ++++- assets/stylesheets/style.css | 12 ++--- config/locales/en.yml | 9 ++-- config/locales/ru.yml | 13 +++-- .../20120831064944_create_example_workflow.rb | 1 + ...5222_add_after_save_to_custom_workflows.rb | 11 ++++ .../20120908120613_fix_example_workflow.rb | 30 +++++++++++ lib/redmine_custom_workflows/issue_patch.rb | 26 +++++++--- 10 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 db/migrate/20120908085222_add_after_save_to_custom_workflows.rb create mode 100644 db/migrate/20120908120613_fix_example_workflow.rb 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 @@

<%= f.text_field :name, :required => true, :size => 50 %>

<%= f.text_area :description, :cols => 80, :rows => 5 %>

-

<%= f.text_area :script, :cols => 80, :rows => 20, :wrap => 'off', :required => true %> - <%= l(:text_custom_workflow_script_note) %> +

+ + <%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :no_label => true %> + <%= l(:text_custom_workflow_before_save_note) %> + + + <%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :no_label => true %> + <%= l(:text_custom_workflow_after_save_note) %> + +

+

+ <%= l(:text_custom_workflow_general_note) %>

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