From fce4615f10ad81b9070e65a45f9d37b1c571ccd7 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sun, 30 Nov 2008 11:18:22 +0000 Subject: [PATCH] Display latest user's activity on account/show view. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2066 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/account_controller.rb | 8 ++++-- app/helpers/application_helper.rb | 12 +++++++++ app/helpers/projects_helper.rb | 12 --------- app/models/attachment.rb | 2 ++ app/models/changeset.rb | 1 + app/models/issue.rb | 3 ++- app/models/journal.rb | 1 + app/models/message.rb | 3 ++- app/models/news.rb | 3 ++- app/models/wiki_content.rb | 1 + app/views/account/show.rhtml | 25 +++++++++++++++++++ lib/redmine/activity/fetcher.rb | 10 ++++++-- test/unit/activity_test.rb | 9 +++++++ .../lib/acts_as_activity_provider.rb | 18 ++++++++++--- 14 files changed, 86 insertions(+), 22 deletions(-) diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index 4b2ec8317..f327dd5b6 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2008 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 @@ -31,6 +31,10 @@ class AccountController < ApplicationController @memberships = @user.memberships.select do |membership| membership.project.is_public? || (User.current.member_of?(membership.project)) end + + events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) + @events_by_day = events.group_by(&:event_date) + rescue ActiveRecord::RecordNotFound render_404 end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 248704609..5995187d7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -105,6 +105,18 @@ module ApplicationHelper @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format) include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format) end + + def format_activity_title(text) + h(truncate_single_line(text, 100)) + end + + def format_activity_day(date) + date == Date.today ? l(:label_today).titleize : format_date(date) + end + + def format_activity_description(text) + h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...')) + end def distance_of_date_in_words(from_date, to_date = 0) from_date = from_date.to_date if from_date.respond_to?(:to_date) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index cd2e743fe..6f5e03349 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -21,18 +21,6 @@ module ProjectsHelper link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options end - def format_activity_title(text) - h(truncate_single_line(text, 100)) - end - - def format_activity_day(date) - date == Date.today ? l(:label_today).titleize : format_date(date) - end - - def format_activity_description(text) - h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...')) - end - def project_settings_tabs tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 95ba8491f..f838076c6 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -30,12 +30,14 @@ class Attachment < ActiveRecord::Base acts_as_activity_provider :type => 'files', :permission => :view_files, + :author_key => :author_id, :find_options => {: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"} acts_as_activity_provider :type => 'documents', :permission => :view_documents, + :author_key => :author_id, :find_options => {: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"} diff --git a/app/models/changeset.rb b/app/models/changeset.rb index dd38ce757..759d240df 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -34,6 +34,7 @@ class Changeset < ActiveRecord::Base :date_column => 'committed_on' acts_as_activity_provider :timestamp => "#{table_name}.committed_on", + :author_key => :user_id, :find_options => {:include => {:repository => :project}} validates_presence_of :repository_id, :revision, :committed_on, :commit_date diff --git a/app/models/issue.rb b/app/models/issue.rb index 4701e41f1..7488850af 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -42,7 +42,8 @@ class Issue < ActiveRecord::Base acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"}, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]} + acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}, + :author_key => :author_id validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status validates_length_of :subject, :maximum => 255 diff --git a/app/models/journal.rb b/app/models/journal.rb index 71a51290b..72e7eb91c 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -33,6 +33,7 @@ class Journal < ActiveRecord::Base acts_as_activity_provider :type => 'issues', :permission => :view_issues, + :author_key => :user_id, :find_options => {:include => [{:issue => :project}, :details, :user], :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} diff --git a/app/models/message.rb b/app/models/message.rb index 9a313e822..acb300f46 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -32,7 +32,8 @@ class Message < ActiveRecord::Base :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :anchor => "message-#{o.id}"})} - acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]} + acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}, + :author_key => :author_id acts_as_watchable attr_protected :locked, :sticky diff --git a/app/models/news.rb b/app/models/news.rb index 969b37a0d..5949a731b 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -26,7 +26,8 @@ class News < ActiveRecord::Base acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => [:project, :author]} + acts_as_activity_provider :find_options => {:include => [:project, :author]}, + :author_key => :author_id # returns latest news for projects visible by user def self.latest(user = User.current, count = 5) diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 4a4c5c270..e958e7b24 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -37,6 +37,7 @@ class WikiContent < ActiveRecord::Base acts_as_activity_provider :type => 'wiki_edits', :timestamp => "#{WikiContent.versioned_table_name}.updated_on", + :author_key => "#{WikiContent.versioned_table_name}.author_id", :permission => :view_wiki_edits, :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + diff --git a/app/views/account/show.rhtml b/app/views/account/show.rhtml index 175a11c75..e7b0276f7 100644 --- a/app/views/account/show.rhtml +++ b/app/views/account/show.rhtml @@ -4,6 +4,7 @@

<%= avatar @user %> <%=h @user.name %>

+

<%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %>

<% end %> +
+ +
+<% unless @events_by_day.empty? %>

<%=l(:label_activity)%>

+

<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>

+ +
+<% @events_by_day.keys.sort.reverse.each do |day| %> +

<%= format_activity_day(day) %>

+
+<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> +
+ <%= format_time(e.event_datetime, false) %> + <%= content_tag('span', h(e.project), :class => 'project') %> + <%= link_to format_activity_title(e.event_title), e.event_url %>
+
<%= format_activity_description(e.event_description) %>
+<% end -%> +
+<% end -%> +
+<% end %> +
+ +<% html_title @user.name %> diff --git a/lib/redmine/activity/fetcher.rb b/lib/redmine/activity/fetcher.rb index adaead564..b12caa441 100644 --- a/lib/redmine/activity/fetcher.rb +++ b/lib/redmine/activity/fetcher.rb @@ -25,7 +25,7 @@ module Redmine @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } def initialize(user, options={}) - options.assert_valid_keys(:project, :with_subprojects) + options.assert_valid_keys(:project, :with_subprojects, :author) @user = user @project = options[:project] @options = options @@ -58,14 +58,20 @@ module Redmine end # Returns an array of events for the given date range - def events(from, to) + def events(from = nil, to = nil, options={}) e = [] + @options[:limit] = options[:limit] @scope.each do |event_type| constantized_providers(event_type).each do |provider| e += provider.find_events(event_type, @user, from, to, @options) end end + + if options[:limit] + e.sort! {|a,b| b.event_date <=> a.event_date} + e = e.slice(0, options[:limit]) + end e end diff --git a/test/unit/activity_test.rb b/test/unit/activity_test.rb index ccda9f119..e5bc0d266 100644 --- a/test/unit/activity_test.rb +++ b/test/unit/activity_test.rb @@ -63,6 +63,15 @@ class ActivityTest < Test::Unit::TestCase assert events.include?(Issue.find(4)) end + def test_user_activity + user = User.find(2) + events = Redmine::Activity::Fetcher.new(User.anonymous, :author => user).events(nil, nil, :limit => 10) + + assert(events.size > 0) + assert(events.size <= 10) + assert_nil(events.detect {|e| e.event_author != user}) + end + private def find_events(user, options={}) diff --git a/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb b/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb index 7c4fac8b1..d631935e0 100644 --- a/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb +++ b/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb @@ -29,7 +29,7 @@ module Redmine send :include, Redmine::Acts::ActivityProvider::InstanceMethods end - options.assert_valid_keys(:type, :permission, :timestamp, :find_options) + options.assert_valid_keys(:type, :permission, :timestamp, :author_key, :find_options) self.activity_provider_options ||= {} # One model can provide different event types @@ -39,6 +39,7 @@ module Redmine options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless options.has_key?(:permission) options[:timestamp] ||= "#{table_name}.created_on" options[:find_options] ||= {} + options[:author_key] = "#{table_name}.#{options[:author_key]}" if options[:author_key].is_a?(Symbol) self.activity_provider_options[event_type] = options end end @@ -54,10 +55,21 @@ module Redmine provider_options = activity_provider_options[event_type] raise "#{self.name} can not provide #{event_type} events." if provider_options.nil? - cond = ARCondition.new(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to]) + scope_options = {} + cond = ARCondition.new + if from && to + cond.add(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to]) + end + if options[:author] + return [] if provider_options[:author_key].nil? + cond.add(["#{provider_options[:author_key]} = ?", options[:author].id]) + end cond.add(Project.allowed_to_condition(user, provider_options[:permission], options)) if provider_options[:permission] + scope_options[:conditions] = cond.conditions + scope_options[:order] = "#{provider_options[:timestamp]} DESC" + scope_options[:limit] = options[:limit] - with_scope(:find => { :conditions => cond.conditions }) do + with_scope(:find => scope_options) do find(:all, provider_options[:find_options]) end end -- 2.39.5