Class: Discussion

Inherits:
Object
  • Object
show all
Includes:
GlobalID::Identification, ResolvableDiscussion
Defined in:
app/models/discussion.rb

Overview

A non-diff discussion on an issue, merge request, commit, or snippet, consisting of ‘DiscussionNote` notes.

A discussion of this type can be resolvable.

Constant Summary collapse

CACHE_VERSION =

Bump this if we need to refresh the cached versions of discussions

1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ResolvableDiscussion

#can_resolve?, #clear_memoized_values, #first_note, #first_note_to_resolve, #last_resolved_note, #resolvable?, #resolve!, #resolved?, #resolved_by_push?, #resolved_notes, #to_be_resolved?, #unresolve!

Constructor Details

#initialize(notes, context_noteable = nil, inverse_relations: true) ⇒ Discussion



111
112
113
114
115
116
# File 'app/models/discussion.rb', line 111

def initialize(notes, context_noteable = nil, inverse_relations: true)
  @notes = notes
  @context_noteable = context_noteable

  notes.each { |n| n.discussion = self } if inverse_relations
end

Instance Attribute Details

#context_noteableObject (readonly)

Returns the value of attribute context_noteable.



13
14
15
# File 'app/models/discussion.rb', line 13

def context_noteable
  @context_noteable
end

#notesObject

Returns the value of attribute notes.



14
15
16
# File 'app/models/discussion.rb', line 14

def notes
  @notes
end

Class Method Details

.base_discussion_id(note) ⇒ Object



81
82
83
84
# File 'app/models/discussion.rb', line 81

def self.base_discussion_id(note)
  noteable_id = note.noteable_id || note.commit_id
  [:discussion, note.noteable_type.try(:underscore), noteable_id]
end

.build(notes, context_noteable = nil, inverse_relations: true) ⇒ Object



43
44
45
# File 'app/models/discussion.rb', line 43

def self.build(notes, context_noteable = nil, inverse_relations: true)
  notes.first.discussion_class(context_noteable).new(notes, context_noteable, inverse_relations: inverse_relations)
end

.build_collection(notes, context_noteable = nil) ⇒ Object



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

def self.build_collection(notes, context_noteable = nil)
  grouped_notes = notes.group_by { |n| n.discussion_id(context_noteable) }
  grouped_notes.values.map { |notes| build(notes, context_noteable) }
end

.build_discussion_id(note) ⇒ Object

Returns an array of discussion ID components



77
78
79
# File 'app/models/discussion.rb', line 77

def self.build_discussion_id(note)
  [*base_discussion_id(note), SecureRandom.hex]
end

.build_discussions(discussion_ids, context_noteable = nil, preload_note_diff_file: false) ⇒ Object



52
53
54
55
56
57
58
# File 'app/models/discussion.rb', line 52

def self.build_discussions(discussion_ids, context_noteable = nil, preload_note_diff_file: false)
  notes = model_class.where(discussion_id: discussion_ids).fresh
  notes = notes.inc_note_diff_file if preload_note_diff_file

  grouped_notes = notes.group_by(&:discussion_id)
  grouped_notes.transform_values { |notes| Discussion.build(notes, context_noteable) }
end

.discussion_id(note) ⇒ Object

Returns an alphanumeric discussion ID based on ‘build_discussion_id`



72
73
74
# File 'app/models/discussion.rb', line 72

def self.discussion_id(note)
  Digest::SHA1.hexdigest(build_discussion_id(note).join("-"))
end

.lazy_find(discussion_id) ⇒ Object



60
61
62
63
64
65
66
67
68
69
# File 'app/models/discussion.rb', line 60

def self.lazy_find(discussion_id)
  BatchLoader.for(discussion_id).batch do |discussion_ids, loader|
    results = model_class.where(discussion_id: discussion_ids).fresh.to_a.group_by(&:discussion_id)
    results.each do |discussion_id, notes|
      next if notes.empty?

      loader.call(discussion_id, Discussion.build(notes))
    end
  end
end

.model_classObject



107
108
109
# File 'app/models/discussion.rb', line 107

def self.model_class
  Note
end

.note_classObject



103
104
105
# File 'app/models/discussion.rb', line 103

def self.note_class
  DiscussionNote
end

.override_discussion_id(note) ⇒ Object

When notes on a commit are displayed in context of a merge request that contains that commit, these notes are to be displayed as if they were part of one discussion, even though they were actually individual notes on the commit with different discussion IDs, so that it’s clear that these are not notes on the merge request itself.

To turn a list of notes into a list of discussions, they are grouped by discussion ID, so to get these out-of-context notes to end up in the same discussion, we need to get them to return the same ‘discussion_id` when this grouping happens. To enable this, `Note#discussion_id` calls out to the `override_discussion_id` method on the appropriate `Discussion` subclass, as determined by the `discussion_class` method on `Note` or a subclass of `Note`.

If no override is necessary, return ‘nil`. For the case described above, see `OutOfContextDiscussion.override_discussion_id`.



99
100
101
# File 'app/models/discussion.rb', line 99

def self.override_discussion_id(note)
  nil
end

Instance Method Details

#==(other) ⇒ Object



122
123
124
125
126
127
# File 'app/models/discussion.rb', line 122

def ==(other)
  other.class == self.class &&
    other.context_noteable == self.context_noteable &&
    other.id == self.id &&
    other.notes == self.notes
end

#cache_keyObject



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'app/models/discussion.rb', line 181

def cache_key
  # Need to use the notes' cache key so cache will be invalidated when note
  # within a discussion has been deleted or has different data after post
  # processing of content.
  notes_sha = Digest::SHA1.hexdigest(notes.map(&:post_processed_cache_key).join(':'))

  [
    CACHE_VERSION,
    id,
    notes_sha,
    resolved_at
  ].join(':')
end

#can_convert_to_discussion?Boolean



161
162
163
# File 'app/models/discussion.rb', line 161

def can_convert_to_discussion?
  false
end

#can_resolve_discussion?(user) ⇒ Boolean



210
211
212
# File 'app/models/discussion.rb', line 210

def can_resolve_discussion?(user)
  !first_note.system? && user&.can?(:resolve_note, first_note)
end

#collapsed?Boolean



169
170
171
# File 'app/models/discussion.rb', line 169

def collapsed?
  resolved?
end

#declarative_policy_delegateObject



35
36
37
# File 'app/models/discussion.rb', line 35

def declarative_policy_delegate
  first_note
end

#diff_discussion?Boolean



153
154
155
# File 'app/models/discussion.rb', line 153

def diff_discussion?
  false
end

#discussion_classObject



202
203
204
# File 'app/models/discussion.rb', line 202

def discussion_class
  ::Discussion
end

#expanded?Boolean



173
174
175
# File 'app/models/discussion.rb', line 173

def expanded?
  !collapsed?
end

#idObject Also known as: to_param



141
142
143
# File 'app/models/discussion.rb', line 141

def id
  first_note.discussion_id(context_noteable)
end

#individual_note?Boolean



157
158
159
# File 'app/models/discussion.rb', line 157

def individual_note?
  false
end

#last_noteObject



165
166
167
# File 'app/models/discussion.rb', line 165

def last_note
  @last_note ||= notes.last
end

#last_updated_atObject



129
130
131
# File 'app/models/discussion.rb', line 129

def last_updated_at
  last_note.created_at
end

#last_updated_byObject



133
134
135
# File 'app/models/discussion.rb', line 133

def last_updated_by
  last_note.author
end

#noteable_collection_nameObject



206
207
208
# File 'app/models/discussion.rb', line 206

def noteable_collection_name
  noteable.class.underscore.pluralize
end

#on_image?Boolean



118
119
120
# File 'app/models/discussion.rb', line 118

def on_image?
  false
end

#project_idObject



39
40
41
# File 'app/models/discussion.rb', line 39

def project_id
  project&.id
end

#reply_attributesObject



177
178
179
# File 'app/models/discussion.rb', line 177

def reply_attributes
  first_note.slice(:type, :noteable_type, :noteable_id, :commit_id, :discussion_id)
end

#reply_idObject



145
146
147
148
149
# File 'app/models/discussion.rb', line 145

def reply_id
  # To reply to this discussion, we need the actual discussion_id from the database,
  # not the potentially overwritten one based on the noteable.
  first_note.discussion_id
end

#to_global_id(options = {}) ⇒ Object

Consolidate discussions GID. There is no need to have different GID for different class names as the discussion_id hash is already unique per discussion. This also fixes the issue where same discussion may return different GIDs depending on number of notes it has.



198
199
200
# File 'app/models/discussion.rb', line 198

def to_global_id(options = {})
  GlobalID.new(::Gitlab::GlobalId.build(model_name: discussion_class.to_s, id: id))
end

#updated?Boolean



137
138
139
# File 'app/models/discussion.rb', line 137

def updated?
  last_updated_at != created_at
end