summaryrefslogtreecommitdiff
path: root/app/models/discussion.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/discussion.rb')
-rw-r--r--app/models/discussion.rb200
1 files changed, 57 insertions, 143 deletions
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index bbe813db823..0b6b920ed66 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -1,7 +1,10 @@
+# A non-diff discussion on an issue, merge request, commit, or snippet, consisting of `DiscussionNote` notes.
+#
+# A discussion of this type can be resolvable.
class Discussion
- NUMBER_OF_TRUNCATED_DIFF_LINES = 16
+ include ResolvableDiscussion
- attr_reader :notes
+ attr_reader :notes, :context_noteable
delegate :created_at,
:project,
@@ -11,43 +14,62 @@ class Discussion
:for_commit?,
:for_merge_request?,
- :line_code,
- :original_line_code,
- :diff_file,
- :for_line?,
- :active?,
-
to: :first_note
- delegate :resolved_at,
- :resolved_by,
+ def self.build(notes, context_noteable = nil)
+ notes.first.discussion_class(context_noteable).new(notes, context_noteable)
+ end
- to: :last_resolved_note,
- allow_nil: true
+ def self.build_collection(notes, context_noteable = nil)
+ notes.group_by { |n| n.discussion_id(context_noteable) }.values.map { |notes| build(notes, context_noteable) }
+ end
- delegate :blob,
- :highlighted_diff_lines,
- :diff_lines,
+ # Returns an alphanumeric discussion ID based on `build_discussion_id`
+ def self.discussion_id(note)
+ Digest::SHA1.hexdigest(build_discussion_id(note).join("-"))
+ end
- to: :diff_file,
- allow_nil: true
+ # Returns an array of discussion ID components
+ def self.build_discussion_id(note)
+ [*base_discussion_id(note), SecureRandom.hex]
+ end
- def self.for_notes(notes)
- notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
+ def self.base_discussion_id(note)
+ noteable_id = note.noteable_id || note.commit_id
+ [:discussion, note.noteable_type.try(:underscore), noteable_id]
end
- def self.for_diff_notes(notes)
- notes.group_by(&:line_code).values.map { |notes| new(notes) }
+ # When notes on a commit are displayed in context of a merge request that contains that commit,
+ # these notes are to be displayed as if they were part of one discussion, even though they were actually
+ # individual notes on the commit with different discussion IDs, so that it's clear that these are not
+ # notes on the merge request itself.
+ #
+ # To turn a list of notes into a list of discussions, they are grouped by discussion ID, so to
+ # get these out-of-context notes to end up in the same discussion, we need to get them to return the same
+ # `discussion_id` when this grouping happens. To enable this, `Note#discussion_id` calls out
+ # to the `override_discussion_id` method on the appropriate `Discussion` subclass, as determined by
+ # the `discussion_class` method on `Note` or a subclass of `Note`.
+ #
+ # If no override is necessary, return `nil`.
+ # For the case described above, see `OutOfContextDiscussion.override_discussion_id`.
+ def self.override_discussion_id(note)
+ nil
end
- def initialize(notes)
- @notes = notes
+ def self.note_class
+ DiscussionNote
end
- def last_resolved_note
- return unless resolved?
+ def initialize(notes, context_noteable = nil)
+ @notes = notes
+ @context_noteable = context_noteable
+ end
- @last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last
+ def ==(other)
+ other.class == self.class &&
+ other.context_noteable == self.context_noteable &&
+ other.id == self.id &&
+ other.notes == self.notes
end
def last_updated_at
@@ -59,91 +81,29 @@ class Discussion
end
def id
- first_note.discussion_id
+ first_note.discussion_id(context_noteable)
end
alias_method :to_param, :id
def diff_discussion?
- first_note.diff_note?
- end
-
- def legacy_diff_discussion?
- notes.any?(&:legacy_diff_note?)
+ false
end
- def resolvable?
- return @resolvable if @resolvable.present?
-
- @resolvable = diff_discussion? && notes.any?(&:resolvable?)
+ def individual_note?
+ false
end
- def resolved?
- return @resolved if @resolved.present?
-
- @resolved = resolvable? && notes.none?(&:to_be_resolved?)
- end
-
- def first_note
- @first_note ||= @notes.first
- end
-
- def first_note_to_resolve
- @first_note_to_resolve ||= notes.detect(&:to_be_resolved?)
+ def new_discussion?
+ notes.length == 1
end
def last_note
- @last_note ||= @notes.last
- end
-
- def resolved_notes
- notes.select(&:resolved?)
- end
-
- def to_be_resolved?
- resolvable? && !resolved?
- end
-
- def can_resolve?(current_user)
- return false unless current_user
- return false unless resolvable?
-
- current_user == self.noteable.author ||
- current_user.can?(:resolve_note, self.project)
- end
-
- def resolve!(current_user)
- return unless resolvable?
-
- update { |notes| notes.resolve!(current_user) }
- end
-
- def unresolve!
- return unless resolvable?
-
- update { |notes| notes.unresolve! }
- end
-
- def for_target?(target)
- self.noteable == target && !diff_discussion?
- end
-
- def active?
- return @active if @active.present?
-
- @active = first_note.active?
+ @last_note ||= notes.last
end
def collapsed?
- return false unless diff_discussion?
-
- if resolvable?
- # New diff discussions only disappear once they are marked resolved
- resolved?
- else
- # Old diff discussions disappear once they become outdated
- !active?
- end
+ resolved?
end
def expanded?
@@ -151,52 +111,6 @@ class Discussion
end
def reply_attributes
- data = {
- noteable_type: first_note.noteable_type,
- noteable_id: first_note.noteable_id,
- commit_id: first_note.commit_id,
- discussion_id: self.id,
- }
-
- if diff_discussion?
- data[:note_type] = first_note.type
-
- data.merge!(first_note.diff_attributes)
- end
-
- data
- end
-
- # Returns an array of at most 16 highlighted lines above a diff note
- def truncated_diff_lines(highlight: true)
- lines = highlight ? highlighted_diff_lines : diff_lines
- prev_lines = []
-
- lines.each do |line|
- if line.meta?
- prev_lines.clear
- else
- prev_lines << line
-
- break if for_line?(line)
-
- prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
- end
- end
-
- prev_lines
- end
-
- private
-
- def update
- notes_relation = DiffNote.where(id: notes.map(&:id)).fresh
- yield(notes_relation)
-
- # Set the notes array to the updated notes
- @notes = notes_relation.to_a
-
- # Reset the memoized values
- @last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil
+ first_note.slice(:type, :noteable_type, :noteable_id, :commit_id, :discussion_id)
end
end