For now, it's only usefull for users who are not members of the project, since members receive notifications for each issue (this behaviour will change). git-svn-id: http://redmine.rubyforge.org/svn/trunk@453 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/0.5.1
@@ -0,0 +1,38 @@ | |||
# redMine - project management software | |||
# Copyright (C) 2006-2007 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. | |||
class WatchersController < ApplicationController | |||
layout 'base' | |||
before_filter :require_login, :find_project, :check_project_privacy | |||
def add | |||
@issue.add_watcher(logged_in_user) | |||
redirect_to :controller => 'issues', :action => 'show', :id => @issue | |||
end | |||
def remove | |||
@issue.remove_watcher(logged_in_user) | |||
redirect_to :controller => 'issues', :action => 'show', :id => @issue | |||
end | |||
private | |||
def find_project | |||
@issue = Issue.find(params[:issue_id]) | |||
@project = @issue.project | |||
end | |||
end |
@@ -0,0 +1,19 @@ | |||
# redMine - project management software | |||
# Copyright (C) 2006-2007 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. | |||
module WatchersHelper | |||
end |
@@ -32,6 +32,8 @@ class Issue < ActiveRecord::Base | |||
has_many :custom_values, :dependent => :delete_all, :as => :customized | |||
has_many :custom_fields, :through => :custom_values | |||
acts_as_watchable | |||
validates_presence_of :subject, :description, :priority, :tracker, :author, :status | |||
validates_inclusion_of :done_ratio, :in => 0..100 | |||
validates_associated :custom_values, :on => :update |
@@ -32,6 +32,8 @@ class Mailer < ActionMailer::Base | |||
# Sends to all project members | |||
issue = journal.journalized | |||
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact | |||
# Watchers in cc | |||
@cc = issue.watcher_recipients - @recipients | |||
@from = Setting.mail_from | |||
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}" | |||
@body['issue'] = issue |
@@ -0,0 +1,23 @@ | |||
# redMine - project management software | |||
# Copyright (C) 2006-2007 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. | |||
class Watcher < ActiveRecord::Base | |||
belongs_to :watchable, :polymorphic => true | |||
belongs_to :user | |||
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] | |||
end |
@@ -53,6 +53,13 @@ end %> | |||
<div class="contextual"> | |||
<%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit' %> | |||
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %> | |||
<% if @logged_in_user %> | |||
<% if @issue.watched_by?(@logged_in_user) %> | |||
<%= link_to l(:button_unwatch), {:controller => 'watchers', :action => 'remove', :issue_id => @issue}, :class => 'icon icon-fav' %> | |||
<% else %> | |||
<%= link_to l(:button_watch), {:controller => 'watchers', :action => 'add', :issue_id => @issue}, :class => 'icon icon-fav' %> | |||
<% end %> | |||
<% end %> | |||
<%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %> | |||
<%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> | |||
</div> |
@@ -82,4 +82,5 @@ GLoc.set_kcode | |||
GLoc.load_localized_strings | |||
GLoc.set_config(:raise_string_not_found_errors => false) | |||
require 'redmine' | |||
@@ -383,6 +383,8 @@ button_activate: Aktivieren | |||
button_sort: Sortieren | |||
button_log_time: Log time | |||
button_rollback: Rollback to this version | |||
button_watch: Watch | |||
button_unwatch: Unwatch | |||
status_active: aktiv | |||
status_registered: angemeldet |
@@ -383,6 +383,8 @@ button_activate: Activate | |||
button_sort: Sort | |||
button_log_time: Log time | |||
button_rollback: Rollback to this version | |||
button_watch: Watch | |||
button_unwatch: Unwatch | |||
status_active: active | |||
status_registered: registered |
@@ -383,6 +383,8 @@ button_activate: Activar | |||
button_sort: Clasificar | |||
button_log_time: Log time | |||
button_rollback: Rollback to this version | |||
button_watch: Watch | |||
button_unwatch: Unwatch | |||
status_active: active | |||
status_registered: registered |
@@ -383,6 +383,8 @@ button_activate: Activer | |||
button_sort: Trier | |||
button_log_time: Saisir temps | |||
button_rollback: Revenir à cette version | |||
button_watch: Surveiller | |||
button_unwatch: Ne plus surveiller | |||
status_active: actif | |||
status_registered: enregistré |
@@ -383,6 +383,8 @@ button_activate: Attiva | |||
button_sort: Ordina | |||
button_log_time: Log time | |||
button_rollback: Rollback to this version | |||
button_watch: Watch | |||
button_unwatch: Unwatch | |||
status_active: active | |||
status_registered: registered |
@@ -384,6 +384,8 @@ button_activate: 有効にする | |||
button_sort: ソート | |||
button_log_time: 時間を記録 | |||
button_rollback: このバージョンにロールバック | |||
button_watch: Watch | |||
button_unwatch: Unwatch | |||
status_active: 有効 | |||
status_registered: 登録 |
@@ -386,6 +386,8 @@ button_activate: 激活 | |||
button_sort: 排序 | |||
button_log_time: 登记工时 | |||
button_rollback: Rollback to this version | |||
button_watch: Watch | |||
button_unwatch: Unwatch | |||
status_active: 激活 | |||
status_registered: 已注册 |
@@ -1,11 +1,2 @@ | |||
module Redmine | |||
module VERSION #:nodoc: | |||
MAJOR = 0 | |||
MINOR = 5 | |||
TINY = 0 | |||
STRING= [MAJOR, MINOR, TINY].join('.') | |||
def self.to_s; STRING end | |||
end | |||
end | |||
require 'redmine/version' | |||
require 'redmine/acts_as_watchable/init' |
@@ -0,0 +1,3 @@ | |||
# Include hook code here | |||
require File.dirname(__FILE__) + '/lib/acts_as_watchable' | |||
ActiveRecord::Base.send(:include, Redmine::Acts::Watchable) |
@@ -0,0 +1,53 @@ | |||
# ActsAsWatchable | |||
module Redmine | |||
module Acts | |||
module Watchable | |||
def self.included(base) | |||
base.extend ClassMethods | |||
end | |||
module ClassMethods | |||
def acts_as_watchable(options = {}) | |||
return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods) | |||
send :include, Redmine::Acts::Watchable::InstanceMethods | |||
class_eval do | |||
has_many :watchers, :as => :watchable, :dependent => :delete_all | |||
end | |||
end | |||
end | |||
module InstanceMethods | |||
def self.included(base) | |||
base.extend ClassMethods | |||
end | |||
def add_watcher(user) | |||
self.watchers << Watcher.new(:user => user) | |||
end | |||
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 | |||
def watched_by?(user) | |||
!self.watchers.find(:first, | |||
:conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil? | |||
end | |||
def watcher_recipients | |||
self.watchers.collect { |w| w.user.mail if w.user.mail_notification }.compact | |||
end | |||
module ClassMethods | |||
def watched_by(user) | |||
find(:all, | |||
:include => :watchers, | |||
:conditions => ["#{Watcher.table_name}.user_id = ?", user.id]) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,11 @@ | |||
module Redmine | |||
module VERSION #:nodoc: | |||
MAJOR = 0 | |||
MINOR = 5 | |||
TINY = 0 | |||
STRING= [MAJOR, MINOR, TINY].join('.') | |||
def self.to_s; STRING end | |||
end | |||
end |
@@ -158,6 +158,7 @@ vertical-align: middle; | |||
.icon-time { background-image: url(../images/time.png); } | |||
.icon-stats { background-image: url(../images/stats.png); } | |||
.icon-warning { background-image: url(../images/warning.png); } | |||
.icon-fav { background-image: url(../images/fav.png); } | |||
.icon22-projects { background-image: url(../images/22x22/projects.png); } | |||
.icon22-users { background-image: url(../images/22x22/users.png); } |
@@ -0,0 +1,69 @@ | |||
# redMine - project management software | |||
# Copyright (C) 2006-2007 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' | |||
class WatcherTest < Test::Unit::TestCase | |||
fixtures :issues, :users | |||
def setup | |||
@user = User.find(1) | |||
@issue = Issue.find(1) | |||
end | |||
def test_watch | |||
assert @issue.add_watcher(@user) | |||
@issue.reload | |||
assert @issue.watchers.detect { |w| w.user == @user } | |||
end | |||
def test_cant_watch_twice | |||
assert @issue.add_watcher(@user) | |||
assert !@issue.add_watcher(@user) | |||
end | |||
def test_watched_by | |||
assert @issue.add_watcher(@user) | |||
@issue.reload | |||
assert @issue.watched_by?(@user) | |||
assert Issue.watched_by(@user).include?(@issue) | |||
end | |||
def test_recipients | |||
@issue.watchers.delete_all | |||
@issue.reload | |||
assert @issue.watcher_recipients.empty? | |||
assert @issue.add_watcher(@user) | |||
@user.mail_notification = true | |||
@user.save | |||
@issue.reload | |||
assert @issue.watcher_recipients.include?(@user.mail) | |||
@user.mail_notification = false | |||
@user.save | |||
@issue.reload | |||
assert !@issue.watcher_recipients.include?(@user.mail) | |||
end | |||
def test_unwatch | |||
assert @issue.add_watcher(@user) | |||
@issue.reload | |||
assert_equal 1, @issue.remove_watcher(@user) | |||
end | |||
end |