Class: Note
- Inherits:
-
ApplicationRecord
show all
- Extended by:
- ActiveModel::Naming
- Includes:
- AfterCommitQueue, Awardable, CacheMarkdownField, Editable, FasterCacheKeys, FromUnion, Gitlab::SQL::Pattern, Gitlab::Utils::StrongMemoize, Importable, Mentionable, Participable, Redactable, ResolvableNote, ThrottledTouch
- Defined in:
- app/models/note.rb
Overview
A note on the root of an issue, merge request, commit, or snippet.
A note of this type is never resolvable.
Defined Under Namespace
Modules: SpecialRole
Constant Summary
collapse
- TYPES_RESTRICTED_BY_ABILITY =
{
branch: :download_code
}.freeze
ThrottledTouch::TOUCH_INTERVAL
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD
ResolvableNote::RESOLVABLE_TYPES
CacheMarkdownField::INVALIDATED_BY
Constants included
from Redactable
Redactable::UNSUBSCRIBE_PATTERN
Instance Attribute Summary collapse
Attributes included from Importable
#imported, #importing
Class Method Summary
collapse
Instance Method Summary
collapse
Methods included from Editable
#last_edited_by
#potentially_resolvable?, #resolvable?, #resolve!, #resolve_without_save, #resolved?, #to_be_resolved?, #unresolve!, #unresolve_without_save
#run_after_commit, #run_after_commit_or_now
#attribute_invalidated?, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #updated_cached_html_for
#cache_key
Methods included from Awardable
#awarded_emoji?, #downvotes, #grouped_awards, #upvotes, #user_authored?, #user_can_award?
#all_references, #create_cross_references!, #create_new_cross_references!, #directly_addressed_users, #extractors, #gfm_reference, #local_reference, #matches_cross_reference_regex?, #mentioned_users, #referenced_group_users, #referenced_groups, #referenced_mentionables, #referenced_project_users, #referenced_projects, #referenced_users, #store_mentions!
#participants
#clear_memoization, #strong_memoize, #strong_memoized?
at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order
Instance Attribute Details
#commands_changes ⇒ Object
Attribute used to store the attributes that have been changed by quick actions.
61
62
63
|
# File 'app/models/note.rb', line 61
def commands_changes
@commands_changes
end
|
#redacted_note_html ⇒ Object
Attribute containing rendered and redacted Markdown as generated by Banzai::ObjectRenderer.
52
53
54
|
# File 'app/models/note.rb', line 52
def redacted_note_html
@redacted_note_html
end
|
#special_role ⇒ Object
A special role that may be displayed on issuable's discussions
64
65
66
|
# File 'app/models/note.rb', line 64
def special_role
@special_role
end
|
#total_reference_count ⇒ Object
Total of all references as generated by Banzai::ObjectRenderer
55
56
57
|
# File 'app/models/note.rb', line 55
def total_reference_count
@total_reference_count
end
|
#user_visible_reference_count ⇒ Object
Number of user visible references as generated by Banzai::ObjectRenderer
58
59
60
|
# File 'app/models/note.rb', line 58
def user_visible_reference_count
@user_visible_reference_count
end
|
Class Method Details
.count_for_collection(ids, type) ⇒ Object
217
218
219
220
221
|
# File 'app/models/note.rb', line 217
def count_for_collection(ids, type)
user.select('noteable_id', 'COUNT(*) as count')
.group(:noteable_id)
.where(noteable_type: type, noteable_id: ids)
end
|
.discussions(context_noteable = nil) ⇒ Object
174
175
176
|
# File 'app/models/note.rb', line 174
def discussions(context_noteable = nil)
Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
end
|
.find_discussion(discussion_id) ⇒ Object
Note: Where possible consider using Discussion#lazy_find to return Discussions in order to benefit from having records batch loaded.
180
181
182
183
184
185
186
|
# File 'app/models/note.rb', line 180
def find_discussion(discussion_id)
notes = where(discussion_id: discussion_id).fresh.to_a
return if notes.empty?
Discussion.build(notes)
end
|
.grouped_diff_discussions(diff_refs = nil) ⇒ Object
Group diff discussions by line code or file path. It is not needed to group by line code when comment is on an image.
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
# File 'app/models/note.rb', line 191
def grouped_diff_discussions(diff_refs = nil)
groups = {}
diff_notes.fresh.discussions.each do |discussion|
group_key =
if discussion.on_image?
discussion.file_new_path
else
discussion.line_code_in_diffs(diff_refs)
end
if group_key
discussions = groups[group_key] ||= []
discussions << discussion
end
end
groups
end
|
.has_special_role?(role, note) ⇒ Boolean
223
224
225
|
# File 'app/models/note.rb', line 223
def has_special_role?(role, note)
note.special_role == role
end
|
.model_name ⇒ Object
170
171
172
|
# File 'app/models/note.rb', line 170
def model_name
ActiveModel::Name.new(self, nil, 'note')
end
|
.positions ⇒ Object
211
212
213
214
215
|
# File 'app/models/note.rb', line 211
def positions
where.not(position: nil)
.select(:id, :type, :position) .map(&:position)
end
|
.search(query) ⇒ Object
227
228
229
|
# File 'app/models/note.rb', line 227
def search(query)
fuzzy_search(query, [:note])
end
|
Instance Method Details
#active? ⇒ Boolean
248
249
250
|
# File 'app/models/note.rb', line 248
def active?
true
end
|
#award_emoji? ⇒ Boolean
385
386
387
|
# File 'app/models/note.rb', line 385
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
|
#banzai_render_context(field) ⇒ Object
540
541
542
|
# File 'app/models/note.rb', line 540
def banzai_render_context(field)
super.merge(noteable: noteable, system_note: system?, label_url_method: noteable_label_url_method)
end
|
#can_be_award_emoji? ⇒ Boolean
393
394
395
|
# File 'app/models/note.rb', line 393
def can_be_award_emoji?
noteable.is_a?(Awardable) && !part_of_discussion?
end
|
#can_be_discussion_note? ⇒ Boolean
415
416
417
|
# File 'app/models/note.rb', line 415
def can_be_discussion_note?
self.noteable.supports_discussions? && !part_of_discussion?
end
|
#can_create_todo? ⇒ Boolean
419
420
421
422
|
# File 'app/models/note.rb', line 419
def can_create_todo?
!system? && !for_snippet?
end
|
#commit ⇒ Object
304
305
306
|
# File 'app/models/note.rb', line 304
def commit
@commit ||= project.commit(commit_id) if commit_id.present?
end
|
#confidential?(include_noteable: false) ⇒ Boolean
361
362
363
364
365
|
# File 'app/models/note.rb', line 361
def confidential?(include_noteable: false)
return true if confidential
include_noteable && noteable.try(:confidential?)
end
|
#contains_emoji_only? ⇒ Boolean
#diff_note? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
244
245
246
|
# File 'app/models/note.rb', line 244
def diff_note?
false
end
|
#discussion ⇒ Object
Returns the entire discussion this note is part of. Consider using `#to_discussion` if we do not need to render the discussion and all its notes and if we don't care about the discussion's resolvability status.
451
452
453
454
455
456
|
# File 'app/models/note.rb', line 451
def discussion
strong_memoize(:discussion) do
full_discussion = self.noteable.notes.find_discussion(self.discussion_id) if part_of_discussion?
full_discussion || to_discussion
end
end
|
#discussion_class(noteable = nil) ⇒ Object
#discussion_id(noteable = nil) ⇒ Object
See `Discussion.override_discussion_id` for details.
436
437
438
|
# File 'app/models/note.rb', line 436
def discussion_id(noteable = nil)
discussion_class(noteable).override_discussion_id(self) || super() || ensure_discussion_id
end
|
#editable? ⇒ Boolean
367
368
369
|
# File 'app/models/note.rb', line 367
def editable?
!system?
end
|
#edited? ⇒ Boolean
Since we're using `updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note. This makes sure it is only marked as edited when the note body is updated.
373
374
375
376
377
|
# File 'app/models/note.rb', line 373
def edited?
return false if updated_by.blank?
super
end
|
#emoji_awardable? ⇒ Boolean
389
390
391
|
# File 'app/models/note.rb', line 389
def emoji_awardable?
!system?
end
|
#expire_etag_cache ⇒ Object
493
494
495
|
# File 'app/models/note.rb', line 493
def expire_etag_cache
noteable&.expire_note_etag_cache
end
|
#for_alert_mangement_alert? ⇒ Boolean
280
281
282
|
# File 'app/models/note.rb', line 280
def for_alert_mangement_alert?
noteable_type == 'AlertManagement::Alert'
end
|
#for_commit? ⇒ Boolean
264
265
266
|
# File 'app/models/note.rb', line 264
def for_commit?
noteable_type == "Commit"
end
|
#for_design? ⇒ Boolean
292
293
294
|
# File 'app/models/note.rb', line 292
def for_design?
noteable_type == DesignManagement::Design.name
end
|
#for_issuable? ⇒ Boolean
296
297
298
|
# File 'app/models/note.rb', line 296
def for_issuable?
for_issue? || for_merge_request?
end
|
#for_issue? ⇒ Boolean
268
269
270
|
# File 'app/models/note.rb', line 268
def for_issue?
noteable_type == "Issue"
end
|
#for_merge_request? ⇒ Boolean
272
273
274
|
# File 'app/models/note.rb', line 272
def for_merge_request?
noteable_type == "MergeRequest"
end
|
#for_personal_snippet? ⇒ Boolean
284
285
286
|
# File 'app/models/note.rb', line 284
def for_personal_snippet?
noteable.is_a?(PersonalSnippet)
end
|
#for_project_noteable? ⇒ Boolean
288
289
290
|
# File 'app/models/note.rb', line 288
def for_project_noteable?
!for_personal_snippet?
end
|
#for_snippet? ⇒ Boolean
276
277
278
|
# File 'app/models/note.rb', line 276
def for_snippet?
noteable_type == "Snippet"
end
|
#has_special_role?(role) ⇒ Boolean
351
352
353
|
# File 'app/models/note.rb', line 351
def has_special_role?(role)
self.class.has_special_role?(role, self)
end
|
#in_reply_to?(other) ⇒ Boolean
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
|
# File 'app/models/note.rb', line 466
def in_reply_to?(other)
case other
when Note
if part_of_discussion?
in_reply_to?(other.noteable) && in_reply_to?(other.to_discussion)
else
in_reply_to?(other.noteable)
end
when Discussion
self.discussion_id == other.id
when Noteable
self.noteable == other
else
false
end
end
|
#max_attachment_size ⇒ Object
252
253
254
|
# File 'app/models/note.rb', line 252
def max_attachment_size
Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end
|
#merge_requests ⇒ Object
Notes on merge requests and commits can be traced back to one or several MRs. This method returns a relation if the note is for one of these types, or nil if it is a note on some other object.
311
312
313
314
315
316
317
318
319
|
# File 'app/models/note.rb', line 311
def merge_requests
if for_commit?
project.merge_requests.by_commit_sha(commit_id)
elsif for_merge_request?
MergeRequest.id_in(noteable_id)
else
nil
end
end
|
#noteable ⇒ Object
override to return commits, which are not active record
322
323
324
325
326
327
328
329
330
|
# File 'app/models/note.rb', line 322
def noteable
return commit if for_commit?
super
rescue
nil
end
|
#noteable_ability_name ⇒ Object
405
406
407
408
409
410
411
412
413
|
# File 'app/models/note.rb', line 405
def noteable_ability_name
if for_snippet?
'snippet'
elsif for_alert_mangement_alert?
'alert_management_alert'
else
noteable_type.demodulize.underscore
end
end
|
#noteable_assignee_or_author?(user) ⇒ Boolean
338
339
340
341
342
343
|
# File 'app/models/note.rb', line 338
def noteable_assignee_or_author?(user)
return false unless user
return noteable.assignee_or_author?(user) if [MergeRequest, Issue].include?(noteable.class)
noteable.author_id == user.id
end
|
#noteable_type=(noteable_type) ⇒ Object
FIXME: Hack for polymorphic associations with STI
For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
334
335
336
|
# File 'app/models/note.rb', line 334
def noteable_type=(noteable_type)
super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
|
#notify_after_create ⇒ Object
532
533
534
|
# File 'app/models/note.rb', line 532
def notify_after_create
noteable&.after_note_created(self)
end
|
#notify_after_destroy ⇒ Object
536
537
538
|
# File 'app/models/note.rb', line 536
def notify_after_destroy
noteable&.after_note_destroyed(self)
end
|
#parent_user ⇒ Object
564
565
566
|
# File 'app/models/note.rb', line 564
def parent_user
noteable.author if for_personal_snippet?
end
|
#part_of_discussion? ⇒ Boolean
462
463
464
|
# File 'app/models/note.rb', line 462
def part_of_discussion?
!to_discussion.individual_note?
end
|
#readable_by?(user) ⇒ Boolean
This method is to be used for checking read permissions on a note instead of `system_note_with_references_visible_for?`
380
381
382
383
|
# File 'app/models/note.rb', line 380
def readable_by?(user)
Ability.allowed?(user, :read_note, self)
end
|
#references ⇒ Object
483
484
485
486
487
488
489
490
491
|
# File 'app/models/note.rb', line 483
def references
refs = [noteable]
if part_of_discussion?
refs += discussion.notes.take_while { |n| n.id < id }
end
refs
end
|
#resource_parent ⇒ Object
548
549
550
|
# File 'app/models/note.rb', line 548
def resource_parent
project
end
|
#retrieve_upload(_identifier, paths) ⇒ Object
544
545
546
|
# File 'app/models/note.rb', line 544
def retrieve_upload(_identifier, paths)
Upload.find_by(model: self, path: paths)
end
|
#skip_notification? ⇒ Boolean
568
569
570
|
# File 'app/models/note.rb', line 568
def skip_notification?
review.present?
end
|
#skip_project_check? ⇒ Boolean
300
301
302
|
# File 'app/models/note.rb', line 300
def skip_project_check?
!for_project_noteable?
end
|
#specialize_for_first_contribution!(noteable) ⇒ Object
355
356
357
358
359
|
# File 'app/models/note.rb', line 355
def specialize_for_first_contribution!(noteable)
return unless noteable.author_id == self.author_id
self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
end
|
#start_of_discussion? ⇒ Boolean
458
459
460
|
# File 'app/models/note.rb', line 458
def start_of_discussion?
discussion.first_note == self
end
|
#supports_suggestion? ⇒ Boolean
260
261
262
|
# File 'app/models/note.rb', line 260
def supports_suggestion?
false
end
|
#system_note_with_references? ⇒ Boolean
rubocop: disable CodeReuse/ServiceClass
233
234
235
236
237
238
239
240
241
|
# File 'app/models/note.rb', line 233
def system_note_with_references?
return unless system?
if force_cross_reference_regex_check?
matches_cross_reference_regex?
else
::SystemNotes::IssuablesService.cross_reference?(note)
end
end
|
#system_note_with_references_visible_for?(user) ⇒ Boolean
558
559
560
561
562
|
# File 'app/models/note.rb', line 558
def system_note_with_references_visible_for?(user)
return true unless system?
(!system_note_with_references? || all_referenced_mentionables_allowed?(user)) && system_note_viewable_by?(user)
end
|
#to_ability_name ⇒ Object
401
402
403
|
# File 'app/models/note.rb', line 401
def to_ability_name
model_name.singular
end
|
#to_discussion(noteable = nil) ⇒ Object
Returns a discussion containing just this note. This method exists as an alternative to `#discussion` to use when the methods we intend to call on the Discussion object don't require it to have all of its notes, and just depend on the first note or the type of discussion. This saves us a DB query.
444
445
446
|
# File 'app/models/note.rb', line 444
def to_discussion(noteable = nil)
Discussion.build([self], noteable)
end
|
#touch(*args) ⇒ Object
497
498
499
500
501
502
503
504
505
506
|
# File 'app/models/note.rb', line 497
def touch(*args)
touch_noteable
super
end
|
#touch_noteable ⇒ Object
By default Rails will issue an “SELECT *” for the relation, which is overkill for just updating the timestamps. To work around this we manually touch the data so we can SELECT only the columns we need.
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
|
# File 'app/models/note.rb', line 511
def touch_noteable
return if for_commit?
assoc = association(:noteable)
noteable_object =
if assoc.loaded?
noteable
else
assoc.scope.select(:id, :updated_at).take
end
noteable_object&.touch
noteable_object
end
|
#user_mentions ⇒ Object
552
553
554
555
556
|
# File 'app/models/note.rb', line 552
def user_mentions
return Note.none unless noteable.present?
noteable.user_mentions.where(note: self)
end
|