@@ -1,5 +1,5 | |||
|
1 |
# |
|
|
2 |
# Copyright (C) 2006-200 |
|
|
1 | # Redmine - project management software | |
|
2 | # Copyright (C) 2006-2008 Jean-Philippe Lang | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software; you can redistribute it and/or |
|
5 | 5 | # modify it under the terms of the GNU General Public License |
@@ -31,6 +31,10 class AccountController < ApplicationController | |||
|
31 | 31 | @memberships = @user.memberships.select do |membership| |
|
32 | 32 | membership.project.is_public? || (User.current.member_of?(membership.project)) |
|
33 | 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 | 38 | rescue ActiveRecord::RecordNotFound |
|
35 | 39 | render_404 |
|
36 | 40 | end |
@@ -105,6 +105,18 module ApplicationHelper | |||
|
105 | 105 | @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format) |
|
106 | 106 | include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format) |
|
107 | 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 | 121 | def distance_of_date_in_words(from_date, to_date = 0) |
|
110 | 122 | from_date = from_date.to_date if from_date.respond_to?(:to_date) |
@@ -21,18 +21,6 module ProjectsHelper | |||
|
21 | 21 | link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options |
|
22 | 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 | 24 | def project_settings_tabs |
|
37 | 25 | tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, |
|
38 | 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 | 31 | acts_as_activity_provider :type => 'files', |
|
32 | 32 | :permission => :view_files, |
|
33 | :author_key => :author_id, | |
|
33 | 34 | :find_options => {:select => "#{Attachment.table_name}.*", |
|
34 | 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 | 36 | "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"} |
|
36 | 37 | |
|
37 | 38 | acts_as_activity_provider :type => 'documents', |
|
38 | 39 | :permission => :view_documents, |
|
40 | :author_key => :author_id, | |
|
39 | 41 | :find_options => {:select => "#{Attachment.table_name}.*", |
|
40 | 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 | 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 | 34 | :date_column => 'committed_on' |
|
35 | 35 | |
|
36 | 36 | acts_as_activity_provider :timestamp => "#{table_name}.committed_on", |
|
37 | :author_key => :user_id, | |
|
37 | 38 | :find_options => {:include => {:repository => :project}} |
|
38 | 39 | |
|
39 | 40 | validates_presence_of :repository_id, :revision, :committed_on, :commit_date |
@@ -42,7 +42,8 class Issue < ActiveRecord::Base | |||
|
42 | 42 | acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"}, |
|
43 | 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 | 48 | validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status |
|
48 | 49 | validates_length_of :subject, :maximum => 255 |
@@ -33,6 +33,7 class Journal < ActiveRecord::Base | |||
|
33 | 33 | |
|
34 | 34 | acts_as_activity_provider :type => 'issues', |
|
35 | 35 | :permission => :view_issues, |
|
36 | :author_key => :user_id, | |
|
36 | 37 | :find_options => {:include => [{:issue => :project}, :details, :user], |
|
37 | 38 | :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + |
|
38 | 39 | " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} |
@@ -32,7 +32,8 class Message < ActiveRecord::Base | |||
|
32 | 32 | :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : |
|
33 | 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 | 37 | acts_as_watchable |
|
37 | 38 | |
|
38 | 39 | attr_protected :locked, :sticky |
@@ -26,7 +26,8 class News < ActiveRecord::Base | |||
|
26 | 26 | |
|
27 | 27 | acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project |
|
28 | 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 | 32 | # returns latest news for projects visible by user |
|
32 | 33 | def self.latest(user = User.current, count = 5) |
@@ -37,6 +37,7 class WikiContent < ActiveRecord::Base | |||
|
37 | 37 | |
|
38 | 38 | acts_as_activity_provider :type => 'wiki_edits', |
|
39 | 39 | :timestamp => "#{WikiContent.versioned_table_name}.updated_on", |
|
40 | :author_key => "#{WikiContent.versioned_table_name}.author_id", | |
|
40 | 41 | :permission => :view_wiki_edits, |
|
41 | 42 | :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + |
|
42 | 43 | "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + |
@@ -4,6 +4,7 | |||
|
4 | 4 | |
|
5 | 5 | <h2><%= avatar @user %> <%=h @user.name %></h2> |
|
6 | 6 | |
|
7 | <div class="splitcontentleft"> | |
|
7 | 8 | <p> |
|
8 | 9 | <%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %> |
|
9 | 10 | <ul> |
@@ -25,8 +26,32 | |||
|
25 | 26 | <% end %> |
|
26 | 27 | </ul> |
|
27 | 28 | <% end %> |
|
29 | </div> | |
|
30 | ||
|
31 | <div class="splitcontentright"> | |
|
28 | 32 | |
|
33 | <% unless @events_by_day.empty? %> | |
|
29 | 34 | <h3><%=l(:label_activity)%></h3> |
|
35 | ||
|
30 | 36 | <p> |
|
31 | 37 | <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> |
|
32 | 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 | 25 | @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } |
|
26 | 26 | |
|
27 | 27 | def initialize(user, options={}) |
|
28 | options.assert_valid_keys(:project, :with_subprojects) | |
|
28 | options.assert_valid_keys(:project, :with_subprojects, :author) | |
|
29 | 29 | @user = user |
|
30 | 30 | @project = options[:project] |
|
31 | 31 | @options = options |
@@ -58,14 +58,20 module Redmine | |||
|
58 | 58 | end |
|
59 | 59 | |
|
60 | 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 | 62 | e = [] |
|
63 | @options[:limit] = options[:limit] | |
|
63 | 64 | |
|
64 | 65 | @scope.each do |event_type| |
|
65 | 66 | constantized_providers(event_type).each do |provider| |
|
66 | 67 | e += provider.find_events(event_type, @user, from, to, @options) |
|
67 | 68 | end |
|
68 | 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 | 75 | e |
|
70 | 76 | end |
|
71 | 77 |
@@ -63,6 +63,15 class ActivityTest < Test::Unit::TestCase | |||
|
63 | 63 | assert events.include?(Issue.find(4)) |
|
64 | 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 | 75 | private |
|
67 | 76 | |
|
68 | 77 | def find_events(user, options={}) |
@@ -29,7 +29,7 module Redmine | |||
|
29 | 29 | send :include, Redmine::Acts::ActivityProvider::InstanceMethods |
|
30 | 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 | 33 | self.activity_provider_options ||= {} |
|
34 | 34 | |
|
35 | 35 | # One model can provide different event types |
@@ -39,6 +39,7 module Redmine | |||
|
39 | 39 | options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless options.has_key?(:permission) |
|
40 | 40 | options[:timestamp] ||= "#{table_name}.created_on" |
|
41 | 41 | options[:find_options] ||= {} |
|
42 | options[:author_key] = "#{table_name}.#{options[:author_key]}" if options[:author_key].is_a?(Symbol) | |
|
42 | 43 | self.activity_provider_options[event_type] = options |
|
43 | 44 | end |
|
44 | 45 | end |
@@ -54,10 +55,21 module Redmine | |||
|
54 | 55 | provider_options = activity_provider_options[event_type] |
|
55 | 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 | 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 | 73 | find(:all, provider_options[:find_options]) |
|
62 | 74 | end |
|
63 | 75 | end |
General Comments 0
You need to be logged in to leave comments.
Login now