##// END OF EJS Templates
Display latest user's activity on account/show view....
Jean-Philippe Lang -
r2064:fce4615f10ad
parent child
Show More
@@ -1,5 +1,5
1 # redMine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
@@ -31,6 +31,10 class AccountController < ApplicationController
31 @memberships = @user.memberships.select do |membership|
31 @memberships = @user.memberships.select do |membership|
32 membership.project.is_public? || (User.current.member_of?(membership.project))
32 membership.project.is_public? || (User.current.member_of?(membership.project))
33 end
33 end
34
35 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
36 @events_by_day = events.group_by(&:event_date)
37
34 rescue ActiveRecord::RecordNotFound
38 rescue ActiveRecord::RecordNotFound
35 render_404
39 render_404
36 end
40 end
@@ -105,6 +105,18 module ApplicationHelper
105 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
105 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
106 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
106 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
107 end
107 end
108
109 def format_activity_title(text)
110 h(truncate_single_line(text, 100))
111 end
112
113 def format_activity_day(date)
114 date == Date.today ? l(:label_today).titleize : format_date(date)
115 end
116
117 def format_activity_description(text)
118 h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...'))
119 end
108
120
109 def distance_of_date_in_words(from_date, to_date = 0)
121 def distance_of_date_in_words(from_date, to_date = 0)
110 from_date = from_date.to_date if from_date.respond_to?(:to_date)
122 from_date = from_date.to_date if from_date.respond_to?(:to_date)
@@ -21,18 +21,6 module ProjectsHelper
21 link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
21 link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
22 end
22 end
23
23
24 def format_activity_title(text)
25 h(truncate_single_line(text, 100))
26 end
27
28 def format_activity_day(date)
29 date == Date.today ? l(:label_today).titleize : format_date(date)
30 end
31
32 def format_activity_description(text)
33 h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...'))
34 end
35
36 def project_settings_tabs
24 def project_settings_tabs
37 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
25 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
38 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
26 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
@@ -30,12 +30,14 class Attachment < ActiveRecord::Base
30
30
31 acts_as_activity_provider :type => 'files',
31 acts_as_activity_provider :type => 'files',
32 :permission => :view_files,
32 :permission => :view_files,
33 :author_key => :author_id,
33 :find_options => {:select => "#{Attachment.table_name}.*",
34 :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 :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 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"}
36
37
37 acts_as_activity_provider :type => 'documents',
38 acts_as_activity_provider :type => 'documents',
38 :permission => :view_documents,
39 :permission => :view_documents,
40 :author_key => :author_id,
39 :find_options => {:select => "#{Attachment.table_name}.*",
41 :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 " +
42 :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"}
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
@@ -34,6 +34,7 class Changeset < ActiveRecord::Base
34 :date_column => 'committed_on'
34 :date_column => 'committed_on'
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 :author_key => :user_id,
37 :find_options => {:include => {:repository => :project}}
38 :find_options => {:include => {:repository => :project}}
38
39
39 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
@@ -42,7 +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]}
45 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
46 :author_key => :author_id
46
47
47 validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
48 validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
48 validates_length_of :subject, :maximum => 255
49 validates_length_of :subject, :maximum => 255
@@ -33,6 +33,7 class Journal < ActiveRecord::Base
33
33
34 acts_as_activity_provider :type => 'issues',
34 acts_as_activity_provider :type => 'issues',
35 :permission => :view_issues,
35 :permission => :view_issues,
36 :author_key => :user_id,
36 :find_options => {:include => [{:issue => :project}, :details, :user],
37 :find_options => {:include => [{:issue => :project}, :details, :user],
37 :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
38 :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
38 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
39 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
@@ -32,7 +32,8 class Message < ActiveRecord::Base
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]}
35 acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
36 :author_key => :author_id
36 acts_as_watchable
37 acts_as_watchable
37
38
38 attr_protected :locked, :sticky
39 attr_protected :locked, :sticky
@@ -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 acts_as_activity_provider :find_options => {:include => [:project, :author]}
29 acts_as_activity_provider :find_options => {:include => [:project, :author]},
30 :author_key => :author_id
30
31
31 # returns latest news for projects visible by user
32 # returns latest news for projects visible by user
32 def self.latest(user = User.current, count = 5)
33 def self.latest(user = User.current, count = 5)
@@ -37,6 +37,7 class WikiContent < ActiveRecord::Base
37
37
38 acts_as_activity_provider :type => 'wiki_edits',
38 acts_as_activity_provider :type => 'wiki_edits',
39 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
39 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
40 :author_key => "#{WikiContent.versioned_table_name}.author_id",
40 :permission => :view_wiki_edits,
41 :permission => :view_wiki_edits,
41 :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
42 :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}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
@@ -4,6 +4,7
4
4
5 <h2><%= avatar @user %> <%=h @user.name %></h2>
5 <h2><%= avatar @user %> <%=h @user.name %></h2>
6
6
7 <div class="splitcontentleft">
7 <p>
8 <p>
8 <%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %>
9 <%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %>
9 <ul>
10 <ul>
@@ -25,8 +26,32
25 <% end %>
26 <% end %>
26 </ul>
27 </ul>
27 <% end %>
28 <% end %>
29 </div>
30
31 <div class="splitcontentright">
28
32
33 <% unless @events_by_day.empty? %>
29 <h3><%=l(:label_activity)%></h3>
34 <h3><%=l(:label_activity)%></h3>
35
30 <p>
36 <p>
31 <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>
37 <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>
32 </p>
38 </p>
39
40 <div id="activity">
41 <% @events_by_day.keys.sort.reverse.each do |day| %>
42 <h4><%= format_activity_day(day) %></h4>
43 <dl>
44 <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
45 <dt class="<%= e.event_type %>">
46 <span class="time"><%= format_time(e.event_datetime, false) %></span>
47 <%= content_tag('span', h(e.project), :class => 'project') %>
48 <%= link_to format_activity_title(e.event_title), e.event_url %></dt>
49 <dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd>
50 <% end -%>
51 </dl>
52 <% end -%>
53 </div>
54 <% end %>
55 </div>
56
57 <% html_title @user.name %>
@@ -25,7 +25,7 module Redmine
25 @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } }
25 @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } }
26
26
27 def initialize(user, options={})
27 def initialize(user, options={})
28 options.assert_valid_keys(:project, :with_subprojects)
28 options.assert_valid_keys(:project, :with_subprojects, :author)
29 @user = user
29 @user = user
30 @project = options[:project]
30 @project = options[:project]
31 @options = options
31 @options = options
@@ -58,14 +58,20 module Redmine
58 end
58 end
59
59
60 # Returns an array of events for the given date range
60 # Returns an array of events for the given date range
61 def events(from, to)
61 def events(from = nil, to = nil, options={})
62 e = []
62 e = []
63 @options[:limit] = options[:limit]
63
64
64 @scope.each do |event_type|
65 @scope.each do |event_type|
65 constantized_providers(event_type).each do |provider|
66 constantized_providers(event_type).each do |provider|
66 e += provider.find_events(event_type, @user, from, to, @options)
67 e += provider.find_events(event_type, @user, from, to, @options)
67 end
68 end
68 end
69 end
70
71 if options[:limit]
72 e.sort! {|a,b| b.event_date <=> a.event_date}
73 e = e.slice(0, options[:limit])
74 end
69 e
75 e
70 end
76 end
71
77
@@ -63,6 +63,15 class ActivityTest < Test::Unit::TestCase
63 assert events.include?(Issue.find(4))
63 assert events.include?(Issue.find(4))
64 end
64 end
65
65
66 def test_user_activity
67 user = User.find(2)
68 events = Redmine::Activity::Fetcher.new(User.anonymous, :author => user).events(nil, nil, :limit => 10)
69
70 assert(events.size > 0)
71 assert(events.size <= 10)
72 assert_nil(events.detect {|e| e.event_author != user})
73 end
74
66 private
75 private
67
76
68 def find_events(user, options={})
77 def find_events(user, options={})
@@ -29,7 +29,7 module Redmine
29 send :include, Redmine::Acts::ActivityProvider::InstanceMethods
29 send :include, Redmine::Acts::ActivityProvider::InstanceMethods
30 end
30 end
31
31
32 options.assert_valid_keys(:type, :permission, :timestamp, :find_options)
32 options.assert_valid_keys(:type, :permission, :timestamp, :author_key, :find_options)
33 self.activity_provider_options ||= {}
33 self.activity_provider_options ||= {}
34
34
35 # One model can provide different event types
35 # One model can provide different event types
@@ -39,6 +39,7 module Redmine
39 options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless options.has_key?(:permission)
39 options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless options.has_key?(:permission)
40 options[:timestamp] ||= "#{table_name}.created_on"
40 options[:timestamp] ||= "#{table_name}.created_on"
41 options[:find_options] ||= {}
41 options[:find_options] ||= {}
42 options[:author_key] = "#{table_name}.#{options[:author_key]}" if options[:author_key].is_a?(Symbol)
42 self.activity_provider_options[event_type] = options
43 self.activity_provider_options[event_type] = options
43 end
44 end
44 end
45 end
@@ -54,10 +55,21 module Redmine
54 provider_options = activity_provider_options[event_type]
55 provider_options = activity_provider_options[event_type]
55 raise "#{self.name} can not provide #{event_type} events." if provider_options.nil?
56 raise "#{self.name} can not provide #{event_type} events." if provider_options.nil?
56
57
57 cond = ARCondition.new(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to])
58 scope_options = {}
59 cond = ARCondition.new
60 if from && to
61 cond.add(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to])
62 end
63 if options[:author]
64 return [] if provider_options[:author_key].nil?
65 cond.add(["#{provider_options[:author_key]} = ?", options[:author].id])
66 end
58 cond.add(Project.allowed_to_condition(user, provider_options[:permission], options)) if provider_options[:permission]
67 cond.add(Project.allowed_to_condition(user, provider_options[:permission], options)) if provider_options[:permission]
68 scope_options[:conditions] = cond.conditions
69 scope_options[:order] = "#{provider_options[:timestamp]} DESC"
70 scope_options[:limit] = options[:limit]
59
71
60 with_scope(:find => { :conditions => cond.conditions }) do
72 with_scope(:find => scope_options) do
61 find(:all, provider_options[:find_options])
73 find(:all, provider_options[:find_options])
62 end
74 end
63 end
75 end
General Comments 0
You need to be logged in to leave comments. Login now