Project

General

Profile

Feature #5577 » category_watchers_patch-20120602-trunk_r9748.patch

Nayuta Taga, 2012-06-02 10:40

View differences:

test/functional/watchers_controller_test.rb (working copy)
152 152
        assert_select 'input[name=?][value=4]', 'issue[watcher_user_ids][]'
153 153
        assert_select 'input[name=?][value=7]', 'issue[watcher_user_ids][]'
154 154
      end
155
      assert response.body =~ /issue_new_setupDefaultWatchersEvents/
155 156
    end
156 157
  end
157 158

  
......
164 165
    end
165 166
    assert !Issue.find(2).watched_by?(User.find(3))
166 167
  end
168

  
169
  def test_show
170
    watchable_type = 'Issue'
171
    watchable_id = 2
172
    
173
    @request.session[:user_id] = 2
174
    xhr :get, :show, :watchable_type => watchable_type.underscore, :watchable_id => watchable_id.to_s, :format => 'json'
175
    assert_response :success
176
    r = ActiveSupport::JSON.decode(response.body)
177
    assert r
178
    assert_equal r.length, 2
179
    assert_equal watchable_type, r[0]['watcher']['watchable_type']
180
    assert_equal watchable_type, r[1]['watcher']['watchable_type']
181
    assert_equal watchable_id, r[0]['watcher']['watchable_id']
182
    assert_equal watchable_id, r[1]['watcher']['watchable_id']
183
    fixtures_user_ids = [ watchers(:watchers_001).user_id,
184
                          watchers(:watchers_002).user_id ].sort
185
    r_user_ids = [r[0]['watcher']['user_id'], r[1]['watcher']['user_id']].sort
186
    assert_equal fixtures_user_ids, r_user_ids
187
  end
167 188
end
test/functional/issue_categories_controller_test.rb (working copy)
38 38
    assert_response :success
39 39
    assert_template 'new'
40 40
    assert_select 'input[name=?]', 'issue_category[name]'
41
    assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '2'}
42
    assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '3'}
41 43
  end
42 44

  
43 45
  def test_create
......
51 53
    assert_equal 1, category.project_id
52 54
  end
53 55

  
56
  def test_create_with_watchers
57
    @request.session[:user_id] = 2 # manager
58
    assert_difference 'Watcher.count', 2 do
59
      post :create, :project_id => '1', :issue_category => {:name => 'New category with watchers', :watcher_user_ids => ['2', '3']}
60
    end
61
    assert_redirected_to '/projects/ecookbook/settings/categories'
62
    category = IssueCategory.find_by_name('New category with watchers')
63
    assert_not_nil category
64
    assert_equal 1, category.project_id
65
    # Watchers added
66
    assert_equal [2, 3], category.watcher_user_ids.sort
67
    assert category.watched_by?(User.find(3))
68
  end
69

  
54 70
  def test_create_failure
55 71
    @request.session[:user_id] = 2
56 72
    post :create, :project_id => '1', :issue_category => {:name => ''}
......
70 86
    assert_select_rjs :replace, 'issue_category_id' do
71 87
      assert_select "option[value=#{category.id}][selected=selected]"
72 88
    end
89
    assert response.body =~ /issue_new_setupDefaultWatchersEvents/
73 90
  end
74 91

  
75 92
  def test_create_from_issue_form_with_failure
......
90 107
    assert_response :success
91 108
    assert_template 'edit'
92 109
    assert_select 'input[name=?][value=?]', 'issue_category[name]', 'Recipes'
110
    assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '2'}
111
    assert_tag 'input', :attributes => {:name => 'issue_category[watcher_user_ids][]', :value => '3'}
93 112
  end
94 113

  
95 114
  def test_update
......
100 119
    assert_equal 'Testing', IssueCategory.find(2).name
101 120
  end
102 121

  
122
  def test_update_with_watchers
123
    assert_difference 'Watcher.count', 2 do
124
      put :update, :id => 2, :issue_category => { :name => 'Testing', :watcher_user_ids => ['2', '3']}
125
    end
126
    assert_redirected_to '/projects/ecookbook/settings/categories'
127
    category = IssueCategory.find(2)
128
    assert_equal 'Testing', category.name
129
    # Watchers added
130
    assert_equal [2, 3], category.watcher_user_ids.sort
131
    assert category.watched_by?(User.find(3))
132
  end
133

  
103 134
  def test_update_failure
104 135
    put :update, :id => 2, :issue_category => { :name => '' }
105 136
    assert_response :success
test/integration/routing/watchers_test.rb (working copy)
47 47
        { :method => 'post', :path => "/watchers/unwatch" },
48 48
        { :controller => 'watchers', :action => 'unwatch' }
49 49
      )
50
    assert_routing(
51
        { :method => 'post', :path => "/watchers/show/issue_category/1.json" },
52
        { :controller => 'watchers', :action => 'show', :watchable_type => 'issue_category', :watchable_id => '1', :format => 'json' }
53
      )
50 54
  end
51 55
end
app/helpers/issue_categories_helper.rb (working copy)
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
module IssueCategoriesHelper
21

  
22
  def url_for_issue_category_watchers_json(watchable_id)
23
    url_for :controller => 'watchers', :action => 'show', :watchable_type => 'issue_category', :watchable_id => watchable_id, :format => 'json'
24
  end
25

  
21 26
end
app/helpers/watchers_helper.rb (working copy)
68 68
    content.present? ? content_tag('ul', content) : content
69 69
  end
70 70

  
71
  def watchers_checkboxes(object, users, checked=nil)
71
  def watchers_checkboxes(object, object_type, users, checked=nil)
72
    name = (object_type ? object_type.name.underscore : 'issue')
73
    
72 74
    users.map do |user|
73 75
      c = checked.nil? ? object.watched_by?(user) : checked
74
      tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
76
      tag = check_box_tag "#{name}[watcher_user_ids][]", user.id, c, :id => nil
75 77
      content_tag 'label', "#{tag} #{h(user)}".html_safe,
76
                  :id => "issue_watcher_user_ids_#{user.id}",
78
                  :id => "#{name}_watcher_user_ids_#{user.id}",
77 79
                  :class => "floating"
78 80
    end.join.html_safe
79 81
  end
app/models/issue_category.rb (working copy)
21 21
  belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
22 22
  has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
23 23

  
24
  acts_as_watchable
25

  
24 26
  validates_presence_of :name
25 27
  validates_uniqueness_of :name, :scope => [:project_id]
26 28
  validates_length_of :name, :maximum => 30
app/controllers/watchers_controller.rb (working copy)
16 16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 17

  
18 18
class WatchersController < ApplicationController
19
  before_filter :find_project
19
  helper :issue_categories
20
  before_filter :find_project, :except => [:show]
20 21
  before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
21 22
  before_filter :authorize, :only => [:new, :destroy]
22 23

  
......
32 33
    set_watcher(User.current, false)
33 34
  end
34 35

  
36
  def show
37
    respond_to do |format|
38
      format.api do
39
        result =  Watcher.find(:all, :conditions => [ "watchable_type = ? and watchable_id = ?", params['watchable_type'].camelcase, params['watchable_id'].to_i])
40
        render :json => result
41
      end
42
    end
43
  end
44

  
35 45
  def new
36 46
    respond_to do |format|
37 47
      format.js do
38 48
        render :update do |page|
39
          page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
49
          page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched, :watched_type => @watched_type}
40 50
          page << "showModal('ajax-modal', '400px');"
41 51
          page << "$('ajax-modal').addClassName('new-watcher');"
42 52
        end
......
55 65
      format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
56 66
      format.js do
57 67
        render :update do |page|
58
          page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
68
          page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched, :watched_type => @watched_type}
59 69
          page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
60 70
        end
61 71
      end
......
68 78
      users = User.active.find_all_by_id(user_ids)
69 79
      respond_to do |format|
70 80
        format.js do
81
          name = (@watched_type ? @watched_type.name.underscore : 'issue')
71 82
          render :update do |page|
72 83
            users.each do |user|
73
              page << %|$$("#issue_watcher_user_ids_#{user.id}").each(function(el){el.remove();});|
84
              page << %|$$("##{name}_watcher_user_ids_#{user.id}").each(function(el){el.remove();});|
85
            end
86
            page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, @watched_type, users, true)
87
            if name == 'issue'
88
              page << %|issue_new_setupDefaultWatchersEvents('#{j(url_for_issue_category_watchers_json('WATCHABLE_ID'))}')|
74 89
            end
75
            page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true)
76 90
          end
77 91
        end
78 92
      end
......
109 123
    elsif params[:project_id]
110 124
      @project = Project.visible.find_by_param(params[:project_id])
111 125
    end
126
    if !@watched && params[:object_type]
127
      klass = Object.const_get(params[:object_type].camelcase)
128
      @watched_type = klass if klass.respond_to?('watched_by')
129
    end
112 130
  rescue
113 131
    render_404
114 132
  end
app/controllers/issue_categories_controller.rb (working copy)
41 41
  def new
42 42
    @category = @project.issue_categories.build
43 43
    @category.safe_attributes = params[:issue_category]
44
    assign_watchers_from_params
44 45

  
45 46
    respond_to do |format|
46 47
      format.html
......
57 58
  def create
58 59
    @category = @project.issue_categories.build
59 60
    @category.safe_attributes = params[:issue_category]
61
    assign_watchers_from_params
60 62
    if @category.save
61 63
      respond_to do |format|
62 64
        format.html do
......
69 71
            # IE doesn't support the replace_html rjs method for select box options
70 72
            page.replace "issue_category_id",
71 73
              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]')
74
            page << %|issue_new_setupDefaultWatchersEvents('#{j(url_for_issue_category_watchers_json('WATCHABLE_ID'))}')|
72 75
          }
73 76
        end
74 77
        format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
......
92 95

  
93 96
  def update
94 97
    @category.safe_attributes = params[:issue_category]
98
    assign_watchers_from_params
95 99
    if @category.save
96 100
      respond_to do |format|
97 101
        format.html {
......
132 136
    super
133 137
    @category = @object
134 138
  end
139

  
140
  def assign_watchers_from_params
141
    if params[:issue_category].is_a?(Hash)
142
      @category.watcher_user_ids = params[:issue_category]['watcher_user_ids']
143
    end
144
  end
135 145
end
app/views/watchers/_new.html.erb (working copy)
2 2

  
3 3
<%= form_remote_tag :url => {:controller => 'watchers',
4 4
                            :action => (watched ? 'create' : 'append'),
5
                            :object_type => watched.class.name.underscore,
5
                            :object_type => (watched_type ? watched_type.name.underscore : watched.class.name.underscore),
6 6
                            :object_id => watched},
7 7
                     :method => :post,
8 8
                     :html => {:id => 'new-watcher-form'} do %>
app/views/issue_categories/edit.html.erb (working copy)
2 2

  
3 3
<%= labelled_form_for @category, :as => :issue_category,
4 4
                     :url => issue_category_path(@category), :html => {:method => :put} do |f| %>
5
<%= render :partial => 'issue_categories/form', :locals => { :f => f } %>
5
<%= render :partial => 'issue_categories/form', :locals => { :f => f, :is_modal => false } %>
6 6
<%= submit_tag l(:button_save) %>
7 7
<% end %>
app/views/issue_categories/new.html.erb (working copy)
2 2

  
3 3
<%= labelled_form_for @category, :as => :issue_category,
4 4
                      :url => project_issue_categories_path(@project) do |f| %>
5
<%= render :partial => 'issue_categories/form', :locals => { :f => f } %>
5
<%= render :partial => 'issue_categories/form', :locals => { :f => f, :is_modal => false } %>
6 6
<%= submit_tag l(:button_create) %>
7 7
<% end %>
app/views/issue_categories/_form.html.erb (working copy)
3 3
<div class="box tabular">
4 4
<p><%= f.text_field :name, :size => 30, :required => true %></p>
5 5
<p><%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %></p>
6
<p id="watchers_form"><label><%= l(:label_issue_category_watchers) %></label>
7
<span id="watchers_inputs">
8
  <%= watchers_checkboxes(@category, IssueCategory, @project.users) %>
9
</span>
10
<span class="search_for_watchers">
11
<% if !is_modal %>
12
<%= link_to_remote l(:label_search_for_watchers),
13
             :url => {:controller => 'watchers', :action => 'new', :project_id => @project, :object_type => 'issue_category'},
14
             :method => 'get' %>
15
<% end %>
16
</span>
17
</p>
6 18
</div>
app/views/issue_categories/_new_modal.html.erb (working copy)
1 1
<h3 class="title"><%=l(:label_issue_category_new)%></h3>
2 2

  
3 3
<%= labelled_remote_form_for @category, :as => 'issue_category', :url => project_issue_categories_path(@project) do |f| %>
4
<%= render :partial => 'issue_categories/form', :locals => { :f => f } %>
4
<%= render :partial => 'issue_categories/form', :locals => { :f => f, :is_modal => true } %>
5 5
  <p class="buttons">
6 6
    <%= submit_tag l(:button_create), :name => nil %>
7 7
    <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
app/views/issues/new.html.erb (working copy)
23 23
    <% if @issue.safe_attribute? 'watcher_user_ids' -%>
24 24
      <p id="watchers_form"><label><%= l(:label_issue_watchers) %></label>
25 25
      <span id="watchers_inputs">
26
        <%= watchers_checkboxes(@issue, @available_watchers) %>
26
        <%= watchers_checkboxes(@issue, Issue, @available_watchers) %>
27 27
      </span>
28 28
      <span class="search_for_watchers">
29 29
      <%= link_to_remote l(:label_search_for_watchers),
30
                   :url => {:controller => 'watchers', :action => 'new', :project_id => @issue.project},
30
                   :url => {:controller => 'watchers', :action => 'new', :project_id => @issue.project, :object_type => 'issue'},
31 31
                   :method => 'get' %>
32 32
      </span>
33
      <%= javascript_tag "issue_new_setupDefaultWatchersEvents('#{j(url_for_issue_category_watchers_json('WATCHABLE_ID'))}')" %>
33 34
      </p>
34 35
    <% end %>
35 36
  </div>
config/locales/en.yml (working copy)
502 502
  label_issue_category: Issue category
503 503
  label_issue_category_plural: Issue categories
504 504
  label_issue_category_new: New category
505
  label_issue_category_watchers: Default watchers
505 506
  label_custom_field: Custom field
506 507
  label_custom_field_plural: Custom fields
507 508
  label_custom_field_new: New custom field
config/locales/ja.yml (working copy)
510 510
  label_issue_category: チケットのカテゴリ
511 511
  label_issue_category_plural: チケットのカテゴリ
512 512
  label_issue_category_new: 新しいカテゴリ
513
  label_issue_category_watchers: デフォルトウォッチャー
513 514
  label_custom_field: カスタムフィールド
514 515
  label_custom_field_plural: カスタムフィールド
515 516
  label_custom_field_new: 新しいカスタムフィールドを作成
config/routes.rb (working copy)
84 84
  match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post
85 85
  match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post
86 86
  match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get
87
  match 'watchers/show/:watchable_type/:watchable_id.:format', :controller => 'watchers', :action => 'show', :via => :post
87 88

  
88 89
  match 'projects/:id/settings/:tab', :to => "projects#settings"
89 90

  
public/javascripts/application.js (working copy)
534 534
  });
535 535
}
536 536

  
537
function issue_new_resetDefaultWatchersByCategory() {
538
  $$('#watchers_inputs input').each(function(el){
539
    el.checked = false;
540
    el.parentNode.removeClassName('watchers_default');
541
  });
542
}
543

  
544
function issue_new_setDefaultWatchersByCategory(url_for_issue_category_watchers_json) {
545
  issue_category_id = $('issue_category_id');
546
  if (!issue_category_id) {
547
    return;
548
  }
549
  if (0 < issue_category_id.value.length) {
550
    url = url_for_issue_category_watchers_json.replace(/WATCHABLE_ID/, issue_category_id.value);
551
    new Ajax.Request(url, {
552
      onSuccess: function (request) {
553
        eval("result="+ request.responseText);
554
        issue_new_resetDefaultWatchersByCategory();
555
        $$('#watchers_inputs input').each(function(el){
556
          user_id = parseInt(el.value);
557
          result.each(function(a){
558
            if (user_id === a.watcher.user_id) {
559
              el.checked = true;
560
              el.parentNode.addClassName('watchers_default');
561
              throw $break;
562
            }
563
          });
564
        });
565
      }
566
    });
567
  } else {
568
    issue_new_resetDefaultWatchersByCategory();
569
  }
570
}
571

  
572
function issue_new_setupDefaultWatchersEvents(url_for_issue_category_watchers_json) {
573
  issue_category_id = $('issue_category_id');
574
  if (!issue_category_id) {
575
    return;
576
  }
577
  function f() {
578
    issue_new_setDefaultWatchersByCategory(
579
      url_for_issue_category_watchers_json);
580
  }
581
  Event.observe(issue_category_id, 'change', f);
582
  f();
583
}
584

  
537 585
Event.observe(window, 'load', hideOnLoad);
538 586
Event.observe(window, 'load', addFormObserversForDoubleSubmit);
public/stylesheets/application.css (working copy)
266 266
#watchers img.gravatar {margin: 0 4px 2px 0;}
267 267

  
268 268
span#watchers_inputs {overflow:auto; display:block;}
269
span#watchers_inputs .watchers_default {color: #080; font-weight:bold;}
269 270
span.search_for_watchers {display:block;}
270 271
span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
271 272
span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
(5-5/6)