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 %>
<%= 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' %>
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 Binary files /dev/null and b/public/images/fav.png 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