2016-07-20 18:18:18 -04:00
|
|
|
class Discussion
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES = [] # rubocop:disable Style/MutableConstant
|
2016-07-20 18:18:18 -04:00
|
|
|
|
2016-09-02 16:29:47 -04:00
|
|
|
attr_reader :notes
|
2016-07-20 18:18:18 -04:00
|
|
|
|
|
|
|
delegate :created_at,
|
|
|
|
:project,
|
|
|
|
:author,
|
|
|
|
|
|
|
|
:noteable,
|
|
|
|
:for_commit?,
|
|
|
|
:for_merge_request?,
|
|
|
|
|
|
|
|
to: :first_note
|
|
|
|
|
2016-07-26 00:36:03 -04:00
|
|
|
delegate :resolved_at,
|
|
|
|
:resolved_by,
|
|
|
|
|
2016-07-28 23:14:45 -04:00
|
|
|
to: :last_resolved_note,
|
|
|
|
allow_nil: true
|
2016-07-26 00:36:03 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def self.build(notes, noteable = nil)
|
|
|
|
notes.first.discussion_class(noteable).new(notes, noteable)
|
|
|
|
end
|
Change diff highlight/truncate for reusability
Previously the `truncated_diff_lines` method for outputting a discussion diff took in already highlighted lines, which meant it wasn't reuseable for truncating ANY lines. In the way it was used, it also meant that for any email truncation, the whole diff was being highlighted before being truncated, meaning wasted time highlighting lines that wouldn't even be used (granted, they were being memoized, so perhaps this wasn't that great of an issue). I refactored truncation away from highlighting, in order to truncate formatted diffs for text templates in email, using `>`s to designate each line, but otherwise retaining the parsing already done to create `diff_lines`.
Additionally, while notes on merge requests or commits had already been tested, there was no existing test for notes on a diff on an MR or commit. Added mailer tests for such, and a unit test for truncating diff lines.
2016-08-25 12:38:07 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def self.build_collection(notes, noteable = nil)
|
|
|
|
notes.group_by { |n| n.discussion_id(noteable) }.values.map { |notes| build(notes, noteable) }
|
|
|
|
end
|
2016-07-20 18:18:18 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def self.discussion_id(note)
|
|
|
|
Digest::SHA1.hexdigest(build_discussion_id(note).join("-"))
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
# Optionally override the discussion ID at runtime depending on circumstances
|
|
|
|
def self.override_discussion_id(note)
|
|
|
|
nil
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def self.build_discussion_id(note)
|
|
|
|
noteable_id = note.noteable_id || note.commit_id
|
|
|
|
[:discussion, note.noteable_type.try(:underscore), noteable_id]
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def self.original_discussion_id(note)
|
|
|
|
original_discussion_id = build_original_discussion_id(note)
|
|
|
|
if original_discussion_id
|
|
|
|
Digest::SHA1.hexdigest(original_discussion_id.join("-"))
|
|
|
|
else
|
|
|
|
note.discussion_id
|
|
|
|
end
|
|
|
|
end
|
2016-07-28 23:14:45 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
# Optionally build a separate original discussion ID that will never change,
|
|
|
|
# if the main discussion ID _can_ change, like in the case of DiffDiscussion.
|
|
|
|
def self.build_original_discussion_id(note)
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(notes, noteable = nil)
|
|
|
|
@notes = notes
|
|
|
|
@noteable = noteable
|
2016-07-28 23:14:45 -04:00
|
|
|
end
|
|
|
|
|
2016-07-26 00:49:21 -04:00
|
|
|
def last_updated_at
|
|
|
|
last_note.created_at
|
|
|
|
end
|
|
|
|
|
|
|
|
def last_updated_by
|
|
|
|
last_note.author
|
|
|
|
end
|
|
|
|
|
2016-07-20 18:18:18 -04:00
|
|
|
def id
|
2017-03-09 20:29:11 -05:00
|
|
|
first_note.discussion_id(noteable)
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
2016-08-17 13:14:44 -04:00
|
|
|
|
2016-08-15 19:45:23 -04:00
|
|
|
alias_method :to_param, :id
|
2016-07-20 18:18:18 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def original_id
|
|
|
|
first_note.original_discussion_id
|
|
|
|
end
|
|
|
|
|
2016-07-20 18:18:18 -04:00
|
|
|
def diff_discussion?
|
2017-03-09 20:29:11 -05:00
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def render_as_individual_notes?
|
|
|
|
false
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
|
|
|
|
2017-03-15 20:14:58 -04:00
|
|
|
def new_discussion?
|
|
|
|
notes.length == 1
|
|
|
|
end
|
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
def potentially_resolvable?
|
|
|
|
first_note.for_merge_request?
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
|
|
|
|
2016-07-26 00:36:03 -04:00
|
|
|
def resolvable?
|
2016-09-02 16:29:47 -04:00
|
|
|
return @resolvable if @resolvable.present?
|
2016-08-03 18:32:00 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
@resolvable = potentially_resolvable? && notes.any?(&:resolvable?)
|
2016-07-26 00:36:03 -04:00
|
|
|
end
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES << :resolvable
|
2016-07-26 00:36:03 -04:00
|
|
|
|
|
|
|
def resolved?
|
2016-09-02 16:29:47 -04:00
|
|
|
return @resolved if @resolved.present?
|
2016-08-03 18:32:00 -04:00
|
|
|
|
|
|
|
@resolved = resolvable? && notes.none?(&:to_be_resolved?)
|
2016-07-26 00:36:03 -04:00
|
|
|
end
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES << :resolved
|
2016-07-26 00:36:03 -04:00
|
|
|
|
2016-09-02 16:29:47 -04:00
|
|
|
def first_note
|
2017-03-09 20:29:11 -05:00
|
|
|
@first_note ||= notes.first
|
2016-09-02 16:29:47 -04:00
|
|
|
end
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES << :first_note
|
2016-09-02 16:29:47 -04:00
|
|
|
|
2016-10-26 17:21:50 -04:00
|
|
|
def first_note_to_resolve
|
2017-03-09 20:29:11 -05:00
|
|
|
return unless resolvable?
|
|
|
|
|
|
|
|
@first_note_to_resolve ||= notes.find(&:to_be_resolved?)
|
2016-10-26 17:21:50 -04:00
|
|
|
end
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES << :first_note_to_resolve
|
|
|
|
|
|
|
|
def last_resolved_note
|
|
|
|
return unless resolved?
|
|
|
|
|
|
|
|
@last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last
|
|
|
|
end
|
|
|
|
MEMOIZED_VALUES << :last_resolved_note
|
2016-10-26 17:21:50 -04:00
|
|
|
|
2016-09-02 16:29:47 -04:00
|
|
|
def last_note
|
2017-03-09 20:29:11 -05:00
|
|
|
@last_note ||= notes.last
|
2016-09-02 16:29:47 -04:00
|
|
|
end
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES << :last_note
|
2016-09-02 16:29:47 -04:00
|
|
|
|
2016-07-28 23:14:45 -04:00
|
|
|
def resolved_notes
|
|
|
|
notes.select(&:resolved?)
|
|
|
|
end
|
|
|
|
|
2016-07-26 00:36:03 -04:00
|
|
|
def to_be_resolved?
|
2016-08-12 16:49:25 -04:00
|
|
|
resolvable? && !resolved?
|
2016-07-26 00:36:03 -04:00
|
|
|
end
|
|
|
|
|
2016-07-26 00:40:44 -04:00
|
|
|
def can_resolve?(current_user)
|
|
|
|
return false unless current_user
|
|
|
|
return false unless resolvable?
|
|
|
|
|
|
|
|
current_user == self.noteable.author ||
|
2016-08-17 13:14:44 -04:00
|
|
|
current_user.can?(:resolve_note, self.project)
|
2016-07-26 00:40:44 -04:00
|
|
|
end
|
|
|
|
|
2016-07-26 00:37:22 -04:00
|
|
|
def resolve!(current_user)
|
2016-08-12 16:49:25 -04:00
|
|
|
return unless resolvable?
|
|
|
|
|
2016-09-02 16:29:47 -04:00
|
|
|
update { |notes| notes.resolve!(current_user) }
|
2016-07-26 00:37:22 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def unresolve!
|
2016-08-12 16:49:25 -04:00
|
|
|
return unless resolvable?
|
|
|
|
|
2016-09-02 16:29:47 -04:00
|
|
|
update { |notes| notes.unresolve! }
|
2016-07-26 00:37:22 -04:00
|
|
|
end
|
|
|
|
|
2016-07-26 00:46:13 -04:00
|
|
|
def collapsed?
|
|
|
|
if resolvable?
|
|
|
|
# New diff discussions only disappear once they are marked resolved
|
|
|
|
resolved?
|
|
|
|
else
|
2017-03-09 20:29:11 -05:00
|
|
|
false
|
2016-07-26 00:46:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-20 18:18:18 -04:00
|
|
|
def expanded?
|
2016-07-26 00:46:13 -04:00
|
|
|
!collapsed?
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def reply_attributes
|
2017-03-09 20:29:11 -05:00
|
|
|
first_note.slice(:type, :noteable_type, :noteable_id, :commit_id)
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|
2016-09-02 16:29:47 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def update
|
2017-03-09 20:29:11 -05:00
|
|
|
# Do not select `Note.resolvable`, so that system notes remain in the collection
|
|
|
|
notes_relation = Note.where(id: notes.map(&:id))
|
|
|
|
|
2016-09-02 16:29:47 -04:00
|
|
|
yield(notes_relation)
|
|
|
|
|
|
|
|
# Set the notes array to the updated notes
|
2017-03-09 20:29:11 -05:00
|
|
|
@notes = notes_relation.fresh.to_a
|
2016-09-02 16:29:47 -04:00
|
|
|
|
2017-03-09 20:29:11 -05:00
|
|
|
MEMOIZED_VALUES.each do |var|
|
|
|
|
instance_variable_set(:"@#{var}", nil)
|
|
|
|
end
|
2016-09-02 16:29:47 -04:00
|
|
|
end
|
2016-07-20 18:18:18 -04:00
|
|
|
end
|