##// END OF EJS Templates
Speeds up rendering of the project list for users who belong to hundreds of projects....
Speeds up rendering of the project list for users who belong to hundreds of projects. git-svn-id: http://svn.redmine.org/redmine/trunk@16123 e93f8b46-1217-0410-a6f0-8f06a7374b81

File last commit:

r15575:fcbd06e09069
r15741:f8df935dcada
Show More
journal.rb
320 lines | 10.7 KiB | text/x-ruby | RubyLexer
Jean-Philippe Lang
Adds a visible scope to the Journal model....
r5022 # Redmine - project management software
Jean-Philippe Lang
Updates copyright for 2016....
r14856 # Copyright (C) 2006-2016 Jean-Philippe Lang
Jean-Philippe Lang
improved issues change history...
r52 #
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735 #
Jean-Philippe Lang
improved issues change history...
r52 # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735 #
Jean-Philippe Lang
improved issues change history...
r52 # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Journal < ActiveRecord::Base
Jean-Philippe Lang
Wrap journal attributes with a journal parameter and use safe_attributes (#22575)....
r15239 include Redmine::SafeAttributes
Jean-Philippe Lang
improved issues change history...
r52 belongs_to :journalized, :polymorphic => true
Jean-Philippe Lang
added rss/atom feeds at project levels for:...
r336 # added as a quick fix to allow eager loading of the polymorphic association
# since always associated to an issue, for now
belongs_to :issue, :foreign_key => :journalized_id
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
improved issues change history...
r52 belongs_to :user
Jean-Philippe Lang
Set :inverse_of on details association....
r13554 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all, :inverse_of => :journal
Jean-Philippe Lang
Add a user preference to choose how comments/replies are displayed: in chronological or reverse chronological order (#589, #776)....
r1183 attr_accessor :indice
Jean-Philippe Lang
Merged rails-4.1 branch (#14534)....
r13100 attr_protected :id
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Display status change before subject of issue on the activity view otherwise it may be truncated....
r1491 acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
Jean-Philippe Lang
ProjectsController#list_issues, #export_issues_csv and #export_issues_pdf merged into IssuesController#index...
r874 :description => :notes,
Jean-Philippe Lang
Added issues status changes on the activity view (initial patch by Cyril Mougel)....
r879 :author => :user,
Jean-Philippe Lang
Group events in the activity view (#12542)....
r10724 :group => :issue,
Jean-Philippe Lang
Display issue notes in the activity view (#1509)....
r1553 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
Jean-Philippe Lang
Atom feeds:...
r1140 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
Jean-Philippe Lang
ProjectsController#list_issues, #export_issues_csv and #export_issues_pdf merged into IssuesController#index...
r874
Jean-Philippe Lang
Activity refactoring....
r1692 acts_as_activity_provider :type => 'issues',
Jean-Philippe Lang
Display latest user's activity on account/show view....
r2064 :author_key => :user_id,
Jean-Philippe Lang
Merged rails-4.1 branch (#14534)....
r13100 :scope => preload({:issue => :project}, :user).
joins("LEFT OUTER JOIN #{JournalDetail.table_name} ON #{JournalDetail.table_name}.journal_id = #{Journal.table_name}.id").
where("#{Journal.table_name}.journalized_type = 'Issue' AND" +
Jean-Philippe Lang
Use .distinct instead of .uniq....
r15272 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')").distinct
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 before_create :split_private_notes
Jean-Philippe Lang
Use AR callbacks instead of observers (removed in Rails4) for notifications....
r11791 after_create :send_notification
Jean-Philippe Lang
Private issue notes (#1554)....
r10336
scope :visible, lambda {|*args|
user = args.shift || User.current
Jean-Philippe Lang
Merged rails-4.1 branch (#14534)....
r13100 joins(:issue => :project).
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 where(Issue.visible_condition(user, *args)).
where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
}
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Wrap journal attributes with a journal parameter and use safe_attributes (#22575)....
r15239 safe_attributes 'notes',
:if => lambda {|journal, user| journal.new_record? || journal.editable_by?(user)}
safe_attributes 'private_notes',
:if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)}
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 def initialize(*args)
super
Jean-Philippe Lang
Don't create a journal when creating an issue....
r13350 if journalized
if journalized.new_record?
self.notify = false
else
start
end
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
end
Jean-Philippe Lang
Fixes Journal#save signature (#3014)....
r2539 def save(*args)
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 journalize_changes
Jean-Philippe Lang
Fixed: unable to add a file to an issue without entering a note....
r840 # Do not save an empty journal
(details.empty? && notes.blank?) ? false : super
end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Fixed that relations to issues that are not visible are displayed in the issue history (#1005)....
r11784 # Returns journal details that are visible to user
Jean-Philippe Lang
Role-based issue custom field visibility (#5037)....
r11782 def visible_details(user=User.current)
details.select do |detail|
Toshi MARUYAMA
remove trailing white-space from app/models/journal.rb...
r11891 if detail.property == 'cf'
Jean-Philippe Lang
Avoid lots of CustomField.find_by_id calls when displaying an issue history with custom fields (#15072)....
r11987 detail.custom_field && detail.custom_field.visible_by?(project, user)
Jean-Philippe Lang
Fixed that relations to issues that are not visible are displayed in the issue history (#1005)....
r11784 elsif detail.property == 'relation'
Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user)
Jean-Philippe Lang
Role-based issue custom field visibility (#5037)....
r11782 else
true
end
end
end
Jean-Philippe Lang
Don't notify users about relations that are not visible (#1005)....
r11785 def each_notification(users, &block)
if users.any?
users_by_details_visibility = users.group_by do |user|
visible_details(user)
end
users_by_details_visibility.each do |visible_details, users|
if notes? || visible_details.any?
yield(users)
end
end
end
end
Jean-Philippe Lang
Adds an option to send email on "Assignee updated" in application settings (#16362)....
r12699 # Returns the JournalDetail for the given attribute, or nil if the attribute
# was not updated
def detail_for_attribute(attribute)
details.detect {|detail| detail.prop_key == attribute}
end
Jean-Philippe Lang
Added issues status changes on the activity view (initial patch by Cyril Mougel)....
r879 # Returns the new status if the journal contains a status change, otherwise nil
def new_status
Jean-Philippe Lang
Adds an option to send email on "Assignee updated" in application settings (#16362)....
r12699 s = new_value_for('status_id')
s ? IssueStatus.find_by_id(s.to_i) : nil
Jean-Philippe Lang
Added issues status changes on the activity view (initial patch by Cyril Mougel)....
r879 end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Display the issue status in the email subject only if the status was actually changed....
r1065 def new_value_for(prop)
Jean-Philippe Lang
Adds an option to send email on "Assignee updated" in application settings (#16362)....
r12699 detail_for_attribute(prop).try(:value)
Jean-Philippe Lang
Display the issue status in the email subject only if the status was actually changed....
r1065 end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Administrators can edit issue notes....
r1091 def editable_by?(usr)
Jean-Philippe Lang
Added the following permissions (#527, #585, #627):...
r1138 usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
Jean-Philippe Lang
Administrators can edit issue notes....
r1091 end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Fixes:...
r1147 def project
journalized.respond_to?(:project) ? journalized.project : nil
end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Fixes:...
r1147 def attachments
Jean-Philippe Lang
Allow Journal to return empty Array instead nil in Journal#attachments (#24308)....
r15575 journalized.respond_to?(:attachments) ? journalized.attachments : []
Jean-Philippe Lang
Fixes:...
r1147 end
Eric Davis
Refactor: move method to model. (references r4057)...
r3948
# Returns a string of css classes
def css_classes
s = 'journal'
s << ' has-notes' unless notes.blank?
s << ' has-details' unless details.blank?
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 s << ' private-notes' if private_notes?
Eric Davis
Refactor: move method to model. (references r4057)...
r3948 s
end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Fixed: notes are lost when copying issue(s) (#6901, #8239)....
r5482 def notify?
@notify != false
end
Toshi MARUYAMA
remove trailing white-spaces from app/models/journal.rb....
r6735
Jean-Philippe Lang
Fixed: notes are lost when copying issue(s) (#6901, #8239)....
r5482 def notify=(arg)
@notify = arg
end
Jean-Philippe Lang
Private issue notes (#1554)....
r10336
Jean-Philippe Lang
Role-based issue custom field visibility (#5037)....
r11782 def notified_users
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 notified = journalized.notified_users
if private_notes?
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
end
Jean-Philippe Lang
Role-based issue custom field visibility (#5037)....
r11782 notified
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 end
Jean-Philippe Lang
Role-based issue custom field visibility (#5037)....
r11782 def recipients
notified_users.map(&:mail)
end
def notified_watchers
Jean-Philippe Lang
Fixed that watchers receive notifications for private comments without permission (#12286)....
r10567 notified = journalized.notified_watchers
if private_notes?
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
end
Jean-Philippe Lang
Role-based issue custom field visibility (#5037)....
r11782 notified
end
def watcher_recipients
notified_watchers.map(&:mail)
Jean-Philippe Lang
Fixed that watchers receive notifications for private comments without permission (#12286)....
r10567 end
Jean-Philippe Lang
Avoid lots of CustomField.find_by_id calls when displaying an issue history with custom fields (#15072)....
r11987 # Sets @custom_field instance variable on journals details using a single query
def self.preload_journals_details_custom_fields(journals)
field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
if field_ids.any?
Toshi MARUYAMA
remove unneeded Relation#all from Journal model...
r12452 fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
Jean-Philippe Lang
Avoid lots of CustomField.find_by_id calls when displaying an issue history with custom fields (#15072)....
r11987 journals.each do |journal|
journal.details.each do |detail|
if detail.property == 'cf'
detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i]
end
end
end
end
journals
end
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 # Stores the values of the attributes and custom fields of the journalized object
def start
if journalized
@attributes_before_change = journalized.journalized_attribute_names.inject({}) do |h, attribute|
h[attribute] = journalized.send(attribute)
h
end
@custom_values_before_change = journalized.custom_field_values.inject({}) do |h, c|
h[c.custom_field_id] = c.value
h
end
end
self
end
# Adds a journal detail for an attachment that was added or removed
def journalize_attachment(attachment, added_or_removed)
key = (added_or_removed == :removed ? :old_value : :value)
details << JournalDetail.new(
:property => 'attachment',
:prop_key => attachment.id,
key => attachment.filename
)
end
# Adds a journal detail for an issue relation that was added or removed
def journalize_relation(relation, added_or_removed)
key = (added_or_removed == :removed ? :old_value : :value)
details << JournalDetail.new(
:property => 'relation',
:prop_key => relation.relation_type_for(journalized),
key => relation.other_issue(journalized).try(:id)
)
end
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 private
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 # Generates journal details for attribute and custom field changes
def journalize_changes
# attributes changes
if @attributes_before_change
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 attrs = (journalized.journalized_attribute_names + @attributes_before_change.keys).uniq
attrs.each do |attribute|
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 before = @attributes_before_change[attribute]
after = journalized.send(attribute)
next if before == after || (before.blank? && after.blank?)
add_attribute_detail(attribute, before, after)
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 end
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 # custom fields changes
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 if @custom_values_before_change
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 values_by_custom_field_id = {}
@custom_values_before_change.each do |custom_field_id, value|
values_by_custom_field_id[custom_field_id] = nil
end
journalized.custom_field_values.each do |c|
values_by_custom_field_id[c.custom_field_id] = c.value
end
values_by_custom_field_id.each do |custom_field_id, after|
before = @custom_values_before_change[custom_field_id]
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 next if before == after || (before.blank? && after.blank?)
if before.is_a?(Array) || after.is_a?(Array)
before = [before] unless before.is_a?(Array)
after = [after] unless after.is_a?(Array)
# values removed
(before - after).reject(&:blank?).each do |value|
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 add_custom_field_detail(custom_field_id, value, nil)
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
# values added
(after - before).reject(&:blank?).each do |value|
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 add_custom_field_detail(custom_field_id, nil, value)
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
else
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 add_custom_field_detail(custom_field_id, before, after)
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 end
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
start
end
# Adds a journal detail for an attribute change
def add_attribute_detail(attribute, old_value, value)
add_detail('attr', attribute, old_value, value)
end
# Adds a journal detail for a custom field value change
Jean-Philippe Lang
Journalize values that are cleared after project or tracker change (#21623)....
r15429 def add_custom_field_detail(custom_field_id, old_value, value)
add_detail('cf', custom_field_id, old_value, value)
Jean-Philippe Lang
Moved journal details generation to Journal model....
r13321 end
# Adds a journal detail
def add_detail(property, prop_key, old_value, value)
details << JournalDetail.new(
:property => property,
:prop_key => prop_key,
:old_value => old_value,
:value => value
)
end
Jean-Philippe Lang
Private issue notes (#1554)....
r10336 def split_private_notes
if private_notes?
if notes.present?
if details.any?
# Split the journal (notes/changes) so we don't have half-private journals
journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
journal.details = details
journal.save
self.details = []
self.created_on = journal.created_on
end
else
# Blank notes should not be private
self.private_notes = false
end
end
true
end
Jean-Philippe Lang
Use AR callbacks instead of observers (removed in Rails4) for notifications....
r11791
def send_notification
if notify? && (Setting.notified_events.include?('issue_updated') ||
(Setting.notified_events.include?('issue_note_added') && notes.present?) ||
(Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
Jean-Philippe Lang
Adds an option to send email on "Assignee updated" in application settings (#16362)....
r12700 (Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
Jean-Philippe Lang
Use AR callbacks instead of observers (removed in Rails4) for notifications....
r11791 (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
)
Mailer.deliver_issue_edit(self)
end
end
Jean-Philippe Lang
improved issues change history...
r52 end