diff --git a/app/controllers/custom_workflows_controller.rb b/app/controllers/custom_workflows_controller.rb index e9c57cf..5346136 100644 --- a/app/controllers/custom_workflows_controller.rb +++ b/app/controllers/custom_workflows_controller.rb @@ -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 diff --git a/app/models/custom_workflow.rb b/app/models/custom_workflow.rb index 2b3d66c..a158137 100644 --- a/app/models/custom_workflow.rb +++ b/app/models/custom_workflow.rb @@ -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 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 + Rails.logger.error "== Custom workflow exception: #{e.message}\n #{e.backtrace.join("\n ")}" + object.errors.add :base, :custom_workflow_error + false + end + + 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 event, :invalid_script, :error => e 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 - rescue WorkflowError => e - rescue Exception => e - errors.add :before_save, :invalid_script, :error => e - end - begin - issue.instance_eval(after_save) if respond_to?(:after_save) && after_save - rescue WorkflowError => e - rescue Exception => e - errors.add :after_save, :invalid_script, :error => e + 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 diff --git a/app/views/custom_workflows/_form.html.erb b/app/views/custom_workflows/_form.html.erb index ffdc292..44508dd 100644 --- a/app/views/custom_workflows/_form.html.erb +++ b/app/views/custom_workflows/_form.html.erb @@ -5,48 +5,89 @@

<%= f.text_field :author, :size => 50, :label => :field_custom_workflow_author %> <%= l(:text_custom_workflow_author) %>

+

<%= f.select :observable, + CustomWorkflow::OBSERVABLES.collect {|o| [l("custom_workflow_observable_#{o}"), o]}, {}, + :onchange => 'this.form.submit()', + :disabled => !@workflow.new_record? %>

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

-

<%= f.check_box :is_for_all, :onclick => "checkAndDisable('custom_workflow_enabled_projects', this.checked);", :label => :field_enabled_for_all_projects %>

+ <% if @workflow.has_projects_association? %> +

<%= f.check_box :is_for_all, :onclick => "checkAndDisable('custom_workflow_enabled_projects', this.checked);", :label => :field_enabled_for_all_projects %>

+ <% end %>

<%= f.check_box :active, :label => :field_custom_workflow_active %>

-
-
- <%= l(:label_enabled_projects) %> - <%= custom_workflows_render_nested_projects(Project.visible.active) do |p| - content_tag('label', check_box_tag('custom_workflow[project_ids][]', p.id, @workflow.project_ids.include?(p.id) || @workflow.is_for_all?, - :id => nil, :disabled => @workflow.is_for_all?) + ' ' + h(p), :class => 'block') - end %> - <%= hidden_field_tag('custom_workflow[project_ids][]', '', :id => nil) %> -

<%= check_all_links 'custom_workflow_enabled_projects' %>

-
-
+ <% if @workflow.has_projects_association? %> +
+
+ <%= l(:label_enabled_projects) %> + <%= custom_workflows_render_nested_projects(Project.visible.active) do |p| + content_tag('label', check_box_tag('custom_workflow[project_ids][]', p.id, @workflow.project_ids.include?(p.id) || @workflow.is_for_all?, + :id => nil, :disabled => @workflow.is_for_all?) + ' ' + h(p), :class => 'block') + end %> + <%= hidden_field_tag('custom_workflow[project_ids][]', '', :id => nil) %> +

<%= check_all_links 'custom_workflow_enabled_projects' %>

+
+
+ <% end %>
<%= l(:label_workflow_scripts) %> -
-
- <%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off' %> - <%= l(:text_custom_workflow_before_save_note) %> + <% case @workflow.observable %> + <% when 'shared' %> + <%= f.text_area :shared_code, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> + <% when 'group_users' %> +
+
+ <%= f.text_area :before_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> +
+
+ <%= f.text_area :after_add, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> +
-
- <%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off' %> - <%= l(:text_custom_workflow_after_save_note) %> +
+
+
+ <%= f.text_area :before_remove, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> +
+
+ <%= f.text_area :after_remove, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> +
-
-
+
+ <% when 'user', 'group' %> +
+
+ <%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> +
+
+ <%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> +
+
+
+ <% when 'issue' %> +
+
+ <%= f.text_area :before_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> + <%= l(:text_custom_workflow_before_save_note) %> +
+
+ <%= f.text_area :after_save, :cols => 40, :rows => 20, :wrap => 'off', :class => 'custom_workflow_script' %> + <%= l(:text_custom_workflow_after_save_note) %> +
+
+
+ <% end %>

- <%= l(:text_custom_workflow_general_note) %> + <%= l("text_custom_workflow_#{@workflow.observable}_code_note") %>