Commit 6e48e0e1 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre
Browse files

Merge branch 'sh-fix-discussions-api-perf' into 'master'

Eliminate many Gitaly calls in discussions API

Closes #65957

See merge request gitlab-org/gitlab-ce!31834
title: Eliminate many Gitaly calls in discussions API
merge_request: 31834
type: performance
......@@ -4,6 +4,7 @@ module API
class Discussions < Grape::API
include PaginationParams
helpers ::API::Helpers::NotesHelpers
helpers ::RendersNotes
before { authenticate! }
......@@ -23,21 +24,15 @@ class Discussions < Grape::API
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
use :pagination
# rubocop: disable CodeReuse/ActiveRecord
get ":id/#{noteables_path}/:noteable_id/discussions" do
noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
notes = noteable.notes
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes = readable_discussion_notes(noteable)
discussions = Kaminari.paginate_array(Discussion.build_collection(notes, noteable))
present paginate(discussions), with: Entities::Discussion
# rubocop: enable CodeReuse/ActiveRecord
desc "Get a single #{noteable_type.to_s.downcase} discussion" do
success Entities::Discussion
......@@ -226,13 +221,24 @@ class Discussions < Grape::API
helpers do
# rubocop: disable CodeReuse/ActiveRecord
def readable_discussion_notes(noteable, discussion_id)
def readable_discussion_notes(noteable, discussion_id = nil)
notes = noteable.notes
.where(discussion_id: discussion_id)
notes = notes.where(discussion_id: discussion_id) if discussion_id
notes = notes
# Without RendersActions#prepare_notes_for_rendering,
# Note#cross_reference_not_visible_for? will attempt to render
# Markdown references mentioned in the note to see whether they
# should be redacted. For notes that reference a commit, this
# would also incur a Gitaly call to verify the commit exists.
# With prepare_notes_for_rendering, we can avoid Gitaly calls
# because notes are redacted if they point to projects that
# cannot be accessed by the user.
notes = prepare_notes_for_rendering(notes)
notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
# rubocop: enable CodeReuse/ActiveRecord
......@@ -9,6 +9,59 @@
context 'with cross-reference system notes', :request_store do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:new_merge_request) { create(:merge_request) }
let(:commit) { new_merge_request.project.commit }
let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) }
let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') }
let(:cross_reference) { "test commit #{commit.to_reference(project)}" }
let(:url) { "/projects/#{}/merge_requests/#{merge_request.iid}/discussions" }
before do
it 'returns only the note that the user should see' do
hidden_merge_request = create(:merge_request)
new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
create(:system_note_metadata, note: new_note, action: 'cross_reference')
get api(url, user)
expect(response).to have_gitlab_http_status(200)
expect(json_response.count).to eq(1)
expect(json_response.first['notes'].count).to eq(1)
parsed_note = json_response.first['notes'].first
expect(parsed_note['id']).to eq(
expect(parsed_note['body']).to eq(cross_reference)
expect(parsed_note['system']).to be true
it 'avoids Git calls and N+1 SQL queries' do
expect_any_instance_of(Repository).not_to receive(:find_commit).with(
control = do
get api(url, user)
expect(response).to have_gitlab_http_status(200)
new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference)
create(:system_note_metadata, note: new_note, action: 'cross_reference')
expect { get api(url, user) }.not_to exceed_query_limit(control)
expect(response).to have_gitlab_http_status(200)
context 'when noteable is an Issue' do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment