Commit f8fabfcc authored by Douwe Maan's avatar Douwe Maan

Allow commenting on older versions of the diff and comparisons between diff versions

parent 185fd98f
......@@ -425,12 +425,6 @@
float: right;
}
.diffs {
.content-block {
border-bottom: none;
}
}
.files-changed {
border-bottom: none;
}
......
......@@ -511,7 +511,6 @@
.mr-version-controls {
background: $gray-light;
border-bottom: 1px solid $border-color;
color: $gl-text-color;
.mr-version-menus-container {
......
......@@ -120,7 +120,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
define_diff_comment_vars
else
build_merge_request
@diffs = @merge_request.diffs(diff_options)
@compare = @merge_request
@diffs = @compare.diffs(diff_options)
@diff_notes_disabled = true
end
......@@ -584,12 +585,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
@diffs =
@compare =
if @start_sha
@merge_request_diff.compare_with(@start_sha).diffs(diff_options)
@merge_request_diff.compare_with(@start_sha)
else
@merge_request_diff.diffs(diff_options)
@merge_request_diff
end
@diffs = @compare.diffs(diff_options)
end
def define_diff_comment_vars
......@@ -598,11 +601,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
noteable_id: @merge_request.id
}
@diff_notes_disabled = !@merge_request_diff.latest? || @start_sha
@diff_notes_disabled = false
@use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@merge_request_diff.diff_refs)
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
end
......
......@@ -60,20 +60,16 @@ module NotesHelper
note.project.team.human_max_access(note.author_id)
end
def discussion_diff_path(discussion)
if discussion.for_merge_request? && discussion.diff_discussion?
if discussion.active?
# Without a diff ID, the link always points to the latest diff version
diff_id = nil
elsif merge_request_diff = discussion.latest_merge_request_diff
diff_id = merge_request_diff.id
else
# If the discussion is not active, and we cannot find the latest
# merge request diff for this discussion, we return no path at all.
return
end
def discussion_path(discussion)
if discussion.for_merge_request?
return unless discussion.diff_discussion?
version_params = discussion.merge_request_version_params
return unless version_params
path_params = version_params.merge(anchor: discussion.line_code)
diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, diff_id: diff_id, anchor: discussion.line_code)
diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, path_params)
elsif discussion.for_commit?
anchor = discussion.line_code if discussion.diff_discussion?
......
......@@ -11,6 +11,7 @@ module DiscussionOnDiff
:diff_line,
:for_line?,
:active?,
:created_at_diff?,
to: :first_note
......
......@@ -30,6 +30,10 @@ module NoteOnDiff
raise NotImplementedError
end
def created_at_diff?(diff_refs)
false
end
private
def noteable_diff_refs
......
......@@ -10,7 +10,6 @@ class DiffDiscussion < Discussion
delegate :position,
:original_position,
:latest_merge_request_diff,
to: :first_note
......@@ -18,6 +17,25 @@ class DiffDiscussion < Discussion
false
end
def merge_request_version_params
return unless for_merge_request?
if active?
{}
else
diff_refs = position.diff_refs
if diff = noteable.merge_request_diff_for(diff_refs)
{ diff_id: diff.id }
elsif diff = noteable.merge_request_diff_for(diff_refs.head_sha)
{
diff_id: diff.id,
start_sha: diff_refs.start_sha
}
end
end
end
def reply_attributes
super.merge(
original_position: original_position.to_json,
......
......@@ -65,10 +65,11 @@ class DiffNote < Note
self.position.diff_refs == diff_refs
end
def latest_merge_request_diff
return unless for_merge_request?
def created_at_diff?(diff_refs)
return false unless supported?
return true if for_commit?
self.noteable.merge_request_diff_for(self.position.diff_refs)
self.original_position.diff_refs == diff_refs
end
private
......
......@@ -9,14 +9,14 @@ class LegacyDiffDiscussion < Discussion
memoized_values << :active
def legacy_diff_discussion?
true
end
def self.note_class
LegacyDiffNote
end
def legacy_diff_discussion?
true
end
def active?(*args)
return @active if @active.present?
......@@ -27,6 +27,16 @@ class LegacyDiffDiscussion < Discussion
!active?
end
def merge_request_version_params
return unless for_merge_request?
if active?
{}
else
nil
end
end
def reply_attributes
super.merge(line_code: line_code)
end
......
......@@ -374,12 +374,18 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff(true)
end
def merge_request_diff_for(diff_refs)
@merge_request_diffs_by_diff_refs ||= Hash.new do |h, diff_refs|
h[diff_refs] = merge_request_diffs.viewable.select_without_diff.find_by_diff_refs(diff_refs)
def merge_request_diff_for(diff_refs_or_sha)
@merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha|
diffs = merge_request_diffs.viewable.select_without_diff
h[diff_refs_or_sha] =
if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
diffs.find_by_diff_refs(diff_refs_or_sha)
else
diffs.find_by(head_commit_sha: diff_refs_or_sha)
end
end
@merge_request_diffs_by_diff_refs[diff_refs]
@merge_request_diffs_by_diff_refs_or_sha[diff_refs_or_sha]
end
def reload_diff_if_branch_changed
......
......@@ -115,11 +115,19 @@ class Note < ActiveRecord::Base
end
def grouped_diff_discussions(diff_refs = nil)
diff_notes.
fresh.
discussions.
select { |n| n.active?(diff_refs) }.
group_by(&:line_code)
groups = {}
diff_notes.fresh.discussions.each do |discussion|
if discussion.active?(diff_refs)
discussions = groups[discussion.line_code] ||= []
elsif diff_refs && discussion.created_at_diff?(diff_refs)
discussions = groups[discussion.original_line_code] ||= []
end
discussions << discussion if discussions
end
groups
end
def count_for_collection(ids, type)
......@@ -141,10 +149,6 @@ class Note < ActiveRecord::Base
true
end
def latest_merge_request_diff
nil
end
def max_attachment_size
current_application_settings.max_attachment_size.megabytes.to_i
end
......
......@@ -3,7 +3,7 @@
.diff-file.file-holder
.js-file-title.file-title
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion)
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_path(discussion)
.diff-content.code.js-syntax-highlight
%table
......
......@@ -20,7 +20,7 @@
= discussion.author.to_reference
started a discussion
- url = discussion_diff_path(discussion)
- url = discussion_path(discussion)
- if discussion.for_commit? && @noteable != discussion.noteable
on
- commit = discussion.noteable
......
......@@ -35,6 +35,6 @@
- else
= diff_line_content(line.text)
- if line_discussions
- if line_discussions&.any?
- discussion_expanded = local_assigns.fetch(:discussion_expanded, line_discussions.any?(&:expanded?))
= render "discussions/diff_discussion", discussions: line_discussions, expanded: discussion_expanded
......@@ -35,7 +35,7 @@
%span.dropdown.inline.mr-version-compare-dropdown
%a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} }
%span
- if @start_sha
- if @start_version
version #{version_index(@start_version)}
- else
#{@merge_request.target_branch}
......@@ -59,7 +59,7 @@
%small
= time_ago_with_tooltip(merge_request_diff.created_at)
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_version) do
%strong
#{@merge_request.target_branch} (base)
.monospace= short_sha(@merge_request_diff.base_commit_sha)
......@@ -75,13 +75,15 @@
= succeed '.' do
%code= @merge_request.target_branch
- if @diff_notes_disabled
- if @start_version || !@merge_request_diff.latest?
.comments-disabled-notif.content-block
= icon('info-circle')
- if @start_sha
Comments are disabled because you're comparing two versions of this merge request.
Not all comments are displayed because you're
- if @start_version
comparing two versions
- else
Discussions on this version of the merge request are displayed but comment creation is disabled.
viewing an old version
of this merge request.
.pull-right
= link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
---
title: Allow commenting on older versions of the diff and comparisons between diff versions
merge_request:
author:
......@@ -24,7 +24,12 @@ feature 'Merge Request versions', js: true, feature: true do
before do
page.within '.mr-version-dropdown' do
find('.btn-default').click
find(:link, 'version 1').trigger('click')
click_link 'version 1'
end
# Wait for the page to load
page.within '.mr-version-dropdown' do
expect(page).to have_content 'version 1'
end
end
......@@ -36,8 +41,8 @@ feature 'Merge Request versions', js: true, feature: true do
expect(page).to have_content '5 changed files'
end
it 'show the message about disabled comment creation' do
expect(page).to have_content 'comment creation is disabled'
it 'show the message about comments' do
expect(page).to have_content 'Not all comments are displayed'
end
it 'shows comments that were last relevant at that version' do
......@@ -52,15 +57,41 @@ feature 'Merge Request versions', js: true, feature: true do
outdated_diff_note.position = outdated_diff_note.original_position
outdated_diff_note.save!
visit current_url
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
it 'allows commenting' do
diff_file_selector = ".diff-file[id='7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44']"
line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_2_2'
page.within(diff_file_selector) do
find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").trigger 'mouseover'
find(".line_holder[id='#{line_code}'] button").trigger 'click'
page.within("form[data-line-code='#{line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").click
end
wait_for_ajax
expect(page).to have_content("Typo, please fix")
end
end
end
describe 'compare with older version' do
before do
page.within '.mr-version-compare-dropdown' do
find('.btn-default').click
find(:link, 'version 1').trigger('click')
click_link 'version 1'
end
# Wait for the page to load
page.within '.mr-version-compare-dropdown' do
expect(page).to have_content 'version 1'
end
end
......@@ -80,8 +111,43 @@ feature 'Merge Request versions', js: true, feature: true do
end
end
it 'show the message about disabled comments' do
expect(page).to have_content 'Comments are disabled'
it 'show the message about comments' do
expect(page).to have_content 'Not all comments are displayed'
end
it 'shows comments that were last relevant at that version' do
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
old_line: 4,
new_line: 4,
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
)
outdated_diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position)
visit current_url
wait_for_ajax
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
it 'allows commenting' do
diff_file_selector = ".diff-file[id='7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44']"
line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_4'
page.within(diff_file_selector) do
find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").trigger 'mouseover'
find(".line_holder[id='#{line_code}'] button").trigger 'click'
page.within("form[data-line-code='#{line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").click
end
wait_for_ajax
expect(page).to have_content("Typo, please fix")
end
end
it 'show diff between new and old version' do
......
require "spec_helper"
describe NotesHelper do
include RepoHelpers
let(:owner) { create(:owner) }
let(:group) { create(:group) }
let(:project) { create(:empty_project, namespace: group) }
......@@ -36,4 +38,141 @@ describe NotesHelper do
expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
end
end
describe '#discussion_path' do
let(:project) { create(:project) }
context 'for a merge request discusion' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) }
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { merge_request.merge_request_diffs.create(head_commit_sha: nil) }
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
context 'for a diff discussion' do
context 'when the discussion is active' do
let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
it 'returns the diff path with the line code' do
expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
end
end
context 'when the discussion is on an older merge request version' do
let(:position) do
Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
old_line: nil,
new_line: 4,
diff_refs: merge_request_diff1.diff_refs
)
end
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
let(:discussion) { diff_note.to_discussion }
before do
diff_note.position = diff_note.original_position
diff_note.save!
end
it 'returns the diff version path with the line code' do
expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
end
end
context 'when the discussion is on a comparison between merge request versions' do
let(:position) do
Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
old_line: 4,
new_line: 4,
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
)
end
let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position).to_discussion }
it 'returns the diff version comparison path with the line code' do
expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
end
end
context 'when the discussion does not have a merge request version' do
let(:outdated_diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, diff_refs: project.commit(sample_commit.id).diff_refs) }
let(:discussion) { outdated_diff_note.to_discussion }
before do
outdated_diff_note.position = outdated_diff_note.original_position
outdated_diff_note.save!
end
it 'returns nil' do
expect(helper.discussion_path(discussion)).to be_nil
end
end
end
context 'for a legacy diff discussion' do
let(:discussion) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
context 'when the discussion is active' do
before do
allow(discussion).to receive(:active?).and_return(true)
end
it 'returns the diff path with the line code' do
expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
end
end
context 'when the discussion is outdated' do
before do
allow(discussion).to receive(:active?).and_return(false)
end
it 'returns nil' do
expect(helper.discussion_path(discussion)).to be_nil
end
end
end
context 'for a non-diff discussion' do
let(:discussion) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
it 'returns nil' do
expect(helper.discussion_path(discussion)).to be_nil
end
end
end
context 'for a commit discussion' do
let(:commit) { discussion.noteable }
context 'for a diff discussion' do
let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do
expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
end
end
context 'for a legacy diff discussion' do
let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do
expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
end
end
context 'for a non-diff discussion' do
let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion }
it 'returns the commit path' do
expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit))
end
end
end
end
end
require 'spec_helper'
describe DiffDiscussion, model: true do
subject { described_class.new([first_note, second_note, third_note]) }
include RepoHelpers
let(:first_note) { create(:diff_note_on_merge_request) }
let(:merge_request) { first_note.noteable }
let(:project) { first_note.project }
let(:second_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, in_reply_to: first_note) }
let(:third_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, in_reply_to: first_note) }
subject { described_class.new([diff_note]) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
describe '#reply_attributes' do
it 'includes position and original_position' do
attributes = subject.reply_attributes
expect(attributes[:position]).to eq(first_note.position.to_json)
expect(attributes[:original_position]).to eq(first_note.original_position.to_json)
expect(attributes[:position]).to eq(diff_note.position.to_json)
expect(attributes[:original_position]).to eq(diff_note.original_position.to_json)
end
end
describe '#merge_request_version_params' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) }
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { merge_request.merge_request_diffs.create(head_commit_sha: nil) }
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
context 'when the discussion is active' do
it 'returns an empty hash, which will end up showing the latest version' do
expect(subject.merge_request_version_params).to eq({})
end
end
context 'when the discussion is on an older merge request version' do
let(:position) do
Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
old_line: nil,
new_line: 4,
diff_refs: merge_request_diff1.diff_refs
)
end
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
before do
diff_note.position = diff_note.original_position
diff_note.save!
end
it 'returns the diff ID for the version to show' do
expect(diff_id: merge_request_diff1.id)
end
end
context 'when the discussion is on a comparison between merge request versions' do
let(:position) do
Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
old_line: 4,
new_line: 4,
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
)
end
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
it 'returns the diff ID and start sha of the versions to compare' do
expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)
end
end
context 'when the discussion does not have a merge request version' do
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, diff_refs: project.commit(sample_commit.id).diff_refs) }
before do
diff_note.position = diff_note.original_position
diff_note.save!
end
it 'returns nil' do
expect(subject.merge_request_version_params).to be_nil
end
end
end
end
......@@ -155,23 +155,6 @@ describe DiffNote, models: true do
end
end
describe '#latest_merge_request_diff' do
context 'when active' do
it 'returns the current merge request diff' do
expect(subject.latest_merge_request_diff).to eq(merge_request.merge_request_diff)
end
end