Class: Note

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

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 included from Import::HasImportSource

Import::HasImportSource::IMPORT_SOURCES

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from HasCheckConstraints

HasCheckConstraints::NOT_NULL_CHECK_PATTERN

Constants included from ResetOnColumnErrors

ResetOnColumnErrors::MAX_RESET_PERIOD

Instance Attribute Summary collapse

Attributes included from CacheMarkdownField

#skip_markdown_cache_validation

Attributes included from Importable

#importing, #user_contributions

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, #mentionable_attributes_changed?, #refresh_markdown_cache!, #rendered_field_content, #store_mentions!, #store_mentions?, #store_mentions_after_commit?, #updated_cached_html_for

Methods included from FasterCacheKeys

#cache_key

Methods included from Import::HasImportSource

#imported?

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 included from Notes::Discussion

#derive_discussion_id, #discussion, #discussion=, #discussion_class, #discussion_id, #ensure_discussion_id, #in_reply_to?, #part_of_discussion?, #start_of_discussion?, #to_discussion

Methods included from Notes::ActiveRecord

#last_edited_by

Methods inherited from ApplicationRecord

===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, sharding_keys, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Methods included from Gitlab::SensitiveSerializableHash

#serializable_hash

Instance Attribute Details

#commands_changesObject



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
# File 'app/models/note.rb', line 673

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

#quick_actions_statusObject

Attribute used to store the status of quick actions.



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

def quick_actions_status
  @quick_actions_status
end

#redacted_note_htmlObject

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



48
49
50
# File 'app/models/note.rb', line 48

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.



63
64
65
# File 'app/models/note.rb', line 63

def skip_keep_around_commits
  @skip_keep_around_commits
end

#skip_touch_noteableObject

Attribute used to skip updates of ‘updated_at` for the noteable when it could impact database health.



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

def skip_touch_noteable
  @skip_touch_noteable
end

#total_reference_countObject

Total of all references as generated by Banzai::ObjectRenderer



51
52
53
# File 'app/models/note.rb', line 51

def total_reference_count
  @total_reference_count
end

#user_visible_reference_countObject

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



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

def user_visible_reference_count
  @user_visible_reference_count
end

Class Method Details

.cherry_picked_merge_requests(shas) ⇒ Object



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

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



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

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

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



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'app/models/note.rb', line 270

def grouped_diff_discussions(diff_refs = nil)
  groups = {}

  diff_notes.order_created_at_id_asc.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



259
260
261
# File 'app/models/note.rb', line 259

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

.parent_object_fieldObject



263
264
265
# File 'app/models/note.rb', line 263

def parent_object_field
  :noteable
end

.positionsObject



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

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

.search(query) ⇒ Object



302
303
304
# File 'app/models/note.rb', line 302

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

.simple_sortsObject



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

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

.with_web_entity_associationsObject



317
318
319
# File 'app/models/note.rb', line 317

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

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


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

def active?
  true
end

#award_emoji?Boolean

Returns:

  • (Boolean)


491
492
493
# File 'app/models/note.rb', line 491

def award_emoji?
  can_be_award_emoji? && contains_emoji_only?
end

#banzai_render_context(field) ⇒ Object



605
606
607
608
609
610
# File 'app/models/note.rb', line 605

def banzai_render_context(field)
  additional_attributes = { noteable: noteable, system_note: system?, label_url_method: noteable_label_url_method }
  additional_attributes[:group] = namespace if namespace.is_a?(Group)

  super.merge(additional_attributes)
end

#broadcast_noteable_notes_changedObject



557
558
559
# File 'app/models/note.rb', line 557

def broadcast_noteable_notes_changed
  noteable&.broadcast_notes_changed
end

#bump_updated_atObject



542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'app/models/note.rb', line 542

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)


499
500
501
# File 'app/models/note.rb', line 499

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

#can_be_discussion_note?Boolean

Returns:

  • (Boolean)


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

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

#can_create_todo?Boolean

Returns:

  • (Boolean)


527
528
529
530
# File 'app/models/note.rb', line 527

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)


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

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



418
419
420
# File 'app/models/note.rb', line 418

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

#confidential?(include_noteable: false) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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

#contains_emoji_only?Boolean

Returns:

  • (Boolean)


503
504
505
# File 'app/models/note.rb', line 503

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

#contributor?Boolean

Returns:

  • (Boolean)


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

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

#diff_note?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)


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

def diff_note?
  false
end

#editable?Boolean

Returns:

  • (Boolean)


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

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)


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

def edited?
  return false if read_attribute(:last_edited_at).blank? && updated_by.blank?

  super
end

#emoji_awardable?Boolean

Returns:

  • (Boolean)


495
496
497
# File 'app/models/note.rb', line 495

def emoji_awardable?
  !system?
end

#exportable_record?(user) ⇒ Boolean

Returns:

  • (Boolean)


731
732
733
734
735
# File 'app/models/note.rb', line 731

def exportable_record?(user)
  return true unless system?

  readable_by?(user)
end

#for_abuse_report?Boolean

Returns:

  • (Boolean)


410
411
412
# File 'app/models/note.rb', line 410

def for_abuse_report?
  noteable_type == AbuseReport.name
end

#for_alert_mangement_alert?Boolean

Returns:

  • (Boolean)


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

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

#for_commit?Boolean

Returns:

  • (Boolean)


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

def for_commit?
  noteable_type == "Commit"
end

#for_compliance_violation?Boolean

Returns:

  • (Boolean)


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

def for_compliance_violation?
  noteable_type == 'ComplianceManagement::Projects::ComplianceViolation'
end

#for_design?Boolean

Returns:

  • (Boolean)


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

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

#for_issuable?Boolean

Returns:

  • (Boolean)


406
407
408
# File 'app/models/note.rb', line 406

def for_issuable?
  for_issue? || for_merge_request?
end

#for_issue?Boolean

Returns:

  • (Boolean)


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

def for_issue?
  noteable_type == "Issue"
end

#for_merge_request?Boolean

Returns:

  • (Boolean)


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

def for_merge_request?
  noteable_type == "MergeRequest"
end

#for_personal_snippet?Boolean

Returns:

  • (Boolean)


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

def for_personal_snippet?
  noteable.is_a?(PersonalSnippet)
end

#for_project_noteable?Boolean

Returns:

  • (Boolean)


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

def for_project_noteable?
  !(for_personal_snippet? || for_abuse_report? || group_level_issue?)
end

#for_project_snippet?Boolean

Returns:

  • (Boolean)


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

def for_project_snippet?
  noteable.is_a?(ProjectSnippet)
end

#for_snippet?Boolean

Returns:

  • (Boolean)


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

def for_snippet?
  noteable_type == "Snippet"
end

#for_vulnerability?Boolean

Returns:

  • (Boolean)


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

def for_vulnerability?
  noteable_type == "Vulnerability"
end

#for_wiki_page?Boolean

Returns:

  • (Boolean)


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

def for_wiki_page?
  noteable_type == "WikiPage::Meta"
end

#for_work_item?Boolean

Returns:

  • (Boolean)


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

def for_work_item?
  noteable.is_a?(WorkItem)
end

#group_level_issue?Boolean

Returns:

  • (Boolean)


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

def group_level_issue?
  (for_issue? || for_work_item?) && noteable&.project_id.blank?
end

#hook_attrsObject



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

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

#human_max_accessObject

overridden in ee



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

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

#issuable_ability_nameObject



727
728
729
# File 'app/models/note.rb', line 727

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.



479
480
481
# File 'app/models/note.rb', line 479

def last_edited_at
  super || updated_at
end

#mentioned_filtered_user_ids_for(references) ⇒ Object



716
717
718
719
720
721
722
723
724
725
# File 'app/models/note.rb', line 716

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



708
709
710
711
712
713
714
# File 'app/models/note.rb', line 708

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.



425
426
427
428
429
430
431
# File 'app/models/note.rb', line 425

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

#noteableObject

override to return commits, which are not active record



434
435
436
437
438
439
440
441
442
# File 'app/models/note.rb', line 434

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



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

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

#noteable_author?(noteable) ⇒ Boolean

Returns:

  • (Boolean)


459
460
461
# File 'app/models/note.rb', line 459

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


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

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

#notify_after_createObject



597
598
599
# File 'app/models/note.rb', line 597

def notify_after_create
  noteable&.after_note_created(self)
end

#notify_after_destroyObject



601
602
603
# File 'app/models/note.rb', line 601

def notify_after_destroy
  noteable&.after_note_destroyed(self)
end

#parent_userObject



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

def parent_user
  noteable.author if for_personal_snippet?
end

#post_processed_cache_keyObject



640
641
642
643
644
645
646
# File 'app/models/note.rb', line 640

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



463
464
465
# File 'app/models/note.rb', line 463

def project_name
  project&.name
end

#referencesObject



532
533
534
535
536
537
538
539
540
# File 'app/models/note.rb', line 532

def references
  refs = [noteable]

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

  refs
end

#refresh_markdown_cacheObject

TODO: Remove when sharding key NOT NULL constraint is validated This should not happen often, and will only happen once for any record gitlab.com/gitlab-org/gitlab/-/work_items/570340



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

def refresh_markdown_cache
  return super if sharding_key_columns.any? { |s_column| self[s_column].present? }

  # Using model callbacks to get sharding key values
  ensure_organization_id
  ensure_namespace_id

  sharding_key_changes = changes.select { |k, _| sharding_key_columns.include?(k.to_sym) }
  sharding_key_updates = sharding_key_changes.transform_values(&:last)

  super.merge(sharding_key_updates)
end

#resource_parentObject



616
617
618
# File 'app/models/note.rb', line 616

def resource_parent
  noteable.try(:resource_parent) || project
end

#retrieve_upload(_identifier, paths) ⇒ Object



612
613
614
# File 'app/models/note.rb', line 612

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

#show_outdated_changes?Boolean

Returns:

  • (Boolean)


664
665
666
667
668
669
670
671
# File 'app/models/note.rb', line 664

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)


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

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

#skip_project_check?Boolean

Returns:

  • (Boolean)


414
415
416
# File 'app/models/note.rb', line 414

def skip_project_check?
  !for_project_noteable?
end

#supports_suggestion?Boolean

Returns:

  • (Boolean)


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

def supports_suggestion?
  false
end

#system_note_visible_for?(user) ⇒ Boolean

Returns:

  • (Boolean)


626
627
628
629
630
# File 'app/models/note.rb', line 626

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)


323
324
325
326
327
328
329
330
331
# File 'app/models/note.rb', line 323

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

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



561
562
563
564
565
566
567
568
569
570
# File 'app/models/note.rb', line 561

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.



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
# File 'app/models/note.rb', line 575

def touch_noteable
  # Commits are not stored in the DB so we can't touch them.
  # Vulnerabilities should not be touched as they are tracked in the same manner as other issuable types
  return if for_vulnerability? || 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



220
221
222
223
224
# File 'app/models/note.rb', line 220

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



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'app/models/note.rb', line 232

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



226
227
228
229
230
# File 'app/models/note.rb', line 226

def trigger_note_subscription_update
  return unless trigger_note_subscription?

  GraphqlTriggers.work_item_note_updated(noteable.to_work_item_global_id, self)
end

#trigger_work_item_updated_subscriptionObject



249
250
251
252
253
254
# File 'app/models/note.rb', line 249

def trigger_work_item_updated_subscription
  return unless trigger_note_subscription?
  return unless system_note_work_item_reference?

  GraphqlTriggers.work_item_updated(noteable)
end

#user_mention_classObject



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

def user_mention_class
  return if noteable.blank?

  noteable.user_mention_class
end

#user_mention_identifierObject



656
657
658
659
660
661
662
# File 'app/models/note.rb', line 656

def user_mention_identifier
  return if noteable.blank?

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

#user_mentionsObject



620
621
622
623
624
# File 'app/models/note.rb', line 620

def user_mentions
  return Note.none unless noteable.present?

  noteable.user_mentions.where(note: self)
end