Display comments for personal snippets
This commit is contained in:
		
							parent
							
								
									8100f9686d
								
							
						
					
					
						commit
						8c3a03c1b9
					
				| 
						 | 
					@ -0,0 +1,136 @@
 | 
				
			||||||
 | 
					module NotesActions
 | 
				
			||||||
 | 
					  include RendersNotes
 | 
				
			||||||
 | 
					  extend ActiveSupport::Concern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  included do
 | 
				
			||||||
 | 
					    before_action :authorize_admin_note!, only: [:update, :destroy]
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def index
 | 
				
			||||||
 | 
					    current_fetched_at = Time.now.to_i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notes_json = { notes: [], last_fetched_at: current_fetched_at }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @notes = notes_finder.execute.inc_relations_for_view
 | 
				
			||||||
 | 
					    @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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render json: notes_json
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create
 | 
				
			||||||
 | 
					    create_params = note_params.merge(
 | 
				
			||||||
 | 
					      merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
 | 
				
			||||||
 | 
					      in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    @note = Notes::CreateService.new(project, current_user, create_params).execute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if @note.is_a?(Note)
 | 
				
			||||||
 | 
					      Banzai::NoteRenderer.render([@note], @project, current_user)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    respond_to do |format|
 | 
				
			||||||
 | 
					      format.json { render json: note_json(@note) }
 | 
				
			||||||
 | 
					      format.html { redirect_back_or_default }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update
 | 
				
			||||||
 | 
					    @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if @note.is_a?(Note)
 | 
				
			||||||
 | 
					      Banzai::NoteRenderer.render([@note], @project, current_user)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    respond_to do |format|
 | 
				
			||||||
 | 
					      format.json { render json: note_json(@note) }
 | 
				
			||||||
 | 
					      format.html { redirect_back_or_default }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    if note.editable?
 | 
				
			||||||
 | 
					      Notes::DestroyService.new(project, current_user).execute(note)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    respond_to do |format|
 | 
				
			||||||
 | 
					      format.js { head :ok }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def note_json(note)
 | 
				
			||||||
 | 
					    attrs = {
 | 
				
			||||||
 | 
					      commands_changes: note.commands_changes
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if note.persisted?
 | 
				
			||||||
 | 
					      attrs.merge!(
 | 
				
			||||||
 | 
					        valid: true,
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      attrs.merge!(
 | 
				
			||||||
 | 
					        valid: false,
 | 
				
			||||||
 | 
					        errors: note.errors
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    attrs
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def authorize_admin_note!
 | 
				
			||||||
 | 
					    return access_denied! unless can?(current_user, :admin_note, note)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def note_params
 | 
				
			||||||
 | 
					    params.require(:note).permit(
 | 
				
			||||||
 | 
					      :project_id,
 | 
				
			||||||
 | 
					      :noteable_type,
 | 
				
			||||||
 | 
					      :noteable_id,
 | 
				
			||||||
 | 
					      :commit_id,
 | 
				
			||||||
 | 
					      :noteable,
 | 
				
			||||||
 | 
					      :type,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      :note,
 | 
				
			||||||
 | 
					      :attachment,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # LegacyDiffNote
 | 
				
			||||||
 | 
					      :line_code,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # DiffNote
 | 
				
			||||||
 | 
					      :position
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def noteable
 | 
				
			||||||
 | 
					    @noteable ||= notes_finder.target
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def last_fetched_at
 | 
				
			||||||
 | 
					    request.headers['X-Last-Fetched-At']
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def notes_finder
 | 
				
			||||||
 | 
					    @notes_finder ||= NotesFinder.new(project, current_user, finder_params)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,8 @@ module RendersNotes
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def preload_max_access_for_authors(notes, project)
 | 
					  def preload_max_access_for_authors(notes, project)
 | 
				
			||||||
 | 
					    return nil unless project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    user_ids = notes.map(&:author_id)
 | 
					    user_ids = notes.map(&:author_id)
 | 
				
			||||||
    project.team.max_member_access_for_user_ids(user_ids)
 | 
					    project.team.max_member_access_for_user_ids(user_ids)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,8 @@ module ToggleAwardEmoji
 | 
				
			||||||
  def to_todoable(awardable)
 | 
					  def to_todoable(awardable)
 | 
				
			||||||
    case awardable
 | 
					    case awardable
 | 
				
			||||||
    when Note
 | 
					    when Note
 | 
				
			||||||
      awardable.noteable
 | 
					      # we don't create todos for personal snippet comments for now
 | 
				
			||||||
 | 
					      awardable.for_personal_snippet? ? nil : awardable.noteable
 | 
				
			||||||
    when MergeRequest, Issue
 | 
					    when MergeRequest, Issue
 | 
				
			||||||
      awardable
 | 
					      awardable
 | 
				
			||||||
    when Snippet
 | 
					    when Snippet
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,68 +1,22 @@
 | 
				
			||||||
class Projects::NotesController < Projects::ApplicationController
 | 
					class Projects::NotesController < Projects::ApplicationController
 | 
				
			||||||
  include RendersNotes
 | 
					  include NotesActions
 | 
				
			||||||
  include ToggleAwardEmoji
 | 
					  include ToggleAwardEmoji
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Authorize
 | 
					 | 
				
			||||||
  before_action :authorize_read_note!
 | 
					  before_action :authorize_read_note!
 | 
				
			||||||
  before_action :authorize_create_note!, only: [:create]
 | 
					  before_action :authorize_create_note!, only: [:create]
 | 
				
			||||||
  before_action :authorize_admin_note!, only: [:update, :destroy]
 | 
					 | 
				
			||||||
  before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
 | 
					  before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def index
 | 
					  #
 | 
				
			||||||
    current_fetched_at = Time.now.to_i
 | 
					  # This is a fix to make spinach feature tests passing:
 | 
				
			||||||
 | 
					  # Controller actions are returned from AbstractController::Base and methods of parent classes are
 | 
				
			||||||
    notes_json = { notes: [], last_fetched_at: current_fetched_at }
 | 
					  #   excluded in order to return only specific controller related methods.
 | 
				
			||||||
 | 
					  # That is ok for the app (no :create method in ancestors)
 | 
				
			||||||
    @notes = notes_finder.execute.inc_relations_for_view
 | 
					  #   but fails for tests because there is a :create method on FactoryGirl (one of the ancestors)
 | 
				
			||||||
    @notes = prepare_notes_for_rendering(@notes)
 | 
					  #
 | 
				
			||||||
 | 
					  # see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
 | 
				
			||||||
    @notes.each do |note|
 | 
					  #
 | 
				
			||||||
      next if note.cross_reference_not_visible_for?(current_user)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      notes_json[:notes] << note_json(note)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    render json: notes_json
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def create
 | 
					  def create
 | 
				
			||||||
    create_params = note_params.merge(
 | 
					    super
 | 
				
			||||||
      merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
 | 
					 | 
				
			||||||
      in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    @note = Notes::CreateService.new(project, current_user, create_params).execute
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if @note.is_a?(Note)
 | 
					 | 
				
			||||||
      Banzai::NoteRenderer.render([@note], @project, current_user)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    respond_to do |format|
 | 
					 | 
				
			||||||
      format.json { render json: note_json(@note) }
 | 
					 | 
				
			||||||
      format.html { redirect_back_or_default }
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def update
 | 
					 | 
				
			||||||
    @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if @note.is_a?(Note)
 | 
					 | 
				
			||||||
      Banzai::NoteRenderer.render([@note], @project, current_user)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    respond_to do |format|
 | 
					 | 
				
			||||||
      format.json { render json: note_json(@note) }
 | 
					 | 
				
			||||||
      format.html { redirect_back_or_default }
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def destroy
 | 
					 | 
				
			||||||
    if note.editable?
 | 
					 | 
				
			||||||
      Notes::DestroyService.new(project, current_user).execute(note)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    respond_to do |format|
 | 
					 | 
				
			||||||
      format.js { head :ok }
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def delete_attachment
 | 
					  def delete_attachment
 | 
				
			||||||
| 
						 | 
					@ -110,7 +64,7 @@ class Projects::NotesController < Projects::ApplicationController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def note_html(note)
 | 
					  def note_html(note)
 | 
				
			||||||
    render_to_string(
 | 
					    render_to_string(
 | 
				
			||||||
      "projects/notes/_note",
 | 
					      "shared/notes/_note",
 | 
				
			||||||
      layout: false,
 | 
					      layout: false,
 | 
				
			||||||
      formats: [:html],
 | 
					      formats: [:html],
 | 
				
			||||||
      locals: { note: note }
 | 
					      locals: { note: note }
 | 
				
			||||||
| 
						 | 
					@ -152,76 +106,11 @@ class Projects::NotesController < Projects::ApplicationController
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def note_json(note)
 | 
					  def finder_params
 | 
				
			||||||
    attrs = {
 | 
					    params.merge(last_fetched_at: last_fetched_at)
 | 
				
			||||||
      commands_changes: note.commands_changes
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if note.persisted?
 | 
					 | 
				
			||||||
      attrs.merge!(
 | 
					 | 
				
			||||||
        valid: true,
 | 
					 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      attrs.merge!(
 | 
					 | 
				
			||||||
        valid: false,
 | 
					 | 
				
			||||||
        errors: note.errors
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    attrs
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def authorize_admin_note!
 | 
					 | 
				
			||||||
    return access_denied! unless can?(current_user, :admin_note, note)
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def authorize_resolve_note!
 | 
					  def authorize_resolve_note!
 | 
				
			||||||
    return access_denied! unless can?(current_user, :resolve_note, note)
 | 
					    return access_denied! unless can?(current_user, :resolve_note, note)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def note_params
 | 
					 | 
				
			||||||
    params.require(:note).permit(
 | 
					 | 
				
			||||||
      :project_id,
 | 
					 | 
				
			||||||
      :noteable_type,
 | 
					 | 
				
			||||||
      :noteable_id,
 | 
					 | 
				
			||||||
      :commit_id,
 | 
					 | 
				
			||||||
      :noteable,
 | 
					 | 
				
			||||||
      :type,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      :note,
 | 
					 | 
				
			||||||
      :attachment,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # LegacyDiffNote
 | 
					 | 
				
			||||||
      :line_code,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # DiffNote
 | 
					 | 
				
			||||||
      :position
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def notes_finder
 | 
					 | 
				
			||||||
    @notes_finder ||= NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at))
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def noteable
 | 
					 | 
				
			||||||
    @noteable ||= notes_finder.target
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def last_fetched_at
 | 
					 | 
				
			||||||
    request.headers['X-Last-Fetched-At']
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					class Snippets::NotesController < ApplicationController
 | 
				
			||||||
 | 
					  include NotesActions
 | 
				
			||||||
 | 
					  include ToggleAwardEmoji
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  skip_before_action :authenticate_user!, only: [:index]
 | 
				
			||||||
 | 
					  before_action :snippet
 | 
				
			||||||
 | 
					  before_action :authorize_read_snippet!, only: [:show, :index, :create]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def note
 | 
				
			||||||
 | 
					    @note ||= snippet.notes.find(params[:id])
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def snippet
 | 
				
			||||||
 | 
					    PersonalSnippet.find_by(id: params[:snippet_id])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def note_params
 | 
				
			||||||
 | 
					    super.merge(noteable_id: params[:snippet_id])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def finder_params
 | 
				
			||||||
 | 
					    params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def authorize_read_snippet!
 | 
				
			||||||
 | 
					    return render_404 unless can?(current_user, :read_personal_snippet, snippet)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
class SnippetsController < ApplicationController
 | 
					class SnippetsController < ApplicationController
 | 
				
			||||||
 | 
					  include RendersNotes
 | 
				
			||||||
  include ToggleAwardEmoji
 | 
					  include ToggleAwardEmoji
 | 
				
			||||||
  include SpammableActions
 | 
					  include SpammableActions
 | 
				
			||||||
  include SnippetsActions
 | 
					  include SnippetsActions
 | 
				
			||||||
| 
						 | 
					@ -64,6 +65,11 @@ class SnippetsController < ApplicationController
 | 
				
			||||||
    blob = @snippet.blob
 | 
					    blob = @snippet.blob
 | 
				
			||||||
    override_max_blob_size(blob)
 | 
					    override_max_blob_size(blob)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @noteable = @snippet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @discussions = @snippet.discussions
 | 
				
			||||||
 | 
					    @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    respond_to do |format|
 | 
					    respond_to do |format|
 | 
				
			||||||
      format.html do
 | 
					      format.html do
 | 
				
			||||||
        render 'show'
 | 
					        render 'show'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,8 @@ class NotesFinder
 | 
				
			||||||
      MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
 | 
					      MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
 | 
				
			||||||
    when "snippet", "project_snippet"
 | 
					    when "snippet", "project_snippet"
 | 
				
			||||||
      SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project)
 | 
					      SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project)
 | 
				
			||||||
 | 
					    when "personal_snippet"
 | 
				
			||||||
 | 
					      PersonalSnippet.all
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      raise 'invalid target_type'
 | 
					      raise 'invalid target_type'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,14 @@
 | 
				
			||||||
module AwardEmojiHelper
 | 
					module AwardEmojiHelper
 | 
				
			||||||
  def toggle_award_url(awardable)
 | 
					  def toggle_award_url(awardable)
 | 
				
			||||||
    return url_for([:toggle_award_emoji, awardable]) unless @project
 | 
					    return url_for([:toggle_award_emoji, awardable]) unless @project || awardable.is_a?(Note)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if awardable.is_a?(Note)
 | 
					    if awardable.is_a?(Note)
 | 
				
			||||||
      # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (4.5x)
 | 
					      # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (4.5x)
 | 
				
			||||||
      toggle_award_emoji_namespace_project_note_url(@project.namespace, @project, awardable.id)
 | 
					      if awardable.for_personal_snippet?
 | 
				
			||||||
 | 
					        toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
 | 
					      url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -281,7 +281,7 @@ class TodoService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def attributes_for_target(target)
 | 
					  def attributes_for_target(target)
 | 
				
			||||||
    attributes = {
 | 
					    attributes = {
 | 
				
			||||||
      project_id: target.project.id,
 | 
					      project_id: target&.project&.id,
 | 
				
			||||||
      target_id: target.id,
 | 
					      target_id: target.id,
 | 
				
			||||||
      target_type: target.class.name,
 | 
					      target_type: target.class.name,
 | 
				
			||||||
      commit_id: nil
 | 
					      commit_id: nil
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
.discussion-notes
 | 
					.discussion-notes
 | 
				
			||||||
  %ul.notes{ data: { discussion_id: discussion.id } }
 | 
					  %ul.notes{ data: { discussion_id: discussion.id } }
 | 
				
			||||||
    = render partial: "projects/notes/note", collection: discussion.notes, as: :note
 | 
					    = render partial: "shared/notes/note", collection: discussion.notes, as: :note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - if current_user
 | 
					  - if current_user
 | 
				
			||||||
    .discussion-reply-holder
 | 
					    .discussion-reply-holder
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					- access = note_max_access_for_user(note)
 | 
				
			||||||
 | 
					- if access
 | 
				
			||||||
 | 
					  %span.note-role= access
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- if note.resolvable?
 | 
				
			||||||
 | 
					  - can_resolve = can?(current_user, :resolve_note, note)
 | 
				
			||||||
 | 
					  %resolve-btn{ "project-path" => project_path(note.project),
 | 
				
			||||||
 | 
					      "discussion-id" => note.discussion_id(@noteable),
 | 
				
			||||||
 | 
					      ":note-id" => note.id,
 | 
				
			||||||
 | 
					      ":resolved" => note.resolved?,
 | 
				
			||||||
 | 
					      ":can-resolve" => can_resolve,
 | 
				
			||||||
 | 
					      ":author-name" => "'#{j(note.author.name)}'",
 | 
				
			||||||
 | 
					      "author-avatar" => note.author.avatar_url,
 | 
				
			||||||
 | 
					      ":note-truncated" => "'#{j(truncate(note.note, length: 17))}'",
 | 
				
			||||||
 | 
					      ":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
 | 
				
			||||||
 | 
					      "v-show" => "#{can_resolve || note.resolved?}",
 | 
				
			||||||
 | 
					      "inline-template" => true,
 | 
				
			||||||
 | 
					      "ref" => "note_#{note.id}" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    %button.note-action-button.line-resolve-btn{ type: "button",
 | 
				
			||||||
 | 
					        class: ("is-disabled" unless can_resolve),
 | 
				
			||||||
 | 
					        ":class" => "{ 'is-active': isResolved }",
 | 
				
			||||||
 | 
					        ":aria-label" => "buttonText",
 | 
				
			||||||
 | 
					        "@click" => "resolve",
 | 
				
			||||||
 | 
					        ":title" => "buttonText",
 | 
				
			||||||
 | 
					        ":ref" => "'button'" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      = icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
 | 
				
			||||||
 | 
					      %div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- if current_user
 | 
				
			||||||
 | 
					  - if note.emoji_awardable?
 | 
				
			||||||
 | 
					    - user_authored = note.user_authored?(current_user)
 | 
				
			||||||
 | 
					    = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
 | 
				
			||||||
 | 
					      = icon('spinner spin')
 | 
				
			||||||
 | 
					      %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
 | 
				
			||||||
 | 
					      %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
 | 
				
			||||||
 | 
					      %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - if note_editable
 | 
				
			||||||
 | 
					    = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
 | 
				
			||||||
 | 
					      = icon('pencil', class: 'link-highlight')
 | 
				
			||||||
 | 
					    = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
 | 
				
			||||||
 | 
					      = icon('trash-o', class: 'danger-highlight')
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					.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
 | 
				
			||||||
| 
						 | 
					@ -1,101 +0,0 @@
 | 
				
			||||||
- return unless note.author
 | 
					 | 
				
			||||||
- return if note.cross_reference_not_visible_for?(current_user)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- note_editable = note_editable?(note)
 | 
					 | 
				
			||||||
%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
 | 
					 | 
				
			||||||
  .timeline-entry-inner
 | 
					 | 
				
			||||||
    .timeline-icon
 | 
					 | 
				
			||||||
      - if note.system
 | 
					 | 
				
			||||||
        = icon_for_system_note(note)
 | 
					 | 
				
			||||||
      - else
 | 
					 | 
				
			||||||
        %a{ href: user_path(note.author) }
 | 
					 | 
				
			||||||
          = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
 | 
					 | 
				
			||||||
    .timeline-content
 | 
					 | 
				
			||||||
      .note-header
 | 
					 | 
				
			||||||
        .note-header-info
 | 
					 | 
				
			||||||
          %a{ href: user_path(note.author) }
 | 
					 | 
				
			||||||
            %span.hidden-xs
 | 
					 | 
				
			||||||
              = sanitize(note.author.name)
 | 
					 | 
				
			||||||
            %span.note-headline-light
 | 
					 | 
				
			||||||
              = note.author.to_reference
 | 
					 | 
				
			||||||
          %span.note-headline-light
 | 
					 | 
				
			||||||
            %span.note-headline-meta
 | 
					 | 
				
			||||||
              - unless note.system
 | 
					 | 
				
			||||||
                commented
 | 
					 | 
				
			||||||
              - if note.system
 | 
					 | 
				
			||||||
                %span.system-note-message
 | 
					 | 
				
			||||||
                  = note.redacted_note_html
 | 
					 | 
				
			||||||
              %a{ href: "##{dom_id(note)}" }
 | 
					 | 
				
			||||||
                = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
 | 
					 | 
				
			||||||
        - unless note.system?
 | 
					 | 
				
			||||||
          .note-actions
 | 
					 | 
				
			||||||
            - access = note_max_access_for_user(note)
 | 
					 | 
				
			||||||
            - if access
 | 
					 | 
				
			||||||
              %span.note-role= access
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            - if note.resolvable?
 | 
					 | 
				
			||||||
              - can_resolve = can?(current_user, :resolve_note, note)
 | 
					 | 
				
			||||||
              %resolve-btn{ "project-path" => project_path(note.project),
 | 
					 | 
				
			||||||
                  "discussion-id" => note.discussion_id(@noteable),
 | 
					 | 
				
			||||||
                  ":note-id" => note.id,
 | 
					 | 
				
			||||||
                  ":resolved" => note.resolved?,
 | 
					 | 
				
			||||||
                  ":can-resolve" => can_resolve,
 | 
					 | 
				
			||||||
                  ":author-name" => "'#{j(note.author.name)}'",
 | 
					 | 
				
			||||||
                  "author-avatar" => note.author.avatar_url,
 | 
					 | 
				
			||||||
                  ":note-truncated" => "'#{j(truncate(note.note, length: 17))}'",
 | 
					 | 
				
			||||||
                  ":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
 | 
					 | 
				
			||||||
                  "v-show" => "#{can_resolve || note.resolved?}",
 | 
					 | 
				
			||||||
                  "inline-template" => true,
 | 
					 | 
				
			||||||
                  "ref" => "note_#{note.id}" }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                %button.note-action-button.line-resolve-btn{ type: "button",
 | 
					 | 
				
			||||||
                    class: ("is-disabled" unless can_resolve),
 | 
					 | 
				
			||||||
                    ":class" => "{ 'is-active': isResolved }",
 | 
					 | 
				
			||||||
                    ":aria-label" => "buttonText",
 | 
					 | 
				
			||||||
                    "@click" => "resolve",
 | 
					 | 
				
			||||||
                    ":title" => "buttonText",
 | 
					 | 
				
			||||||
                    ":ref" => "'button'" }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  = icon("spin spinner", "v-show" => "loading", class: 'loading')
 | 
					 | 
				
			||||||
                  %div{ 'v-show' => '!loading' }= render "shared/icons/icon_status_success.svg"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            - if current_user
 | 
					 | 
				
			||||||
              - if note.emoji_awardable?
 | 
					 | 
				
			||||||
                - user_authored = note.user_authored?(current_user)
 | 
					 | 
				
			||||||
                = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
 | 
					 | 
				
			||||||
                  = icon('spinner spin')
 | 
					 | 
				
			||||||
                  %span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
 | 
					 | 
				
			||||||
                  %span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley')
 | 
					 | 
				
			||||||
                  %span{ class: "link-highlight award-control-icon-super-positive" }= custom_icon('emoji_smile')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              - if note_editable
 | 
					 | 
				
			||||||
                = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
 | 
					 | 
				
			||||||
                  = icon('pencil', class: 'link-highlight')
 | 
					 | 
				
			||||||
                = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
 | 
					 | 
				
			||||||
                  = icon('trash-o', class: 'danger-highlight')
 | 
					 | 
				
			||||||
      .note-body{ class: note_editable ? 'js-task-list-container' : '' }
 | 
					 | 
				
			||||||
        .note-text.md
 | 
					 | 
				
			||||||
          = note.redacted_note_html
 | 
					 | 
				
			||||||
        = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
 | 
					 | 
				
			||||||
        - if note_editable
 | 
					 | 
				
			||||||
          .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
 | 
					 | 
				
			||||||
        .note-awards
 | 
					 | 
				
			||||||
          = render 'award_emoji/awards_block', awardable: note, inline: false
 | 
					 | 
				
			||||||
        - if note.system
 | 
					 | 
				
			||||||
          .system-note-commit-list-toggler
 | 
					 | 
				
			||||||
            Toggle commit list
 | 
					 | 
				
			||||||
            %i.fa.fa-angle-down
 | 
					 | 
				
			||||||
      - if note.attachment.url
 | 
					 | 
				
			||||||
        .note-attachment
 | 
					 | 
				
			||||||
          - if note.attachment.image?
 | 
					 | 
				
			||||||
            = link_to note.attachment.url, target: '_blank' do
 | 
					 | 
				
			||||||
              = image_tag note.attachment.url, class: 'note-image-attach'
 | 
					 | 
				
			||||||
          .attachment
 | 
					 | 
				
			||||||
            = link_to note.attachment.url, target: '_blank' do
 | 
					 | 
				
			||||||
              = icon('paperclip')
 | 
					 | 
				
			||||||
              = note.attachment_identifier
 | 
					 | 
				
			||||||
              = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
 | 
					 | 
				
			||||||
                title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
 | 
					 | 
				
			||||||
                = icon('trash-o', class: 'cred')
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
%ul#notes-list.notes.main-notes-list.timeline
 | 
					%ul#notes-list.notes.main-notes-list.timeline
 | 
				
			||||||
  = render "projects/notes/notes"
 | 
					  = render "shared/notes/notes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
= render 'projects/notes/edit_form'
 | 
					= render 'projects/notes/edit_form'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					- return unless note.author
 | 
				
			||||||
 | 
					- return if note.cross_reference_not_visible_for?(current_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- note_editable = note_editable?(note)
 | 
				
			||||||
 | 
					%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
 | 
				
			||||||
 | 
					  .timeline-entry-inner
 | 
				
			||||||
 | 
					    .timeline-icon
 | 
				
			||||||
 | 
					      - if note.system
 | 
				
			||||||
 | 
					        = icon_for_system_note(note)
 | 
				
			||||||
 | 
					      - else
 | 
				
			||||||
 | 
					        %a{ href: user_path(note.author) }
 | 
				
			||||||
 | 
					          = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
 | 
				
			||||||
 | 
					    .timeline-content
 | 
				
			||||||
 | 
					      .note-header
 | 
				
			||||||
 | 
					        .note-header-info
 | 
				
			||||||
 | 
					          %a{ href: user_path(note.author) }
 | 
				
			||||||
 | 
					            %span.hidden-xs
 | 
				
			||||||
 | 
					              = sanitize(note.author.name)
 | 
				
			||||||
 | 
					            %span.note-headline-light
 | 
				
			||||||
 | 
					              = note.author.to_reference
 | 
				
			||||||
 | 
					          %span.note-headline-light
 | 
				
			||||||
 | 
					            %span.note-headline-meta
 | 
				
			||||||
 | 
					              - unless note.system
 | 
				
			||||||
 | 
					                commented
 | 
				
			||||||
 | 
					              - if note.system
 | 
				
			||||||
 | 
					                %span.system-note-message
 | 
				
			||||||
 | 
					                  = note.redacted_note_html
 | 
				
			||||||
 | 
					              %a{ href: "##{dom_id(note)}" }
 | 
				
			||||||
 | 
					                = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
 | 
				
			||||||
 | 
					        - unless note.system?
 | 
				
			||||||
 | 
					          .note-actions
 | 
				
			||||||
 | 
					            - if note.for_personal_snippet?
 | 
				
			||||||
 | 
					              = render 'snippets/notes/actions', note: note, note_editable: note_editable
 | 
				
			||||||
 | 
					            - else
 | 
				
			||||||
 | 
					              = render 'projects/notes/actions', note: note, note_editable: note_editable
 | 
				
			||||||
 | 
					      .note-body{ class: note_editable ? 'js-task-list-container' : '' }
 | 
				
			||||||
 | 
					        .note-text.md
 | 
				
			||||||
 | 
					          = 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
 | 
				
			||||||
 | 
					        .note-awards
 | 
				
			||||||
 | 
					          = render 'award_emoji/awards_block', awardable: note, inline: false
 | 
				
			||||||
 | 
					        - if note.system
 | 
				
			||||||
 | 
					          .system-note-commit-list-toggler
 | 
				
			||||||
 | 
					            Toggle commit list
 | 
				
			||||||
 | 
					            %i.fa.fa-angle-down
 | 
				
			||||||
 | 
					      - if note.attachment.url
 | 
				
			||||||
 | 
					        .note-attachment
 | 
				
			||||||
 | 
					          - if note.attachment.image?
 | 
				
			||||||
 | 
					            = link_to note.attachment.url, target: '_blank' do
 | 
				
			||||||
 | 
					              = image_tag note.attachment.url, class: 'note-image-attach'
 | 
				
			||||||
 | 
					          .attachment
 | 
				
			||||||
 | 
					            = link_to note.attachment.url, target: '_blank' do
 | 
				
			||||||
 | 
					              = icon('paperclip')
 | 
				
			||||||
 | 
					              = note.attachment_identifier
 | 
				
			||||||
 | 
					              = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
 | 
				
			||||||
 | 
					                title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
 | 
				
			||||||
 | 
					                = icon('trash-o', class: 'cred')
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
- if defined?(@discussions)
 | 
					- if defined?(@discussions)
 | 
				
			||||||
  - @discussions.each do |discussion|
 | 
					  - @discussions.each do |discussion|
 | 
				
			||||||
    - if discussion.individual_note?
 | 
					    - if discussion.individual_note?
 | 
				
			||||||
      = render partial: "projects/notes/note", collection: discussion.notes, as: :note
 | 
					      = render partial: "shared/notes/note", collection: discussion.notes, as: :note
 | 
				
			||||||
    - else
 | 
					    - else
 | 
				
			||||||
      = render 'discussions/discussion', discussion: discussion
 | 
					      = render 'discussions/discussion', discussion: discussion
 | 
				
			||||||
- else
 | 
					- else
 | 
				
			||||||
  = render partial: "projects/notes/note", collection: @notes, as: :note
 | 
					  = render partial: "shared/notes/note", collection: @notes, as: :note
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					- if current_user
 | 
				
			||||||
 | 
					  - if note.emoji_awardable?
 | 
				
			||||||
 | 
					    - user_authored = note.user_authored?(current_user)
 | 
				
			||||||
 | 
					    = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
 | 
				
			||||||
 | 
					      = icon('spinner spin')
 | 
				
			||||||
 | 
					      %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
 | 
				
			||||||
 | 
					      %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
 | 
				
			||||||
 | 
					      %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
 | 
				
			||||||
 | 
					  - if note_editable
 | 
				
			||||||
 | 
					    = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
 | 
				
			||||||
 | 
					      = icon('pencil', class: 'link-highlight')
 | 
				
			||||||
 | 
					    = link_to snippet_note_path(note.noteable, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
 | 
				
			||||||
 | 
					      = icon('trash-o', class: 'danger-highlight')
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					%ul#notes-list.notes.main-notes-list.timeline
 | 
				
			||||||
 | 
					  = render "projects/notes/notes"
 | 
				
			||||||
| 
						 | 
					@ -7,3 +7,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.row-content-block.top-block.content-component-block
 | 
					.row-content-block.top-block.content-component-block
 | 
				
			||||||
  = render 'award_emoji/awards_block', awardable: @snippet, inline: true
 | 
					  = render 'award_emoji/awards_block', awardable: @snippet, inline: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%ul#notes-list.notes.main-notes-list.timeline
 | 
				
			||||||
 | 
					  #notes= render 'shared/notes/notes'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					title: Display comments for personal snippets
 | 
				
			||||||
 | 
					merge_request:
 | 
				
			||||||
 | 
					author:
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,14 @@ resources :snippets, concerns: :awardable do
 | 
				
			||||||
    post :mark_as_spam
 | 
					    post :mark_as_spam
 | 
				
			||||||
    post :preview_markdown
 | 
					    post :preview_markdown
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  scope module: :snippets do
 | 
				
			||||||
 | 
					    resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
 | 
				
			||||||
 | 
					      member do
 | 
				
			||||||
 | 
					        delete :delete_attachment
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get '/s/:username', to: redirect('/u/%{username}/snippets'),
 | 
					get '/s/:username', to: redirect('/u/%{username}/snippets'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -167,6 +167,47 @@ describe Projects::NotesController do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'DELETE destroy' do
 | 
				
			||||||
 | 
					    let(:request_params) do
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        namespace_id: project.namespace,
 | 
				
			||||||
 | 
					        project_id: project,
 | 
				
			||||||
 | 
					        id: note,
 | 
				
			||||||
 | 
					        format: :js
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'user is the author of a note' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        sign_in(note.author)
 | 
				
			||||||
 | 
					        project.team << [note.author, :developer]
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns status 200 for html" do
 | 
				
			||||||
 | 
					        delete :destroy, request_params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "deletes the note" do
 | 
				
			||||||
 | 
					        expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'user is not the author of a note' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        sign_in(user)
 | 
				
			||||||
 | 
					        project.team << [user, :developer]
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns status 404" do
 | 
				
			||||||
 | 
					        delete :destroy, request_params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(404)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe 'POST toggle_award_emoji' do
 | 
					  describe 'POST toggle_award_emoji' do
 | 
				
			||||||
    before do
 | 
					    before do
 | 
				
			||||||
      sign_in(user)
 | 
					      sign_in(user)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,196 @@
 | 
				
			||||||
 | 
					require 'spec_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe Snippets::NotesController do
 | 
				
			||||||
 | 
					  let(:user) { create(:user) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:private_snippet)  { create(:personal_snippet, :private) }
 | 
				
			||||||
 | 
					  let(:internal_snippet) { create(:personal_snippet, :internal) }
 | 
				
			||||||
 | 
					  let(:public_snippet)   { create(:personal_snippet, :public) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:note_on_private)  { create(:note_on_personal_snippet, noteable: private_snippet) }
 | 
				
			||||||
 | 
					  let(:note_on_internal) { create(:note_on_personal_snippet, noteable: internal_snippet) }
 | 
				
			||||||
 | 
					  let(:note_on_public)   { create(:note_on_personal_snippet, noteable: public_snippet) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'GET index' do
 | 
				
			||||||
 | 
					    context 'when a snippet is public' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        note_on_public
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        get :index, { snippet_id: public_snippet }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns status 200" do
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns not empty array of notes" do
 | 
				
			||||||
 | 
					        expect(JSON.parse(response.body)["notes"].empty?).to be_falsey
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when a snippet is internal' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        note_on_internal
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when user not logged in' do
 | 
				
			||||||
 | 
					        it "returns status 404" do
 | 
				
			||||||
 | 
					          get :index, { snippet_id: internal_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(response).to have_http_status(404)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when user logged in' do
 | 
				
			||||||
 | 
					        before do
 | 
				
			||||||
 | 
					          sign_in(user)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "returns status 200" do
 | 
				
			||||||
 | 
					          get :index, { snippet_id: internal_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when a snippet is private' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        note_on_private
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when user not logged in' do
 | 
				
			||||||
 | 
					        it "returns status 404" do
 | 
				
			||||||
 | 
					          get :index, { snippet_id: private_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(response).to have_http_status(404)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when user other than author logged in' do
 | 
				
			||||||
 | 
					        before do
 | 
				
			||||||
 | 
					          sign_in(user)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "returns status 404" do
 | 
				
			||||||
 | 
					          get :index, { snippet_id: private_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(response).to have_http_status(404)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when author logged in' do
 | 
				
			||||||
 | 
					        before do
 | 
				
			||||||
 | 
					          note_on_private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          sign_in(private_snippet.author)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "returns status 200" do
 | 
				
			||||||
 | 
					          get :index, { snippet_id: private_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "returns 1 note" do
 | 
				
			||||||
 | 
					          get :index, { snippet_id: private_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(JSON.parse(response.body)['notes'].count).to eq(1)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'dont show non visible notes' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        note_on_public
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sign_in(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect_any_instance_of(Note).to receive(:cross_reference_not_visible_for?).and_return(true)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "does not return any note" do
 | 
				
			||||||
 | 
					        get :index, { snippet_id: public_snippet }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(JSON.parse(response.body)['notes'].count).to eq(0)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'DELETE destroy' do
 | 
				
			||||||
 | 
					    let(:request_params) do
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        snippet_id: public_snippet,
 | 
				
			||||||
 | 
					        id: note_on_public,
 | 
				
			||||||
 | 
					        format: :js
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when user is the author of a note' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        sign_in(note_on_public.author)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns status 200" do
 | 
				
			||||||
 | 
					        delete :destroy, request_params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "deletes the note" do
 | 
				
			||||||
 | 
					        expect{ delete :destroy, request_params }.to change{ Note.count }.from(1).to(0)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'system note' do
 | 
				
			||||||
 | 
					        before do
 | 
				
			||||||
 | 
					          expect_any_instance_of(Note).to receive(:system?).and_return(true)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "does not delete the note" do
 | 
				
			||||||
 | 
					          expect{ delete :destroy, request_params }.not_to change{ Note.count }
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when user is not the author of a note' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        sign_in(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        note_on_public
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns status 404" do
 | 
				
			||||||
 | 
					        delete :destroy, request_params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(404)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "does not update the note" do
 | 
				
			||||||
 | 
					        expect{ delete :destroy, request_params }.not_to change{ Note.count }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'POST toggle_award_emoji' do
 | 
				
			||||||
 | 
					    let(:note) { create(:note_on_personal_snippet, noteable: public_snippet) }
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      sign_in(user)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subject { post(:toggle_award_emoji, snippet_id: public_snippet, id: note.id, name: "thumbsup") }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "toggles the award emoji" do
 | 
				
			||||||
 | 
					      expect { subject }.to change { note.award_emoji.count }.by(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "removes the already awarded emoji when it exists" do
 | 
				
			||||||
 | 
					      note.toggle_award_emoji('thumbsup', user) # create award emoji before
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect { subject }.to change { AwardEmoji.count }.by(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ include ActionDispatch::TestProcess
 | 
				
			||||||
FactoryGirl.define do
 | 
					FactoryGirl.define do
 | 
				
			||||||
  factory :note do
 | 
					  factory :note do
 | 
				
			||||||
    project factory: :empty_project
 | 
					    project factory: :empty_project
 | 
				
			||||||
    note "Note"
 | 
					    note { generate(:title) }
 | 
				
			||||||
    author
 | 
					    author
 | 
				
			||||||
    on_issue
 | 
					    on_issue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					require 'spec_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe 'Comments on personal snippets', feature: true do
 | 
				
			||||||
 | 
					  let!(:user)    { create(:user) }
 | 
				
			||||||
 | 
					  let!(:snippet) { create(:personal_snippet, :public) }
 | 
				
			||||||
 | 
					  let!(:snippet_notes) do
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					      create(:note_on_personal_snippet, noteable: snippet, author: user),
 | 
				
			||||||
 | 
					      create(:note_on_personal_snippet, noteable: snippet)
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  let!(:other_note) { create(:note_on_personal_snippet) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before do
 | 
				
			||||||
 | 
					    login_as user
 | 
				
			||||||
 | 
					    visit snippet_path(snippet)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  subject { page }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  context '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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # comment authored by current user
 | 
				
			||||||
 | 
					      page.within("#notes-list li#note_#{snippet_notes[0].id}") do
 | 
				
			||||||
 | 
					        expect(page).to have_content(snippet_notes[0].note)
 | 
				
			||||||
 | 
					        expect(page).to have_selector('.js-note-delete')
 | 
				
			||||||
 | 
					        expect(page).to have_selector('.note-emoji-button')
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      page.within("#notes-list li#note_#{snippet_notes[1].id}") do
 | 
				
			||||||
 | 
					        expect(page).to have_content(snippet_notes[1].note)
 | 
				
			||||||
 | 
					        expect(page).not_to have_selector('.js-note-delete')
 | 
				
			||||||
 | 
					        expect(page).to have_selector('.note-emoji-button')
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -110,6 +110,15 @@ describe NotesFinder do
 | 
				
			||||||
        expect(notes.count).to eq(1)
 | 
					        expect(notes.count).to eq(1)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'finds notes on personal snippets' do
 | 
				
			||||||
 | 
					        note = create(:note_on_personal_snippet)
 | 
				
			||||||
 | 
					        params = { target_type: 'personal_snippet', target_id: note.noteable_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        notes = described_class.new(project, user, params).execute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(notes.count).to eq(1)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'raises an exception for an invalid target_type' do
 | 
					      it 'raises an exception for an invalid target_type' do
 | 
				
			||||||
        params[:target_type] = 'invalid'
 | 
					        params[:target_type] = 'invalid'
 | 
				
			||||||
        expect { described_class.new(project, user, params).execute }.to raise_error('invalid target_type')
 | 
					        expect { described_class.new(project, user, params).execute }.to raise_error('invalid target_type')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					require 'spec_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe AwardEmojiHelper do
 | 
				
			||||||
 | 
					  describe '.toggle_award_url' do
 | 
				
			||||||
 | 
					    context 'note on personal snippet' do
 | 
				
			||||||
 | 
					      let(:note) { create(:note_on_personal_snippet) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns correct url' do
 | 
				
			||||||
 | 
					        expected_url = "/snippets/#{note.noteable.id}/notes/#{note.id}/toggle_award_emoji"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(helper.toggle_award_url(note)).to eq(expected_url)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'note on project item' do
 | 
				
			||||||
 | 
					      let(:note) { create(:note_on_project_snippet) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns correct url' do
 | 
				
			||||||
 | 
					        @project = note.noteable.project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expected_url = "/#{@project.namespace.path}/#{@project.path}/notes/#{note.id}/toggle_award_emoji"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(helper.toggle_award_url(note)).to eq(expected_url)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'personal snippet' do
 | 
				
			||||||
 | 
					      let(:snippet) { create(:personal_snippet) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns correct url' do
 | 
				
			||||||
 | 
					        expected_url = "/snippets/#{snippet.id}/toggle_award_emoji"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(helper.toggle_award_url(snippet)).to eq(expected_url)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'merge request' do
 | 
				
			||||||
 | 
					      let(:merge_request) { create(:merge_request) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns correct url' do
 | 
				
			||||||
 | 
					        @project = merge_request.project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.id}/toggle_award_emoji"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(helper.toggle_award_url(merge_request)).to eq(expected_url)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'issue' do
 | 
				
			||||||
 | 
					      let(:issue) { create(:issue) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns correct url' do
 | 
				
			||||||
 | 
					        @project = issue.project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.id}/toggle_award_emoji"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(helper.toggle_award_url(issue)).to eq(expected_url)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
		Reference in New Issue