2018-11-18 11:00:15 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# A non-diff discussion on an issue, merge request, commit, or snippet, consisting of `DiscussionNote` notes.
|
|
|
|
#
|
|
|
|
# A discussion of this type can be resolvable.
|
2016-09-13 17:45:13 +05:30
|
|
|
class Discussion
|
2019-09-04 21:01:54 +05:30
|
|
|
include GlobalID::Identification
|
2017-08-17 22:00:37 +05:30
|
|
|
include ResolvableDiscussion
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
attr_reader :notes, :context_noteable
|
2016-09-13 17:45:13 +05:30
|
|
|
|
|
|
|
delegate :created_at,
|
|
|
|
:project,
|
|
|
|
:author,
|
|
|
|
:noteable,
|
2018-03-17 18:26:18 +05:30
|
|
|
:commit_id,
|
2020-04-08 14:13:33 +05:30
|
|
|
:confidential?,
|
2016-09-13 17:45:13 +05:30
|
|
|
:for_commit?,
|
|
|
|
:for_merge_request?,
|
2019-10-31 01:37:42 +05:30
|
|
|
:noteable_ability_name,
|
2019-09-04 21:01:54 +05:30
|
|
|
:to_ability_name,
|
|
|
|
:editable?,
|
2020-06-23 00:09:42 +05:30
|
|
|
:resolved_by_id,
|
2020-04-08 14:13:33 +05:30
|
|
|
:system_note_with_references_visible_for?,
|
|
|
|
:resource_parent,
|
2016-09-13 17:45:13 +05:30
|
|
|
|
|
|
|
to: :first_note
|
|
|
|
|
2019-09-04 21:01:54 +05:30
|
|
|
def declarative_policy_delegate
|
|
|
|
first_note
|
|
|
|
end
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
def project_id
|
|
|
|
project&.id
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def self.build(notes, context_noteable = nil)
|
|
|
|
notes.first.discussion_class(context_noteable).new(notes, context_noteable)
|
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def self.build_collection(notes, context_noteable = nil)
|
2017-09-10 17:25:29 +05:30
|
|
|
grouped_notes = notes.group_by { |n| n.discussion_id(context_noteable) }
|
|
|
|
grouped_notes.values.map { |notes| build(notes, context_noteable) }
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2019-09-30 21:07:59 +05:30
|
|
|
def self.lazy_find(discussion_id)
|
|
|
|
BatchLoader.for(discussion_id).batch do |discussion_ids, loader|
|
|
|
|
results = Note.where(discussion_id: discussion_ids).fresh.to_a.group_by(&:discussion_id)
|
|
|
|
results.each do |discussion_id, notes|
|
|
|
|
next if notes.empty?
|
|
|
|
|
|
|
|
loader.call(discussion_id, Discussion.build(notes))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# Returns an alphanumeric discussion ID based on `build_discussion_id`
|
|
|
|
def self.discussion_id(note)
|
|
|
|
Digest::SHA1.hexdigest(build_discussion_id(note).join("-"))
|
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# Returns an array of discussion ID components
|
|
|
|
def self.build_discussion_id(note)
|
|
|
|
[*base_discussion_id(note), SecureRandom.hex]
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def self.base_discussion_id(note)
|
|
|
|
noteable_id = note.noteable_id || note.commit_id
|
|
|
|
[:discussion, note.noteable_type.try(:underscore), noteable_id]
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# 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 self.note_class
|
|
|
|
DiscussionNote
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def initialize(notes, context_noteable = nil)
|
|
|
|
@notes = notes
|
|
|
|
@context_noteable = context_noteable
|
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
def on_image?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def ==(other)
|
|
|
|
other.class == self.class &&
|
|
|
|
other.context_noteable == self.context_noteable &&
|
|
|
|
other.id == self.id &&
|
|
|
|
other.notes == self.notes
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def last_updated_at
|
|
|
|
last_note.created_at
|
|
|
|
end
|
|
|
|
|
|
|
|
def last_updated_by
|
|
|
|
last_note.author
|
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
def updated?
|
|
|
|
last_updated_at != created_at
|
|
|
|
end
|
|
|
|
|
2016-09-13 17:45:13 +05:30
|
|
|
def id
|
2017-08-17 22:00:37 +05:30
|
|
|
first_note.discussion_id(context_noteable)
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
def reply_id
|
|
|
|
# To reply to this discussion, we need the actual discussion_id from the database,
|
|
|
|
# not the potentially overwritten one based on the noteable.
|
|
|
|
first_note.discussion_id
|
|
|
|
end
|
|
|
|
|
2016-09-13 17:45:13 +05:30
|
|
|
alias_method :to_param, :id
|
|
|
|
|
|
|
|
def diff_discussion?
|
2017-08-17 22:00:37 +05:30
|
|
|
false
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def individual_note?
|
|
|
|
false
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
def can_convert_to_discussion?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2016-09-29 09:46:39 +05:30
|
|
|
def last_note
|
2017-08-17 22:00:37 +05:30
|
|
|
@last_note ||= notes.last
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def collapsed?
|
2017-08-17 22:00:37 +05:30
|
|
|
resolved?
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def expanded?
|
|
|
|
!collapsed?
|
|
|
|
end
|
|
|
|
|
|
|
|
def reply_attributes
|
2017-08-17 22:00:37 +05:30
|
|
|
first_note.slice(:type, :noteable_type, :noteable_id, :commit_id, :discussion_id)
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
2016-09-13 17:45:13 +05:30
|
|
|
end
|