Module: Mentionable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- app/models/concerns/mentionable.rb,
app/models/concerns/mentionable/reference_regexes.rb
Overview
Mentionable concern
Contains functionality related to objects that can mention Users, Issues, MergeRequests, Commits or Snippets by GFM references.
Used by Issue, Note, MergeRequest, and Commit.
Defined Under Namespace
Modules: ReferenceRegexes
Instance Method Summary collapse
- #all_references(current_user = nil, extractor: nil) ⇒ Object
-
#create_cross_references!(author = self.author, without = []) ⇒ Object
Create a cross-reference Note for each GFM reference to another Mentionable found in the
mentionable_attrs
. -
#create_new_cross_references!(author = self.author) ⇒ Object
When a mentionable field is changed, creates cross-reference notes that don't already exist.
- #directly_addressed_users(current_user = nil) ⇒ Object
- #extractors ⇒ Object
-
#gfm_reference(from = nil) ⇒ Object
Returns the text used as the body of a Note when this object is referenced.
-
#local_reference ⇒ Object
The GFM reference to this Mentionable, which shouldn't be included in its #references.
-
#matches_cross_reference_regex? ⇒ Boolean
Uses regex to quickly determine if mentionables might be referenced Allows heavy processing to be skipped.
- #mentioned_users(current_user = nil) ⇒ Object
- #referenced_group_users(current_user = nil) ⇒ Object
- #referenced_groups(current_user = nil) ⇒ Object
-
#referenced_mentionables(current_user = self.author) ⇒ Object
Extract GFM references to other Mentionables from this Mentionable.
- #referenced_project_users(current_user = nil) ⇒ Object
- #referenced_projects(current_user = nil) ⇒ Object
- #referenced_users ⇒ Object
- #store_mentions! ⇒ Object
Instance Method Details
#all_references(current_user = nil, extractor: nil) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'app/models/concerns/mentionable.rb', line 48 def all_references(current_user = nil, extractor: nil) # Use custom extractor if it's passed in the function parameters. if extractor extractors[current_user] = extractor else extractor = extractors[current_user] ||= Gitlab::ReferenceExtractor.new(project, current_user) extractor.reset_memoized_values end self.class.mentionable_attrs.each do |attr, | text = __send__(attr) # rubocop:disable GitlabSecurity/PublicSend = .merge( cache_key: [self, attr], author: , skip_project_check: skip_project_check? ).merge(mentionable_params) cached_html = self.try(:updated_cached_html_for, attr.to_sym) [:rendered] = cached_html if cached_html extractor.analyze(text, ) end extractor end |
#create_cross_references!(author = self.author, without = []) ⇒ Object
Create a cross-reference Note for each GFM reference to another Mentionable found in the mentionable_attrs
.
172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'app/models/concerns/mentionable.rb', line 172 def create_cross_references!( = self., without = []) refs = referenced_mentionables() # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the # case for otherwise identical Commit objects. refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) } refs.each do |ref| SystemNoteService.cross_reference(ref, local_reference, ) end end |
#create_new_cross_references!(author = self.author) ⇒ Object
When a mentionable field is changed, creates cross-reference notes that don't already exist
187 188 189 190 191 192 193 |
# File 'app/models/concerns/mentionable.rb', line 187 def create_new_cross_references!( = self.) changes = detect_mentionable_changes return if changes.empty? create_cross_references!() end |
#directly_addressed_users(current_user = nil) ⇒ Object
141 142 143 |
# File 'app/models/concerns/mentionable.rb', line 141 def directly_addressed_users(current_user = nil) all_references(current_user).directly_addressed_users end |
#extractors ⇒ Object
75 76 77 |
# File 'app/models/concerns/mentionable.rb', line 75 def extractors @extractors ||= {} end |
#gfm_reference(from = nil) ⇒ Object
Returns the text used as the body of a Note when this object is referenced
By default this will be the class name and the result of calling `to_reference` on the object.
36 37 38 39 40 41 |
# File 'app/models/concerns/mentionable.rb', line 36 def gfm_reference(from = nil) # "MergeRequest" > "merge_request" > "Merge request" > "merge request" friendly_name = self.class.to_s.underscore.humanize.downcase "#{friendly_name} #{to_reference(from)}" end |
#local_reference ⇒ Object
The GFM reference to this Mentionable, which shouldn't be included in its #references.
44 45 46 |
# File 'app/models/concerns/mentionable.rb', line 44 def local_reference self end |
#matches_cross_reference_regex? ⇒ Boolean
Uses regex to quickly determine if mentionables might be referenced Allows heavy processing to be skipped
159 160 161 162 163 164 165 166 167 168 169 |
# File 'app/models/concerns/mentionable.rb', line 159 def matches_cross_reference_regex? reference_pattern = if !project || project.default_issues_tracker? ReferenceRegexes.default_pattern else ReferenceRegexes.external_pattern end self.class.mentionable_attrs.any? do |attr, _| __send__(attr) =~ reference_pattern # rubocop:disable GitlabSecurity/PublicSend end end |
#mentioned_users(current_user = nil) ⇒ Object
79 80 81 |
# File 'app/models/concerns/mentionable.rb', line 79 def mentioned_users(current_user = nil) all_references(current_user).users end |
#referenced_group_users(current_user = nil) ⇒ Object
137 138 139 |
# File 'app/models/concerns/mentionable.rb', line 137 def referenced_group_users(current_user = nil) User.joins(:group_members).where(members: { source_id: referenced_groups }).distinct end |
#referenced_groups(current_user = nil) ⇒ Object
126 127 128 129 130 131 132 133 134 135 |
# File 'app/models/concerns/mentionable.rb', line 126 def referenced_groups(current_user = nil) # TODO: IMPORTANT: Revisit before using it. # Check DB data for max mentioned groups per mentionable: # # select issue_id, count(mentions_count.men_gr_id) gr_count from # (select DISTINCT unnest(mentioned_groups_ids) as men_gr_id, issue_id # from issue_user_mentions group by issue_id, mentioned_groups_ids) as mentions_count # group by mentions_count.issue_id order by gr_count desc limit 10 Group.where(id: user_mentions.select("unnest(mentioned_groups_ids)")).public_or_visible_to_user(current_user) end |
#referenced_mentionables(current_user = self.author) ⇒ Object
Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
146 147 148 149 150 151 152 153 154 155 |
# File 'app/models/concerns/mentionable.rb', line 146 def referenced_mentionables(current_user = self.) return [] unless matches_cross_reference_regex? refs = all_references(current_user) # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the # case for otherwise identical Commit objects. extracted_mentionables(refs).reject { |ref| ref == local_reference } end |
#referenced_project_users(current_user = nil) ⇒ Object
122 123 124 |
# File 'app/models/concerns/mentionable.rb', line 122 def referenced_project_users(current_user = nil) User.joins(:project_members).where(members: { source_id: referenced_projects(current_user) }).distinct end |
#referenced_projects(current_user = nil) ⇒ Object
118 119 120 |
# File 'app/models/concerns/mentionable.rb', line 118 def referenced_projects(current_user = nil) Project.where(id: user_mentions.select("unnest(mentioned_projects_ids)")).public_or_visible_to_user(current_user) end |
#referenced_users ⇒ Object
114 115 116 |
# File 'app/models/concerns/mentionable.rb', line 114 def referenced_users User.where(id: user_mentions.select("unnest(mentioned_users_ids)")) end |
#store_mentions! ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'app/models/concerns/mentionable.rb', line 83 def store_mentions! refs = all_references(self.) references = {} references[:mentioned_users_ids] = refs.mentioned_users&.pluck(:id).presence references[:mentioned_groups_ids] = refs.mentioned_groups&.pluck(:id).presence references[:mentioned_projects_ids] = refs.mentioned_projects&.pluck(:id).presence # One retry should be enough as next time `model_user_mention` should return the existing mention record, that # threw the `ActiveRecord::RecordNotUnique` exception in first place. self.class.safe_ensure_unique(retries: 1) do user_mention = model_user_mention # this may happen due to notes polymorphism, so noteable_id may point to a record that no longer exists # as we cannot have FK on noteable_id break if user_mention.blank? user_mention.mentioned_users_ids = references[:mentioned_users_ids] user_mention.mentioned_groups_ids = references[:mentioned_groups_ids] user_mention.mentioned_projects_ids = references[:mentioned_projects_ids] if user_mention.has_mentions? user_mention.save! else user_mention.destroy! end end true end |