@@ -1,5 +1,5 | |||||
1 |
# |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-200 |
|
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 => |
|
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