BigW Consortium Gitlab

mentionable.rb 3.58 KB
Newer Older
1 2
# == Mentionable concern
#
3 4
# Contains functionality related to objects that can mention Users, Issues, MergeRequests, or Commits by
# GFM references.
5
#
6
# Used by Issue, Note, MergeRequest, and Commit.
7 8 9 10
#
module Mentionable
  extend ActiveSupport::Concern

11 12
  module ClassMethods
    # Indicate which attributes of the Mentionable to search for GFM references.
13
    def attr_mentionable(*attrs)
14 15 16 17 18 19 20 21 22
      mentionable_attrs.concat(attrs.map(&:to_s))
    end

    # Accessor for attributes marked mentionable.
    def mentionable_attrs
      @mentionable_attrs ||= []
    end
  end

23 24 25 26
  # Returns the text used as the body of a Note when this object is referenced
  #
  # By default this will be the class name and the result of calling
  # `to_reference` on the object.
27
  def gfm_reference(from_project = nil)
28
    # "MergeRequest" > "merge_request" > "Merge request" > "merge request"
29 30
    friendly_name = self.class.to_s.underscore.humanize.downcase

31
    "#{friendly_name} #{to_reference(from_project)}"
32 33 34 35
  end

  # Construct a String that contains possible GFM references.
  def mentionable_text
36
    self.class.mentionable_attrs.map { |attr| send(attr) }.compact.join("\n\n")
37 38 39 40 41 42 43 44 45
  end

  # The GFM reference to this Mentionable, which shouldn't be included in its #references.
  def local_reference
    self
  end

  # Determine whether or not a cross-reference Note has already been created between this Mentionable and
  # the specified target.
46
  def has_mentioned?(target)
47
    SystemNoteService.cross_reference_exists?(target, local_reference)
48 49
  end

50
  def mentioned_users(current_user = nil)
51 52
    return [] if mentionable_text.blank?

53
    ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
Douwe Maan committed
54
    ext.analyze(mentionable_text)
55
    ext.users.uniq
56 57
  end

58
  # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
59
  def references(p = project, current_user = self.author, text = mentionable_text)
60
    return [] if text.blank?
61

62
    ext = Gitlab::ReferenceExtractor.new(p, current_user)
63
    ext.analyze(text)
64

65
    (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
66 67 68
  end

  # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
69
  def create_cross_references!(p = project, a = author, without = [])
70 71 72 73 74 75 76
    refs = references(p)

    # We're using this method instead of Array diffing because that requires
    # both of the object's `hash` values to be the same, which may not be the
    # case for otherwise identical Commit objects.
    refs.reject! { |ref| without.include?(ref) }

77
    refs.each do |ref|
78
      SystemNoteService.cross_reference(ref, local_reference, a)
79 80 81
    end
  end

82 83 84
  # When a mentionable field is changed, creates cross-reference notes that
  # don't already exist
  def create_new_cross_references!(p = project, a = author)
85 86 87
    changes = detect_mentionable_changes

    return if changes.empty?
88

89
    original_text = changes.collect { |_, vals| vals.first }.join(' ')
90

91
    preexisting = references(p, self.author, original_text)
92 93
    create_cross_references!(p, a, preexisting)
  end
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

  private

  # Returns a Hash of changed mentionable fields
  #
  # Preference is given to the `changes` Hash, but falls back to
  # `previous_changes` if it's empty (i.e., the changes have already been
  # persisted).
  #
  # See ActiveModel::Dirty.
  #
  # Returns a Hash.
  def detect_mentionable_changes
    source = (changes.present? ? changes : previous_changes).dup

    mentionable = self.class.mentionable_attrs

    # Only include changed fields that are mentionable
    source.select { |key, val| mentionable.include?(key) }
  end
114
end