diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index a57d9e6e6c0..14e755c8d97 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -11,14 +11,16 @@ module NotesActions notes_json = { notes: [], last_fetched_at: current_fetched_at } - @notes = notes_finder.execute.inc_relations_for_view + @notes = notes_finder.execute.inc_relations_for_view.to_a + @notes.reject! { |n| n.cross_reference_not_visible_for?(current_user) } @notes = prepare_notes_for_rendering(@notes) - @notes.each do |note| - next if note.cross_reference_not_visible_for?(current_user) - - notes_json[:notes] << note_json(note) - end + notes_json[:notes] = + if params[:full_data] + note_serializer.represent(@notes) + else + @notes.map { |note| note_json(note) } + end render json: notes_json end @@ -80,22 +82,27 @@ module NotesActions } if note.persisted? - attrs.merge!( - valid: true, - id: note.id, - discussion_id: note.discussion_id(noteable), - html: note_html(note), - note: note.note - ) + attrs[:valid] = true - discussion = note.to_discussion(noteable) - unless discussion.individual_note? + if params[:full_data] + attrs.merge!(note_serializer.represent(note)) + else attrs.merge!( - discussion_resolvable: discussion.resolvable?, - - diff_discussion_html: diff_discussion_html(discussion), - discussion_html: discussion_html(discussion) + id: note.id, + discussion_id: note.discussion_id(noteable), + html: note_html(note), + note: note.note ) + + discussion = note.to_discussion(noteable) + unless discussion.individual_note? + attrs.merge!( + discussion_resolvable: discussion.resolvable?, + + diff_discussion_html: diff_discussion_html(discussion), + discussion_html: discussion_html(discussion) + ) + end end else attrs.merge!( @@ -177,4 +184,8 @@ module NotesActions def notes_finder @notes_finder ||= NotesFinder.new(project, current_user, finder_params) end + + def note_serializer + NoteSerializer.new(project: project, noteable: noteable, current_user: current_user) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 0ac9da2ff0f..153c490ce3f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -97,6 +97,14 @@ class Projects::IssuesController < Projects::ApplicationController end end + def discussions + @discussions = @issue.discussions + @discussions.reject! { |d| d.individual_note? && d.first_note.cross_reference_not_visible_for?(current_user) } + prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + + render json: DiscussionSerializer.new(project: @project, noteable: @issue, current_user: current_user).represent(@discussions) + end + def create create_params = issue_params.merge(spammable_params).merge( merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of], diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 42b6cfdf02f..034b4451b56 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -119,7 +119,7 @@ module IssuesHelper end def awards_sort(awards) - awards.sort_by do |award, notes| + awards.sort_by do |award, award_emojis| if award == "thumbsup" 0 elsif award == "thumbsdown" diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 209bd56b78a..69366825124 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -21,8 +21,14 @@ module SystemNoteHelper 'outdated' => 'icon_edit' }.freeze + def system_note_icon_name(note) + ICON_NAMES_BY_ACTION[note.system_note_metadata&.action] + end + def icon_for_system_note(note) - icon_name = ICON_NAMES_BY_ACTION[note.system_note_metadata&.action] + icon_name = system_note_icon_name(note) custom_icon(icon_name) if icon_name end + + extend self end diff --git a/app/models/discussion.rb b/app/models/discussion.rb index d1cec7613af..b80da7b246a 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -81,6 +81,10 @@ class Discussion last_note.author end + def updated? + last_updated_at != created_at + end + def id first_note.discussion_id(context_noteable) end diff --git a/app/serializers/award_emoji_entity.rb b/app/serializers/award_emoji_entity.rb new file mode 100644 index 00000000000..6e03cd02392 --- /dev/null +++ b/app/serializers/award_emoji_entity.rb @@ -0,0 +1,4 @@ +class AwardEmojiEntity < Grape::Entity + expose :name + expose :user, using: API::Entities::UserSafe +end diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb new file mode 100644 index 00000000000..cb6c3c23807 --- /dev/null +++ b/app/serializers/discussion_entity.rb @@ -0,0 +1,20 @@ +class DiscussionEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :reply_id + expose :expanded?, as: :expanded + expose :author, using: UserEntity + + expose :created_at + + expose :last_updated_at, if: -> (discussion, _) { discussion.updated? } + expose :last_updated_by, if: -> (discussion, _) { discussion.updated? }, using: UserEntity + + expose :notes, using: NoteEntity + + expose :individual_note?, as: :individual_note + + expose :can_reply do |discussion| + can?(request.current_user, :create_note, discussion.project) + end +end diff --git a/app/serializers/discussion_serializer.rb b/app/serializers/discussion_serializer.rb new file mode 100644 index 00000000000..ed5e1224bb2 --- /dev/null +++ b/app/serializers/discussion_serializer.rb @@ -0,0 +1,3 @@ +class DiscussionSerializer < BaseSerializer + entity DiscussionEntity +end diff --git a/app/serializers/note_attachment_entity.rb b/app/serializers/note_attachment_entity.rb new file mode 100644 index 00000000000..1ad50568ab9 --- /dev/null +++ b/app/serializers/note_attachment_entity.rb @@ -0,0 +1,5 @@ +class NoteAttachmentEntity < Grape::Entity + expose :url + expose :filename + expose :image?, as: :image +end diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb new file mode 100644 index 00000000000..7a49ec4ef55 --- /dev/null +++ b/app/serializers/note_entity.rb @@ -0,0 +1,58 @@ +class NoteEntity < API::Entities::Note + include RequestAwareEntity + + expose :type + + expose :author, using: UserEntity + + expose :human_access do |note| + note.project.team.human_max_access(note.author_id) + end + + unexpose :note, as: :body + expose :note + + expose :redacted_note_html, as: :note_html + + expose :last_edited_at, if: -> (note, _) { note.is_edited? } + expose :last_edited_by, using: UserEntity, if: -> (note, _) { note.is_edited? } + + expose :can_edit do |note| + Ability.can_edit_note?(request.current_user, note) + end + + expose :system_note_icon_name, if: -> (note, _) { note.system? } do |note| + SystemNoteHelper.system_note_icon_name(note) + end + + expose :discussion_id do |note| + note.discussion_id(request.noteable) + end + + expose :emoji_awardable?, as: :emoji_awardable + expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity + expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note| + if note.for_personal_snippet? + toggle_award_emoji_snippet_note_path(note.noteable, note) + else + toggle_award_emoji_namespace_project_note_path(note.project.namespace, note.project, note.id) + end + end + + expose :report_abuse_path do |note| + new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note)) + end + + expose :path do |note| + if note.for_personal_snippet? + snippet_note_path(note.noteable, note) + else + namespace_project_note_path(note.project.namespace, note.project, note) + end + end + + expose :attachment, using: NoteAttachmentEntity + expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note| + delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note) + end +end diff --git a/app/serializers/note_serializer.rb b/app/serializers/note_serializer.rb new file mode 100644 index 00000000000..2afe40d7a34 --- /dev/null +++ b/app/serializers/note_serializer.rb @@ -0,0 +1,3 @@ +class NoteSerializer < BaseSerializer + entity NoteEntity +end diff --git a/app/serializers/user_entity.rb b/app/serializers/user_entity.rb index 876512b12dc..3bb340065c4 100644 --- a/app/serializers/user_entity.rb +++ b/app/serializers/user_entity.rb @@ -1,6 +1,8 @@ class UserEntity < API::Entities::UserBasic include RequestAwareEntity + unexpose :web_url + expose :path do |user| user_path(user) end diff --git a/app/views/discussions/_headline.html.haml b/app/views/discussions/_headline.html.haml index c1dabeed387..25e90924413 100644 --- a/app/views/discussions/_headline.html.haml +++ b/app/views/discussions/_headline.html.haml @@ -5,7 +5,7 @@ by = link_to_member(@project, discussion.resolved_by, avatar: false) = time_ago_with_tooltip(discussion.resolved_at, placement: "bottom") -- elsif discussion.last_updated_at != discussion.created_at +- elsif discussion.updated? .discussion-headline-light.js-discussion-headline Last updated - if discussion.last_updated_by diff --git a/config/routes/project.rb b/config/routes/project.rb index 672b5a9a160..d6fb309de8e 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -308,6 +308,7 @@ constraints(ProjectUrlConstrainer.new) do get :can_create_branch get :realtime_changes post :create_merge_request + get :discussions, format: :json end collection do post :bulk_update diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 09a88869063..89ab2118dd5 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,11 +1,11 @@ module API module Entities class UserSafe < Grape::Entity - expose :name, :username + expose :id, :name, :username end class UserBasic < UserSafe - expose :id, :state + expose :state expose :avatar_url do |user, options| user.avatar_url(only_path: false) end