Project

General

Profile

Defect #7293 » activity_new_issue_event_status.patch

v3 - Etienne Massip, 2011-03-05 14:32

View differences:

test/unit/activity_test.rb (working copy)
24 24
  def setup
25 25
    @project = Project.find(1)
26 26
  end
27
  
27

  
28
  def test_activity_contains_issue_status_update_events
29
    events = find_events(User.anonymous, :project => @project)
30
    assert_not_nil events
31

  
32
    events.sort! { |x,y| x.event_datetime <=> y.event_datetime }
33

  
34
    issue_creation_events = events.find_all { |event| event == Issue.find(8) }
35

  
36
    assert_equal 1, issue_creation_events.size
37
    assert_equal IssueStatus.find_by_name('New'), issue_creation_events.first.initial_status
38

  
39
    issue_status_update_events = events.find_all { |event| event.is_a?(Journal) && event.issue == Issue.find(8) }
40

  
41
    assert_equal 2, issue_status_update_events.size
42
    assert_equal IssueStatus.find_by_name('New'), issue_status_update_events.first.prev_status
43
    assert_equal IssueStatus.find_by_name('Resolved'), issue_status_update_events.first.new_status
44

  
45
    assert_equal IssueStatus.find_by_name('Resolved'), issue_status_update_events.last.prev_status
46
    assert_equal IssueStatus.find_by_name('Closed'), issue_status_update_events.last.new_status
47
  end
48

  
28 49
  def test_activity_without_subprojects
29 50
    events = find_events(User.anonymous, :project => @project)
30 51
    assert_not_nil events
vendor/plugins/acts_as_searchable/lib/acts_as_searchable.rb (working copy)
24 24

  
25 25
      module ClassMethods
26 26
        # Options:
27
        # * :title - the title
27 28
        # * :columns - a column or an array of columns to search
28 29
        # * :project_key - project foreign key (default to project_id)
29 30
        # * :date_column - name of the datetime column (default to created_on)
30
        # * :sort_order - name of the column used to sort results (default to :date_column or created_on)
31
        # * :order_column - name of the column used to sort results (default to :date_column or created_on)
31 32
        # * :permission - permission required to search the model (default to :view_"objects")
32 33
        def acts_as_searchable(options = {})
33 34
          return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods)
34
  
35

  
36
          default_options = { :datetime => :created_on,
37
                              :title => :title,
38
                              :description => :description,
39
                              :author => :author,
40
                              :url => {:controller => 'welcome'},
41
                              :type => self.name.underscore.dasherize }
42

  
35 43
          cattr_accessor :searchable_options
36
          self.searchable_options = options
44
          options.assert_valid_keys(:title, :description, :datetime, :url, :type, :columns, :project_key, :date_column, :order_column, :permission, :include)
45
          self.searchable_options = default_options.merge(options)
37 46

  
38 47
          if searchable_options[:columns].nil?
39 48
            raise 'No searchable column defined.'
......
44 53
          searchable_options[:project_key] ||= "#{table_name}.project_id"
45 54
          searchable_options[:date_column] ||= "#{table_name}.created_on"
46 55
          searchable_options[:order_column] ||= searchable_options[:date_column]
47
          
56

  
48 57
          # Permission needed to search this model
49 58
          searchable_options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless searchable_options.has_key?(:permission)
50
          
59

  
51 60
          # Should we search custom fields on this model ?
52 61
          searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil?
53
          
62

  
54 63
          send :include, Redmine::Acts::Searchable::InstanceMethods
55 64
        end
56 65
      end
......
60 69
          base.extend ClassMethods
61 70
        end
62 71

  
72
        %w(datetime title description author type).each do |attr|
73
          src = <<-END_SRC
74
            def searchable_#{attr}
75
              option = searchable_options[:#{attr}]
76
              if option.is_a?(Proc)
77
                option.call(self)
78
              elsif option.is_a?(Symbol)
79
                send(option)
80
              else
81
                option
82
              end
83
            end
84
          END_SRC
85
          class_eval src, __FILE__, __LINE__
86
        end
87

  
88
        def searchable_url(options = {})
89
          option = searchable_options[:url]
90
          if option.is_a?(Proc)
91
            option.call(self).merge(options)
92
          elsif option.is_a?(Hash)
93
            option.merge(options)
94
          elsif option.is_a?(Symbol)
95
            send(option).merge(options)
96
          else
97
            option
98
          end
99
        end
100

  
63 101
        module ClassMethods
64 102
          # Searches the model for the given tokens
65 103
          # projects argument can be either nil (will search all projects), a project or an array of projects
......
94 132
                token_clauses << custom_field_sql
95 133
              end
96 134
            end
97
            
135

  
98 136
            sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ')
99
            
137

  
100 138
            find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort]
101
            
139

  
102 140
            project_conditions = []
103 141
            project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) :
104 142
                                                 Project.allowed_to_condition(User.current, searchable_options[:permission]))
105 143
            project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
106
            
144

  
107 145
            results = []
108 146
            results_count = 0
109
            
147

  
110 148
            with_scope(:find => {:conditions => project_conditions.join(' AND ')}) do
111 149
              with_scope(:find => find_options) do
112 150
                results_count = count(:all)
app/models/project.rb (working copy)
61 61
                     :delete_permission => :manage_files
62 62

  
63 63
  acts_as_customizable
64
  acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
64
  acts_as_searchable :title => lambda { |p| p.event_title } , :url => lambda { |p| p.event_url },
65
                     :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
65 66
  acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
66 67
                :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
67 68
                :author => nil
app/models/issue.rb (working copy)
17 17

  
18 18
class Issue < ActiveRecord::Base
19 19
  include Redmine::SafeAttributes
20
  
20

  
21 21
  belongs_to :project
22 22
  belongs_to :tracker
23 23
  belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
......
38 38
  acts_as_attachable :after_remove => :attachment_removed
39 39
  acts_as_customizable
40 40
  acts_as_watchable
41
  acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
41

  
42
  acts_as_searchable :title => lambda { |i| "#{i.tracker.name} ##{i.id} (#{i.status()}): #{i.subject}" },
43
                     :url => lambda { |i| i.event_url }, :type => lambda { |i| i.event_type },
44
                     :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
42 45
                     :include => [:project, :journals],
43 46
                     # sort by id so that limited eager loading doesn't break with postgresql
44 47
                     :order_column => "#{table_name}.id"
45
  acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
48

  
49
  acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.initial_status()}): #{o.subject}"},
46 50
                :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 51
                :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48 52
  
......
80 84
    }
81 85
  }
82 86

  
83
  named_scope :with_query, lambda {|query|
84
    {
85
      :conditions => Query.merge_conditions(query.statement)
86
    }
87
  }
88

  
89 87
  before_create :default_assign
90 88
  before_save :close_duplicates, :update_done_ratio_from_issue_status
91 89
  after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
92 90
  after_destroy :update_parent_attributes
93
  
91

  
92
  # Retrieves issue's original status from journal if modified since issue creation
93
  def initial_status()
94

  
95
    first_status_modification_journal = self.journals.first( {
96
      :joins => :details,
97
      :conditions => { JournalDetail.table_name => { :property => 'attr', :prop_key => 'status_id' } },
98
      :order => :created_on } )
99

  
100
    first_status_modification_journal ? first_status_modification_journal.prev_status : self.status
101
  end
102

  
94 103
  # Returns true if usr or current user is allowed to view the issue
95 104
  def visible?(usr=nil)
96 105
    (usr || User.current).allowed_to?(:view_issues, self.project)
app/models/document.rb (working copy)
20 20
  belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
21 21
  acts_as_attachable :delete_permission => :manage_documents
22 22

  
23
  acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
23
  acts_as_searchable :title => lambda { |d| d.event_title }, :url => lambda { |d| d.event_url },
24
                     :columns => ['title', "#{table_name}.description"], :include => :project
24 25
  acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
25 26
                :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
26 27
                :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
app/models/journal.rb (working copy)
48 48
    c = details.detect {|detail| detail.prop_key == 'status_id'}
49 49
    (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil
50 50
  end
51
  
51

  
52
  def prev_status
53
    c = details.detect {|detail| detail.prop_key == 'status_id'}
54
    (c && c.old_value) ? IssueStatus.find_by_id(c.old_value.to_i) : nil
55
  end
56

  
52 57
  def new_value_for(prop)
53 58
    c = details.detect {|detail| detail.prop_key == prop}
54 59
    c ? c.value : nil
app/models/changeset.rb (working copy)
23 23
  has_many :changes, :dependent => :delete_all
24 24
  has_and_belongs_to_many :issues
25 25

  
26
  acts_as_searchable :title => lambda { |c| c.event_title }, :url => lambda { |c| c.event_url },
27
                     :description => :long_comments, :datetime => :committed_on,
28
                     :columns => 'comments',
29
                     :include => {:repository => :project},
30
                     :project_key => "#{Repository.table_name}.project_id",
31
                     :date_column => 'committed_on'
32

  
26 33
  acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 34
                :description => :long_comments,
28 35
                :datetime => :committed_on,
29 36
                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
30
                
31
  acts_as_searchable :columns => 'comments',
32
                     :include => {:repository => :project},
33
                     :project_key => "#{Repository.table_name}.project_id",
34
                     :date_column => 'committed_on'
35 37
                     
36 38
  acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 39
                            :author_key => :user_id,
app/models/news.rb (working copy)
24 24
  validates_length_of :title, :maximum => 60
25 25
  validates_length_of :summary, :maximum => 255
26 26

  
27
  acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project
27
  acts_as_searchable :url => lambda { |n| n.event_url },
28
                     :columns => ['title', 'summary', "#{table_name}.description"], :include => :project
28 29
  acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
29 30
  acts_as_activity_provider :find_options => {:include => [:project, :author]},
30 31
                            :author_key => :author_id
app/models/message.rb (working copy)
22 22
  acts_as_attachable
23 23
  belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
24 24
  
25
  acts_as_searchable :columns => ['subject', 'content'],
25
  acts_as_searchable :title => lambda { |m| m.event_title }, :url => lambda { |m| m.event_url },
26
                     :type => lambda { |m| m.event_type },
27
                     :description => :content,
28
                     :columns => ['subject', 'content'],
26 29
                     :include => {:board => :project},
27 30
                     :project_key => 'project_id',
28 31
                     :date_column => "#{table_name}.created_on"
app/models/wiki_page.rb (working copy)
25 25
  acts_as_tree :dependent => :nullify, :order => 'title'
26 26

  
27 27
  acts_as_watchable
28
  acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
29
                :description => :text,
30
                :datetime => :created_on,
31
                :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
32 28

  
33
  acts_as_searchable :columns => ['title', 'text'],
29
  acts_as_searchable :title => lambda { |wp| wp.event_title }, :url => lambda { |wp| wp.event_url },
30
                     :description => :text,
31
                     :columns => ['title', 'text'],
34 32
                     :include => [{:wiki => :project}, :content],
35 33
                     :project_key => "#{Wiki.table_name}.project_id"
36 34

  
35
  acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
36
                :description => :text,
37
                :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
38

  
37 39
  attr_accessor :redirect_existing_links
38 40
  
39 41
  validates_presence_of :title
app/views/search/index.rhtml (working copy)
25 25
    
26 26
    <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
27 27
    <dl id="search-results">
28
      <% @results.each do |e| %>
29
        <dt class="<%= e.event_type %>"><%= 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 %></dt>
30
        <dd><span class="description"><%= highlight_tokens(e.event_description, @tokens) %></span>
31
        <span class="author"><%= format_time(e.event_datetime) %></span></dd>
28
      <% @results.each do |r| %>
29
        <dt class="<%= r.searchable_type %>"><%= content_tag('span', h(r.project), :class => 'project') unless @project == r.project %> <%= link_to highlight_tokens(truncate(r.searchable_title, :length => 255), @tokens), r.searchable_url %></dt>
30
        <dd><span class="description"><%= highlight_tokens(r.searchable_description, @tokens) %></span>
31
        <span class="datetime"><%= format_time(r.searchable_datetime) %></span></dd>
32 32
      <% end %>
33 33
    </dl>
34 34
<% end %>
app/controllers/search_controller.rb (working copy)
83 83
        @results += r
84 84
        @results_by_type[s] += c
85 85
      end
86
      @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
86
      @results = @results.sort {|a,b| b.searchable_datetime <=> a.searchable_datetime}
87 87
      if params[:previous].nil?
88
        @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
88
        @pagination_previous_date = @results[0].searchable_datetime if offset && @results[0]
89 89
        if @results.size > limit
90
          @pagination_next_date = @results[limit-1].event_datetime 
90
          @pagination_next_date = @results[limit-1].searchable_datetime 
91 91
          @results = @results[0, limit]
92 92
        end
93 93
      else
94
        @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
94
        @pagination_next_date = @results[-1].searchable_datetime if offset && @results[-1]
95 95
        if @results.size > limit
96
          @pagination_previous_date = @results[-(limit)].event_datetime 
96
          @pagination_previous_date = @results[-(limit)].searchable_datetime 
97 97
          @results = @results[-(limit), limit]
98 98
        end
99 99
      end
test/fixtures/journal_details.yml (working copy)
27 27
  value: "This word was and an other was added"
28 28
  prop_key: description
29 29
  journal_id: 3
30
journal_details_005:
31
  old_value: "1"
32
  property: attr
33
  id: 5
34
  value: "3"
35
  prop_key: status_id
36
  journal_id: 5
37
journal_details_006:
38
  old_value: "3"
39
  property: attr
40
  id: 6
41
  value: "5"
42
  prop_key: status_id
43
  journal_id: 6
test/fixtures/issues.yml (working copy)
132 132
  lft: 1
133 133
  rgt: 2
134 134
issues_008: 
135
  created_on: <%= 10.days.ago.to_date.to_s(:db) %>
135
  created_on: <%= 12.days.ago.to_date.to_s(:db) %>
136 136
  project_id: 1
137 137
  updated_on: <%= 10.days.ago.to_date.to_s(:db) %>
138 138
  priority_id: 5
test/fixtures/journals.yml (working copy)
27 27
  journalized_type: Issue
28 28
  user_id: 1
29 29
  journalized_id: 6
30
journals_005:
31
  created_on: <%= 11.days.ago.to_date.to_s(:db) %>
32
  notes: "Resolving issue 8."
33
  id: 5
34
  journalized_type: Issue
35
  user_id: 1
36
  journalized_id: 8
37
journals_006:
38
  created_on: <%= 10.days.ago.to_date.to_s(:db) %>
39
  notes: "Closing issue 8."
40
  id: 6
41
  journalized_type: Issue
42
  user_id: 1
43
  journalized_id: 8
(3-3/4)