From e4f7b87ddb4ba83456871eb83b841192b1b56799 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Wed, 3 May 2017 10:48:01 +0200 Subject: [PATCH] Support comments for personal snippets --- app/controllers/concerns/notes_actions.rb | 44 +++++++++++ app/controllers/projects/notes_controller.rb | 44 ----------- app/controllers/snippets/notes_controller.rb | 9 --- app/controllers/snippets_controller.rb | 1 + app/helpers/gitlab_routing_helper.rb | 6 +- app/helpers/notes_helper.rb | 43 +++++++++++ app/services/notes/build_service.rb | 18 ++++- app/views/layouts/snippets.html.haml | 6 ++ app/views/projects/commit/show.html.haml | 2 +- .../projects/issues/_discussion.html.haml | 2 +- .../merge_requests/_discussion.html.haml | 2 +- app/views/projects/milestones/_form.html.haml | 2 +- app/views/projects/notes/_edit.html.haml | 3 - app/views/projects/releases/edit.html.haml | 2 +- app/views/projects/snippets/show.html.haml | 2 +- app/views/projects/tags/new.html.haml | 2 +- app/views/projects/wikis/_form.html.haml | 2 +- .../issuable/form/_description.html.haml | 2 +- .../notes/_comment_button.html.haml | 0 app/views/shared/notes/_edit.html.haml | 3 + .../notes/_edit_form.html.haml | 2 +- .../notes/_form.html.haml | 6 +- .../notes/_hints.html.haml | 0 app/views/shared/notes/_note.html.haml | 5 +- .../notes/_notes_with_form.html.haml | 8 +- app/views/snippets/notes/_edit.html.haml | 0 app/views/snippets/notes/_notes.html.haml | 2 - app/views/snippets/show.html.haml | 12 +-- .../12910-personal-snippets-notes.yml | 4 + spec/factories/notes.rb | 2 + .../notes_on_personal_snippets_spec.rb | 64 +++++++++++++++- spec/helpers/notes_helper_spec.rb | 75 +++++++++++++++++++ spec/services/notes/build_service_spec.rb | 74 +++++++++++++++++- .../notes/_form.html.haml_spec.rb | 2 +- 34 files changed, 359 insertions(+), 92 deletions(-) delete mode 100644 app/views/projects/notes/_edit.html.haml rename app/views/{projects => shared}/notes/_comment_button.html.haml (100%) create mode 100644 app/views/shared/notes/_edit.html.haml rename app/views/{projects => shared}/notes/_edit_form.html.haml (95%) rename app/views/{projects => shared}/notes/_form.html.haml (74%) rename app/views/{projects => shared}/notes/_hints.html.haml (100%) rename app/views/{projects => shared}/notes/_notes_with_form.html.haml (64%) delete mode 100644 app/views/snippets/notes/_edit.html.haml delete mode 100644 app/views/snippets/notes/_notes.html.haml create mode 100644 changelogs/unreleased/12910-personal-snippets-notes.yml rename spec/views/{projects => shared}/notes/_form.html.haml_spec.rb (96%) diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index c32038d07bf..a57d9e6e6c0 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -65,6 +65,15 @@ module NotesActions private + def note_html(note) + render_to_string( + "shared/notes/_note", + layout: false, + formats: [:html], + locals: { note: note } + ) + end + def note_json(note) attrs = { commands_changes: note.commands_changes @@ -98,6 +107,41 @@ module NotesActions attrs end + def diff_discussion_html(discussion) + return unless discussion.diff_discussion? + + if params[:view] == 'parallel' + template = "discussions/_parallel_diff_discussion" + locals = + if params[:line_type] == 'old' + { discussions_left: [discussion], discussions_right: nil } + else + { discussions_left: nil, discussions_right: [discussion] } + end + else + template = "discussions/_diff_discussion" + locals = { discussions: [discussion] } + end + + render_to_string( + template, + layout: false, + formats: [:html], + locals: locals + ) + end + + def discussion_html(discussion) + return if discussion.individual_note? + + render_to_string( + "discussions/_discussion", + layout: false, + formats: [:html], + locals: { discussion: discussion } + ) + end + def authorize_admin_note! return access_denied! unless can?(current_user, :admin_note, note) end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 37f51b2ebe3..41a13f6f577 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -62,50 +62,6 @@ class Projects::NotesController < Projects::ApplicationController end alias_method :awardable, :note - def note_html(note) - render_to_string( - "shared/notes/_note", - layout: false, - formats: [:html], - locals: { note: note } - ) - end - - def discussion_html(discussion) - return if discussion.individual_note? - - render_to_string( - "discussions/_discussion", - layout: false, - formats: [:html], - locals: { discussion: discussion } - ) - end - - def diff_discussion_html(discussion) - return unless discussion.diff_discussion? - - if params[:view] == 'parallel' - template = "discussions/_parallel_diff_discussion" - locals = - if params[:line_type] == 'old' - { discussions_left: [discussion], discussions_right: nil } - else - { discussions_left: nil, discussions_right: [discussion] } - end - else - template = "discussions/_diff_discussion" - locals = { discussions: [discussion] } - end - - render_to_string( - template, - layout: false, - formats: [:html], - locals: locals - ) - end - def finder_params params.merge(last_fetched_at: last_fetched_at) end diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb index 3c4ddc1680d..f9496787b15 100644 --- a/app/controllers/snippets/notes_controller.rb +++ b/app/controllers/snippets/notes_controller.rb @@ -13,15 +13,6 @@ class Snippets::NotesController < ApplicationController end alias_method :awardable, :note - def note_html(note) - render_to_string( - "shared/notes/_note", - layout: false, - formats: [:html], - locals: { note: note } - ) - end - def project nil end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index afed27a41d1..19e07e3ab86 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -64,6 +64,7 @@ class SnippetsController < ApplicationController blob = @snippet.blob override_max_blob_size(blob) + @note = Note.new(noteable: @snippet) @noteable = @snippet @discussions = @snippet.discussions diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 88f9a691a17..3769830de2a 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -123,7 +123,11 @@ module GitlabRoutingHelper end def preview_markdown_path(project, *args) - preview_markdown_namespace_project_path(project.namespace, project, *args) + if @snippet.is_a?(PersonalSnippet) + preview_markdown_snippet_path(@snippet) + else + preview_markdown_namespace_project_path(project.namespace, project, *args) + end end def toggle_subscription_path(entity, *args) diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 08180883eb9..52403640c05 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -76,4 +76,47 @@ module NotesHelper namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor) end end + + def notes_url + if @snippet.is_a?(PersonalSnippet) + snippet_notes_path(@snippet) + else + namespace_project_noteable_notes_path( + namespace_id: @project.namespace, + project_id: @project, + target_id: @noteable.id, + target_type: @noteable.class.name.underscore + ) + end + end + + def note_url(note) + if note.noteable.is_a?(PersonalSnippet) + snippet_note_path(note.noteable, note) + else + namespace_project_note_path(@project.namespace, @project, note) + end + end + + def form_resources + if @snippet.is_a?(PersonalSnippet) + [@note] + else + [@project.namespace.becomes(Namespace), @project, @note] + end + end + + def new_form_url + return nil unless @snippet.is_a?(PersonalSnippet) + + snippet_notes_path(@snippet) + end + + def can_create_note? + if @snippet.is_a?(PersonalSnippet) + can?(current_user, :comment_personal_snippet, @snippet) + else + can?(current_user, :create_note, @project) + end + end end diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb index ea7cacc956c..abf25bb778b 100644 --- a/app/services/notes/build_service.rb +++ b/app/services/notes/build_service.rb @@ -3,8 +3,8 @@ module Notes def execute in_reply_to_discussion_id = params.delete(:in_reply_to_discussion_id) - if project && in_reply_to_discussion_id.present? - discussion = project.notes.find_discussion(in_reply_to_discussion_id) + if in_reply_to_discussion_id.present? + discussion = find_discussion(in_reply_to_discussion_id) unless discussion note = Note.new @@ -21,5 +21,19 @@ module Notes note end + + def find_discussion(discussion_id) + if project + project.notes.find_discussion(discussion_id) + else + # only PersonalSnippets can have discussions without project association + discussion = Note.find_discussion(discussion_id) + noteable = discussion.noteable + + return nil unless noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable) + + discussion + end + end end end diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml index 02ca3ee7a28..98b75cea03f 100644 --- a/app/views/layouts/snippets.html.haml +++ b/app/views/layouts/snippets.html.haml @@ -1,3 +1,9 @@ - header_title "Snippets", snippets_path +- content_for :page_specific_javascripts do + - if @snippet&.persisted? && current_user + :javascript + window.uploads_path = "#{upload_path('personal_snippet', @snippet)}"; + window.preview_markdown_path = "#{preview_markdown_snippet_path(@snippet)}"; + = render template: "layouts/application" diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 16d2646cb4e..6051ea2f1ce 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -13,7 +13,7 @@ .block-connector = render "projects/diffs/diffs", diffs: @diffs, environment: @environment - = render "projects/notes/notes_with_form" + = render "shared/notes/notes_with_form" - if can_collaborate_with_project? - %w(revert cherry-pick).each do |type| = render "projects/commit/change", type: type, commit: @commit, title: @commit.title diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 5d4e593e4ef..4dfda54feb5 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -4,4 +4,4 @@ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' #notes - = render 'projects/notes/notes_with_form' + = render 'shared/notes/notes_with_form' diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 15b5a51c1d0..2e6420db212 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -8,4 +8,4 @@ %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } } {{ buttonText }} -#notes= render "projects/notes/notes_with_form" +#notes= render "shared/notes/notes_with_form" diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 2e978fda624..9a95b2a82ff 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -11,7 +11,7 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project) } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' - = render 'projects/notes/hints' + = render 'shared/notes/hints' .clearfix .error-alert = render "shared/milestones/form_dates", f: f diff --git a/app/views/projects/notes/_edit.html.haml b/app/views/projects/notes/_edit.html.haml deleted file mode 100644 index f1e251d65b7..00000000000 --- a/app/views/projects/notes/_edit.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } - #{note.note} -%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index faa24a3c88e..93ee9382a6e 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -13,7 +13,7 @@ = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." - = render 'projects/notes/hints' + = render 'shared/notes/hints' .error-alert .prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 7a175f63eeb..aab1c043e66 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -9,4 +9,4 @@ .row-content-block.top-block.content-component-block = render 'award_emoji/awards_block', awardable: @snippet, inline: true - #notes= render "projects/notes/notes_with_form" + #notes= render "shared/notes/notes_with_form" diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index a6894b9adc0..7c607d2956b 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -30,7 +30,7 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." - = render 'projects/notes/hints' + = render 'shared/notes/hints' .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 00869aff27b..6cb7c1e9c4d 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -14,7 +14,7 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...' - = render 'projects/notes/hints' + = render 'shared/notes/hints' .clearfix .error-alert diff --git a/app/views/shared/issuable/form/_description.html.haml b/app/views/shared/issuable/form/_description.html.haml index cbc7125c0d5..7ef0ae96be2 100644 --- a/app/views/shared/issuable/form/_description.html.haml +++ b/app/views/shared/issuable/form/_description.html.haml @@ -17,6 +17,6 @@ classes: 'note-textarea', placeholder: "Write a comment or drag your files here...", supports_slash_commands: supports_slash_commands - = render 'projects/notes/hints', supports_slash_commands: supports_slash_commands + = render 'shared/notes/hints', supports_slash_commands: supports_slash_commands .clearfix .error-alert diff --git a/app/views/projects/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml similarity index 100% rename from app/views/projects/notes/_comment_button.html.haml rename to app/views/shared/notes/_comment_button.html.haml diff --git a/app/views/shared/notes/_edit.html.haml b/app/views/shared/notes/_edit.html.haml new file mode 100644 index 00000000000..4a020865828 --- /dev/null +++ b/app/views/shared/notes/_edit.html.haml @@ -0,0 +1,3 @@ +.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } + #{note.note} +%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: note_url(note) } }= note.note diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml similarity index 95% rename from app/views/projects/notes/_edit_form.html.haml rename to app/views/shared/notes/_edit_form.html.haml index 3867072225f..8923e5602a4 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/shared/notes/_edit_form.html.haml @@ -4,7 +4,7 @@ = hidden_field_tag :target_type, '', class: 'js-form-target-type' = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(project), referenced_users: true } do = render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..." - = render 'projects/notes/hints' + = render 'shared/notes/hints' .note-form-actions.clearfix .settings-message.note-edit-warning.js-finish-edit-warning diff --git a/app/views/projects/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml similarity index 74% rename from app/views/projects/notes/_form.html.haml rename to app/views/shared/notes/_form.html.haml index 46f785fefca..eaf50bc2115 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -4,7 +4,7 @@ - else - preview_url = preview_markdown_path(@project) -= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f| += form_for form_resources, url: new_form_url, remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f| = hidden_field_tag :view, diff_view = hidden_field_tag :line_type = hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha) @@ -28,11 +28,11 @@ classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here...", supports_slash_commands: supports_slash_commands - = render 'projects/notes/hints', supports_slash_commands: supports_slash_commands + = render 'shared/notes/hints', supports_slash_commands: supports_slash_commands .error-alert .note-form-actions.clearfix - = render partial: 'projects/notes/comment_button' + = render partial: 'shared/notes/comment_button' = yield(:note_actions) diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml similarity index 100% rename from app/views/projects/notes/_hints.html.haml rename to app/views/shared/notes/_hints.html.haml diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 9657b4eea82..071c48fa2e4 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -42,10 +42,7 @@ = note.redacted_note_html = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable - - if note.for_personal_snippet? - = render 'snippets/notes/edit', note: note - - else - = render 'projects/notes/edit', note: note + = render 'shared/notes/edit', note: note .note-awards = render 'award_emoji/awards_block', awardable: note, inline: false - if note.system diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml similarity index 64% rename from app/views/projects/notes/_notes_with_form.html.haml rename to app/views/shared/notes/_notes_with_form.html.haml index 2a66addb08a..9930cbd96d7 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/shared/notes/_notes_with_form.html.haml @@ -1,18 +1,18 @@ %ul#notes-list.notes.main-notes-list.timeline = render "shared/notes/notes" -= render 'projects/notes/edit_form', project: @project += render 'shared/notes/edit_form', project: @project %ul.notes.notes-form.timeline %li.timeline-entry .flash-container.timeline-content - - if can? current_user, :create_note, @project + - if can_create_note? .timeline-icon.hidden-xs.hidden-sm %a.author_link{ href: user_path(current_user) } = image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40' .timeline-content.timeline-content-form - = render "projects/notes/form", view: diff_view + = render "shared/notes/form", view: diff_view - elsif !current_user .disabled-comment.text-center .disabled-comment-text.inline @@ -23,4 +23,4 @@ to post a comment :javascript - var notes = new Notes("#{namespace_project_noteable_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") + var notes = new Notes("#{notes_url}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") diff --git a/app/views/snippets/notes/_edit.html.haml b/app/views/snippets/notes/_edit.html.haml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/views/snippets/notes/_notes.html.haml b/app/views/snippets/notes/_notes.html.haml deleted file mode 100644 index f07d6b8c126..00000000000 --- a/app/views/snippets/notes/_notes.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%ul#notes-list.notes.main-notes-list.timeline - = render "projects/notes/notes" diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 98287cba5b4..51dbbc32cc9 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -2,11 +2,11 @@ = render 'shared/snippets/header' -%article.file-holder.snippet-file-content - = render 'shared/snippets/blob' +.personal-snippets + %article.file-holder.snippet-file-content + = render 'shared/snippets/blob' -.row-content-block.top-block.content-component-block - = render 'award_emoji/awards_block', awardable: @snippet, inline: true + .row-content-block.top-block.content-component-block + = render 'award_emoji/awards_block', awardable: @snippet, inline: true -%ul#notes-list.notes.main-notes-list.timeline - #notes= render 'shared/notes/notes' + #notes= render "shared/notes/notes_with_form" diff --git a/changelogs/unreleased/12910-personal-snippets-notes.yml b/changelogs/unreleased/12910-personal-snippets-notes.yml new file mode 100644 index 00000000000..7f1576c3513 --- /dev/null +++ b/changelogs/unreleased/12910-personal-snippets-notes.yml @@ -0,0 +1,4 @@ +--- +title: Support comments for personal snippets +merge_request: +author: diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 44c3186d813..046974dcd6e 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -29,6 +29,8 @@ FactoryGirl.define do factory :discussion_note_on_commit, traits: [:on_commit], class: DiscussionNote + factory :discussion_note_on_personal_snippet, traits: [:on_personal_snippet], class: DiscussionNote + factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index c646039e0b1..957baac02eb 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Comments on personal snippets', feature: true do +describe 'Comments on personal snippets', :js, feature: true do let!(:user) { create(:user) } let!(:snippet) { create(:personal_snippet, :public) } let!(:snippet_notes) do @@ -18,7 +18,7 @@ describe 'Comments on personal snippets', feature: true do subject { page } - context 'viewing the snippet detail page' do + context 'when viewing the snippet detail page' do it 'contains notes for a snippet with correct action icons' do expect(page).to have_selector('#notes-list li', count: 2) @@ -36,4 +36,64 @@ describe 'Comments on personal snippets', feature: true do end end end + + context 'when submitting a note' do + it 'shows a valid form' do + is_expected.to have_css('.js-main-target-form', visible: true, count: 1) + expect(find('.js-main-target-form .js-comment-button').value). + to eq('Comment') + + page.within('.js-main-target-form') do + expect(page).not_to have_link('Cancel') + end + end + + it 'previews a note' do + fill_in 'note[note]', with: 'This is **awesome**!' + find('.js-md-preview-button').click + + page.within('.new-note .md-preview') do + expect(page).to have_content('This is awesome!') + expect(page).to have_selector('strong') + end + end + + it 'creates a note' do + fill_in 'note[note]', with: 'This is **awesome**!' + click_button 'Comment' + + expect(find('div#notes')).to have_content('This is awesome!') + end + end + + context 'when editing a note' do + it 'changes the text' do + page.within("#notes-list li#note_#{snippet_notes[0].id}") do + click_on 'Edit comment' + end + + page.within('.current-note-edit-form') do + fill_in 'note[note]', with: 'new content' + find('.btn-save').click + end + + page.within("#notes-list li#note_#{snippet_notes[0].id}") do + expect(page).to have_css('.note_edited_ago') + expect(page).to have_content('new content') + expect(find('.note_edited_ago').text).to match(/less than a minute ago/) + end + end + end + + context 'when deleting a note' do + it 'removes the note from the snippet detail page' do + page.within("#notes-list li#note_#{snippet_notes[0].id}") do + click_on 'Remove comment' + end + + wait_for_ajax + + expect(page).not_to have_selector("#notes-list li#note_#{snippet_notes[0].id}") + end + end end diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 6c990f94175..099146678ae 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -175,4 +175,79 @@ describe NotesHelper do end end end + + describe '#notes_url' do + it 'return snippet notes path for personal snippet' do + @snippet = create(:personal_snippet) + + expect(helper.notes_url).to eq("/snippets/#{@snippet.id}/notes") + end + + it 'return project notes path for project snippet' do + namespace = create(:namespace, path: 'nm') + @project = create(:empty_project, path: 'test', namespace: namespace) + @snippet = create(:project_snippet, project: @project) + @noteable = @snippet + + expect(helper.notes_url).to eq("/nm/test/noteable/project_snippet/#{@noteable.id}/notes") + end + + it 'return project notes path for other noteables' do + namespace = create(:namespace, path: 'nm') + @project = create(:empty_project, path: 'test', namespace: namespace) + @noteable = create(:issue, project: @project) + + expect(helper.notes_url).to eq("/nm/test/noteable/issue/#{@noteable.id}/notes") + end + end + + describe '#note_url' do + it 'return snippet notes path for personal snippet' do + note = create(:note_on_personal_snippet) + + expect(helper.note_url(note)).to eq("/snippets/#{note.noteable.id}/notes/#{note.id}") + end + + it 'return project notes path for project snippet' do + namespace = create(:namespace, path: 'nm') + @project = create(:empty_project, path: 'test', namespace: namespace) + note = create(:note_on_project_snippet, project: @project) + + expect(helper.note_url(note)).to eq("/nm/test/notes/#{note.id}") + end + + it 'return project notes path for other noteables' do + namespace = create(:namespace, path: 'nm') + @project = create(:empty_project, path: 'test', namespace: namespace) + note = create(:note_on_issue, project: @project) + + expect(helper.note_url(note)).to eq("/nm/test/notes/#{note.id}") + end + end + + describe '#form_resurces' do + it 'returns note for personal snippet' do + @snippet = create(:personal_snippet) + @note = create(:note_on_personal_snippet) + + expect(helper.form_resources).to eq([@note]) + end + + it 'returns namespace, project and note for project snippet' do + namespace = create(:namespace, path: 'nm') + @project = create(:empty_project, path: 'test', namespace: namespace) + @snippet = create(:project_snippet, project: @project) + @note = create(:note_on_personal_snippet) + + expect(helper.form_resources).to eq([@project.namespace, @project, @note]) + end + + it 'returns namespace, project and note path for other noteables' do + namespace = create(:namespace, path: 'nm') + @project = create(:empty_project, path: 'test', namespace: namespace) + @note = create(:note_on_issue, project: @project) + + expect(helper.form_resources).to eq([@project.namespace, @project, @note]) + end + end end diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb index f9dd5541b10..133175769ca 100644 --- a/spec/services/notes/build_service_spec.rb +++ b/spec/services/notes/build_service_spec.rb @@ -29,10 +29,82 @@ describe Notes::BuildService, services: true do expect(new_note.errors[:base]).to include('Discussion to reply to cannot be found') end end + + context 'personal snippet note' do + def reply(note, user = nil) + user ||= create(:user) + + described_class.new(nil, + user, + note: 'Test', + in_reply_to_discussion_id: note.discussion_id).execute + end + + let(:snippet_author) { create(:user) } + + context 'when a snippet is public' do + it 'creates a reply note' do + snippet = create(:personal_snippet, :public) + note = create(:discussion_note_on_personal_snippet, noteable: snippet) + + new_note = reply(note) + + expect(new_note).to be_valid + expect(new_note.in_reply_to?(note)).to be_truthy + end + end + + context 'when a snippet is private' do + let(:snippet) { create(:personal_snippet, :private, author: snippet_author) } + let(:note) { create(:discussion_note_on_personal_snippet, noteable: snippet) } + + it 'creates a reply note when the author replies' do + new_note = reply(note, snippet_author) + + expect(new_note).to be_valid + expect(new_note.in_reply_to?(note)).to be_truthy + end + + it 'sets an error when another user replies' do + new_note = reply(note) + + expect(new_note.errors[:base]).to include('Discussion to reply to cannot be found') + end + end + + context 'when a snippet is internal' do + let(:snippet) { create(:personal_snippet, :internal, author: snippet_author) } + let(:note) { create(:discussion_note_on_personal_snippet, noteable: snippet) } + + it 'creates a reply note when the author replies' do + new_note = reply(note, snippet_author) + + expect(new_note).to be_valid + expect(new_note.in_reply_to?(note)).to be_truthy + end + + it 'creates a reply note when a regular user replies' do + new_note = reply(note) + + expect(new_note).to be_valid + expect(new_note.in_reply_to?(note)).to be_truthy + end + + it 'sets an error when an external user replies' do + new_note = reply(note, create(:user, :external)) + + expect(new_note.errors[:base]).to include('Discussion to reply to cannot be found') + end + end + end end it 'builds a note without saving it' do - new_note = described_class.new(project, author, noteable_type: note.noteable_type, noteable_id: note.noteable_id, note: 'Test').execute + new_note = described_class.new(project, + author, + noteable_type: note.noteable_type, + noteable_id: note.noteable_id, + note: 'Test').execute expect(new_note).to be_valid expect(new_note).not_to be_persisted end diff --git a/spec/views/projects/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb similarity index 96% rename from spec/views/projects/notes/_form.html.haml_spec.rb rename to spec/views/shared/notes/_form.html.haml_spec.rb index a364f9bce92..d7d0a5bf56a 100644 --- a/spec/views/projects/notes/_form.html.haml_spec.rb +++ b/spec/views/shared/notes/_form.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'projects/notes/_form' do +describe 'shared/notes/_form' do include Devise::Test::ControllerHelpers let(:user) { create(:user) }