Project

General

Profile

Defect #7293 » activity_new_issue_event_status_v4.patch

v4 - Etienne Massip, 2011-03-30 21:38

View differences:

vendor/plugins/acts_as_searchable/lib/acts_as_searchable.rb (working copy)
31 31
        # * :permission - permission required to search the model (default to :view_"objects")
32 32
        def acts_as_searchable(options = {})
33 33
          return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods)
34
  
34

  
35 35
          cattr_accessor :searchable_options
36
          options.assert_valid_keys(:result_type, :columns, :project_key, :date_column, :order_column, :permission, :include)
36 37
          self.searchable_options = options
37 38

  
38 39
          if searchable_options[:columns].nil?
......
44 45
          searchable_options[:project_key] ||= "#{table_name}.project_id"
45 46
          searchable_options[:date_column] ||= "#{table_name}.created_on"
46 47
          searchable_options[:order_column] ||= searchable_options[:date_column]
47
          
48
          searchable_options[:result_type] ||=  self.name.underscore
49

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

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

  
54 56
          send :include, Redmine::Acts::Searchable::InstanceMethods
55 57
        end
56 58
      end
......
60 62
          base.extend ClassMethods
61 63
        end
62 64

  
65
        def searchable_result_type
66
          searchable_options[:result_type]
67
        end
68

  
63 69
        module ClassMethods
64 70
          # Searches the model for the given tokens
65 71
          # projects argument can be either nil (will search all projects), a project or an array of projects
......
67 73
          def search(tokens, projects=nil, options={})
68 74
            tokens = [] << tokens unless tokens.is_a?(Array)
69 75
            projects = [] << projects unless projects.nil? || projects.is_a?(Array)
70
            
76

  
71 77
            find_options = {:include => searchable_options[:include]}
72 78
            find_options[:order] = "#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC')
73
            
79

  
74 80
            limit_options = {}
75 81
            limit_options[:limit] = options[:limit] if options[:limit]
76 82
            if options[:offset]
77 83
              limit_options[:conditions] = "(#{searchable_options[:date_column]} " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')"
78 84
            end
79
            
85

  
80 86
            columns = searchable_options[:columns]
81 87
            columns = columns[0..0] if options[:titles_only]
82
            
88

  
83 89
            token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"}
84
            
90

  
85 91
            if !options[:titles_only] && searchable_options[:search_custom_fields]
86 92
              searchable_custom_field_ids = CustomField.find(:all,
87 93
                                                             :select => 'id',
......
94 100
                token_clauses << custom_field_sql
95 101
              end
96 102
            end
97
            
103

  
98 104
            sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ')
99 105
            
100 106
            find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort]
101
            
107

  
102 108
            project_conditions = []
103 109
            project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) :
104 110
                                                 Project.allowed_to_condition(User.current, searchable_options[:permission]))
105 111
            project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
106
            
112

  
107 113
            results = []
108 114
            results_count = 0
109
            
115

  
110 116
            with_scope(:find => {:conditions => project_conditions.join(' AND ')}) do
111 117
              with_scope(:find => find_options) do
112 118
                results_count = count(:all)
app/models/issue.rb (working copy)
42 42
                     :include => [:project, :journals],
43 43
                     # sort by id so that limited eager loading doesn't break with postgresql
44 44
                     :order_column => "#{table_name}.id"
45
  acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
45

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

  
49 50
  acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 51
                            :author_key => :author_id
51 52

  
......
85 86
  before_save :close_duplicates, :update_done_ratio_from_issue_status
86 87
  after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
87 88
  after_destroy :update_parent_attributes
88
  
89

  
90
  # Retrieves issue's original status from journal if modified since issue creation
91
  def initial_status
92

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

  
98
    first_status_modification_journal ? first_status_modification_journal.prev_status : self.status
99
  end
100

  
89 101
  # Returns a SQL conditions string used to find all issues visible by the specified user
90 102
  def self.visible_condition(user, options={})
91 103
    Project.allowed_to_condition(user, :view_issues, options)
app/models/journal.rb (working copy)
37 37
                            :find_options => {:include => [{:issue => :project}, :details, :user],
38 38
                                              :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
39 39
                                                             " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
40
  
40

  
41 41
  named_scope :visible, lambda {|*args| {
42 42
    :include => {:issue => :project},
43 43
    :conditions => Issue.visible_condition(args.first || User.current)
......
53 53
    c = details.detect {|detail| detail.prop_key == 'status_id'}
54 54
    (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil
55 55
  end
56
  
56

  
57
  def prev_status
58
    c = details.detect {|detail| detail.prop_key == 'status_id'}
59
    (c && c.old_value) ? IssueStatus.find_by_id(c.old_value.to_i) : nil
60
  end
61

  
57 62
  def new_value_for(prop)
58 63
    c = details.detect {|detail| detail.prop_key == prop}
59 64
    c ? c.value : nil
app/views/search/results/_changeset.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => changeset %>
app/views/search/results/_document.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => document %>
app/views/search/results/_event.html.erb (revision 0)
1
<dt class="<%= event.event_type %>"><%= 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 %></dt>
2
<dd><span class="description"><%= highlight_tokens(event.event_description, @tokens) %></span>
3
<span class="author"><%= format_time(event.event_datetime) %></span></dd>
app/views/search/results/_issue.html.erb (revision 0)
1
<dt class="<%= issue.event_type %>">
2
<%= content_tag('span', h(issue.project), :class => 'project') unless @project == issue.project %>
3
<%= link_to highlight_tokens(truncate("#{issue.tracker.name} ##{issue.id} (#{issue.status()}): #{issue.subject}", :length => 255), @tokens), issue.event_url %></dt>
4
<dd><span class="description"><%= highlight_tokens(issue.event_description, @tokens) %></span>
5
<span class="datetime"><%= format_time(issue.event_datetime) %></span></dd>
app/views/search/results/_journal.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => journal %>
app/views/search/results/_message.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => message %>
app/views/search/results/_news.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => news %>
app/views/search/results/_project.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => project %>
app/views/search/results/_wiki_page.html.erb (revision 0)
1
<%= render :partial => "search/results/event", :object => wiki_page %>
app/views/search/index.rhtml (working copy)
22 22
    <div id="search-results-counts">
23 23
    <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %>
24 24
    </div>
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
	    <%= render :partial => "search/results/#{r.searchable_result_type.underscore}", :object => r %>
32 30
      <% end %>
33 31
    </dl>
34 32
<% end %>
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
test/fixtures/journal_details.yml (working copy)
34 34
  value: New value
35 35
  prop_key: 2
36 36
  journal_id: 3
37
journal_details_006:
38
  old_value: "1"
39
  property: attr
40
  id: 6
41
  value: "3"
42
  prop_key: status_id
43
  journal_id: 5
44
journal_details_007:
45
  old_value: "3"
46
  property: attr
47
  id: 7
48
  value: "5"
49
  prop_key: status_id
50
  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
(4-4/4)