Class: Note

Inherits:
ApplicationRecord show all
Extended by:
ActiveModel::Naming, Gitlab::Utils::Override
Includes:
AfterCommitQueue, Awardable, CacheMarkdownField, EachBatch, Editable, FasterCacheKeys, FromUnion, Gitlab::SQL::Pattern, Gitlab::Utils::StrongMemoize, IgnorableColumns, Importable, Mentionable, Participable, Redactable, ResolvableNote, Sortable, Spammable, 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.

Constant Summary collapse

ISSUE_TASK_SYSTEM_NOTE_PATTERN =
/\A.*marked\sthe\stask.+as\s(completed|incomplete).*\z/
TYPES_RESTRICTED_BY_PROJECT_ABILITY =
{
  branch: :download_code
}.freeze
TYPES_RESTRICTED_BY_GROUP_ABILITY =
{
  contact: :read_crm_contact
}.freeze
NON_DIFF_NOTE_TYPES =
['Note', 'DiscussionNote', nil].freeze

Constants included from ThrottledTouch

ThrottledTouch::TOUCH_INTERVAL

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM

Constants included from ResolvableNote

ResolvableNote::RESOLVABLE_TYPES

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Constants included from Redactable

Redactable::UNSUBSCRIBE_PATTERN

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from ResetOnUnionError

ResetOnUnionError::MAX_RESET_PERIOD

Instance Attribute Summary collapse

Attributes included from CacheMarkdownField

#skip_markdown_cache_validation

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::Override

extended, extensions, included, method_added, override, prepended, queue_verification, verify!

Methods included from Spammable

#allow_possible_spam?, #check_for_spam, #clear_spam_flags!, #invalidate_if_spam, #needs_recaptcha!, #recaptcha_error!, #render_recaptcha?, #spam, #spam!, #spam_description, #spam_title, #spammable_attribute_changed?, #spammable_entity_type, #spammable_text, #submittable_as_spam?, #submittable_as_spam_by?, #supports_recaptcha?, #unrecoverable_spam_error!

Methods included from Gitlab::SQL::Pattern

split_query_to_search_terms

Methods included from Editable

#last_edited_by

Methods included from ResolvableNote

#potentially_resolvable?, #resolvable?, #resolve!, #resolve_without_save, #resolved?, #to_be_resolved?, #unresolve!, #unresolve_without_save

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from CacheMarkdownField

#attribute_invalidated?, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #mentionable_attributes_changed?, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #store_mentions!, #updated_cached_html_for

Methods included from FasterCacheKeys

#cache_key

Methods included from Awardable

#awarded_emoji?, #downvotes, #grouped_awards, #upvotes, #user_authored?, #user_can_award?

Methods included from Mentionable

#all_references, #create_cross_references!, #create_new_cross_references!, #directly_addressed_users, #extractors, #gfm_reference, #local_reference, #matches_cross_reference_regex?, #referenced_group_users, #referenced_groups, #referenced_mentionables, #referenced_project_users, #referenced_projects, #referenced_users

Methods included from Participable

#participant?, #participants, #visible_participants

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Instance Attribute Details

#command_namesObject

Attribute used to store the quick action command names.



65
66
67
# File 'app/models/note.rb', line 65

def command_names
  @command_names
end

#commands_changesObject



706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
# File 'app/models/note.rb', line 706

def commands_changes
  @commands_changes&.slice(
    :due_date,
    :label_ids,
    :remove_label_ids,
    :add_label_ids,
    :canonical_issue_id,
    :clone_with_notes,
    :confidential,
    :create_merge_request,
    :add_contacts,
    :remove_contacts,
    :assignee_ids,
    :milestone_id,
    :time_estimate,
    :spend_time,
    :discussion_locked,
    :merge,
    :rebase,
    :wip_event,
    :target_branch,
    :reviewer_ids,
    :health_status,
    :promote_to_epic,
    :weight,
    :emoji_award,
    :todo_event,
    :subscription_event,
    :state_event,
    :title,
    :tag_message,
    :tag_name
  )
end

#redacted_note_htmlObject

Attribute containing rendered and redacted Markdown as generated by Banzai::ObjectRenderer.



53
54
55
# File 'app/models/note.rb', line 53

def redacted_note_html
  @redacted_note_html
end

#skip_keep_around_commitsObject

Attribute used to determine whether keep_around_commits will be skipped for diff notes.



68
69
70
# File 'app/models/note.rb', line 68

def skip_keep_around_commits
  @skip_keep_around_commits
end

#total_reference_countObject

Total of all references as generated by Banzai::ObjectRenderer



56
57
58
# File 'app/models/note.rb', line 56

def total_reference_count
  @total_reference_count
end

#user_visible_reference_countObject

Number of user visible references as generated by Banzai::ObjectRenderer



59
60
61
# File 'app/models/note.rb', line 59

def user_visible_reference_count
  @user_visible_reference_count
end

Class Method Details

.cherry_picked_merge_requests(shas) ⇒ Object



308
309
310
# File 'app/models/note.rb', line 308

def cherry_picked_merge_requests(shas)
  where(noteable_type: 'MergeRequest', commit_id: shas).select(:noteable_id)
end

.count_for_collection(ids, type, count_column = 'COUNT(*) as count') ⇒ Object



291
292
293
294
295
# File 'app/models/note.rb', line 291

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

.discussions(context_noteable = nil) ⇒ Object



248
249
250
# File 'app/models/note.rb', line 248

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.



254
255
256
257
258
259
260
# File 'app/models/note.rb', line 254

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.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'app/models/note.rb', line 265

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

.model_nameObject



244
245
246
# File 'app/models/note.rb', line 244

def model_name
  ActiveModel::Name.new(self, nil, 'note')
end

.positionsObject



285
286
287
288
289
# File 'app/models/note.rb', line 285

def positions
  where.not(position: nil)
    .select(:id, :type, :position) # ActiveRecord needs id and type for typecasting.
    .map(&:position)
end

.search(query) ⇒ Object



297
298
299
# File 'app/models/note.rb', line 297

def search(query)
  fuzzy_search(query, [:note])
end

.simple_sortsObject



304
305
306
# File 'app/models/note.rb', line 304

def simple_sorts
  super.except('name_asc', 'name_desc')
end

.with_web_entity_associationsObject



312
313
314
# File 'app/models/note.rb', line 312

def with_web_entity_associations
  preload(:project, :author, :noteable)
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


333
334
335
# File 'app/models/note.rb', line 333

def active?
  true
end

#attribute_names_for_serializationObject

Use attributes.keys instead of attribute_names to filter out the fields that are skipped during export:

  • note_html

  • cached_markdown_version



800
801
802
# File 'app/models/note.rb', line 800

def attribute_names_for_serialization
  attributes.keys
end

#award_emoji?Boolean

Returns:

  • (Boolean)


473
474
475
# File 'app/models/note.rb', line 473

def award_emoji?
  can_be_award_emoji? && contains_emoji_only?
end

#banzai_render_context(field) ⇒ Object



641
642
643
# File 'app/models/note.rb', line 641

def banzai_render_context(field)
  super.merge(noteable: noteable, system_note: system?, label_url_method: noteable_label_url_method)
end

#broadcast_noteable_notes_changedObject



594
595
596
# File 'app/models/note.rb', line 594

def broadcast_noteable_notes_changed
  noteable&.broadcast_notes_changed
end

#bump_updated_atObject



579
580
581
582
583
584
585
586
587
588
589
590
591
592
# File 'app/models/note.rb', line 579

def bump_updated_at
  # Instead of calling touch which is throttled via ThrottledTouch concern,
  # we bump the updated_at column directly. This also prevents executing
  # after_commit callbacks that we don't need.
  attributes_to_update = { updated_at: Time.current }

  # Notes that were edited before the `last_edited_at` column was added, fall back to `updated_at` for the edit time.
  # We copy this over to the correct column so we don't erroneously change the edit timestamp.
  if updated_by_id.present? && read_attribute(:last_edited_at).blank?
    attributes_to_update[:last_edited_at] = updated_at
  end

  update_columns(attributes_to_update)
end

#can_be_award_emoji?Boolean

Returns:

  • (Boolean)


481
482
483
# File 'app/models/note.rb', line 481

def can_be_award_emoji?
  noteable.is_a?(Awardable) && !part_of_discussion?
end

#can_be_discussion_note?Boolean

Returns:

  • (Boolean)


501
502
503
# File 'app/models/note.rb', line 501

def can_be_discussion_note?
  self.noteable.supports_discussions? && !part_of_discussion? && !system?
end

#can_create_todo?Boolean

Returns:

  • (Boolean)


505
506
507
508
# File 'app/models/note.rb', line 505

def can_create_todo?
  # Skip system notes, and notes on snippets
  !system? && !for_snippet?
end

#check_for_spam?Boolean

Override method defined in Spammable Wildcard argument because user: argument is not used

Returns:

  • (Boolean)


788
789
790
791
792
793
794
# File 'app/models/note.rb', line 788

def check_for_spam?(*)
  return false if system? || !spammable_attribute_changed? || confidential?
  return false if noteable.try(:confidential?) == true || noteable.try(:public?) == false
  return false if noteable.try(:group)&.public? == false || project&.public? == false

  true
end

#commitObject



401
402
403
# File 'app/models/note.rb', line 401

def commit
  @commit ||= project.commit(commit_id) if commit_id.present?
end

#confidential?(include_noteable: false) ⇒ Boolean

Returns:

  • (Boolean)


449
450
451
452
453
# File 'app/models/note.rb', line 449

def confidential?(include_noteable: false)
  return true if confidential

  include_noteable && noteable.try(:confidential?)
end

#contains_emoji_only?Boolean

Returns:

  • (Boolean)


485
486
487
# File 'app/models/note.rb', line 485

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

#contributor?Boolean

Returns:

  • (Boolean)


433
434
435
# File 'app/models/note.rb', line 433

def contributor?
  project&.team&.contributor?(self.author_id)
end

#diff_note?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)


329
330
331
# File 'app/models/note.rb', line 329

def diff_note?
  false
end

#discussionObject

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.



537
538
539
540
541
542
# File 'app/models/note.rb', line 537

def discussion
  strong_memoize(:discussion) do
    full_discussion = self.noteable.notes.find_discussion(self.discussion_id) if self.noteable && part_of_discussion?
    full_discussion || to_discussion
  end
end

#discussion_class(noteable = nil) ⇒ Object



510
511
512
513
514
515
516
517
518
519
# File 'app/models/note.rb', line 510

def discussion_class(noteable = nil)
  # When commit notes are rendered on an MR's Discussion page, they are
  # displayed in one discussion instead of individually.
  # See also `#discussion_id` and `Discussion.override_discussion_id`.
  if noteable && noteable != self.noteable
    OutOfContextDiscussion
  else
    IndividualNoteDiscussion
  end
end

#discussion_id(noteable = nil) ⇒ Object

See ‘Discussion.override_discussion_id` for details.



522
523
524
# File 'app/models/note.rb', line 522

def discussion_id(noteable = nil)
  discussion_class(noteable).override_discussion_id(self) || super() || ensure_discussion_id
end

#editable?Boolean

Returns:

  • (Boolean)


455
456
457
# File 'app/models/note.rb', line 455

def editable?
  !system?
end

#edited?Boolean

Since we used ‘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.

Returns:

  • (Boolean)


467
468
469
470
471
# File 'app/models/note.rb', line 467

def edited?
  return false if updated_by.blank?

  super
end

#emoji_awardable?Boolean

Returns:

  • (Boolean)


477
478
479
# File 'app/models/note.rb', line 477

def emoji_awardable?
  !system?
end

#exportable_record?(user) ⇒ Boolean

Returns:

  • (Boolean)


780
781
782
783
784
# File 'app/models/note.rb', line 780

def exportable_record?(user)
  return true unless system?

  readable_by?(user)
end

#for_alert_mangement_alert?Boolean

Returns:

  • (Boolean)


369
370
371
# File 'app/models/note.rb', line 369

def for_alert_mangement_alert?
  noteable_type == 'AlertManagement::Alert'
end

#for_commit?Boolean

Returns:

  • (Boolean)


349
350
351
# File 'app/models/note.rb', line 349

def for_commit?
  noteable_type == "Commit"
end

#for_design?Boolean

Returns:

  • (Boolean)


389
390
391
# File 'app/models/note.rb', line 389

def for_design?
  noteable_type == DesignManagement::Design.name
end

#for_issuable?Boolean

Returns:

  • (Boolean)


393
394
395
# File 'app/models/note.rb', line 393

def for_issuable?
  for_issue? || for_merge_request?
end

#for_issue?Boolean

Returns:

  • (Boolean)


353
354
355
# File 'app/models/note.rb', line 353

def for_issue?
  noteable_type == "Issue"
end

#for_merge_request?Boolean

Returns:

  • (Boolean)


361
362
363
# File 'app/models/note.rb', line 361

def for_merge_request?
  noteable_type == "MergeRequest"
end

#for_personal_snippet?Boolean

Returns:

  • (Boolean)


381
382
383
# File 'app/models/note.rb', line 381

def for_personal_snippet?
  noteable.is_a?(PersonalSnippet)
end

#for_project_noteable?Boolean

Returns:

  • (Boolean)


385
386
387
# File 'app/models/note.rb', line 385

def for_project_noteable?
  !for_personal_snippet?
end

#for_project_snippet?Boolean

Returns:

  • (Boolean)


377
378
379
# File 'app/models/note.rb', line 377

def for_project_snippet?
  noteable.is_a?(ProjectSnippet)
end

#for_snippet?Boolean

Returns:

  • (Boolean)


365
366
367
# File 'app/models/note.rb', line 365

def for_snippet?
  noteable_type == "Snippet"
end

#for_vulnerability?Boolean

Returns:

  • (Boolean)


373
374
375
# File 'app/models/note.rb', line 373

def for_vulnerability?
  noteable_type == "Vulnerability"
end

#for_work_item?Boolean

Returns:

  • (Boolean)


357
358
359
# File 'app/models/note.rb', line 357

def for_work_item?
  noteable.is_a?(WorkItem)
end

#hook_attrsObject



341
342
343
# File 'app/models/note.rb', line 341

def hook_attrs
  Gitlab::HookData::NoteBuilder.new(self).build
end

#human_max_accessObject



437
438
439
# File 'app/models/note.rb', line 437

def human_max_access
  project&.team&.human_max_access(self.author_id)
end

#in_reply_to?(other) ⇒ Boolean

Returns:

  • (Boolean)


552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'app/models/note.rb', line 552

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

#issuable_ability_nameObject



776
777
778
# File 'app/models/note.rb', line 776

def issuable_ability_name
  confidential? ? :read_internal_note : :read_note
end

#last_edited_atObject

We used ‘last_edited_at` as an alias of `updated_at` before. This makes it compatible with the previous way without data migration.



461
462
463
# File 'app/models/note.rb', line 461

def last_edited_at
  super || updated_at
end

#max_attachment_sizeObject



337
338
339
# File 'app/models/note.rb', line 337

def max_attachment_size
  Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end

#mentioned_filtered_user_ids_for(references) ⇒ Object



749
750
751
752
753
754
755
756
757
758
# File 'app/models/note.rb', line 749

def mentioned_filtered_user_ids_for(references)
  return super unless confidential?

  user_ids = references.mentioned_user_ids.presence

  return [] if user_ids.blank?

  users = User.where(id: user_ids)
  Ability.users_that_can_read_internal_notes(users, resource_parent).pluck(:id)
end

#mentioned_users(current_user = nil) ⇒ Object



741
742
743
744
745
746
747
# File 'app/models/note.rb', line 741

def mentioned_users(current_user = nil)
  users = super

  return users unless confidential?

  Ability.users_that_can_read_internal_notes(users, resource_parent)
end

#merge_requestsObject

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.



408
409
410
411
412
413
414
# File 'app/models/note.rb', line 408

def merge_requests
  if for_commit?
    project.merge_requests.by_commit_sha(commit_id)
  elsif for_merge_request?
    MergeRequest.id_in(noteable_id)
  end
end

#noteObject

Method necessary while we transition into the new format for task system notes TODO: gitlab.com/gitlab-org/gitlab/-/issues/369923



762
763
764
765
766
# File 'app/models/note.rb', line 762

def note
  return super unless system? && for_issue? && super&.match?(ISSUE_TASK_SYSTEM_NOTE_PATTERN)

  super.sub!('task', 'checklist item')
end

#note_htmlObject

Method necesary while we transition into the new format for task system notes TODO: gitlab.com/gitlab-org/gitlab/-/issues/369923



770
771
772
773
774
# File 'app/models/note.rb', line 770

def note_html
  return super unless system? && for_issue? && super&.match?(ISSUE_TASK_SYSTEM_NOTE_PATTERN)

  super.sub!('task', 'checklist item')
end

#noteableObject

override to return commits, which are not active record



417
418
419
420
421
422
423
424
425
# File 'app/models/note.rb', line 417

def noteable
  return commit if for_commit?

  super
rescue StandardError
  # Temp fix to prevent app crash
  # if note commit id doesn't exist
  nil
end

#noteable_ability_nameObject



489
490
491
492
493
494
495
496
497
498
499
# File 'app/models/note.rb', line 489

def noteable_ability_name
  if for_snippet?
    'snippet'
  elsif for_alert_mangement_alert?
    'alert_management_alert'
  elsif for_vulnerability?
    'security_resource'
  else
    noteable_type.demodulize.underscore
  end
end

#noteable_author?(noteable) ⇒ Boolean

Returns:

  • (Boolean)


441
442
443
# File 'app/models/note.rb', line 441

def noteable_author?(noteable)
  noteable.author == self.author
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


429
430
431
# File 'app/models/note.rb', line 429

def noteable_type=(noteable_type)
  super(noteable_type.to_s.classify.constantize.base_class.to_s)
end

#notify_after_createObject



633
634
635
# File 'app/models/note.rb', line 633

def notify_after_create
  noteable&.after_note_created(self)
end

#notify_after_destroyObject



637
638
639
# File 'app/models/note.rb', line 637

def notify_after_destroy
  noteable&.after_note_destroyed(self)
end

#parent_userObject



665
666
667
# File 'app/models/note.rb', line 665

def parent_user
  noteable.author if for_personal_snippet?
end

#part_of_discussion?Boolean

Returns:

  • (Boolean)


548
549
550
# File 'app/models/note.rb', line 548

def part_of_discussion?
  !to_discussion.individual_note?
end

#post_processed_cache_keyObject



673
674
675
676
677
678
679
# File 'app/models/note.rb', line 673

def post_processed_cache_key
  cache_key_items = [cache_key, author&.cache_key]
  cache_key_items << project.team.human_max_access(author&.id) if author.present?
  cache_key_items << Digest::SHA1.hexdigest(redacted_note_html) if redacted_note_html.present?

  cache_key_items.join(':')
end

#project_nameObject



445
446
447
# File 'app/models/note.rb', line 445

def project_name
  project&.name
end

#referencesObject



569
570
571
572
573
574
575
576
577
# File 'app/models/note.rb', line 569

def references
  refs = [noteable]

  if part_of_discussion?
    refs += discussion.notes.take_while { |n| n.id < id }
  end

  refs
end

#resource_parentObject



649
650
651
# File 'app/models/note.rb', line 649

def resource_parent
  project
end

#retrieve_upload(_identifier, paths) ⇒ Object



645
646
647
# File 'app/models/note.rb', line 645

def retrieve_upload(_identifier, paths)
  Upload.find_by(model: self, path: paths)
end

#show_outdated_changes?Boolean

Returns:

  • (Boolean)


697
698
699
700
701
702
703
704
# File 'app/models/note.rb', line 697

def show_outdated_changes?
  return false unless for_merge_request?
  return false unless system?
  return false if change_position&.on_file?
  return false unless change_position&.line_range

  change_position.line_range["end"] || change_position.line_range["start"]
end

#skip_notification?Boolean

Returns:

  • (Boolean)


669
670
671
# File 'app/models/note.rb', line 669

def skip_notification?
  review.present? || !author.can_trigger_notifications?
end

#skip_project_check?Boolean

Returns:

  • (Boolean)


397
398
399
# File 'app/models/note.rb', line 397

def skip_project_check?
  !for_project_noteable?
end

#start_of_discussion?Boolean

Returns:

  • (Boolean)


544
545
546
# File 'app/models/note.rb', line 544

def start_of_discussion?
  discussion.first_note == self
end

#supports_suggestion?Boolean

Returns:

  • (Boolean)


345
346
347
# File 'app/models/note.rb', line 345

def supports_suggestion?
  false
end

#system_note_visible_for?(user) ⇒ Boolean

Returns:

  • (Boolean)


659
660
661
662
663
# File 'app/models/note.rb', line 659

def system_note_visible_for?(user)
  return true unless system?

  system_note_viewable_by?(user) && all_referenced_mentionables_allowed?(user)
end

#system_note_with_references?Boolean

rubocop: disable CodeReuse/ServiceClass

Returns:

  • (Boolean)


318
319
320
321
322
323
324
325
326
# File 'app/models/note.rb', line 318

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

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



530
531
532
# File 'app/models/note.rb', line 530

def to_discussion(noteable = nil)
  Discussion.build([self], noteable)
end

#touch(*args, **kwargs) ⇒ Object



598
599
600
601
602
603
604
605
606
607
# File 'app/models/note.rb', line 598

def touch(*args, **kwargs)
  # We're not using an explicit transaction here because this would in all
  # cases result in all future queries going to the primary, even if no writes
  # are performed.
  #
  # We touch the noteable first so its SELECT query can run before our writes,
  # ensuring it runs on a secondary (if no prior write took place).
  touch_noteable
  super
end

#touch_noteableObject

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.



612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'app/models/note.rb', line 612

def touch_noteable
  # Commits are not stored in the DB so we can't touch them.
  return if for_commit?

  assoc = association(:noteable)

  noteable_object =
    if assoc.loaded?
      noteable
    else
      # If the object is not loaded (e.g. when notes are loaded async) we
      # _only_ want the data we actually need.
      assoc.scope.select(:id, :updated_at).take
    end

  noteable_object&.touch

  # We return the noteable object so we can re-use it in EE for Elasticsearch.
  noteable_object
end

#trigger_note_subscription_createObject



212
213
214
215
216
# File 'app/models/note.rb', line 212

def trigger_note_subscription_create
  return unless trigger_note_subscription?

  GraphqlTriggers.work_item_note_created(noteable.to_work_item_global_id, self)
end

#trigger_note_subscription_destroyObject



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'app/models/note.rb', line 224

def trigger_note_subscription_destroy
  return unless trigger_note_subscription?

  # when deleting a note, we cannot pass it on as a Note instance, as GitlabSchema.object_from_id
  # would try to resolve the given Note and fetch it from DB which would raise NotFound exception.
  # So instead we just pass over the string representations of the note and discussion IDs,
  # so that the subscriber can identify the discussion and the note.
  deleted_note_data = {
    id: self.id,
    model_name: self.class.name,
    discussion_id: self.discussion_id,
    last_discussion_note: discussion.notes == [self]
  }

  GraphqlTriggers.work_item_note_deleted(noteable.to_work_item_global_id, deleted_note_data)
end

#trigger_note_subscription_updateObject



218
219
220
221
222
# File 'app/models/note.rb', line 218

def trigger_note_subscription_update
  return unless trigger_note_subscription?

  GraphqlTriggers.work_item_note_updated(noteable.to_work_item_global_id, self)
end

#user_mention_classObject



682
683
684
685
686
# File 'app/models/note.rb', line 682

def user_mention_class
  return if noteable.blank?

  noteable.user_mention_class
end

#user_mention_identifierObject



689
690
691
692
693
694
695
# File 'app/models/note.rb', line 689

def user_mention_identifier
  return if noteable.blank?

  noteable.user_mention_identifier.merge({
    note_id: id
  })
end

#user_mentionsObject



653
654
655
656
657
# File 'app/models/note.rb', line 653

def user_mentions
  return Note.none unless noteable.present?

  noteable.user_mentions.where(note: self)
end