From 624723d0cef9bd8a9528745f4b75ee22f6b1ad2e Mon Sep 17 00:00:00 2001
From: Jean-Philippe Lang <jp_lang@yahoo.fr>
Date: Tue, 11 Mar 2008 19:33:38 +0000
Subject: [PATCH] Activity enhancements: * overall activity view and feed
 added, link is available on the project list (#423, #494) * switch added on
 the project activity view to include subprojects (closes #530)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1227 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/controllers/projects_controller.rb      | 80 +++++++++++++++------
 app/models/attachment.rb                    |  1 -
 app/models/changeset.rb                     |  4 ++
 app/models/project.rb                       | 27 ++++---
 app/models/wiki_content.rb                  |  4 ++
 app/views/common/feed.atom.rxml             |  5 +-
 app/views/projects/activity.rhtml           | 12 ++--
 app/views/projects/list.rhtml               |  5 ++
 app/views/welcome/index.rhtml               |  5 +-
 lang/bg.yml                                 |  1 +
 lang/cs.yml                                 |  1 +
 lang/da.yml                                 |  1 +
 lang/de.yml                                 |  1 +
 lang/en.yml                                 |  1 +
 lang/es.yml                                 |  1 +
 lang/fi.yml                                 |  1 +
 lang/fr.yml                                 |  3 +-
 lang/he.yml                                 |  1 +
 lang/it.yml                                 |  1 +
 lang/ja.yml                                 |  1 +
 lang/ko.yml                                 |  1 +
 lang/lt.yml                                 |  1 +
 lang/nl.yml                                 |  1 +
 lang/pl.yml                                 |  1 +
 lang/pt-br.yml                              |  1 +
 lang/pt.yml                                 |  1 +
 lang/ro.yml                                 |  1 +
 lang/ru.yml                                 |  1 +
 lang/sr.yml                                 |  1 +
 lang/sv.yml                                 |  1 +
 lang/uk.yml                                 |  1 +
 lang/zh-tw.yml                              |  1 +
 lang/zh.yml                                 |  1 +
 lib/ar_condition.rb                         |  2 +-
 public/stylesheets/application.css          |  1 +
 test/fixtures/issues.yml                    | 18 ++++-
 test/fixtures/messages.yml                  |  4 +-
 test/functional/projects_controller_test.rb | 58 ++++++++++++++-
 test/unit/project_test.rb                   |  9 ---
 39 files changed, 198 insertions(+), 63 deletions(-)

diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 7e4cc81f3..9268a19ea 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -24,8 +24,9 @@ class ProjectsController < ApplicationController
   menu_item :settings, :only => :settings
   menu_item :issues, :only => [:changelog]
   
-  before_filter :find_project, :except => [ :index, :list, :add ]
-  before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
+  before_filter :find_project, :except => [ :index, :list, :add, :activity ]
+  before_filter :find_optional_project, :only => :activity
+  before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
   before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
   accept_key_auth :activity, :calendar
   
@@ -228,12 +229,14 @@ class ProjectsController < ApplicationController
     @date_from = @date_to - @days
     
     @event_types = %w(issues news files documents changesets wiki_pages messages)
-    @event_types.delete('wiki_pages') unless @project.wiki
-    @event_types.delete('changesets') unless @project.repository
-    @event_types.delete('messages') unless @project.boards.any?
-    # only show what the user is allowed to view
-    @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
-    
+    if @project
+      @event_types.delete('wiki_pages') unless @project.wiki
+      @event_types.delete('changesets') unless @project.repository
+      @event_types.delete('messages') unless @project.boards.any?
+      # only show what the user is allowed to view
+      @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
+      @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
+    end
     @scope = @event_types.select {|t| params["show_#{t}"]}
     # default events if none is specified in parameters
     @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
@@ -241,21 +244,41 @@ class ProjectsController < ApplicationController
     @events = []    
     
     if @scope.include?('issues')
-      @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
-      @events += @project.issues_status_changes(@date_from, @date_to)
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Issue.find(:all, :include => [:project, :author, :tracker], :conditions => cond.conditions)
+      
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Journal.table_name}.journalized_type = 'Issue' AND #{JournalDetail.table_name}.prop_key = 'status_id' AND #{Journal.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Journal.find(:all, :include => [{:issue => :project}, :details, :user], :conditions => cond.conditions)
     end
     
     if @scope.include?('news')
-      @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_news, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += News.find(:all, :include => [:project, :author], :conditions => cond.conditions)
     end
     
     if @scope.include?('files')
-      @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_files, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", 
+                                       :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
+                                                 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id",
+                                       :conditions => cond.conditions)
     end
     
     if @scope.include?('documents')
-      @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
-      @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Document.find(:all, :include => :project, :conditions => cond.conditions)
+      
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", 
+                                       :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
+                                                 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id",
+                                       :conditions => cond.conditions)
     end
     
     if @scope.include?('wiki_pages')
@@ -264,28 +287,31 @@ class ProjectsController < ApplicationController
                "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
                "#{WikiContent.versioned_table_name}.id"
       joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
-              "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
-      conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
-                    @project.id, @date_from, @date_to]
+              "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
+              "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"
 
-      @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_wiki_pages, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => cond.conditions)
     end
 
     if @scope.include?('changesets')
-      @events += Changeset.find(:all, :include => :repository, :conditions => ["#{Repository.table_name}.project_id = ? AND #{Changeset.table_name}.committed_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_changesets, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Changeset.find(:all, :include => {:repository => :project}, :conditions => cond.conditions)
     end
     
     if @scope.include?('messages')
-      @events += Message.find(:all, 
-                              :include => [:board, :author], 
-                              :conditions => ["#{Board.table_name}.project_id=? AND #{Message.table_name}.created_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
+      cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_messages, :project => @project, :with_subprojects => @with_subprojects))
+      cond.add(["#{Message.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
+      @events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions)
     end
     
     @events_by_day = @events.group_by(&:event_date)
     
     respond_to do |format|
       format.html { render :layout => false if request.xhr? }
-      format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
+      format.atom { render_feed(@events, :title => "#{@project || Setting.app_title}: #{l(:label_activity)}") }
     end
   end
   
@@ -381,6 +407,14 @@ private
     render_404
   end
   
+  def find_optional_project
+    return true unless params[:id]
+    @project = Project.find(params[:id])
+    authorize
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
   def retrieve_selected_tracker_ids(selectable_trackers)
     if ids = params[:tracker_ids]
       @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
index c9783b9ce..cdcb8d231 100644
--- a/app/models/attachment.rb
+++ b/app/models/attachment.rb
@@ -26,7 +26,6 @@ class Attachment < ActiveRecord::Base
   validates_length_of :disk_filename, :maximum => 255
 
   acts_as_event :title => :filename,
-                :description => :filename,
                 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
 
   cattr_accessor :storage_path
diff --git a/app/models/changeset.rb b/app/models/changeset.rb
index 6bd15b158..dbe06935d 100644
--- a/app/models/changeset.rb
+++ b/app/models/changeset.rb
@@ -45,6 +45,10 @@ class Changeset < ActiveRecord::Base
     super
   end
   
+  def project
+    repository.project
+  end
+  
   def after_create
     scan_comment_for_issue_ids
   end
diff --git a/app/models/project.rb b/app/models/project.rb
index 95a201454..ad2f1fb81 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -84,16 +84,6 @@ class Project < ActiveRecord::Base
     end 
   end
 
-  # Return all issues status changes for the project between the 2 given dates
-  def issues_status_changes(from, to)
-    Journal.find(:all, :include => [:issue, :details, :user],
-                       :conditions => ["#{Journal.table_name}.journalized_type = 'Issue'" +
-                                       " AND #{Issue.table_name}.project_id = ?" +
-                                       " AND #{JournalDetail.table_name}.prop_key = 'status_id'" +
-                                       " AND #{Journal.table_name}.created_on BETWEEN ? AND ?",
-                                       id, from, to+1])
-  end
-
   # returns latest created projects
   # non public projects will be returned only if user is a member of those
   def self.latest(user=nil, count=5)
@@ -110,19 +100,28 @@ class Project < ActiveRecord::Base
     end
   end
   
-  def self.allowed_to_condition(user, permission)
+  def self.allowed_to_condition(user, permission, options={})
     statements = []
-    active_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
+    base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
+    if options[:project]
+      project_statement = "#{Project.table_name}.id = #{options[:project].id}"
+      project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
+      base_statement = "(#{project_statement}) AND (#{base_statement})"
+    end
     if user.admin?
       # no restriction
     elsif user.logged?
       statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
       allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
       statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
+    elsif Role.anonymous.allowed_to?(permission)
+      # anonymous user allowed on public project
+      statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" 
     else
-      statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.anonymous.allowed_to?(permission)
+      # anonymous user is not authorized
+      statements << "1=0"
     end
-    statements.empty? ? active_statement : "(#{active_statement} AND (#{statements.join(' OR ')}))"
+    statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
   end
   
   def self.find(*args)
diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
index c307beb1d..13915c274 100644
--- a/app/models/wiki_content.rb
+++ b/app/models/wiki_content.rb
@@ -61,6 +61,10 @@ class WikiContent < ActiveRecord::Base
       end      
     end
     
+    def project
+      page.project
+    end
+    
     # Returns the previous version or nil
     def previous
       @previous ||= WikiContent::Version.find(:first, 
diff --git a/app/views/common/feed.atom.rxml b/app/views/common/feed.atom.rxml
index 6695565f4..b5cbeeed9 100644
--- a/app/views/common/feed.atom.rxml
+++ b/app/views/common/feed.atom.rxml
@@ -9,9 +9,10 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
   xml.generator(:uri => Redmine::Info.url, :version => Redmine::VERSION) { xml.text! Redmine::Info.versioned_name; }
   @items.each do |item|
     xml.entry do
+      url = url_for(item.event_url(:only_path => false))
       xml.title truncate(item.event_title, 100)
-      xml.link "rel" => "alternate", "href" => url_for(item.event_url(:only_path => false))
-      xml.id url_for(item.event_url(:only_path => false))
+      xml.link "rel" => "alternate", "href" => url
+      xml.id url
       xml.updated item.event_datetime.xmlschema
       author = item.event_author if item.respond_to?(:author)
       xml.author do
diff --git a/app/views/projects/activity.rhtml b/app/views/projects/activity.rhtml
index 95c8d485f..e8832fccc 100644
--- a/app/views/projects/activity.rhtml
+++ b/app/views/projects/activity.rhtml
@@ -6,7 +6,7 @@
 <dl>
 <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
   <dt class="<%= e.class.name.downcase %>"><span class="time"><%= format_time(e.event_datetime, false) %></span>
-  <%= link_to h(truncate(e.event_title, 100)), e.event_url %></dt>
+  <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> <%= link_to h(truncate(e.event_title, 100)), e.event_url %></dt>
   <dd><% unless e.event_description.blank? -%>
   <span class="description"><%= format_activity_description(e.event_description) %></span><br />
   <% end %>
@@ -35,16 +35,20 @@
 </p>
 
 <% content_for :header_tags do %>
-<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :year => nil, :month => nil, :key => User.current.rss_key).delete_if{|k,v|k=="commit"}) %>
+<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :year => nil, :month => nil, :key => User.current.rss_key)) %>
 <% end %>
 
 <% content_for :sidebar do %>
-<% form_tag do %>
+<% form_tag({}, :method => :get) do %>
 <h3><%= l(:label_activity) %></h3>
 <p><% @event_types.each do |t| %>
 <label><%= check_box_tag "show_#{t}", 1, @scope.include?(t) %> <%= l("label_#{t.singularize}_plural")%></label><br />
 <% end %></p>
-<p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
+<% if @project && @project.active_children.any? %>
+    <p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
+    <%= hidden_field_tag 'with_subprojects', 0 %>
+<% end %>
+<p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
 <% end %>
 <% end %>
 
diff --git a/app/views/projects/list.rhtml b/app/views/projects/list.rhtml
index 15ea06483..b8bb62ebb 100644
--- a/app/views/projects/list.rhtml
+++ b/app/views/projects/list.rhtml
@@ -1,3 +1,8 @@
+<div class="contextual">
+    <%= link_to l(:label_issue_view_all), { :controller => 'issues' } %> |
+    <%= link_to l(:label_overall_activity), { :controller => 'projects', :action => 'activity' }%>
+</div>
+
 <h2><%=l(:label_project_plural)%></h2>
 
 <% @project_tree.keys.sort.each do |project| %>
diff --git a/app/views/welcome/index.rhtml b/app/views/welcome/index.rhtml
index d618fa6e1..5da5a1ed3 100644
--- a/app/views/welcome/index.rhtml
+++ b/app/views/welcome/index.rhtml
@@ -26,5 +26,8 @@
 </div>	
 
 <% content_for :header_tags do %>
-<%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, {:title => l(:label_news_latest)}) %>
+<%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'},
+                                   :title => "#{Setting.app_title}: #{l(:label_news_latest)}") %>
+<%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :key => User.current.rss_key, :format => 'atom'},
+                                   :title => "#{Setting.app_title}: #{l(:label_activity)}") %>
 <% end %>
diff --git a/lang/bg.yml b/lang/bg.yml
index 87808230e..99c8eb296 100644
--- a/lang/bg.yml
+++ b/lang/bg.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/cs.yml b/lang/cs.yml
index c5cef3777..7d11bb6a8 100644
--- a/lang/cs.yml
+++ b/lang/cs.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/da.yml b/lang/da.yml
index ed2eb26a7..825f15ecc 100644
--- a/lang/da.yml
+++ b/lang/da.yml
@@ -615,3 +615,4 @@ field_comments_sorting: Display comments
 text_reassign_time_entries: 'Reassign reported hours to this issue:'
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
+label_overall_activity: Overall activity
diff --git a/lang/de.yml b/lang/de.yml
index aa4938534..eeb851018 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -614,3 +614,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/en.yml b/lang/en.yml
index af6c8faa9..26c7b4766 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -280,6 +280,7 @@ label_last_updates: Last updated
 label_last_updates_plural: %d last updated
 label_registered_on: Registered on
 label_activity: Activity
+label_overall_activity: Overall activity
 label_new: New
 label_logged_as: Logged as
 label_environment: Environment
diff --git a/lang/es.yml b/lang/es.yml
index 47ee41524..554020155 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -616,3 +616,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/fi.yml b/lang/fi.yml
index b84d6cd78..4923440fd 100644
--- a/lang/fi.yml
+++ b/lang/fi.yml
@@ -620,3 +620,4 @@ setting_display_subprojects_issues: Display subprojects issues on main projects
 field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
+label_overall_activity: Overall activity
diff --git a/lang/fr.yml b/lang/fr.yml
index 243088f04..c7cfc54e5 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -165,6 +165,7 @@ field_start_page: Page de démarrage
 field_subproject: Sous-projet
 field_hours: Heures
 field_activity: Activité
+label_overall_activity: Activité globale
 field_spent_on: Date
 field_identifier: Identifiant
 field_is_filter: Utilisé comme filtre
@@ -452,7 +453,7 @@ label_start_to_start: début à début
 label_start_to_end: début à fin
 label_stay_logged_in: Rester connecté
 label_disabled: désactivé
-label_show_completed_versions: Voire les versions passées
+label_show_completed_versions: Voir les versions passées
 label_me: moi
 label_board: Forum
 label_board_new: Nouveau forum
diff --git a/lang/he.yml b/lang/he.yml
index 972b1134a..5d585eb61 100644
--- a/lang/he.yml
+++ b/lang/he.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/it.yml b/lang/it.yml
index c3250ebbc..1d2cb742d 100644
--- a/lang/it.yml
+++ b/lang/it.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/ja.yml b/lang/ja.yml
index 79bb572a8..de883fe58 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -614,3 +614,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/ko.yml b/lang/ko.yml
index 3ca077631..bfe7be912 100644
--- a/lang/ko.yml
+++ b/lang/ko.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/lt.yml b/lang/lt.yml
index 93f48580f..b5bb15df0 100644
--- a/lang/lt.yml
+++ b/lang/lt.yml
@@ -614,3 +614,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/nl.yml b/lang/nl.yml
index e90665878..65d13ddab 100644
--- a/lang/nl.yml
+++ b/lang/nl.yml
@@ -614,3 +614,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/pl.yml b/lang/pl.yml
index 7003fb871..10a8885d1 100644
--- a/lang/pl.yml
+++ b/lang/pl.yml
@@ -613,3 +613,4 @@ setting_display_subprojects_issues: Display subprojects issues on main projects
 field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
+label_overall_activity: Overall activity
diff --git a/lang/pt-br.yml b/lang/pt-br.yml
index 10836d336..3bd38a557 100644
--- a/lang/pt-br.yml
+++ b/lang/pt-br.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/pt.yml b/lang/pt.yml
index f59e3b06a..65d26b1e3 100644
--- a/lang/pt.yml
+++ b/lang/pt.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/ro.yml b/lang/ro.yml
index cb7bb69be..8e91d277f 100644
--- a/lang/ro.yml
+++ b/lang/ro.yml
@@ -613,3 +613,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/ru.yml b/lang/ru.yml
index f12b2264f..f08271e45 100644
--- a/lang/ru.yml
+++ b/lang/ru.yml
@@ -617,3 +617,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/sr.yml b/lang/sr.yml
index e59b3cba9..82d06f93b 100644
--- a/lang/sr.yml
+++ b/lang/sr.yml
@@ -614,3 +614,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/sv.yml b/lang/sv.yml
index 5ff3f8f17..cc9fcd212 100644
--- a/lang/sv.yml
+++ b/lang/sv.yml
@@ -614,3 +614,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/uk.yml b/lang/uk.yml
index 8c33c7806..08a36e352 100644
--- a/lang/uk.yml
+++ b/lang/uk.yml
@@ -615,3 +615,4 @@ field_comments_sorting: Display comments
 label_reverse_chronological_order: In reverse chronological order
 label_preferences: Preferences
 setting_display_subprojects_issues: Display subprojects issues on main projects by default
+label_overall_activity: Overall activity
diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml
index 9334c72bb..95e5afbb4 100644
--- a/lang/zh-tw.yml
+++ b/lang/zh-tw.yml
@@ -614,3 +614,4 @@ default_activity_development: 開發
 enumeration_issue_priorities: 項目優先權
 enumeration_doc_categories: 文件分類
 enumeration_activities: 活動 (time tracking)
+label_overall_activity: Overall activity
diff --git a/lang/zh.yml b/lang/zh.yml
index a917f0da7..39ed5fc44 100644
--- a/lang/zh.yml
+++ b/lang/zh.yml
@@ -614,3 +614,4 @@ default_activity_development: 开发
 enumeration_issue_priorities: 问题优先级
 enumeration_doc_categories: 文档类别
 enumeration_activities: 活动(时间跟踪)
+label_overall_activity: Overall activity
diff --git a/lib/ar_condition.rb b/lib/ar_condition.rb
index b08e90761..30c5572ed 100644
--- a/lib/ar_condition.rb
+++ b/lib/ar_condition.rb
@@ -20,7 +20,7 @@ class ARCondition
   
   def initialize(condition=nil)
     @conditions = ['1=1']
-    @conditions.add(condition) if condition
+    add(condition) if condition
   end
   
   def add(condition)
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index b3a472720..26f8408d6 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -169,6 +169,7 @@ div#activity dd { margin-bottom: 1em; }
 div#activity dt { margin-bottom: 1px; }
 div#activity dt .time { color: #777; font-size: 80%; }
 div#activity dd .description { font-style: italic; }
+div#activity span.project:after { content: " -"; }
 
 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
diff --git a/test/fixtures/issues.yml b/test/fixtures/issues.yml
index b3c662039..4f42d93c4 100644
--- a/test/fixtures/issues.yml
+++ b/test/fixtures/issues.yml
@@ -44,9 +44,9 @@ issues_003:
   start_date: <%= 1.day.from_now.to_date.to_s(:db) %>
   due_date: <%= 40.day.ago.to_date.to_s(:db) %>
 issues_004: 
-  created_on: 2006-07-19 21:07:27 +02:00
+  created_on: <%= 5.days.ago.to_date.to_s(:db) %>
   project_id: 2
-  updated_on: 2006-07-19 21:07:27 +02:00
+  updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
   priority_id: 4
   subject: Issue on project 2
   id: 4
@@ -57,4 +57,18 @@ issues_004:
   assigned_to_id: 
   author_id: 2
   status_id: 1
+issues_005: 
+  created_on: <%= 5.days.ago.to_date.to_s(:db) %>
+  project_id: 3
+  updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
+  priority_id: 4
+  subject: Subproject issue
+  id: 5
+  fixed_version_id: 
+  category_id: 
+  description: This is an issue on a cookbook subproject
+  tracker_id: 1
+  assigned_to_id: 
+  author_id: 2
+  status_id: 1
   
diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml
index 5bb2438dd..f82f376c1 100644
--- a/test/fixtures/messages.yml
+++ b/test/fixtures/messages.yml
@@ -45,8 +45,8 @@ messages_004:
   parent_id: 
   board_id: 1
 messages_005: 
-  created_on: 2007-09-12 17:18:00 +02:00
-  updated_on: 2007-09-12 17:18:00 +02:00
+  created_on: <%= 3.days.ago.to_date.to_s(:db) %>
+  updated_on: <%= 3.days.ago.to_date.to_s(:db) %>
   subject: 'RE: post 2'
   id: 5
   replies_count: 0
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index f610469df..75b4673a1 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -22,7 +22,8 @@ require 'projects_controller'
 class ProjectsController; def rescue_action(e) raise e end; end
 
 class ProjectsControllerTest < Test::Unit::TestCase
-  fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
+  fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
+           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
 
   def setup
     @controller = ProjectsController.new
@@ -129,11 +130,15 @@ class ProjectsControllerTest < Test::Unit::TestCase
     assert assigns(:versions).include?(Version.find(1))
   end
 
-  def test_activity
-    get :activity, :id => 1
+  def test_project_activity
+    get :activity, :id => 1, :with_subprojects => 0
     assert_response :success
     assert_template 'activity'
     assert_not_nil assigns(:events_by_day)
+    assert_not_nil assigns(:events)
+
+    # subproject issue not included by default
+    assert !assigns(:events).include?(Issue.find(5))
     
     assert_tag :tag => "h3", 
                :content => /#{2.days.ago.to_date.day}/,
@@ -163,6 +168,53 @@ class ProjectsControllerTest < Test::Unit::TestCase
                }
   end
   
+  def test_activity_with_subprojects
+    get :activity, :id => 1, :with_subprojects => 1
+    assert_response :success
+    assert_template 'activity'
+    assert_not_nil assigns(:events)
+    
+    assert assigns(:events).include?(Issue.find(1))
+    assert !assigns(:events).include?(Issue.find(4))
+    # subproject issue
+    assert assigns(:events).include?(Issue.find(5))
+  end
+  
+  def test_global_activity_anonymous
+    get :activity
+    assert_response :success
+    assert_template 'activity'
+    assert_not_nil assigns(:events)
+    
+    assert assigns(:events).include?(Issue.find(1))
+    # Issue of a private project
+    assert !assigns(:events).include?(Issue.find(4))
+  end
+  
+  def test_global_activity_logged_user
+    @request.session[:user_id] = 2 # manager
+    get :activity
+    assert_response :success
+    assert_template 'activity'
+    assert_not_nil assigns(:events)
+    
+    assert assigns(:events).include?(Issue.find(1))
+    # Issue of a private project the user belongs to
+    assert assigns(:events).include?(Issue.find(4))
+  end
+
+  
+  def test_global_activity_with_all_types
+    get :activity, :show_issues => 1, :show_news => 1, :show_files => 1, :show_documents => 1, :show_changesets => 1, :show_wiki_pages => 1, :show_messages => 1
+    assert_response :success
+    assert_template 'activity'
+    assert_not_nil assigns(:events)
+    
+    assert assigns(:events).include?(Issue.find(1))
+    assert !assigns(:events).include?(Issue.find(4))
+    assert assigns(:events).include?(Message.find(5))
+  end
+
   def test_calendar
     get :calendar, :id => 1
     assert_response :success
diff --git a/test/unit/project_test.rb b/test/unit/project_test.rb
index b05b7b09f..f7da6ecb5 100644
--- a/test/unit/project_test.rb
+++ b/test/unit/project_test.rb
@@ -126,13 +126,4 @@ class ProjectTest < Test::Unit::TestCase
     assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
     assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
   end
-
-  def test_issues_status_changes
-    journals = @ecookbook.issues_status_changes 3.days.ago.to_date, Date.today
-    assert_equal 1, journals.size
-    assert_kind_of Journal, journals.first
-    
-    journals = @ecookbook.issues_status_changes 30.days.ago.to_date, 10.days.ago.to_date
-    assert_equal 0, journals.size
-  end
 end
-- 
GitLab