Index: test/functional/watchers_controller_test.rb =================================================================== --- test/functional/watchers_controller_test.rb (revision 9748) +++ test/functional/watchers_controller_test.rb (working copy) @@ -127,6 +127,7 @@ assert_select 'input[name=?][value=4]', 'issue[watcher_user_ids][]' assert_select 'input[name=?][value=7]', 'issue[watcher_user_ids][]' end + assert @response.body =~ /issue_new_setupDefaultWatchersEvents/ end end @@ -139,4 +140,24 @@ end assert !Issue.find(2).watched_by?(User.find(3)) end + + def test_show + watchable_type = 'Issue' + watchable_id = 2 + + @request.session[:user_id] = 2 + xhr :get, :show, :watchable_type => watchable_type.underscore, :watchable_id => watchable_id.to_s, :format => 'json' + assert_response :success + r = ActiveSupport::JSON.decode(@response.body) + assert r + assert_equal r.length, 2 + assert_equal watchable_type, r[0]['watchable_type'] + assert_equal watchable_type, r[1]['watchable_type'] + assert_equal watchable_id, r[0]['watchable_id'] + assert_equal watchable_id, r[1]['watchable_id'] + fixtures_user_ids = [ watchers(:watchers_001).user_id, + watchers(:watchers_002).user_id ].sort + r_user_ids = [r[0]['user_id'], r[1]['user_id']].sort + assert_equal fixtures_user_ids, r_user_ids + end end Index: test/functional/issue_categories_controller_test.rb =================================================================== --- test/functional/issue_categories_controller_test.rb (revision 9748) +++ test/functional/issue_categories_controller_test.rb (working copy) @@ -37,6 +37,8 @@ get :new, :project_id => '1' assert_response :success assert_template 'new' + assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '2'} + assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '3'} end def test_create @@ -50,6 +52,20 @@ assert_equal 1, category.project_id end + def test_create_with_watchers + @request.session[:user_id] = 2 # manager + assert_difference 'Watcher.count', 2 do + post :create, :project_id => '1', :issue_category => {:name => 'New category with watchers', :watcher_user_ids => ['2', '3']} + end + assert_redirected_to '/projects/ecookbook/settings/categories' + category = IssueCategory.find_by_name('New category with watchers') + assert_not_nil category + assert_equal 1, category.project_id + # Watchers added + assert_equal [2, 3], category.watcher_user_ids.sort + assert category.watched_by?(User.find(3)) + end + def test_create_failure @request.session[:user_id] = 2 post :create, :project_id => '1', :issue_category => {:name => ''} @@ -69,6 +85,7 @@ assert_select_rjs :replace, 'issue_category_id' do assert_select "option[value=#{category.id}][selected=selected]" end + assert @response.body =~ /issue_new_setupDefaultWatchersEvents/ end def test_create_from_issue_form_with_failure @@ -86,6 +103,8 @@ get :edit, :id => 2 assert_response :success assert_template 'edit' + assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '2'} + assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '3'} end def test_update @@ -96,6 +115,18 @@ assert_equal 'Testing', IssueCategory.find(2).name end + def test_update_with_watchers + assert_difference 'Watcher.count', 2 do + put :update, :id => 2, :issue_category => { :name => 'Testing', :watcher_user_ids => ['2', '3']} + end + assert_redirected_to '/projects/ecookbook/settings/categories' + category = IssueCategory.find(2) + assert_equal 'Testing', category.name + # Watchers added + assert_equal [2, 3], category.watcher_user_ids.sort + assert category.watched_by?(User.find(3)) + end + def test_update_failure put :update, :id => 2, :issue_category => { :name => '' } assert_response :success Index: test/integration/routing/watchers_test.rb =================================================================== --- test/integration/routing/watchers_test.rb (revision 9748) +++ test/integration/routing/watchers_test.rb (working copy) @@ -47,5 +47,9 @@ { :method => 'post', :path => "/watchers/unwatch" }, { :controller => 'watchers', :action => 'unwatch' } ) + assert_routing( + { :method => 'post', :path => "/watchers/show/issue_category/1.json" }, + { :controller => 'watchers', :action => 'show', :watchable_type => 'issue_category', :watchable_id => '1', :format => 'json' } + ) end end Index: app/helpers/issue_categories_helper.rb =================================================================== --- app/helpers/issue_categories_helper.rb (revision 9748) +++ app/helpers/issue_categories_helper.rb (working copy) @@ -18,4 +18,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module IssueCategoriesHelper + + def url_for_issue_category_watchers_json(watchable_id) + url_for :controller => 'watchers', :action => 'show', :watchable_type => 'issue_category', :watchable_id => watchable_id, :format => 'json' + end + end Index: app/helpers/watchers_helper.rb =================================================================== --- app/helpers/watchers_helper.rb (revision 9748) +++ app/helpers/watchers_helper.rb (working copy) @@ -64,11 +64,13 @@ (lis.empty? ? "" : "").html_safe end - def watchers_checkboxes(object, users, checked=nil) + def watchers_checkboxes(object, object_type, users, checked=nil) + name = (object_type ? object_type.name.underscore : 'issue') + users.map do |user| c = checked.nil? ? object.watched_by?(user) : checked - tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil - content_tag 'label', "#{tag} #{h(user)}", :id => "issue_watcher_user_ids_#{user.id}", :class => "floating" + tag = check_box_tag "#{name}[watcher_user_ids][]", user.id, c, :id => nil + content_tag 'label', "#{tag} #{h(user)}", :id => "#{name}_watcher_user_ids_#{user.id}", :class => "floating" end.join end end Index: app/models/issue_category.rb =================================================================== --- app/models/issue_category.rb (revision 9748) +++ app/models/issue_category.rb (working copy) @@ -21,6 +21,8 @@ belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' has_many :issues, :foreign_key => 'category_id', :dependent => :nullify + acts_as_watchable + validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id] validates_length_of :name, :maximum => 30 Index: app/controllers/issues_controller.rb =================================================================== --- app/controllers/issues_controller.rb (revision 9748) +++ app/controllers/issues_controller.rb (working copy) @@ -52,6 +52,7 @@ helper :timelog helper :gantt include Redmine::Export::PDF + helper :issue_categories def index retrieve_query Index: app/controllers/watchers_controller.rb =================================================================== --- app/controllers/watchers_controller.rb (revision 9748) +++ app/controllers/watchers_controller.rb (working copy) @@ -16,7 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WatchersController < ApplicationController - before_filter :find_project + helper :issue_categories + before_filter :find_project, :except => [:show] before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] before_filter :authorize, :only => [:new, :destroy] @@ -32,11 +33,20 @@ set_watcher(User.current, false) end + def show + respond_to do |format| + format.api do + result = Watcher.find(:all, :conditions => [ "watchable_type = ? and watchable_id = ?", params['watchable_type'].camelcase, params['watchable_id'].to_i]) + render :json => result + end + end + end + def new respond_to do |format| format.js do render :update do |page| - page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched} + page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched, :watched_type => @watched_type} page << "showModal('ajax-modal', '400px');" page << "$('ajax-modal').addClassName('new-watcher');" end @@ -55,7 +65,7 @@ format.html { redirect_to :back } format.js do render :update do |page| - page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched} + page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched, :watched_type => @watched_type} page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} end end @@ -70,13 +80,17 @@ users = User.active.find_all_by_id(user_ids) respond_to do |format| format.js do + name = (@watched_type ? @watched_type.name.underscore : 'issue') render :update do |page| users.each do |user| - page.select("#issue_watcher_user_ids_#{user.id}").each do |item| + page.select("##{name}_watcher_user_ids_#{user.id}").each do |item| page.remove item end end - page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true) + page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, @watched_type, users, true) + if name == 'issue' + page << %|issue_new_setupDefaultWatchersEvents('#{j(url_for_issue_category_watchers_json('WATCHABLE_ID'))}')| + end end end end @@ -113,6 +127,10 @@ elsif params[:project_id] @project = Project.visible.find(params[:project_id]) end + if !@watched && params[:object_type] + klass = Object.const_get(params[:object_type].camelcase) + @watched_type = klass if klass.respond_to?('watched_by') + end rescue render_404 end Index: app/controllers/issue_categories_controller.rb =================================================================== --- app/controllers/issue_categories_controller.rb (revision 9748) +++ app/controllers/issue_categories_controller.rb (working copy) @@ -23,6 +23,7 @@ before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :authorize accept_api_auth :index, :show, :create, :update, :destroy + helper :watchers def index respond_to do |format| @@ -41,11 +42,13 @@ def new @category = @project.issue_categories.build @category.safe_attributes = params[:issue_category] + assign_watchers_from_params end def create @category = @project.issue_categories.build @category.safe_attributes = params[:issue_category] + assign_watchers_from_params if @category.save respond_to do |format| format.html do @@ -56,6 +59,7 @@ # IE doesn't support the replace_html rjs method for select box options render(:update) {|page| page.replace "issue_category_id", content_tag('select', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]') + page << %|issue_new_setupDefaultWatchersEvents('#{j(url_for_issue_category_watchers_json('WATCHABLE_ID'))}')| } end format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) } @@ -76,6 +80,7 @@ def update @category.safe_attributes = params[:issue_category] + assign_watchers_from_params if @category.save respond_to do |format| format.html { @@ -116,4 +121,10 @@ super @category = @object end + + def assign_watchers_from_params + if params[:issue_category].is_a?(Hash) + @category.watcher_user_ids = params[:issue_category]['watcher_user_ids'] + end + end end Index: app/views/watchers/_new.html.erb =================================================================== --- app/views/watchers/_new.html.erb (revision 9748) +++ app/views/watchers/_new.html.erb (working copy) @@ -2,7 +2,7 @@ <% form_remote_tag :url => {:controller => 'watchers', :action => (watched ? 'create' : 'append'), - :object_type => watched.class.name.underscore, + :object_type => (watched_type ? watched_type.name.underscore : watched.class.name.underscore), :object_id => watched}, :method => :post, :html => {:id => 'new-watcher-form'} do %> Index: app/views/issue_categories/_form.html.erb =================================================================== --- app/views/issue_categories/_form.html.erb (revision 9748) +++ app/views/issue_categories/_form.html.erb (working copy) @@ -3,4 +3,14 @@

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

<%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %>

+

+ + <%= watchers_checkboxes(@category, IssueCategory, @project.users) %> + + +<%= link_to_remote l(:label_search_for_watchers), + :url => {:controller => 'watchers', :action => 'new', :project_id => @project, :object_type => 'issue_category'}, + :method => 'get' %> + +

Index: app/views/issues/new.html.erb =================================================================== --- app/views/issues/new.html.erb (revision 9748) +++ app/views/issues/new.html.erb (working copy) @@ -23,13 +23,14 @@ <% if @issue.safe_attribute? 'watcher_user_ids' -%>

- <%= watchers_checkboxes(@issue, @available_watchers) %> + <%= watchers_checkboxes(@issue, Issue, @available_watchers) %> <%= link_to_remote l(:label_search_for_watchers), - :url => {:controller => 'watchers', :action => 'new', :project_id => @issue.project}, + :url => {:controller => 'watchers', :action => 'new', :project_id => @issue.project, :object_type => 'issue'}, :method => 'get' %> + <%= javascript_tag "issue_new_setupDefaultWatchersEvents('#{j(url_for_issue_category_watchers_json('WATCHABLE_ID'))}')" %>

<% end %> Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (revision 9748) +++ config/locales/en.yml (working copy) @@ -502,6 +502,7 @@ label_issue_category: Issue category label_issue_category_plural: Issue categories label_issue_category_new: New category + label_issue_category_watchers: Default watchers label_custom_field: Custom field label_custom_field_plural: Custom fields label_custom_field_new: New custom field Index: config/locales/ja.yml =================================================================== --- config/locales/ja.yml (revision 9748) +++ config/locales/ja.yml (working copy) @@ -510,6 +510,7 @@ label_issue_category: チケットのカテゴリ label_issue_category_plural: チケットのカテゴリ label_issue_category_new: 新しいカテゴリ + label_issue_category_watchers: デフォルトウォッチャー label_custom_field: カスタムフィールド label_custom_field_plural: カスタムフィールド label_custom_field_new: 新しいカスタムフィールドを作成 Index: config/routes.rb =================================================================== --- config/routes.rb (revision 9748) +++ config/routes.rb (working copy) @@ -136,6 +136,8 @@ :conditions => {:method => :post} map.connect 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :conditions => {:method => :get} + map.connect 'watchers/show/:watchable_type/:watchable_id.:format', :controller => 'watchers', :action => 'show', + :conditions => {:method => :post} # TODO: port to be part of the resources route(s) map.with_options :conditions => {:method => :get} do |project_views| Index: public/javascripts/application.js =================================================================== --- public/javascripts/application.js (revision 9748) +++ public/javascripts/application.js (working copy) @@ -534,5 +534,53 @@ }); } +function issue_new_resetDefaultWatchersByCategory() { + $$('#watchers_inputs input').each(function(el){ + el.checked = false; + el.parentNode.removeClassName('watchers_default'); + }); +} + +function issue_new_setDefaultWatchersByCategory(url_for_issue_category_watchers_json) { + issue_category_id = $('issue_category_id'); + if (!issue_category_id) { + return; + } + if (0 < issue_category_id.value.length) { + url = url_for_issue_category_watchers_json.replace(/WATCHABLE_ID/, issue_category_id.value); + new Ajax.Request(url, { + onSuccess: function (request) { + eval("result="+ request.responseText); + issue_new_resetDefaultWatchersByCategory(); + $$('#watchers_inputs input').each(function(el){ + user_id = parseInt(el.value); + result.each(function(a){ + if (user_id === a.user_id) { + el.checked = true; + el.parentNode.addClassName('watchers_default'); + throw $break; + } + }); + }); + } + }); + } else { + issue_new_resetDefaultWatchersByCategory(); + } +} + +function issue_new_setupDefaultWatchersEvents(url_for_issue_category_watchers_json) { + issue_category_id = $('issue_category_id'); + if (!issue_category_id) { + return; + } + function f() { + issue_new_setDefaultWatchersByCategory( + url_for_issue_category_watchers_json); + } + Event.observe(issue_category_id, 'change', f); + f(); +} + Event.observe(window, 'load', hideOnLoad); Event.observe(window, 'load', addFormObserversForDoubleSubmit); Index: public/stylesheets/application.css =================================================================== --- public/stylesheets/application.css (revision 9748) +++ public/stylesheets/application.css (working copy) @@ -266,6 +266,7 @@ #watchers img.gravatar {margin: 0 4px 2px 0;} span#watchers_inputs {overflow:auto; display:block;} +span#watchers_inputs .watchers_default {color: #080; font-weight:bold;} span.search_for_watchers {display:block;} span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;} span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }