BigW Consortium Gitlab

note.rb 7.05 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 47
  validates :note, presence: true
  validates :project, presence: true, unless: :for_personal_snippet?
48

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

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

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

63
  mount_uploader :attachment, AttachmentUploader
Andrey Kumanyaev committed
64 65

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

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

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

84
  after_initialize :ensure_discussion_id
85
  before_validation :nullify_blank_type, :nullify_blank_line_code
86
  before_validation :set_discussion_id
87
  after_save :keep_around_commit, unless: :for_personal_snippet?
88

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

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

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

102
    def discussions
103
      Discussion.for_notes(fresh)
104
    end
105

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

    def count_for_collection(ids, type)
      user.select('noteable_id', 'COUNT(*) as count').
        group(:noteable_id).
        where(noteable_type: type, noteable_id: ids)
    end
117
  end
118

119
  def cross_reference?
120
    system && SystemNoteService.cross_reference?(note)
121 122
  end

123 124
  def diff_note?
    false
125 126
  end

127 128
  def legacy_diff_note?
    false
129 130
  end

Douwe Maan committed
131 132 133 134
  def new_diff_note?
    false
  end

135
  def active?
136
    true
137 138
  end

139 140 141 142 143 144 145 146 147 148 149 150
  def resolvable?
    false
  end

  def resolved?
    false
  end

  def to_be_resolved?
    resolvable? && !resolved?
  end

151 152
  def max_attachment_size
    current_application_settings.max_attachment_size.megabytes.to_i
153 154
  end

155 156
  def hook_attrs
    attributes
157 158 159 160 161 162
  end

  def for_commit?
    noteable_type == "Commit"
  end

Riyad Preukschas committed
163 164 165 166
  def for_issue?
    noteable_type == "Issue"
  end

167 168 169 170
  def for_merge_request?
    noteable_type == "MergeRequest"
  end

171
  def for_snippet?
172 173 174
    noteable_type == "Snippet"
  end

175
  def for_personal_snippet?
Jarka Kadlecova committed
176 177 178 179 180
    noteable.is_a?(PersonalSnippet)
  end

  def skip_project_check?
    for_personal_snippet?
181 182
  end

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

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

202
  def editable?
203
    !system?
204
  end
205

206
  def cross_reference_not_visible_for?(user)
207 208 209 210 211 212 213 214 215
    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
216 217
  end

218
  def award_emoji?
219
    can_be_award_emoji? && contains_emoji_only?
220 221
  end

222 223 224 225
  def emoji_awardable?
    !system?
  end

226
  def can_be_award_emoji?
227
    noteable.is_a?(Awardable)
228 229
  end

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

  def award_emoji_name
235
    note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
236
  end
237

Jarka Kadlecova committed
238 239 240 241
  def to_ability_name
    for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
  end

242 243 244 245 246
  private

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

  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
255 256 257

  def ensure_discussion_id
    return unless self.persisted?
258 259
    # Needed in case the SELECT statement doesn't ask for `discussion_id`
    return unless self.has_attribute?(:discussion_id)
260 261 262 263 264 265 266 267 268 269 270 271
    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
272 273 274
      # Notes on merge requests are always in a discussion of their own,
      # so we generate a unique discussion ID.
      [:discussion, :note, SecureRandom.hex].join("-")
275 276 277 278
    else
      self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
    end
  end
gitlabhq committed
279
end