Commit 2fb84af3 authored by Jean-Philippe Lang's avatar Jean-Philippe Lang

Added "Watch" functionality on issues. It allows users to receive mail…

Added "Watch" functionality on issues. It allows users to receive mail notifications about issue changes.
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-8f06a7374b81
parent 907f906e
# 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
# 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
......
# 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: 已注册
......
module Redmine
module VERSION #:nodoc:
MAJOR = 0
MINOR = 5
TINY = 0
STRING= [MAJOR, MINOR, TINY].join('.')
def self.to_s; STRING end
end
end
\ No newline at end of file
require 'redmine/version'
require 'redmine/acts_as_watchable/init'
# Include hook code here
require File.dirname(__FILE__) + '/lib/acts_as_watchable'
ActiveRecord::Base.send(:include, Redmine::Acts::Watchable)
# 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
\ No newline at end of file
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); }
......
# 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment