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, Import::HasImportSource, Importable, Mentionable, Notes::ActiveRecord, Notes::Discussion, 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
- 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
ThrottledTouch::TOUCH_INTERVAL
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM
ResolvableNote::RESOLVABLE_TYPES
CacheMarkdownField::INVALIDATED_BY
Constants included
from Redactable
Redactable::UNSUBSCRIBE_PATTERN
Import::HasImportSource::IMPORT_SOURCES
ApplicationRecord::MAX_PLUCK
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
ResetOnColumnErrors::MAX_RESET_PERIOD
Instance Attribute Summary collapse
#skip_markdown_cache_validation
Attributes included from Importable
#importing, #user_contributions
Class Method Summary
collapse
Instance Method Summary
collapse
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!
split_query_to_search_terms
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, #mentionable_attributes_changed?, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #store_mentions!, #store_mentions?, #store_mentions_after_commit?, #updated_cached_html_for
#cache_key
#imported?
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?, #referenced_group_users, #referenced_groups, #referenced_mentionables, #referenced_project_users, #referenced_projects, #referenced_users
#participant?, #participants, #visible_participants
#derive_discussion_id, #discussion, #discussion=, #discussion_class, #discussion_id, #ensure_discussion_id, #in_reply_to?, #part_of_discussion?, #start_of_discussion?, #to_discussion
#last_edited_by
===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, nullable_column?, 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
#reset_on_union_error, #reset_on_unknown_attribute_error
#serializable_hash
Instance Attribute Details
#commands_changes ⇒ Object
670
671
672
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
|
# File 'app/models/note.rb', line 670
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_status ⇒ Object
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_html ⇒ Object
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_commits ⇒ Object
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_noteable ⇒ Object
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_count ⇒ Object
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_count ⇒ Object
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
310
311
312
|
# File 'app/models/note.rb', line 310
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
293
294
295
296
297
|
# File 'app/models/note.rb', line 293
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.
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
# File 'app/models/note.rb', line 267
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_name ⇒ Object
256
257
258
|
# File 'app/models/note.rb', line 256
def model_name
ActiveModel::Name.new(self, nil, 'note')
end
|
.parent_object_field ⇒ Object
260
261
262
|
# File 'app/models/note.rb', line 260
def parent_object_field
:noteable
end
|
.positions ⇒ Object
287
288
289
290
291
|
# File 'app/models/note.rb', line 287
def positions
where.not(position: nil)
.select(:id, :type, :position) .map(&:position)
end
|
.search(query) ⇒ Object
299
300
301
|
# File 'app/models/note.rb', line 299
def search(query)
fuzzy_search(query, [:note])
end
|
.simple_sorts ⇒ Object
306
307
308
|
# File 'app/models/note.rb', line 306
def simple_sorts
super.except('name_asc', 'name_desc')
end
|
.with_web_entity_associations ⇒ Object
314
315
316
|
# File 'app/models/note.rb', line 314
def with_web_entity_associations
preload(:project, :author, :noteable)
end
|
Instance Method Details
#active? ⇒ Boolean
335
336
337
|
# File 'app/models/note.rb', line 335
def active?
true
end
|
#attribute_names_for_serialization ⇒ Object
Use attributes.keys instead of attribute_names to filter out the fields that are skipped during export:
-
note_html
-
cached_markdown_version
748
749
750
|
# File 'app/models/note.rb', line 748
def attribute_names_for_serialization
attributes.keys
end
|
#award_emoji? ⇒ Boolean
488
489
490
|
# File 'app/models/note.rb', line 488
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
|
#banzai_render_context(field) ⇒ Object
602
603
604
605
606
607
|
# File 'app/models/note.rb', line 602
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_changed ⇒ Object
554
555
556
|
# File 'app/models/note.rb', line 554
def broadcast_noteable_notes_changed
noteable&.broadcast_notes_changed
end
|
#bump_updated_at ⇒ Object
539
540
541
542
543
544
545
546
547
548
549
550
551
552
|
# File 'app/models/note.rb', line 539
def bump_updated_at
attributes_to_update = { updated_at: Time.current }
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
496
497
498
|
# File 'app/models/note.rb', line 496
def can_be_award_emoji?
noteable.is_a?(Awardable) && !part_of_discussion?
end
|
#can_be_discussion_note? ⇒ Boolean
520
521
522
|
# File 'app/models/note.rb', line 520
def can_be_discussion_note?
self.noteable.supports_discussions? && !part_of_discussion? && !system?
end
|
#can_create_todo? ⇒ Boolean
524
525
526
527
|
# File 'app/models/note.rb', line 524
def can_create_todo?
!system? && !for_snippet?
end
|
#check_for_spam? ⇒ Boolean
Override method defined in Spammable Wildcard argument because user: argument is not used
736
737
738
739
740
741
742
|
# File 'app/models/note.rb', line 736
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
|
#commit ⇒ Object
415
416
417
|
# File 'app/models/note.rb', line 415
def commit
@commit ||= project.commit(commit_id) if commit_id.present?
end
|
#confidential?(include_noteable: false) ⇒ Boolean
464
465
466
467
468
|
# File 'app/models/note.rb', line 464
def confidential?(include_noteable: false)
return true if confidential
include_noteable && noteable.try(:confidential?)
end
|
#contains_emoji_only? ⇒ Boolean
#contributor? ⇒ Boolean
447
448
449
|
# File 'app/models/note.rb', line 447
def contributor?
project&.team&.contributor?(self.author_id)
end
|
#diff_note? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
331
332
333
|
# File 'app/models/note.rb', line 331
def diff_note?
false
end
|
#editable? ⇒ Boolean
470
471
472
|
# File 'app/models/note.rb', line 470
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.
482
483
484
485
486
|
# File 'app/models/note.rb', line 482
def edited?
return false if read_attribute(:last_edited_at).blank? && updated_by.blank?
super
end
|
#emoji_awardable? ⇒ Boolean
492
493
494
|
# File 'app/models/note.rb', line 492
def emoji_awardable?
!system?
end
|
#exportable_record?(user) ⇒ Boolean
728
729
730
731
732
|
# File 'app/models/note.rb', line 728
def exportable_record?(user)
return true unless system?
readable_by?(user)
end
|
#for_abuse_report? ⇒ Boolean
407
408
409
|
# File 'app/models/note.rb', line 407
def for_abuse_report?
noteable_type == AbuseReport.name
end
|
#for_alert_mangement_alert? ⇒ Boolean
367
368
369
|
# File 'app/models/note.rb', line 367
def for_alert_mangement_alert?
noteable_type == 'AlertManagement::Alert'
end
|
#for_commit? ⇒ Boolean
347
348
349
|
# File 'app/models/note.rb', line 347
def for_commit?
noteable_type == "Commit"
end
|
#for_compliance_violation? ⇒ Boolean
375
376
377
|
# File 'app/models/note.rb', line 375
def for_compliance_violation?
noteable_type == 'ComplianceManagement::Projects::ComplianceViolation'
end
|
#for_design? ⇒ Boolean
399
400
401
|
# File 'app/models/note.rb', line 399
def for_design?
noteable_type == DesignManagement::Design.name
end
|
#for_issuable? ⇒ Boolean
403
404
405
|
# File 'app/models/note.rb', line 403
def for_issuable?
for_issue? || for_merge_request?
end
|
#for_issue? ⇒ Boolean
351
352
353
|
# File 'app/models/note.rb', line 351
def for_issue?
noteable_type == "Issue"
end
|
#for_merge_request? ⇒ Boolean
359
360
361
|
# File 'app/models/note.rb', line 359
def for_merge_request?
noteable_type == "MergeRequest"
end
|
#for_personal_snippet? ⇒ Boolean
383
384
385
|
# File 'app/models/note.rb', line 383
def for_personal_snippet?
noteable.is_a?(PersonalSnippet)
end
|
#for_project_noteable? ⇒ Boolean
391
392
393
|
# File 'app/models/note.rb', line 391
def for_project_noteable?
!(for_personal_snippet? || for_abuse_report? || group_level_issue?)
end
|
#for_project_snippet? ⇒ Boolean
379
380
381
|
# File 'app/models/note.rb', line 379
def for_project_snippet?
noteable.is_a?(ProjectSnippet)
end
|
#for_snippet? ⇒ Boolean
363
364
365
|
# File 'app/models/note.rb', line 363
def for_snippet?
noteable_type == "Snippet"
end
|
#for_vulnerability? ⇒ Boolean
371
372
373
|
# File 'app/models/note.rb', line 371
def for_vulnerability?
noteable_type == "Vulnerability"
end
|
#for_wiki_page? ⇒ Boolean
387
388
389
|
# File 'app/models/note.rb', line 387
def for_wiki_page?
noteable_type == "WikiPage::Meta"
end
|
#for_work_item? ⇒ Boolean
355
356
357
|
# File 'app/models/note.rb', line 355
def for_work_item?
noteable.is_a?(WorkItem)
end
|
#group_level_issue? ⇒ Boolean
395
396
397
|
# File 'app/models/note.rb', line 395
def group_level_issue?
(for_issue? || for_work_item?) && noteable&.project_id.blank?
end
|
#human_max_access ⇒ Object
452
453
454
|
# File 'app/models/note.rb', line 452
def human_max_access
project&.team&.human_max_access(self.author_id)
end
|
#issuable_ability_name ⇒ Object
724
725
726
|
# File 'app/models/note.rb', line 724
def issuable_ability_name
confidential? ? :read_internal_note : :read_note
end
|
#last_edited_at ⇒ Object
We used ‘last_edited_at` as an alias of `updated_at` before. This makes it compatible with the previous way without data migration.
476
477
478
|
# File 'app/models/note.rb', line 476
def last_edited_at
super || updated_at
end
|
#mentioned_filtered_user_ids_for(references) ⇒ Object
713
714
715
716
717
718
719
720
721
722
|
# File 'app/models/note.rb', line 713
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
705
706
707
708
709
710
711
|
# File 'app/models/note.rb', line 705
def mentioned_users(current_user = nil)
users = super
return users unless confidential?
Ability.users_that_can_read_internal_notes(users, resource_parent)
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.
422
423
424
425
426
427
428
|
# File 'app/models/note.rb', line 422
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
|
#noteable ⇒ Object
override to return commits, which are not active record
431
432
433
434
435
436
437
438
439
|
# File 'app/models/note.rb', line 431
def noteable
return commit if for_commit?
super
rescue StandardError
nil
end
|
#noteable_ability_name ⇒ Object
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
|
# File 'app/models/note.rb', line 504
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
456
457
458
|
# File 'app/models/note.rb', line 456
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
443
444
445
|
# File 'app/models/note.rb', line 443
def noteable_type=(noteable_type)
super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
|
#notify_after_create ⇒ Object
594
595
596
|
# File 'app/models/note.rb', line 594
def notify_after_create
noteable&.after_note_created(self)
end
|
#notify_after_destroy ⇒ Object
598
599
600
|
# File 'app/models/note.rb', line 598
def notify_after_destroy
noteable&.after_note_destroyed(self)
end
|
#parent_user ⇒ Object
629
630
631
|
# File 'app/models/note.rb', line 629
def parent_user
noteable.author if for_personal_snippet?
end
|
#post_processed_cache_key ⇒ Object
637
638
639
640
641
642
643
|
# File 'app/models/note.rb', line 637
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_name ⇒ Object
460
461
462
|
# File 'app/models/note.rb', line 460
def project_name
project&.name
end
|
#references ⇒ Object
529
530
531
532
533
534
535
536
537
|
# File 'app/models/note.rb', line 529
def references
refs = [noteable]
if part_of_discussion?
refs += discussion.notes.take_while { |n| n.id < id }
end
refs
end
|
#resource_parent ⇒ Object
613
614
615
|
# File 'app/models/note.rb', line 613
def resource_parent
noteable.try(:resource_parent) || project
end
|
#retrieve_upload(_identifier, paths) ⇒ Object
609
610
611
|
# File 'app/models/note.rb', line 609
def retrieve_upload(_identifier, paths)
Upload.find_by(model: self, path: paths)
end
|
#show_outdated_changes? ⇒ Boolean
661
662
663
664
665
666
667
668
|
# File 'app/models/note.rb', line 661
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
633
634
635
|
# File 'app/models/note.rb', line 633
def skip_notification?
review.present? || !author.can_trigger_notifications?
end
|
#skip_project_check? ⇒ Boolean
411
412
413
|
# File 'app/models/note.rb', line 411
def skip_project_check?
!for_project_noteable?
end
|
#supports_suggestion? ⇒ Boolean
343
344
345
|
# File 'app/models/note.rb', line 343
def supports_suggestion?
false
end
|
#system_note_visible_for?(user) ⇒ Boolean
623
624
625
626
627
|
# File 'app/models/note.rb', line 623
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
320
321
322
323
324
325
326
327
328
|
# File 'app/models/note.rb', line 320
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
558
559
560
561
562
563
564
565
566
567
|
# File 'app/models/note.rb', line 558
def touch(*args, **kwargs)
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.
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
|
# File 'app/models/note.rb', line 572
def touch_noteable
return if for_vulnerability? || 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
|
#trigger_note_subscription_create ⇒ Object
217
218
219
220
221
|
# File 'app/models/note.rb', line 217
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_destroy ⇒ Object
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
# File 'app/models/note.rb', line 229
def trigger_note_subscription_destroy
return unless trigger_note_subscription?
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_update ⇒ Object
223
224
225
226
227
|
# File 'app/models/note.rb', line 223
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_subscription ⇒ Object
246
247
248
249
250
251
|
# File 'app/models/note.rb', line 246
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_class ⇒ Object
646
647
648
649
650
|
# File 'app/models/note.rb', line 646
def user_mention_class
return if noteable.blank?
noteable.user_mention_class
end
|
#user_mention_identifier ⇒ Object
653
654
655
656
657
658
659
|
# File 'app/models/note.rb', line 653
def user_mention_identifier
return if noteable.blank?
noteable.user_mention_identifier.merge({
note_id: id
})
end
|
#user_mentions ⇒ Object
617
618
619
620
621
|
# File 'app/models/note.rb', line 617
def user_mentions
return Note.none unless noteable.present?
noteable.user_mentions.where(note: self)
end
|