Index: vendor/plugins/acts_as_searchable/lib/acts_as_searchable.rb =================================================================== --- vendor/plugins/acts_as_searchable/lib/acts_as_searchable.rb (revision 5265) +++ vendor/plugins/acts_as_searchable/lib/acts_as_searchable.rb (working copy) @@ -31,8 +31,9 @@ # * :permission - permission required to search the model (default to :view_"objects") def acts_as_searchable(options = {}) return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods) - + cattr_accessor :searchable_options + options.assert_valid_keys(:result_type, :columns, :project_key, :date_column, :order_column, :permission, :include) self.searchable_options = options if searchable_options[:columns].nil? @@ -44,13 +45,14 @@ searchable_options[:project_key] ||= "#{table_name}.project_id" searchable_options[:date_column] ||= "#{table_name}.created_on" searchable_options[:order_column] ||= searchable_options[:date_column] - + searchable_options[:result_type] ||= self.name.underscore + # Permission needed to search this model searchable_options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless searchable_options.has_key?(:permission) - + # Should we search custom fields on this model ? searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil? - + send :include, Redmine::Acts::Searchable::InstanceMethods end end @@ -60,6 +62,10 @@ base.extend ClassMethods end + def searchable_result_type + searchable_options[:result_type] + end + module ClassMethods # Searches the model for the given tokens # projects argument can be either nil (will search all projects), a project or an array of projects @@ -67,21 +73,21 @@ def search(tokens, projects=nil, options={}) tokens = [] << tokens unless tokens.is_a?(Array) projects = [] << projects unless projects.nil? || projects.is_a?(Array) - + find_options = {:include => searchable_options[:include]} find_options[:order] = "#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC') - + limit_options = {} limit_options[:limit] = options[:limit] if options[:limit] if options[:offset] limit_options[:conditions] = "(#{searchable_options[:date_column]} " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')" end - + columns = searchable_options[:columns] columns = columns[0..0] if options[:titles_only] - + token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} - + if !options[:titles_only] && searchable_options[:search_custom_fields] searchable_custom_field_ids = CustomField.find(:all, :select => 'id', @@ -94,19 +100,19 @@ token_clauses << custom_field_sql end end - + sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] - + project_conditions = [] project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : Project.allowed_to_condition(User.current, searchable_options[:permission])) project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? - + results = [] results_count = 0 - + with_scope(:find => {:conditions => project_conditions.join(' AND ')}) do with_scope(:find => find_options) do results_count = count(:all) Index: app/models/issue.rb =================================================================== --- app/models/issue.rb (revision 5265) +++ app/models/issue.rb (working copy) @@ -42,10 +42,11 @@ :include => [:project, :journals], # sort by id so that limited eager loading doesn't break with postgresql :order_column => "#{table_name}.id" - acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, + + acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.initial_status}): #{o.subject}"}, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}, :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') } - + acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}, :author_key => :author_id @@ -85,7 +86,18 @@ before_save :close_duplicates, :update_done_ratio_from_issue_status after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal after_destroy :update_parent_attributes - + + # Retrieves issue's original status from journal if modified since issue creation + def initial_status + + first_status_modification_journal = self.journals.first( { + :joins => :details, + :conditions => { JournalDetail.table_name => { :property => 'attr', :prop_key => 'status_id' } }, + :order => :created_on } ) + + first_status_modification_journal ? first_status_modification_journal.prev_status : self.status + end + # Returns a SQL conditions string used to find all issues visible by the specified user def self.visible_condition(user, options={}) Project.allowed_to_condition(user, :view_issues, options) Index: app/models/journal.rb =================================================================== --- app/models/journal.rb (revision 5265) +++ app/models/journal.rb (working copy) @@ -37,7 +37,7 @@ :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 <> '')"} - + named_scope :visible, lambda {|*args| { :include => {:issue => :project}, :conditions => Issue.visible_condition(args.first || User.current) @@ -53,7 +53,12 @@ c = details.detect {|detail| detail.prop_key == 'status_id'} (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil end - + + def prev_status + c = details.detect {|detail| detail.prop_key == 'status_id'} + (c && c.old_value) ? IssueStatus.find_by_id(c.old_value.to_i) : nil + end + def new_value_for(prop) c = details.detect {|detail| detail.prop_key == prop} c ? c.value : nil Index: app/views/search/results/_changeset.html.erb =================================================================== --- app/views/search/results/_changeset.html.erb (revision 0) +++ app/views/search/results/_changeset.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => changeset %> Index: app/views/search/results/_document.html.erb =================================================================== --- app/views/search/results/_document.html.erb (revision 0) +++ app/views/search/results/_document.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => document %> Index: app/views/search/results/_event.html.erb =================================================================== --- app/views/search/results/_event.html.erb (revision 0) +++ app/views/search/results/_event.html.erb (revision 0) @@ -0,0 +1,3 @@ +
<%= content_tag('span', h(event.project), :class => 'project') unless @project == event.project %> <%= link_to highlight_tokens(truncate(event.event_title, :length => 255), @tokens), event.event_url %>
+
<%= highlight_tokens(event.event_description, @tokens) %> +<%= format_time(event.event_datetime) %>
Index: app/views/search/results/_issue.html.erb =================================================================== --- app/views/search/results/_issue.html.erb (revision 0) +++ app/views/search/results/_issue.html.erb (revision 0) @@ -0,0 +1,5 @@ +
+<%= content_tag('span', h(issue.project), :class => 'project') unless @project == issue.project %> +<%= link_to highlight_tokens(truncate("#{issue.tracker.name} ##{issue.id} (#{issue.status()}): #{issue.subject}", :length => 255), @tokens), issue.event_url %>
+
<%= highlight_tokens(issue.event_description, @tokens) %> +<%= format_time(issue.event_datetime) %>
Index: app/views/search/results/_journal.html.erb =================================================================== --- app/views/search/results/_journal.html.erb (revision 0) +++ app/views/search/results/_journal.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => journal %> Index: app/views/search/results/_message.html.erb =================================================================== --- app/views/search/results/_message.html.erb (revision 0) +++ app/views/search/results/_message.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => message %> Index: app/views/search/results/_news.html.erb =================================================================== --- app/views/search/results/_news.html.erb (revision 0) +++ app/views/search/results/_news.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => news %> Index: app/views/search/results/_project.html.erb =================================================================== --- app/views/search/results/_project.html.erb (revision 0) +++ app/views/search/results/_project.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => project %> Index: app/views/search/results/_wiki_page.html.erb =================================================================== --- app/views/search/results/_wiki_page.html.erb (revision 0) +++ app/views/search/results/_wiki_page.html.erb (revision 0) @@ -0,0 +1 @@ +<%= render :partial => "search/results/event", :object => wiki_page %> Index: app/views/search/index.rhtml =================================================================== --- app/views/search/index.rhtml (revision 5265) +++ app/views/search/index.rhtml (working copy) @@ -22,13 +22,11 @@
<%= render_results_by_type(@results_by_type) unless @scope.size == 1 %>
- +

<%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)

- <% @results.each do |e| %> -
<%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %>
-
<%= highlight_tokens(e.event_description, @tokens) %> - <%= format_time(e.event_datetime) %>
+ <% @results.each do |r| %> + <%= render :partial => "search/results/#{r.searchable_result_type.underscore}", :object => r %> <% end %>
<% end %> Index: test/unit/activity_test.rb =================================================================== --- test/unit/activity_test.rb (revision 5265) +++ test/unit/activity_test.rb (working copy) @@ -24,7 +24,28 @@ def setup @project = Project.find(1) end - + + def test_activity_contains_issue_status_update_events + events = find_events(User.anonymous, :project => @project) + assert_not_nil events + + events.sort! { |x,y| x.event_datetime <=> y.event_datetime } + + issue_creation_events = events.find_all { |event| event == Issue.find(8) } + + assert_equal 1, issue_creation_events.size + assert_equal IssueStatus.find_by_name('New'), issue_creation_events.first.initial_status + + issue_status_update_events = events.find_all { |event| event.is_a?(Journal) && event.issue == Issue.find(8) } + + assert_equal 2, issue_status_update_events.size + assert_equal IssueStatus.find_by_name('New'), issue_status_update_events.first.prev_status + assert_equal IssueStatus.find_by_name('Resolved'), issue_status_update_events.first.new_status + + assert_equal IssueStatus.find_by_name('Resolved'), issue_status_update_events.last.prev_status + assert_equal IssueStatus.find_by_name('Closed'), issue_status_update_events.last.new_status + end + def test_activity_without_subprojects events = find_events(User.anonymous, :project => @project) assert_not_nil events Index: test/fixtures/journal_details.yml =================================================================== --- test/fixtures/journal_details.yml (revision 5265) +++ test/fixtures/journal_details.yml (working copy) @@ -34,3 +34,17 @@ value: New value prop_key: 2 journal_id: 3 +journal_details_006: + old_value: "1" + property: attr + id: 6 + value: "3" + prop_key: status_id + journal_id: 5 +journal_details_007: + old_value: "3" + property: attr + id: 7 + value: "5" + prop_key: status_id + journal_id: 6 Index: test/fixtures/issues.yml =================================================================== --- test/fixtures/issues.yml (revision 5265) +++ test/fixtures/issues.yml (working copy) @@ -132,7 +132,7 @@ lft: 1 rgt: 2 issues_008: - created_on: <%= 10.days.ago.to_date.to_s(:db) %> + created_on: <%= 12.days.ago.to_date.to_s(:db) %> project_id: 1 updated_on: <%= 10.days.ago.to_date.to_s(:db) %> priority_id: 5 Index: test/fixtures/journals.yml =================================================================== --- test/fixtures/journals.yml (revision 5265) +++ test/fixtures/journals.yml (working copy) @@ -27,3 +27,17 @@ journalized_type: Issue user_id: 1 journalized_id: 6 +journals_005: + created_on: <%= 11.days.ago.to_date.to_s(:db) %> + notes: "Resolving issue 8." + id: 5 + journalized_type: Issue + user_id: 1 + journalized_id: 8 +journals_006: + created_on: <%= 10.days.ago.to_date.to_s(:db) %> + notes: "Closing issue 8." + id: 6 + journalized_type: Issue + user_id: 1 + journalized_id: 8