summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-04-21 12:08:31 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-04-21 12:08:31 +0000
commit2fb84af3e91dc17aa0f84a8fa0e02cabe2ac712c (patch)
tree5d90bc5dec82cac5531dd7b14c65f9d35ee64dec
parent907f906ec6e0fb60cbbd4abe7b579c59d78405d4 (diff)
downloadredmine-2fb84af3e91dc17aa0f84a8fa0e02cabe2ac712c.tar.gz
redmine-2fb84af3e91dc17aa0f84a8fa0e02cabe2ac712c.zip
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
-rw-r--r--app/controllers/watchers_controller.rb38
-rw-r--r--app/helpers/watchers_helper.rb19
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/mailer.rb2
-rw-r--r--app/models/watcher.rb23
-rw-r--r--app/views/issues/show.rhtml7
-rw-r--r--config/environment.rb1
-rw-r--r--lang/de.yml2
-rw-r--r--lang/en.yml2
-rw-r--r--lang/es.yml2
-rw-r--r--lang/fr.yml2
-rw-r--r--lang/it.yml2
-rw-r--r--lang/ja.yml2
-rw-r--r--lang/zh.yml2
-rw-r--r--lib/redmine.rb13
-rw-r--r--lib/redmine/acts_as_watchable/init.rb3
-rw-r--r--lib/redmine/acts_as_watchable/lib/acts_as_watchable.rb53
-rw-r--r--lib/redmine/version.rb11
-rw-r--r--public/images/fav.pngbin0 -> 492 bytes
-rw-r--r--public/stylesheets/application.css1
-rw-r--r--test/unit/watcher_test.rb69
21 files changed, 245 insertions, 11 deletions
diff --git a/app/controllers/watchers_controller.rb b/app/controllers/watchers_controller.rb
new file mode 100644
index 000000000..09ec5bcd7
--- /dev/null
+++ b/app/controllers/watchers_controller.rb
@@ -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
diff --git a/app/helpers/watchers_helper.rb b/app/helpers/watchers_helper.rb
new file mode 100644
index 000000000..23f767611
--- /dev/null
+++ b/app/helpers/watchers_helper.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index bb7797c40..0f44cdd30 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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
diff --git a/app/models/mailer.rb b/app/models/mailer.rb
index 36bcddc2a..5d835289a 100644
--- a/app/models/mailer.rb
+++ b/app/models/mailer.rb
@@ -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
diff --git a/app/models/watcher.rb b/app/models/watcher.rb
new file mode 100644
index 000000000..cb6ff52ea
--- /dev/null
+++ b/app/models/watcher.rb
@@ -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
diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml
index a5569c9d4..3c1ac0e2e 100644
--- a/app/views/issues/show.rhtml
+++ b/app/views/issues/show.rhtml
@@ -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>
diff --git a/config/environment.rb b/config/environment.rb
index a73dc9a4c..8ff43e0ac 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -82,4 +82,5 @@ GLoc.set_kcode
GLoc.load_localized_strings
GLoc.set_config(:raise_string_not_found_errors => false)
+require 'redmine'
diff --git a/lang/de.yml b/lang/de.yml
index 95ae4a635..590e5e0d5 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -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
diff --git a/lang/en.yml b/lang/en.yml
index 0e66a1faf..e40c915f2 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -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
diff --git a/lang/es.yml b/lang/es.yml
index a245d05fb..103c399c0 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -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
diff --git a/lang/fr.yml b/lang/fr.yml
index 5cc05ef64..d6666169c 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -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é
diff --git a/lang/it.yml b/lang/it.yml
index 6beb963ed..6af04b2f4 100644
--- a/lang/it.yml
+++ b/lang/it.yml
@@ -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
diff --git a/lang/ja.yml b/lang/ja.yml
index 8bcf77fe3..5cc9bb907 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -384,6 +384,8 @@ button_activate: 有効にする
button_sort: ソート
button_log_time: 時間を記録
button_rollback: このバージョンにロールバック
+button_watch: Watch
+button_unwatch: Unwatch
status_active: 有効
status_registered: 登録
diff --git a/lang/zh.yml b/lang/zh.yml
index be1c91f8a..78e093d59 100644
--- a/lang/zh.yml
+++ b/lang/zh.yml
@@ -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: 已注册
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 20b30c037..cfcccccf9 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -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 \ No newline at end of file
+require 'redmine/version'
+require 'redmine/acts_as_watchable/init'
diff --git a/lib/redmine/acts_as_watchable/init.rb b/lib/redmine/acts_as_watchable/init.rb
new file mode 100644
index 000000000..f39cc7d18
--- /dev/null
+++ b/lib/redmine/acts_as_watchable/init.rb
@@ -0,0 +1,3 @@
+# Include hook code here
+require File.dirname(__FILE__) + '/lib/acts_as_watchable'
+ActiveRecord::Base.send(:include, Redmine::Acts::Watchable)
diff --git a/lib/redmine/acts_as_watchable/lib/acts_as_watchable.rb b/lib/redmine/acts_as_watchable/lib/acts_as_watchable.rb
new file mode 100644
index 000000000..d62742cac
--- /dev/null
+++ b/lib/redmine/acts_as_watchable/lib/acts_as_watchable.rb
@@ -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 \ No newline at end of file
diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb
new file mode 100644
index 000000000..630fb1ff8
--- /dev/null
+++ b/lib/redmine/version.rb
@@ -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
diff --git a/public/images/fav.png b/public/images/fav.png
new file mode 100644
index 000000000..49c0f473a
--- /dev/null
+++ b/public/images/fav.png
Binary files differ
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 7461c74a3..ced43768d 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -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); }
diff --git a/test/unit/watcher_test.rb b/test/unit/watcher_test.rb
new file mode 100644
index 000000000..b8a095426
--- /dev/null
+++ b/test/unit/watcher_test.rb
@@ -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