1123057ab7
When a merge request can only be merged when all discussions are resolved. This feature allows to easily delegate those discussions to a new issue, while marking them as resolved in the merge request. The user is presented with a new issue, prepared with mentions of all unresolved discussions, including the first unresolved note of the discussion, time and link to the note. When the issue is created, the discussions in the merge request will get a system note directing the user to the newly created issue.
202 lines
3.9 KiB
Ruby
202 lines
3.9 KiB
Ruby
class Discussion
|
|
NUMBER_OF_TRUNCATED_DIFF_LINES = 16
|
|
|
|
attr_reader :notes
|
|
|
|
delegate :created_at,
|
|
:project,
|
|
:author,
|
|
|
|
:noteable,
|
|
:for_commit?,
|
|
:for_merge_request?,
|
|
|
|
:line_code,
|
|
:original_line_code,
|
|
:diff_file,
|
|
:for_line?,
|
|
:active?,
|
|
|
|
to: :first_note
|
|
|
|
delegate :resolved_at,
|
|
:resolved_by,
|
|
|
|
to: :last_resolved_note,
|
|
allow_nil: true
|
|
|
|
delegate :blob,
|
|
:highlighted_diff_lines,
|
|
:diff_lines,
|
|
|
|
to: :diff_file,
|
|
allow_nil: true
|
|
|
|
def self.for_notes(notes)
|
|
notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
|
|
end
|
|
|
|
def self.for_diff_notes(notes)
|
|
notes.group_by(&:line_code).values.map { |notes| new(notes) }
|
|
end
|
|
|
|
def initialize(notes)
|
|
@notes = notes
|
|
end
|
|
|
|
def last_resolved_note
|
|
return unless resolved?
|
|
|
|
@last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last
|
|
end
|
|
|
|
def last_updated_at
|
|
last_note.created_at
|
|
end
|
|
|
|
def last_updated_by
|
|
last_note.author
|
|
end
|
|
|
|
def id
|
|
first_note.discussion_id
|
|
end
|
|
|
|
alias_method :to_param, :id
|
|
|
|
def diff_discussion?
|
|
first_note.diff_note?
|
|
end
|
|
|
|
def legacy_diff_discussion?
|
|
notes.any?(&:legacy_diff_note?)
|
|
end
|
|
|
|
def resolvable?
|
|
return @resolvable if @resolvable.present?
|
|
|
|
@resolvable = diff_discussion? && notes.any?(&:resolvable?)
|
|
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?)
|
|
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?
|
|
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
|
|
end
|
|
|
|
def expanded?
|
|
!collapsed?
|
|
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
|
|
end
|
|
end
|