##// END OF EJS Templates
Activity refactoring....
Jean-Philippe Lang -
r1692:a774c5c48b5e
parent child
Show More
@@ -0,0 +1,54
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module Activity
20
21 mattr_accessor :available_event_types, :default_event_types, :providers
22
23 @@available_event_types = []
24 @@default_event_types = []
25 @@providers = Hash.new {|h,k| h[k]=[] }
26
27 class << self
28 def map(&block)
29 yield self
30 end
31
32 # Registers an activity provider
33 #
34 # Options:
35 # * :class_name - one or more model(s) that provide these events (inferred from event_type by default)
36 # * :default - setting this option to false will make the events not displayed by default
37 #
38 # Examples:
39 # register :issues
40 # register :myevents, :class_name => 'Meeting'
41 def register(event_type, options={})
42 options.assert_valid_keys(:class_name, :default)
43
44 event_type = event_type.to_s
45 providers = options[:class_name] || event_type.classify
46 providers = ([] << providers) unless providers.is_a?(Array)
47
48 @@available_event_types << event_type unless @@available_event_types.include?(event_type)
49 @@default_event_types << event_type unless options[:default] == false
50 @@providers[event_type] += providers
51 end
52 end
53 end
54 end
@@ -0,0 +1,79
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module Activity
20 # Class used to retrieve activity events
21 class Fetcher
22 attr_reader :user, :project, :scope
23
24 # Needs to be unloaded in development mode
25 @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } }
26
27 def initialize(user, options={})
28 options.assert_valid_keys(:project, :with_subprojects)
29 @user = user
30 @project = options[:project]
31 @options = options
32
33 @scope = event_types
34 end
35
36 # Returns an array of available event types
37 def event_types
38 return @event_types unless @event_types.nil?
39
40 @event_types = Redmine::Activity.available_event_types
41 @event_types = @event_types.select {|o| @user.allowed_to?("view_#{o}".to_sym, @project)} if @project
42 @event_types
43 end
44
45 # Yields to filter the activity scope
46 def scope_select(&block)
47 @scope = @scope.select {|t| yield t }
48 end
49
50 # Sets the scope
51 def scope=(s)
52 @scope = s & event_types
53 end
54
55 # Resets the scope to the default scope
56 def default_scope!
57 @scope = Redmine::Activity.default_event_types
58 end
59
60 # Returns an array of events for the given date range
61 def events(from, to)
62 e = []
63
64 @scope.each do |event_type|
65 constantized_providers(event_type).each do |provider|
66 e += provider.find_events(event_type, @user, from, to, @options)
67 end
68 end
69 e
70 end
71
72 private
73
74 def constantized_providers(event_type)
75 @@constantized_providers[event_type]
76 end
77 end
78 end
79 end
@@ -0,0 +1,71
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../test_helper'
19
20 class ActivityTest < Test::Unit::TestCase
21 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
23
24 def setup
25 @project = Project.find(1)
26 end
27
28 def test_activity_without_subprojects
29 events = find_events(User.anonymous, :project => @project)
30 assert_not_nil events
31
32 assert events.include?(Issue.find(1))
33 assert !events.include?(Issue.find(4))
34 # subproject issue
35 assert !events.include?(Issue.find(5))
36 end
37
38 def test_activity_with_subprojects
39 events = find_events(User.anonymous, :project => @project, :with_subprojects => 1)
40 assert_not_nil events
41
42 assert events.include?(Issue.find(1))
43 # subproject issue
44 assert events.include?(Issue.find(5))
45 end
46
47 def test_global_activity_anonymous
48 events = find_events(User.anonymous)
49 assert_not_nil events
50
51 assert events.include?(Issue.find(1))
52 assert events.include?(Message.find(5))
53 # Issue of a private project
54 assert !events.include?(Issue.find(4))
55 end
56
57 def test_global_activity_logged_user
58 events = find_events(User.find(2)) # manager
59 assert_not_nil events
60
61 assert events.include?(Issue.find(1))
62 # Issue of a private project the user belongs to
63 assert events.include?(Issue.find(4))
64 end
65
66 private
67
68 def find_events(user, options={})
69 Redmine::Activity::Fetcher.new(user, options).events(Date.today - 30, Date.today + 1)
70 end
71 end
@@ -0,0 +1,2
1 require File.dirname(__FILE__) + '/lib/acts_as_activity_provider'
2 ActiveRecord::Base.send(:include, Redmine::Acts::ActivityProvider)
@@ -0,0 +1,68
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module Acts
20 module ActivityProvider
21 def self.included(base)
22 base.extend ClassMethods
23 end
24
25 module ClassMethods
26 def acts_as_activity_provider(options = {})
27 unless self.included_modules.include?(Redmine::Acts::ActivityProvider::InstanceMethods)
28 cattr_accessor :activity_provider_options
29 send :include, Redmine::Acts::ActivityProvider::InstanceMethods
30 end
31
32 options.assert_valid_keys(:type, :permission, :timestamp, :find_options)
33 self.activity_provider_options ||= {}
34
35 # One model can provide different event types
36 # We store these options in activity_provider_options hash
37 event_type = options.delete(:type) || self.name.underscore.pluralize
38
39 options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless options.has_key?(:permission)
40 options[:timestamp] ||= "#{table_name}.created_on"
41 options[:find_options] ||= {}
42 self.activity_provider_options[event_type] = options
43 end
44 end
45
46 module InstanceMethods
47 def self.included(base)
48 base.extend ClassMethods
49 end
50
51 module ClassMethods
52 # Returns events of type event_type visible by user that occured between from and to
53 def find_events(event_type, user, from, to, options)
54 provider_options = activity_provider_options[event_type]
55 raise "#{self.name} can not provide #{event_type} events." if provider_options.nil?
56
57 cond = ARCondition.new(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to])
58 cond.add(Project.allowed_to_condition(user, provider_options[:permission], options)) if provider_options[:permission]
59
60 with_scope(:find => { :conditions => cond.conditions }) do
61 find(:all, provider_options[:find_options])
62 end
63 end
64 end
65 end
66 end
67 end
68 end
@@ -226,94 +226,22 class ProjectsController < ApplicationController
226
226
227 @date_to ||= Date.today + 1
227 @date_to ||= Date.today + 1
228 @date_from = @date_to - @days
228 @date_from = @date_to - @days
229 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
229
230
230 @event_types = %w(issues news files documents changesets wiki_pages messages)
231 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, :with_subprojects => @with_subprojects)
231 if @project
232 @activity.scope_select {|t| !params["show_#{t}"].nil?}
232 @event_types.delete('wiki_pages') unless @project.wiki
233 @activity.default_scope! if @activity.scope.empty?
233 @event_types.delete('changesets') unless @project.repository
234 @event_types.delete('messages') unless @project.boards.any?
235 # only show what the user is allowed to view
236 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
237 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
238 end
239 @scope = @event_types.select {|t| params["show_#{t}"]}
240 # default events if none is specified in parameters
241 @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
242
243 @events = []
244
245 if @scope.include?('issues')
246 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
247 cond.add(["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
248 @events += Issue.find(:all, :include => [:project, :author, :tracker], :conditions => cond.conditions)
249
250 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
251 cond.add(["#{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
252 cond.add("#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> ''")
253 @events += Journal.find(:all, :include => [{:issue => :project}, :details, :user], :conditions => cond.conditions)
254 end
255
256 if @scope.include?('news')
257 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_news, :project => @project, :with_subprojects => @with_subprojects))
258 cond.add(["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
259 @events += News.find(:all, :include => [:project, :author], :conditions => cond.conditions)
260 end
261
262 if @scope.include?('files')
263 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_files, :project => @project, :with_subprojects => @with_subprojects))
264 cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
265 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
266 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
267 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id",
268 :conditions => cond.conditions)
269 end
270
271 if @scope.include?('documents')
272 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
273 cond.add(["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
274 @events += Document.find(:all, :include => :project, :conditions => cond.conditions)
275
276 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
277 cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
278 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
279 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
280 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id",
281 :conditions => cond.conditions)
282 end
283
284 if @scope.include?('wiki_pages')
285 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
286 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
287 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
288 "#{WikiContent.versioned_table_name}.id"
289 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
290 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
291 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"
292
234
293 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_wiki_pages, :project => @project, :with_subprojects => @with_subprojects))
235 events = @activity.events(@date_from, @date_to)
294 cond.add(["#{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?", @date_from, @date_to])
295 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => cond.conditions)
296 end
297
298 if @scope.include?('changesets')
299 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_changesets, :project => @project, :with_subprojects => @with_subprojects))
300 cond.add(["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
301 @events += Changeset.find(:all, :include => {:repository => :project}, :conditions => cond.conditions)
302 end
303
304 if @scope.include?('messages')
305 cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_messages, :project => @project, :with_subprojects => @with_subprojects))
306 cond.add(["#{Message.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
307 @events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions)
308 end
309
310 @events_by_day = @events.group_by(&:event_date)
311
236
312 respond_to do |format|
237 respond_to do |format|
313 format.html { render :layout => false if request.xhr? }
238 format.html {
239 @events_by_day = events.group_by(&:event_date)
240 render :layout => false if request.xhr?
241 }
314 format.atom {
242 format.atom {
315 title = (@scope.size == 1) ? l("label_#{@scope.first.singularize}_plural") : l(:label_activity)
243 title = (@scope.size == 1) ? l("label_#{@scope.first.singularize}_plural") : l(:label_activity)
316 render_feed(@events, :title => "#{@project || Setting.app_title}: #{title}")
244 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
317 }
245 }
318 end
246 end
319 end
247 end
@@ -28,6 +28,18 class Attachment < ActiveRecord::Base
28 acts_as_event :title => :filename,
28 acts_as_event :title => :filename,
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
30
30
31 acts_as_activity_provider :type => 'files',
32 :permission => :view_files,
33 :find_options => {:select => "#{Attachment.table_name}.*",
34 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
35 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"}
36
37 acts_as_activity_provider :type => 'documents',
38 :permission => :view_documents,
39 :find_options => {:select => "#{Attachment.table_name}.*",
40 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
41 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
42
31 cattr_accessor :storage_path
43 cattr_accessor :storage_path
32 @@storage_path = "#{RAILS_ROOT}/files"
44 @@storage_path = "#{RAILS_ROOT}/files"
33
45
@@ -30,6 +30,9 class Changeset < ActiveRecord::Base
30 :include => {:repository => :project},
30 :include => {:repository => :project},
31 :project_key => "#{Repository.table_name}.project_id",
31 :project_key => "#{Repository.table_name}.project_id",
32 :date_column => 'committed_on'
32 :date_column => 'committed_on'
33
34 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
35 :find_options => {:include => {:repository => :project}}
33
36
34 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
37 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
35 validates_uniqueness_of :revision, :scope => :repository_id
38 validates_uniqueness_of :revision, :scope => :repository_id
@@ -24,7 +24,8 class Document < ActiveRecord::Base
24 acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
24 acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
25 :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
25 :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
26 :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
26 :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
27
27 acts_as_activity_provider :find_options => {:include => :project}
28
28 validates_presence_of :project, :title, :category
29 validates_presence_of :project, :title, :category
29 validates_length_of :title, :maximum => 60
30 validates_length_of :title, :maximum => 60
30 end
31 end
@@ -42,6 +42,8 class Issue < ActiveRecord::Base
42 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
42 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
43 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
43 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
44
44
45 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}
46
45 validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
47 validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
46 validates_length_of :subject, :maximum => 255
48 validates_length_of :subject, :maximum => 255
47 validates_inclusion_of :done_ratio, :in => 0..100
49 validates_inclusion_of :done_ratio, :in => 0..100
@@ -31,6 +31,12 class Journal < ActiveRecord::Base
31 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
31 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
32 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
32 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
33
33
34 acts_as_activity_provider :type => 'issues',
35 :permission => :view_issues,
36 :find_options => {:include => [{:issue => :project}, :details, :user],
37 :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
38 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
39
34 def save
40 def save
35 # Do not save an empty journal
41 # Do not save an empty journal
36 (details.empty? && notes.blank?) ? false : super
42 (details.empty? && notes.blank?) ? false : super
@@ -31,7 +31,9 class Message < ActiveRecord::Base
31 :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
31 :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
32 :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
32 :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
33 {:id => o.parent_id, :anchor => "message-#{o.id}"})}
33 {:id => o.parent_id, :anchor => "message-#{o.id}"})}
34
34
35 acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}
36
35 attr_protected :locked, :sticky
37 attr_protected :locked, :sticky
36 validates_presence_of :subject, :content
38 validates_presence_of :subject, :content
37 validates_length_of :subject, :maximum => 255
39 validates_length_of :subject, :maximum => 255
@@ -26,7 +26,8 class News < ActiveRecord::Base
26
26
27 acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
27 acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
28 acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
28 acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
29
29 acts_as_activity_provider :find_options => {:include => [:project, :author]}
30
30 # returns latest news for projects visible by user
31 # returns latest news for projects visible by user
31 def self.latest(user=nil, count=5)
32 def self.latest(user=nil, count=5)
32 find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
33 find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@@ -35,6 +35,17 class WikiContent < ActiveRecord::Base
35 :type => 'wiki-page',
35 :type => 'wiki-page',
36 :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}}
36 :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}}
37
37
38 acts_as_activity_provider :type => 'wiki_pages',
39 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
40 :permission => :view_wiki_pages,
41 :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
42 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
43 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
44 "#{WikiContent.versioned_table_name}.id",
45 :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
46 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
47 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
48
38 def text=(plain)
49 def text=(plain)
39 case Setting.wiki_compression
50 case Setting.wiki_compression
40 when 'gzip'
51 when 'gzip'
@@ -44,8 +44,8
44 <% content_for :sidebar do %>
44 <% content_for :sidebar do %>
45 <% form_tag({}, :method => :get) do %>
45 <% form_tag({}, :method => :get) do %>
46 <h3><%= l(:label_activity) %></h3>
46 <h3><%= l(:label_activity) %></h3>
47 <p><% @event_types.each do |t| %>
47 <p><% @activity.event_types.each do |t| %>
48 <label><%= check_box_tag "show_#{t}", 1, @scope.include?(t) %> <%= l("label_#{t.singularize}_plural")%></label><br />
48 <label><%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> <%= l("label_#{t.singularize}_plural")%></label><br />
49 <% end %></p>
49 <% end %></p>
50 <% if @project && @project.active_children.any? %>
50 <% if @project && @project.active_children.any? %>
51 <p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
51 <p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
@@ -1,5 +1,6
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/activity'
3 require 'redmine/mime_type'
4 require 'redmine/mime_type'
4 require 'redmine/core_ext'
5 require 'redmine/core_ext'
5 require 'redmine/themes'
6 require 'redmine/themes'
@@ -132,3 +133,13 Redmine::MenuManager.map :project_menu do |menu|
132 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
133 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
133 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
134 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
134 end
135 end
136
137 Redmine::Activity.map do |activity|
138 activity.register :issues, :class_name => %w(Issue Journal)
139 activity.register :changesets
140 activity.register :news
141 activity.register :documents, :class_name => %w(Document Attachment)
142 activity.register :files, :class_name => 'Attachment'
143 activity.register :wiki_pages, :class_name => 'WikiContent::Version', :default => false
144 activity.register :messages, :default => false
145 end
@@ -153,10 +153,6 class ProjectsControllerTest < Test::Unit::TestCase
153 assert_response :success
153 assert_response :success
154 assert_template 'activity'
154 assert_template 'activity'
155 assert_not_nil assigns(:events_by_day)
155 assert_not_nil assigns(:events_by_day)
156 assert_not_nil assigns(:events)
157
158 # subproject issue not included by default
159 assert !assigns(:events).include?(Issue.find(5))
160
156
161 assert_tag :tag => "h3",
157 assert_tag :tag => "h3",
162 :content => /#{2.days.ago.to_date.day}/,
158 :content => /#{2.days.ago.to_date.day}/,
@@ -168,7 +164,9 class ProjectsControllerTest < Test::Unit::TestCase
168 }
164 }
169 }
165 }
170 }
166 }
171
167 end
168
169 def test_previous_project_activity
172 get :activity, :id => 1, :from => 3.days.ago.to_date
170 get :activity, :id => 1, :from => 3.days.ago.to_date
173 assert_response :success
171 assert_response :success
174 assert_template 'activity'
172 assert_template 'activity'
@@ -186,53 +184,24 class ProjectsControllerTest < Test::Unit::TestCase
186 }
184 }
187 end
185 end
188
186
189 def test_activity_with_subprojects
187 def test_global_activity
190 get :activity, :id => 1, :with_subprojects => 1
191 assert_response :success
192 assert_template 'activity'
193 assert_not_nil assigns(:events)
194
195 assert assigns(:events).include?(Issue.find(1))
196 assert !assigns(:events).include?(Issue.find(4))
197 # subproject issue
198 assert assigns(:events).include?(Issue.find(5))
199 end
200
201 def test_global_activity_anonymous
202 get :activity
203 assert_response :success
204 assert_template 'activity'
205 assert_not_nil assigns(:events)
206
207 assert assigns(:events).include?(Issue.find(1))
208 # Issue of a private project
209 assert !assigns(:events).include?(Issue.find(4))
210 end
211
212 def test_global_activity_logged_user
213 @request.session[:user_id] = 2 # manager
214 get :activity
188 get :activity
215 assert_response :success
189 assert_response :success
216 assert_template 'activity'
190 assert_template 'activity'
217 assert_not_nil assigns(:events)
191 assert_not_nil assigns(:events_by_day)
218
192
219 assert assigns(:events).include?(Issue.find(1))
193 assert_tag :tag => "h3",
220 # Issue of a private project the user belongs to
194 :content => /#{5.day.ago.to_date.day}/,
221 assert assigns(:events).include?(Issue.find(4))
195 :sibling => { :tag => "dl",
196 :child => { :tag => "dt",
197 :attributes => { :class => /issue/ },
198 :child => { :tag => "a",
199 :content => /#{Issue.find(5).subject}/,
200 }
201 }
202 }
222 end
203 end
223
224
204
225 def test_global_activity_with_all_types
226 get :activity, :show_issues => 1, :show_news => 1, :show_files => 1, :show_documents => 1, :show_changesets => 1, :show_wiki_pages => 1, :show_messages => 1
227 assert_response :success
228 assert_template 'activity'
229 assert_not_nil assigns(:events)
230
231 assert assigns(:events).include?(Issue.find(1))
232 assert !assigns(:events).include?(Issue.find(4))
233 assert assigns(:events).include?(Message.find(5))
234 end
235
236 def test_calendar
205 def test_calendar
237 get :calendar, :id => 1
206 get :calendar, :id => 1
238 assert_response :success
207 assert_response :success
General Comments 0
You need to be logged in to leave comments. Login now