Skip to content
Snippets Groups Projects
Commit ea155ccc authored by Alejandro Rodríguez's avatar Alejandro Rodríguez
Browse files

Optimize discussion notes resolving and unresolving

Use `update_all` to only require one query per discussion to
update the notes resolved status. Some changes had to be made to
the discussion spec to accout for the fact that notes are not
individually updated now
parent e9e8c67f
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -94,6 +94,7 @@ v 8.11.5 (unreleased)
- Optimize branch lookups and force a repository reload for Repository#find_branch
- Fix member expiration date picker after update
- Fix suggested colors options for new labels in the admin area. !6138
- Optimize discussion notes resolving and unresolving
- Fix GitLab import button
- Remove gitorious from import_sources
 
Loading
Loading
Loading
Loading
@@ -13,6 +13,11 @@ class DiffNote < Note
validate :positions_complete
validate :verify_supported
 
# Keep this scope in sync with the logic in `#resolvable?`
scope :resolvable, -> { user.where(noteable_type: 'MergeRequest') }
scope :resolved, -> { resolvable.where.not(resolved_at: nil) }
scope :unresolved, -> { resolvable.where(resolved_at: nil) }
after_initialize :ensure_original_discussion_id
before_validation :set_original_position, :update_position, on: :create
before_validation :set_line_code, :set_original_discussion_id
Loading
Loading
@@ -25,6 +30,16 @@ class DiffNote < Note
def build_discussion_id(noteable_type, noteable_id, position)
[super(noteable_type, noteable_id), *position.key].join("-")
end
# This method must be kept in sync with `#resolve!`
def resolve!(current_user)
unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
end
# This method must be kept in sync with `#unresolve!`
def unresolve!
resolved.update_all(resolved_at: nil, resolved_by_id: nil)
end
end
 
def new_diff_note?
Loading
Loading
@@ -73,6 +88,7 @@ class DiffNote < Note
self.position.diff_refs == diff_refs
end
 
# If you update this method remember to also update the scope `resolvable`
def resolvable?
!system? && for_merge_request?
end
Loading
Loading
@@ -83,6 +99,7 @@ class DiffNote < Note
self.resolved_at.present?
end
 
# If you update this method remember to also update `.resolve!`
def resolve!(current_user)
return unless resolvable?
return if resolved?
Loading
Loading
@@ -92,6 +109,7 @@ class DiffNote < Note
save!
end
 
# If you update this method remember to also update `.unresolve!`
def unresolve!
return unless resolvable?
return unless resolved?
Loading
Loading
class Discussion
NUMBER_OF_TRUNCATED_DIFF_LINES = 16
 
attr_reader :first_note, :last_note, :notes
attr_reader :notes
 
delegate :created_at,
:project,
Loading
Loading
@@ -36,8 +36,6 @@ class Discussion
end
 
def initialize(notes)
@first_note = notes.first
@last_note = notes.last
@notes = notes
end
 
Loading
Loading
@@ -70,17 +68,25 @@ class Discussion
end
 
def resolvable?
return @resolvable if defined?(@resolvable)
return @resolvable if @resolvable.present?
 
@resolvable = diff_discussion? && notes.any?(&:resolvable?)
end
 
def resolved?
return @resolved if defined?(@resolved)
return @resolved if @resolved.present?
 
@resolved = resolvable? && notes.none?(&:to_be_resolved?)
end
 
def first_note
@first_note ||= @notes.first
end
def last_note
@last_note ||= @notes.last
end
def resolved_notes
notes.select(&:resolved?)
end
Loading
Loading
@@ -100,17 +106,13 @@ class Discussion
def resolve!(current_user)
return unless resolvable?
 
notes.each do |note|
note.resolve!(current_user) if note.resolvable?
end
update { |notes| notes.resolve!(current_user) }
end
 
def unresolve!
return unless resolvable?
 
notes.each do |note|
note.unresolve! if note.resolvable?
end
update { |notes| notes.unresolve! }
end
 
def for_target?(target)
Loading
Loading
@@ -118,7 +120,7 @@ class Discussion
end
 
def active?
return @active if defined?(@active)
return @active if @active.present?
 
@active = first_note.active?
end
Loading
Loading
@@ -174,4 +176,17 @@ class Discussion
 
prev_lines
end
private
def update
notes_relation = DiffNote.where(id: notes.map(&:id)).fresh
yield(notes_relation)
# Set the notes array to the updated notes
@notes = notes_relation.to_a
# Reset the memoized values
@last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil
end
end
Loading
Loading
@@ -28,6 +28,11 @@ FactoryGirl.define do
diff_refs: noteable.diff_refs
)
end
trait :resolved do
resolved_at { Time.now }
resolved_by { create(:user) }
end
end
 
factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
Loading
Loading
Loading
Loading
@@ -31,6 +31,43 @@ describe DiffNote, models: true do
 
subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
 
describe ".resolve!" do
let(:current_user) { create(:user) }
let!(:commit_note) { create(:diff_note_on_commit) }
let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) }
let!(:unresolved_note) { create(:diff_note_on_merge_request) }
before do
described_class.resolve!(current_user)
commit_note.reload
resolved_note.reload
unresolved_note.reload
end
it 'resolves only the resolvable, not yet resolved notes' do
expect(commit_note.resolved_at).to be_nil
expect(resolved_note.resolved_by).not_to eq(current_user)
expect(unresolved_note.resolved_at).not_to be_nil
expect(unresolved_note.resolved_by).to eq(current_user)
end
end
describe ".unresolve!" do
let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) }
before do
described_class.unresolve!
resolved_note.reload
end
it 'unresolves the resolved notes' do
expect(resolved_note.resolved_by).to be_nil
expect(resolved_note.resolved_at).to be_nil
end
end
describe "#position=" do
context "when provided a string" do
it "sets the position" do
Loading
Loading
Loading
Loading
@@ -238,27 +238,19 @@ describe Discussion, model: true do
 
context "when resolvable" do
let(:user) { create(:user) }
let(:second_note) { create(:diff_note_on_commit) } # unresolvable
 
before do
allow(subject).to receive(:resolvable?).and_return(true)
allow(first_note).to receive(:resolvable?).and_return(true)
allow(second_note).to receive(:resolvable?).and_return(false)
allow(third_note).to receive(:resolvable?).and_return(true)
end
 
context "when all resolvable notes are resolved" do
before do
first_note.resolve!(user)
third_note.resolve!(user)
end
 
it "calls resolve! on every resolvable note" do
expect(first_note).to receive(:resolve!).with(current_user)
expect(second_note).not_to receive(:resolve!)
expect(third_note).to receive(:resolve!).with(current_user)
subject.resolve!(current_user)
first_note.reload
third_note.reload
end
 
it "doesn't change resolved_at on the resolved notes" do
Loading
Loading
@@ -309,46 +301,44 @@ describe Discussion, model: true do
first_note.resolve!(user)
end
 
it "calls resolve! on every resolvable note" do
expect(first_note).to receive(:resolve!).with(current_user)
expect(second_note).not_to receive(:resolve!)
expect(third_note).to receive(:resolve!).with(current_user)
subject.resolve!(current_user)
end
it "doesn't change resolved_at on the resolved note" do
expect(first_note.resolved_at).not_to be_nil
 
expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_at }
expect { subject.resolve!(current_user) }.
not_to change { first_note.reload.resolved_at }
end
 
it "doesn't change resolved_by on the resolved note" do
expect(first_note.resolved_by).to eq(user)
 
expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_by }
expect { subject.resolve!(current_user) }.
not_to change { first_note.reload && first_note.resolved_by }
end
 
it "doesn't change the resolved state on the resolved note" do
expect(first_note.resolved?).to be true
 
expect { subject.resolve!(current_user) }.not_to change { first_note.resolved? }
expect { subject.resolve!(current_user) }.
not_to change { first_note.reload && first_note.resolved? }
end
 
it "sets resolved_at on the unresolved note" do
subject.resolve!(current_user)
third_note.reload
 
expect(third_note.resolved_at).not_to be_nil
end
 
it "sets resolved_by on the unresolved note" do
subject.resolve!(current_user)
third_note.reload
 
expect(third_note.resolved_by).to eq(current_user)
end
 
it "marks the unresolved note as resolved" do
subject.resolve!(current_user)
third_note.reload
 
expect(third_note.resolved?).to be true
end
Loading
Loading
@@ -373,16 +363,10 @@ describe Discussion, model: true do
end
 
context "when no resolvable notes are resolved" do
it "calls resolve! on every resolvable note" do
expect(first_note).to receive(:resolve!).with(current_user)
expect(second_note).not_to receive(:resolve!)
expect(third_note).to receive(:resolve!).with(current_user)
subject.resolve!(current_user)
end
it "sets resolved_at on the unresolved notes" do
subject.resolve!(current_user)
first_note.reload
third_note.reload
 
expect(first_note.resolved_at).not_to be_nil
expect(third_note.resolved_at).not_to be_nil
Loading
Loading
@@ -390,6 +374,8 @@ describe Discussion, model: true do
 
it "sets resolved_by on the unresolved notes" do
subject.resolve!(current_user)
first_note.reload
third_note.reload
 
expect(first_note.resolved_by).to eq(current_user)
expect(third_note.resolved_by).to eq(current_user)
Loading
Loading
@@ -397,6 +383,8 @@ describe Discussion, model: true do
 
it "marks the unresolved notes as resolved" do
subject.resolve!(current_user)
first_note.reload
third_note.reload
 
expect(first_note.resolved?).to be true
expect(third_note.resolved?).to be true
Loading
Loading
@@ -404,18 +392,24 @@ describe Discussion, model: true do
 
it "sets resolved_at" do
subject.resolve!(current_user)
first_note.reload
third_note.reload
 
expect(subject.resolved_at).not_to be_nil
end
 
it "sets resolved_by" do
subject.resolve!(current_user)
first_note.reload
third_note.reload
 
expect(subject.resolved_by).to eq(current_user)
end
 
it "marks as resolved" do
subject.resolve!(current_user)
first_note.reload
third_note.reload
 
expect(subject.resolved?).to be true
end
Loading
Loading
@@ -451,16 +445,10 @@ describe Discussion, model: true do
third_note.resolve!(user)
end
 
it "calls unresolve! on every resolvable note" do
expect(first_note).to receive(:unresolve!)
expect(second_note).not_to receive(:unresolve!)
expect(third_note).to receive(:unresolve!)
subject.unresolve!
end
it "unsets resolved_at on the resolved notes" do
subject.unresolve!
first_note.reload
third_note.reload
 
expect(first_note.resolved_at).to be_nil
expect(third_note.resolved_at).to be_nil
Loading
Loading
@@ -468,6 +456,8 @@ describe Discussion, model: true do
 
it "unsets resolved_by on the resolved notes" do
subject.unresolve!
first_note.reload
third_note.reload
 
expect(first_note.resolved_by).to be_nil
expect(third_note.resolved_by).to be_nil
Loading
Loading
@@ -475,6 +465,8 @@ describe Discussion, model: true do
 
it "unmarks the resolved notes as resolved" do
subject.unresolve!
first_note.reload
third_note.reload
 
expect(first_note.resolved?).to be false
expect(third_note.resolved?).to be false
Loading
Loading
@@ -482,12 +474,16 @@ describe Discussion, model: true do
 
it "unsets resolved_at" do
subject.unresolve!
first_note.reload
third_note.reload
 
expect(subject.resolved_at).to be_nil
end
 
it "unsets resolved_by" do
subject.unresolve!
first_note.reload
third_note.reload
 
expect(subject.resolved_by).to be_nil
end
Loading
Loading
@@ -504,40 +500,22 @@ describe Discussion, model: true do
first_note.resolve!(user)
end
 
it "calls unresolve! on every resolvable note" do
expect(first_note).to receive(:unresolve!)
expect(second_note).not_to receive(:unresolve!)
expect(third_note).to receive(:unresolve!)
subject.unresolve!
end
it "unsets resolved_at on the resolved note" do
subject.unresolve!
 
expect(first_note.resolved_at).to be_nil
expect(subject.first_note.resolved_at).to be_nil
end
 
it "unsets resolved_by on the resolved note" do
subject.unresolve!
 
expect(first_note.resolved_by).to be_nil
expect(subject.first_note.resolved_by).to be_nil
end
 
it "unmarks the resolved note as resolved" do
subject.unresolve!
 
expect(first_note.resolved?).to be false
end
end
context "when no resolvable notes are resolved" do
it "calls unresolve! on every resolvable note" do
expect(first_note).to receive(:unresolve!)
expect(second_note).not_to receive(:unresolve!)
expect(third_note).to receive(:unresolve!)
subject.unresolve!
expect(subject.first_note.resolved?).to be false
end
end
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment