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 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