##// END OF EJS Templates
Bulk watch/unwatch issues from the context menu (#7159)....
Jean-Philippe Lang -
r11109:856ef810b485
parent child
Show More
@@ -0,0 +1,69
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../test_helper', __FILE__)
19
20 class WatchersHelperTest < ActionView::TestCase
21 include WatchersHelper
22 include Redmine::I18n
23
24 fixtures :users, :issues
25
26 def setup
27 super
28 set_language_if_valid('en')
29 User.current = nil
30 end
31
32 test '#watcher_link with a non-watched object' do
33 expected = link_to(
34 "Watch",
35 "/watchers/watch?object_id=1&object_type=issue",
36 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
37 )
38 assert_equal expected, watcher_link(Issue.find(1), User.find(1))
39 end
40
41 test '#watcher_link with a single objet array' do
42 expected = link_to(
43 "Watch",
44 "/watchers/watch?object_id=1&object_type=issue",
45 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
46 )
47 assert_equal expected, watcher_link([Issue.find(1)], User.find(1))
48 end
49
50 test '#watcher_link with a multiple objets array' do
51 expected = link_to(
52 "Watch",
53 "/watchers/watch?object_id%5B%5D=1&object_id%5B%5D=3&object_type=issue",
54 :remote => true, :method => 'post', :class => "issue-bulk-watcher icon icon-fav-off"
55 )
56 assert_equal expected, watcher_link([Issue.find(1), Issue.find(3)], User.find(1))
57 end
58
59 test '#watcher_link with a watched object' do
60 Watcher.create!(:watchable => Issue.find(1), :user => User.find(1))
61
62 expected = link_to(
63 "Unwatch",
64 "/watchers/unwatch?object_id=1&object_type=issue",
65 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav"
66 )
67 assert_equal expected, watcher_link(Issue.find(1), User.find(1))
68 end
69 end
@@ -16,23 +16,19
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
20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
21 before_filter :authorize, :only => [:new, :destroy]
22 accept_api_auth :create, :destroy
19 before_filter :require_login, :find_watchables, :only => [:watch, :unwatch]
23 20
24 21 def watch
25 if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
26 render_403
27 else
28 set_watcher(User.current, true)
29 end
22 set_watcher(@watchables, User.current, true)
30 23 end
31 24
32 25 def unwatch
33 set_watcher(User.current, false)
26 set_watcher(@watchables, User.current, false)
34 27 end
35 28
29 before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
30 accept_api_auth :create, :destroy
31
36 32 def new
37 33 end
38 34
@@ -77,7 +73,8 class WatchersController < ApplicationController
77 73 render :layout => false
78 74 end
79 75
80 private
76 private
77
81 78 def find_project
82 79 if params[:object_type] && params[:object_id]
83 80 klass = Object.const_get(params[:object_type].camelcase)
@@ -91,11 +88,22 private
91 88 render_404
92 89 end
93 90
94 def set_watcher(user, watching)
95 @watched.set_watcher(user, watching)
91 def find_watchables
92 klass = Object.const_get(params[:object_type].camelcase) rescue nil
93 if klass && klass.respond_to?('watched_by')
94 @watchables = klass.find_all_by_id(Array.wrap(params[:object_id]))
95 raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?}
96 end
97 render_404 unless @watchables.present?
98 end
99
100 def set_watcher(watchables, user, watching)
101 watchables.each do |watchable|
102 watchable.set_watcher(user, watching)
103 end
96 104 respond_to do |format|
97 105 format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
98 format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} }
106 format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} }
99 107 end
100 108 end
101 109 end
@@ -24,21 +24,28 module WatchersHelper
24 24 watcher_link(object, user)
25 25 end
26 26
27 def watcher_link(object, user)
28 return '' unless user && user.logged? && object.respond_to?('watched_by?')
29 watched = object.watched_by?(user)
30 css = [watcher_css(object), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
31 url = {:controller => 'watchers',
32 :action => (watched ? 'unwatch' : 'watch'),
33 :object_type => object.class.to_s.underscore,
34 :object_id => object.id}
35 link_to((watched ? l(:button_unwatch) : l(:button_watch)), url,
36 :remote => true, :method => 'post', :class => css)
27 def watcher_link(objects, user)
28 return '' unless user && user.logged?
29 objects = Array.wrap(objects)
30
31 watched = objects.any? {|object| object.watched_by?(user)}
32 css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
33 text = watched ? l(:button_unwatch) : l(:button_watch)
34 url = {
35 :controller => 'watchers',
36 :action => (watched ? 'unwatch' : 'watch'),
37 :object_type => objects.first.class.to_s.underscore,
38 :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort)
39 }
40
41 link_to text, url, :remote => true, :method => 'post', :class => css
37 42 end
38 43
39 44 # Returns the css class used to identify watch links for a given +object+
40 def watcher_css(object)
41 "#{object.class.to_s.underscore}-#{object.id}-watcher"
45 def watcher_css(objects)
46 objects = Array.wrap(objects)
47 id = (objects.size == 1 ? objects.first.id : 'bulk')
48 "#{objects.first.class.to_s.underscore}-#{id}-watcher"
42 49 end
43 50
44 51 # Returns a comma separated list of users watching the given object
@@ -117,10 +117,11
117 117 </li>
118 118 <% end %>
119 119
120 <% if User.current.logged? %>
121 <li><%= watcher_link(@issues, User.current) %></li>
122 <% end %>
123
120 124 <% if @issue.present? %>
121 <% if User.current.logged? %>
122 <li><%= watcher_link(@issue, User.current) %></li>
123 <% end %>
124 125 <% if @can[:log_time] -%>
125 126 <li><%= context_menu_link l(:button_log_time), new_issue_time_entry_path(@issue),
126 127 :class => 'icon-time-add' %></li>
@@ -2,8 +2,9
2 2
3 3 <%= form_tag({:controller => 'watchers',
4 4 :action => (watched ? 'create' : 'append'),
5 :object_type => watched.class.name.underscore,
6 :object_id => watched},
5 :object_type => (watched && watched.class.name.underscore),
6 :object_id => watched,
7 :project_id => @project},
7 8 :remote => true,
8 9 :method => :post,
9 10 :id => 'new-watcher-form') do %>
@@ -11,8 +12,9
11 12 <p><%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p>
12 13 <%= javascript_tag "observeSearchfield('user_search', 'users_for_watcher', '#{ escape_javascript url_for(:controller => 'watchers',
13 14 :action => 'autocomplete_for_user',
14 :object_type => watched.class.name.underscore,
15 :object_id => watched) }')" %>
15 :object_type => (watched && watched.class.name.underscore),
16 :object_id => watched,
17 :project_id => @project) }')" %>
16 18
17 19 <div id="users_for_watcher">
18 20 <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %>
@@ -127,7 +127,7 Redmine::AccessControl.map do |map|
127 127 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
128 128 # Watchers
129 129 map.permission :view_issue_watchers, {}, :read => true
130 map.permission :add_issue_watchers, {:watchers => :new}
130 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
131 131 map.permission :delete_issue_watchers, {:watchers => :destroy}
132 132 end
133 133
@@ -25,7 +25,7 class WatchersControllerTest < ActionController::TestCase
25 25 User.current = nil
26 26 end
27 27
28 def test_watch
28 def test_watch_a_single_object
29 29 @request.session[:user_id] = 3
30 30 assert_difference('Watcher.count') do
31 31 xhr :post, :watch, :object_type => 'issue', :object_id => '1'
@@ -35,6 +35,27 class WatchersControllerTest < ActionController::TestCase
35 35 assert Issue.find(1).watched_by?(User.find(3))
36 36 end
37 37
38 def test_watch_a_collection_with_a_single_object
39 @request.session[:user_id] = 3
40 assert_difference('Watcher.count') do
41 xhr :post, :watch, :object_type => 'issue', :object_id => ['1']
42 assert_response :success
43 assert_include '$(".issue-1-watcher")', response.body
44 end
45 assert Issue.find(1).watched_by?(User.find(3))
46 end
47
48 def test_watch_a_collection_with_multiple_objects
49 @request.session[:user_id] = 3
50 assert_difference('Watcher.count', 2) do
51 xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3']
52 assert_response :success
53 assert_include '$(".issue-bulk-watcher")', response.body
54 end
55 assert Issue.find(1).watched_by?(User.find(3))
56 assert Issue.find(3).watched_by?(User.find(3))
57 end
58
38 59 def test_watch_should_be_denied_without_permission
39 60 Role.find(2).remove_permission! :view_issues
40 61 @request.session[:user_id] = 3
@@ -70,6 +91,20 class WatchersControllerTest < ActionController::TestCase
70 91 assert !Issue.find(1).watched_by?(User.find(3))
71 92 end
72 93
94 def test_unwatch_a_collection_with_multiple_objects
95 @request.session[:user_id] = 3
96 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
97 Watcher.create!(:user_id => 3, :watchable => Issue.find(3))
98
99 assert_difference('Watcher.count', -2) do
100 xhr :post, :unwatch, :object_type => 'issue', :object_id => ['1', '3']
101 assert_response :success
102 assert_include '$(".issue-bulk-watcher")', response.body
103 end
104 assert !Issue.find(1).watched_by?(User.find(3))
105 assert !Issue.find(3).watched_by?(User.find(3))
106 end
107
73 108 def test_new
74 109 @request.session[:user_id] = 2
75 110 xhr :get, :new, :object_type => 'issue', :object_id => '2'
@@ -77,7 +112,7 class WatchersControllerTest < ActionController::TestCase
77 112 assert_match /ajax-modal/, response.body
78 113 end
79 114
80 def test_new_for_new_record_with_id
115 def test_new_for_new_record_with_project_id
81 116 @request.session[:user_id] = 2
82 117 xhr :get, :new, :project_id => 1
83 118 assert_response :success
@@ -85,7 +120,7 class WatchersControllerTest < ActionController::TestCase
85 120 assert_match /ajax-modal/, response.body
86 121 end
87 122
88 def test_new_for_new_record_with_identifier
123 def test_new_for_new_record_with_project_identifier
89 124 @request.session[:user_id] = 2
90 125 xhr :get, :new, :project_id => 'ecookbook'
91 126 assert_response :success
@@ -117,7 +152,8 class WatchersControllerTest < ActionController::TestCase
117 152 end
118 153
119 154 def test_autocomplete_on_watchable_creation
120 xhr :get, :autocomplete_for_user, :q => 'mi'
155 @request.session[:user_id] = 2
156 xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook'
121 157 assert_response :success
122 158 assert_select 'input', :count => 4
123 159 assert_select 'input[name=?][value=1]', 'watcher[user_ids][]'
@@ -127,7 +163,8 class WatchersControllerTest < ActionController::TestCase
127 163 end
128 164
129 165 def test_autocomplete_on_watchable_update
130 xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue'
166 @request.session[:user_id] = 2
167 xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook'
131 168 assert_response :success
132 169 assert_select 'input', :count => 3
133 170 assert_select 'input[name=?][value=2]', 'watcher[user_ids][]'
@@ -139,7 +176,7 class WatchersControllerTest < ActionController::TestCase
139 176 def test_append
140 177 @request.session[:user_id] = 2
141 178 assert_no_difference 'Watcher.count' do
142 xhr :post, :append, :watcher => {:user_ids => ['4', '7']}
179 xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook'
143 180 assert_response :success
144 181 assert_include 'watchers_inputs', response.body
145 182 assert_include 'issue[watcher_user_ids][]', response.body
General Comments 0
You need to be logged in to leave comments. Login now