Defect #7293 » activity_new_issue_event_status.patch
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 |