1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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 74 75 76 77 78 79 80 81 82 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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
# frozen_string_literal: true
module API
class Discussions < ::API::Base
include PaginationParams
helpers ::API::Helpers::NotesHelpers
helpers ::RendersNotes
before { authenticate! }
urgency :low, [
'/projects/:id/merge_requests/:noteable_id/discussions',
'/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id',
'/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id/notes',
'/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id/notes/:note_id'
]
Helpers::DiscussionsHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
notable_name = noteable_type.to_s.underscore.humanize.downcase
notable_id_type = noteable_type == Commit ? String : Integer
noteables_path = noteable_type == Commit ? "repository/#{noteables_str}" : noteables_str
params do
requires :id, type: String, desc: "The ID of a #{parent_type}"
end
resource parent_type.pluralize.to_sym, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc "Get a list of #{notable_name} discussions" do
success Entities::Discussion
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
use :pagination
end
get ":id/#{noteables_path}/:noteable_id/discussions", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
discussion_ids = paginate(noteable.discussion_ids_relation)
notes = readable_discussion_notes(noteable, discussion_ids)
present Discussion.build_collection(notes, noteable), with: Entities::Discussion
end
desc "Get a single #{notable_name} discussion" do
success Entities::Discussion
end
params do
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
end
get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
if notes.empty?
break not_found!("Discussion")
end
discussion = Discussion.build(notes, noteable)
present discussion, with: Entities::Discussion
end
desc "Create a new #{notable_name} discussion" do
success Entities::Discussion
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
requires :body, type: String, desc: 'The content of a note'
optional :created_at, type: String, desc: 'The creation date of the note'
if [Commit, MergeRequest].include?(noteable_type)
optional :position, type: Hash do
requires :base_sha, type: String, desc: 'Base commit SHA in the source branch'
requires :start_sha, type: String, desc: 'SHA referencing commit in target branch'
requires :head_sha, type: String, desc: 'SHA referencing HEAD of this merge request'
requires :position_type, type: String, desc: 'Type of the position reference', values: %w[text image file]
optional :new_path, type: String, desc: 'File path after change'
optional :new_line, type: Integer, desc: 'Line number after change'
optional :old_path, type: String, desc: 'File path before change'
optional :old_line, type: Integer, desc: 'Line number before change'
optional :width, type: Integer, desc: 'Width of the image'
optional :height, type: Integer, desc: 'Height of the image'
optional :x, type: Integer, desc: 'X coordinate in the image'
optional :y, type: Integer, desc: 'Y coordinate in the image'
if noteable_type == MergeRequest
optional :line_range, type: Hash, desc: 'Multi-line start and end' do
optional :start, type: Hash do
optional :line_code, type: String, desc: 'Start line code for multi-line note'
optional :type, type: String, desc: 'Start line type for multi-line note'
optional :old_line, type: String, desc: 'Start old_line line number'
optional :new_line, type: String, desc: 'Start new_line line number'
end
optional :end, type: Hash do
optional :line_code, type: String, desc: 'End line code for multi-line note'
optional :type, type: String, desc: 'End line type for multi-line note'
optional :old_line, type: String, desc: 'End old_line line number'
optional :new_line, type: String, desc: 'End new_line line number'
end
end
end
end
end
end
post ":id/#{noteables_path}/:noteable_id/discussions", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
type = params[:position] ? 'DiffNote' : 'DiscussionNote'
id_key = noteable.is_a?(Commit) ? :commit_id : :noteable_id
opts = {
note: params[:body],
created_at: params[:created_at],
type: type,
noteable_type: noteables_str.classify,
position: params[:position],
id_key => noteable.id
}
opts[:commit_id] = params[:commit_id] if noteable.is_a?(MergeRequest) && type == 'DiffNote'
note = create_note(noteable, opts)
if note.persisted?
present note.discussion, with: Entities::Discussion
else
bad_request!("Note #{note.errors.messages}")
end
end
desc "Get comments in a single #{notable_name} discussion" do
success Entities::Discussion
end
params do
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
end
get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
if notes.empty?
break not_found!("Notes")
end
present notes, with: Entities::Note
end
desc "Add a comment to a #{notable_name} discussion" do
success Entities::Note
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :body, type: String, desc: 'The content of a note'
optional :created_at, type: String, desc: 'The creation date of the note'
end
post ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
first_note = notes.first
break not_found!("Discussion") if notes.empty?
unless first_note.part_of_discussion? || first_note.to_discussion.can_convert_to_discussion?
break bad_request!("Discussion can not be replied to.")
end
opts = {
note: params[:body],
type: 'DiscussionNote',
in_reply_to_discussion_id: params[:discussion_id],
created_at: params[:created_at]
}
note = create_note(noteable, opts)
if note.persisted?
present note, with: Entities::Note
else
bad_request!("Note #{note.errors.messages}")
end
end
desc "Get a comment in a #{notable_name} discussion" do
success Entities::Note
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :note_id, type: Integer, desc: 'The ID of a note'
end
get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
get_note(noteable, params[:note_id])
end
desc "Edit a comment in a #{notable_name} discussion" do
success Entities::Note
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :note_id, type: Integer, desc: 'The ID of a note'
optional :body, type: String, desc: 'The content of a note'
optional :resolved, type: Boolean, desc: 'Mark note resolved/unresolved'
exactly_one_of :body, :resolved
end
put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
if params[:resolved].nil?
update_note(noteable, params[:note_id])
else
resolve_note(noteable, params[:note_id], params[:resolved])
end
end
desc "Delete a comment in a #{notable_name} discussion" do
success Entities::Note
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :note_id, type: Integer, desc: 'The ID of a note'
end
delete ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
delete_note(noteable, params[:note_id])
end
if Noteable.resolvable_types.include?(noteable_type.to_s)
desc "Resolve/unresolve an existing #{notable_name} discussion" do
success Entities::Discussion
end
params do
requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}"
requires :discussion_id, type: String, desc: 'The ID of a discussion'
requires :resolved, type: Boolean, desc: 'Mark discussion resolved/unresolved'
end
put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id", feature_category: feature_category do
noteable = find_noteable(noteable_type, params[:noteable_id])
resolve_discussion(noteable, params[:discussion_id], params[:resolved])
end
end
end
end
helpers do
# rubocop: disable CodeReuse/ActiveRecord
def readable_discussion_notes(noteable, discussion_ids)
notes = noteable.notes
.with_discussion_ids(discussion_ids)
.inc_relations_for_view(noteable)
.includes(:noteable)
.fresh
# Without RendersActions#prepare_notes_for_rendering,
# Note#system_note_visible_for? will attempt to render
# Markdown references mentioned in the note to see whether they
# should be redacted. For notes that reference a commit, this
# would also incur a Gitaly call to verify the commit exists.
#
# With prepare_notes_for_rendering, we can avoid Gitaly calls
# because notes are redacted if they point to projects that
# cannot be accessed by the user.
notes = prepare_notes_for_rendering(notes)
notes.select { |n| n.readable_by?(current_user) }
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
|