class WatchersController < ApplicationController
layout 'base'
- before_filter :require_login, :find_project, :check_project_privacy
+ before_filter :find_project
+ before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
+ before_filter :authorize, :only => :new
- def add
- user = User.current
- @watched.add_watcher(user)
- respond_to do |format|
- format.html { redirect_to :back }
- format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
- end
- rescue RedirectBackError
- render :text => 'Watcher added.', :layout => true
+ verify :method => :post,
+ :only => [ :watch, :unwatch ],
+ :render => { :nothing => true, :status => :method_not_allowed }
+
+ def watch
+ set_watcher(User.current, true)
+ end
+
+ def unwatch
+ set_watcher(User.current, false)
end
- def remove
- user = User.current
- @watched.remove_watcher(user)
+ def new
+ @watcher = Watcher.new(params[:watcher])
+ @watcher.watchable = @watched
+ @watcher.save if request.post?
respond_to do |format|
format.html { redirect_to :back }
- format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
+ format.js do
+ render :update do |page|
+ page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
+ end
+ end
end
- rescue RedirectBackError
- render :text => 'Watcher removed.', :layout => true
+ rescue ::ActionController::RedirectBackError
+ render :text => 'Watcher added.', :layout => true
end
-
+
private
def find_project
klass = Object.const_get(params[:object_type].camelcase)
rescue
render_404
end
+
+ def set_watcher(user, watching)
+ @watched.set_watcher(user, watching)
+ respond_to do |format|
+ format.html { redirect_to :back }
+ format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
+ end
+ rescue ::ActionController::RedirectBackError
+ render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
+ end
end
return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user)
url = {:controller => 'watchers',
- :action => (watched ? 'remove' : 'add'),
+ :action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
end
+
+ # Returns a comma separated list of users watching the given object
+ def watchers_list(object)
+ object.watcher_users.collect {|u| content_tag('span', link_to_user(u), :class => 'user') }.join(",\n")
+ end
end
belongs_to :watchable, :polymorphic => true
belongs_to :user
+ validates_presence_of :user
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]
+
+ protected
+
+ def validate
+ errors.add :user_id, :activerecord_error_invalid unless user.nil? || user.active?
+ end
end
</div>
<% end %>
+<% if User.current.allowed_to?(:add_issue_watchers, @project) ||
+ (@issue.watchers.any? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
+<hr />
+<div id="watchers">
+<%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
+</div>
+<% end %>
+
</div>
<% if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @project) %>
--- /dev/null
+<div class="contextual">
+<%= link_to_remote l(:button_add),
+ :url => {:controller => 'watchers',
+ :action => 'new',
+ :object_type => watched.class.name.underscore,
+ :object_id => watched} if User.current.allowed_to?(:add_issue_watchers, @project) %>
+</div>
+
+<p><strong><%= l(:label_issue_watchers) %></strong></p>
+<%= watchers_list(watched) %>
+
+<% unless @watcher.nil? %>
+<% remote_form_for(:watcher, @watcher,
+ :url => {:controller => 'watchers',
+ :action => 'new',
+ :object_type => watched.class.name.underscore,
+ :object_id => watched},
+ :method => :post,
+ :html => {:id => 'new-watcher-form'}) do |f| %>
+<p><%= f.select :user_id, (watched.addable_watcher_users.collect {|m| [m.name, m.id]}), :prompt => true %>
+
+<%= submit_tag l(:button_add) %>
+<%= toggle_link l(:button_cancel), 'new-watcher-form'%></p>
+<% end %>
+<% end %>
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
label_planning: Planning
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
+label_issue_watchers: Watchers
button_login: Login
button_submit: Submit
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
label_planning: Planning
label_incoming_emails: Emails entrants
label_generate_key: Générer une clé
+label_issue_watchers: Utilisateurs surveillant cette demande
button_login: Connexion
button_submit: Soumettre
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API kulcs
text_email_delivery_not_configured: "Az E-mail küldés nincs konfigurálva, és az értesítések ki vannak kapcsolva.\nÁllítsd be az SMTP szervert a config/email.yml fájlban és indítsd újra az alkalmazást, hogy érvénybe lépjen."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: chiave API
text_email_delivery_not_configured: "La consegna via e-mail non è configurata e le notifiche sono disabilitate.\nConfigura il tuo server SMTP in config/email.yml e riavvia l'applicazione per abilitarle."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key\r
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."\r
field_parent_title: Parent page\r
+label_issue_watchers: Watchers\r
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API ключ
text_email_delivery_not_configured: "Параметры работы с почтовым сервером не настроены и функция уведомления по email не активна.\nНастроить параметры для вашего SMTP сервера вы можете в файле config/email.yml. Для применения изменений перезапустите приложение."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
setting_mail_handler_api_key: API key
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤)
field_parent_title: Parent page
+label_issue_watchers: Watchers
enumeration_activities: 活动(时间跟踪)
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
field_parent_title: Parent page
+label_issue_watchers: Watchers
# Gantt & calendar
map.permission :view_gantt, :projects => :gantt
map.permission :view_calendar, :projects => :calendar
+ # Watchers
+ map.permission :view_issue_watchers, {}
+ map.permission :add_issue_watchers, {:watchers => :new}
end
map.project_module :time_tracking do |map|
- :add_issue_notes\r
- :move_issues\r
- :delete_issues\r
+ - :view_issue_watchers\r
+ - :add_issue_watchers\r
- :manage_public_queries\r
- :save_queries\r
- :view_gantt\r
- :add_issue_notes\r
- :move_issues\r
- :delete_issues\r
+ - :view_issue_watchers\r
- :save_queries\r
- :view_gantt\r
- :view_calendar\r
- :manage_issue_relations\r
- :add_issue_notes\r
- :move_issues\r
+ - :view_issue_watchers\r
- :save_queries\r
- :view_gantt\r
- :view_calendar\r
--- /dev/null
+---
+watchers_001:
+ watchable_type: Issue
+ watchable_id: 2
+ user_id: 3
+
\ No newline at end of file
--- /dev/null
+# Redmine - project management software
+# Copyright (C) 2006-2008 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'watchers_controller'
+
+# Re-raise errors caught by the controller.
+class WatchersController; def rescue_action(e) raise e end; end
+
+class WatchersControllerTest < Test::Unit::TestCase
+ fixtures :projects, :users, :roles, :members, :enabled_modules,
+ :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers
+
+ def setup
+ @controller = WatchersController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ end
+
+ def test_get_watch_should_be_invalid
+ @request.session[:user_id] = 3
+ get :watch, :object_type => 'issue', :object_id => '1'
+ assert_response 405
+ end
+
+ def test_watch
+ @request.session[:user_id] = 3
+ assert_difference('Watcher.count') do
+ xhr :post, :watch, :object_type => 'issue', :object_id => '1'
+ assert_response :success
+ assert_select_rjs :replace_html, 'watcher'
+ end
+ assert Issue.find(1).watched_by?(User.find(3))
+ end
+
+ def test_unwatch
+ @request.session[:user_id] = 3
+ assert_difference('Watcher.count', -1) do
+ xhr :post, :unwatch, :object_type => 'issue', :object_id => '2'
+ assert_response :success
+ assert_select_rjs :replace_html, 'watcher'
+ end
+ assert !Issue.find(1).watched_by?(User.find(3))
+ end
+
+ def test_new_watcher
+ @request.session[:user_id] = 2
+ assert_difference('Watcher.count') do
+ xhr :post, :new, :object_type => 'issue', :object_id => '2', :watcher => {:user_id => '4'}
+ assert_response :success
+ assert_select_rjs :replace_html, 'watchers'
+ end
+ assert Issue.find(2).watched_by?(User.find(4))
+ end
+end
class_eval do
has_many :watchers, :as => :watchable, :dependent => :delete_all
+ has_many :watcher_users, :through => :watchers, :source => :user
end
end
end
base.extend ClassMethods
end
+ # Returns an array of users that are proposed as watchers
+ def addable_watcher_users
+ self.project.users.sort - self.watcher_users
+ end
+
+ # Adds user as a watcher
def add_watcher(user)
self.watchers << Watcher.new(:user => user)
end
+ # Removes user from the watchers list
def remove_watcher(user)
return nil unless user && user.is_a?(User)
Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}"
end
+ # Adds/removes watcher
+ def set_watcher(user, watching=true)
+ watching ? add_watcher(user) : remove_watcher(user)
+ end
+
+ # Returns if object is watched by user
def watched_by?(user)
!self.watchers.find(:first,
:conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil?
end
+ # Returns an array of watchers' email addresses
def watcher_recipients
self.watchers.collect { |w| w.user.mail if w.user.active? }.compact
end
module ClassMethods
+ # Returns the objects that are watched by user
def watched_by(user)
find(:all,
:include => :watchers,
end
end
end
-end
\ No newline at end of file
+end
def add_options(option_tags, options, value = nil)\r
option_tags = "<option value=\"\"></option>\n" + option_tags if options[:include_blank]\r
\r
- if value.blank? && options[:prompt]\r
- ("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : l(:actionview_instancetag_blank_option)}</option>\n") + option_tags\r
+ if options[:prompt]\r
+ ("<option value=\"\">--- #{options[:prompt].kind_of?(String) ? options[:prompt] : l(:actionview_instancetag_blank_option)} ---</option>\n") + option_tags\r
else\r
option_tags\r
end\r