BigW Consortium Gitlab

note.rb 7.06 KB
Newer Older
gitlabhq committed
1
class Note < ActiveRecord::Base
2
  extend ActiveModel::Naming
3
  include Gitlab::CurrentSettings
4
  include Participable
5
  include Mentionable
6
  include Awardable
7
  include Importable
8
  include FasterCacheKeys
9
  include CacheMarkdownField
10
  include AfterCommitQueue
11 12

  cache_markdown_field :note, pipeline: :note
13

14 15
  # Attribute containing rendered and redacted Markdown as generated by
  # Banzai::ObjectRenderer.
16
  attr_accessor :redacted_note_html
17

18 19 20 21
  # An Array containing the number of visible references as generated by
  # Banzai::ObjectRenderer
  attr_accessor :user_visible_reference_count

22 23 24
  # Attribute used to store the attributes that have ben changed by slash commands.
  attr_accessor :commands_changes

25 26
  default_value_for :system, false

Yorick Peterse committed
27
  attr_mentionable :note, pipeline: :note
28
  participant :author
29

gitlabhq committed
30
  belongs_to :project
31
  belongs_to :noteable, polymorphic: true, touch: true
32
  belongs_to :author, class_name: "User"
33
  belongs_to :updated_by, class_name: "User"
gitlabhq committed
34

35 36 37
  # Only used by DiffNote, but defined here so that it can be used in `Note.includes`
  belongs_to :resolved_by, class_name: "User"

38
  has_many :todos, dependent: :destroy
39
  has_many :events, as: :target, dependent: :destroy
40

41
  delegate :gfm_reference, :local_reference, to: :noteable
42 43
  delegate :name, to: :project, prefix: true
  delegate :name, :email, to: :author, prefix: true
44
  delegate :title, to: :noteable, allow_nil: true
45

46
  validates :note, :project, presence: true
47

48 49
  # Attachments are deprecated and are handled by Markdown uploader
  validates :attachment, file_size: { maximum: :max_attachment_size }
gitlabhq committed
50

51
  validates :noteable_type, presence: true
52
  validates :noteable_id, presence: true, unless: [:for_commit?, :importing?]
53
  validates :commit_id, presence: true, if: :for_commit?
Valery Sizov committed
54
  validates :author, presence: true
55

56
  validate unless: [:for_commit?, :importing?] do |note|
57
    unless note.noteable.try(:project) == note.project
58
      errors.add(:invalid_project, 'Note and noteable project mismatch')
59 60 61
    end
  end

62
  mount_uploader :attachment, AttachmentUploader
Andrey Kumanyaev committed
63 64

  # Scopes
65
  scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
66
  scope :system, ->{ where(system: true) }
67
  scope :user, ->{ where(system: false) }
68
  scope :common, ->{ where(noteable_type: ["", nil]) }
69
  scope :fresh, ->{ order(created_at: :asc, id: :asc) }
70 71
  scope :inc_author_project, ->{ includes(:project, :author) }
  scope :inc_author, ->{ includes(:author) }
72
  scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) }
gitlabhq committed
73

Douwe Maan committed
74
  scope :diff_notes, ->{ where(type: ['LegacyDiffNote', 'DiffNote']) }
75 76
  scope :non_diff_notes, ->{ where(type: ['Note', nil]) }

77
  scope :with_associations, -> do
78 79
    # FYI noteable cannot be loaded for LegacyDiffNote for commits
    includes(:author, :noteable, :updated_by,
80
             project: [:project_members, { group: [:group_members] }])
81
  end
gitlabhq committed
82

83
  after_initialize :ensure_discussion_id
84
  before_validation :nullify_blank_type, :nullify_blank_line_code
85
  before_validation :set_discussion_id
86
  after_save :keep_around_commit
87

88
  class << self
89 90 91
    def model_name
      ActiveModel::Name.new(self, nil, 'note')
    end
92

93 94
    def build_discussion_id(noteable_type, noteable_id)
      [:discussion, noteable_type.try(:underscore), noteable_id].join("-")
95
    end
96

97
    def discussion_id(*args)
98 99 100
      Digest::SHA1.hexdigest(build_discussion_id(*args))
    end

101
    def discussions
102
      Discussion.for_notes(all)
103
    end
104

105
    def grouped_diff_discussions
106 107 108
      active_notes = diff_notes.fresh.select(&:active?)
      Discussion.for_diff_notes(active_notes).
        map { |d| [d.line_code, d] }.to_h
109 110
    end

111 112 113 114
    # Searches for notes matching the given query.
    #
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
    #
115 116
    # query   - The search query as a String.
    # as_user - Limit results to those viewable by a specific user
117 118
    #
    # Returns an ActiveRecord::Relation.
119
    def search(query, as_user: nil)
120
      table   = arel_table
121 122
      pattern = "%#{query}%"

123 124 125
      Note.joins('LEFT JOIN issues ON issues.id = noteable_id').
        where(table[:note].matches(pattern)).
        merge(Issue.visible_to_user(as_user))
126
    end
127
  end
128

129
  def cross_reference?
130
    system && SystemNoteService.cross_reference?(note)
131 132
  end

133 134
  def diff_note?
    false
135 136
  end

137 138
  def legacy_diff_note?
    false
139 140
  end

Douwe Maan committed
141 142 143 144
  def new_diff_note?
    false
  end

145
  def active?
146
    true
147 148
  end

149 150 151 152 153 154 155 156 157 158 159 160
  def resolvable?
    false
  end

  def resolved?
    false
  end

  def to_be_resolved?
    resolvable? && !resolved?
  end

161 162
  def max_attachment_size
    current_application_settings.max_attachment_size.megabytes.to_i
163 164
  end

165 166
  def hook_attrs
    attributes
167 168 169 170 171 172
  end

  def for_commit?
    noteable_type == "Commit"
  end

Riyad Preukschas committed
173 174 175 176
  def for_issue?
    noteable_type == "Issue"
  end

177 178 179 180
  def for_merge_request?
    noteable_type == "MergeRequest"
  end

181
  def for_snippet?
182 183 184
    noteable_type == "Snippet"
  end

185 186 187
  # override to return commits, which are not active record
  def noteable
    if for_commit?
188
      project.commit(commit_id)
189
    else
190
      super
191
    end
192 193
  # Temp fix to prevent app crash
  # if note commit id doesn't exist
194
  rescue
195
    nil
196
  end
197

Andrew8xx8 committed
198
  # FIXME: Hack for polymorphic associations with STI
Steven Burgart committed
199
  #        For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
200 201
  def noteable_type=(noteable_type)
    super(noteable_type.to_s.classify.constantize.base_class.to_s)
Andrew8xx8 committed
202
  end
203

204
  def editable?
205
    !system?
206
  end
207

208
  def cross_reference_not_visible_for?(user)
209 210 211 212 213 214 215 216 217
    cross_reference? && !has_referenced_mentionables?(user)
  end

  def has_referenced_mentionables?(user)
    if user_visible_reference_count.present?
      user_visible_reference_count > 0
    else
      referenced_mentionables(user).any?
    end
218 219
  end

220
  def award_emoji?
221
    can_be_award_emoji? && contains_emoji_only?
222 223
  end

224 225 226 227
  def emoji_awardable?
    !system?
  end

228
  def can_be_award_emoji?
229
    noteable.is_a?(Awardable)
230 231
  end

232
  def contains_emoji_only?
233
    note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
234 235 236
  end

  def award_emoji_name
237
    note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
238
  end
239 240 241 242 243 244

  private

  def keep_around_commit
    project.repository.keep_around(self.commit_id)
  end
245 246 247 248 249 250 251 252

  def nullify_blank_type
    self.type = nil if self.type.blank?
  end

  def nullify_blank_line_code
    self.line_code = nil if self.line_code.blank?
  end
253 254 255

  def ensure_discussion_id
    return unless self.persisted?
256 257
    # Needed in case the SELECT statement doesn't ask for `discussion_id`
    return unless self.has_attribute?(:discussion_id)
258 259 260 261 262 263 264 265 266 267 268 269
    return if self.discussion_id

    set_discussion_id
    update_column(:discussion_id, self.discussion_id)
  end

  def set_discussion_id
    self.discussion_id = Digest::SHA1.hexdigest(build_discussion_id)
  end

  def build_discussion_id
    if for_merge_request?
Douwe Maan committed
270 271 272
      # Notes on merge requests are always in a discussion of their own,
      # so we generate a unique discussion ID.
      [:discussion, :note, SecureRandom.hex].join("-")
273 274 275 276
    else
      self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
    end
  end
gitlabhq committed
277
end