Commit 0c2b766c authored by Andy Soiron's avatar Andy Soiron Committed by Rémy Coutable

Avoid N+1 queries in JiraConnect::client

We expose a user notes count for each MR. Counting
user notes executes an extra SQL query for each MR in the list.
This commit loads all counts in one query and passes it to the
serializer as an option. If the option is not available, it will
fall back to execute a count query.
parent 90e23554
...@@ -20,6 +20,7 @@ module Atlassian ...@@ -20,6 +20,7 @@ module Atlassian
commits: commits, commits: commits,
branches: branches, branches: branches,
merge_requests: merge_requests, merge_requests: merge_requests,
user_notes_count: user_notes_count(merge_requests),
update_sequence_id: update_sequence_id update_sequence_id: update_sequence_id
) )
] ]
...@@ -37,6 +38,14 @@ module Atlassian ...@@ -37,6 +38,14 @@ module Atlassian
private private
def user_notes_count(merge_requests)
return unless merge_requests
Note.count_for_collection(merge_requests.map(&:id), 'MergeRequest').map do |count_group|
[count_group.noteable_id, count_group.count]
end.to_h
end
def jwt_token(http_method, uri) def jwt_token(http_method, uri)
claims = Atlassian::Jwt.build_claims( claims = Atlassian::Jwt.build_claims(
Atlassian::JiraConnect.app_key, Atlassian::JiraConnect.app_key,
......
...@@ -20,7 +20,13 @@ module Atlassian ...@@ -20,7 +20,13 @@ module Atlassian
end end
expose :title expose :title
expose :author, using: JiraConnect::Serializers::AuthorEntity expose :author, using: JiraConnect::Serializers::AuthorEntity
expose :user_notes_count, as: :commentCount expose :commentCount do |mr|
if options[:user_notes_count]
options[:user_notes_count].fetch(mr.id, 0)
else
mr.user_notes_count
end
end
expose :source_branch, as: :sourceBranch expose :source_branch, as: :sourceBranch
expose :target_branch, as: :destinationBranch expose :target_branch, as: :destinationBranch
expose :lastUpdate do |mr| expose :lastUpdate do |mr|
......
...@@ -21,7 +21,11 @@ module Atlassian ...@@ -21,7 +21,11 @@ module Atlassian
JiraConnect::Serializers::BranchEntity.represent options[:branches], project: project, update_sequence_id: options[:update_sequence_id] JiraConnect::Serializers::BranchEntity.represent options[:branches], project: project, update_sequence_id: options[:update_sequence_id]
end end
expose :pullRequests do |project, options| expose :pullRequests do |project, options|
JiraConnect::Serializers::PullRequestEntity.represent options[:merge_requests], project: project, update_sequence_id: options[:update_sequence_id] JiraConnect::Serializers::PullRequestEntity.represent(
options[:merge_requests],
update_sequence_id: options[:update_sequence_id],
user_notes_count: options[:user_notes_count]
)
end end
end end
end end
......
...@@ -20,8 +20,11 @@ RSpec.describe Atlassian::JiraConnect::Client do ...@@ -20,8 +20,11 @@ RSpec.describe Atlassian::JiraConnect::Client do
end end
describe '#store_dev_info' do describe '#store_dev_info' do
it "calls the API with auth headers" do let_it_be(:project) { create_default(:project, :repository) }
expected_jwt = Atlassian::Jwt.encode( let_it_be(:merge_requests) { create_list(:merge_request, 2, :unique_branches) }
let(:expected_jwt) do
Atlassian::Jwt.encode(
Atlassian::Jwt.build_claims( Atlassian::Jwt.build_claims(
Atlassian::JiraConnect.app_key, Atlassian::JiraConnect.app_key,
'/rest/devinfo/0.10/bulk', '/rest/devinfo/0.10/bulk',
...@@ -29,7 +32,9 @@ RSpec.describe Atlassian::JiraConnect::Client do ...@@ -29,7 +32,9 @@ RSpec.describe Atlassian::JiraConnect::Client do
), ),
'sample_secret' 'sample_secret'
) )
end
before do
stub_full_request('https://gitlab-test.atlassian.net/rest/devinfo/0.10/bulk', method: :post) stub_full_request('https://gitlab-test.atlassian.net/rest/devinfo/0.10/bulk', method: :post)
.with( .with(
headers: { headers: {
...@@ -37,8 +42,18 @@ RSpec.describe Atlassian::JiraConnect::Client do ...@@ -37,8 +42,18 @@ RSpec.describe Atlassian::JiraConnect::Client do
'Content-Type' => 'application/json' 'Content-Type' => 'application/json'
} }
) )
end
it "calls the API with auth headers" do
subject.store_dev_info(project: project)
end
it 'avoids N+1 database queries' do
control_count = ActiveRecord::QueryRecorder.new { subject.store_dev_info(project: project, merge_requests: merge_requests) }.count
merge_requests << create(:merge_request, :unique_branches)
subject.store_dev_info(project: create(:project)) expect { subject.store_dev_info(project: project, merge_requests: merge_requests) }.not_to exceed_query_limit(control_count)
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Atlassian::JiraConnect::Serializers::PullRequestEntity do
let_it_be(:project) { create_default(:project, :repository) }
let_it_be(:merge_requests) { create_list(:merge_request, 2, :unique_branches) }
let_it_be(:notes) { create_list(:note, 2, system: false, noteable: merge_requests.first) }
subject { described_class.represent(merge_requests).as_json }
it 'exposes commentCount' do
expect(subject.first[:commentCount]).to eq(2)
end
context 'with user_notes_count option' do
let(:user_notes_count) { merge_requests.map { |merge_request| [merge_request.id, 1] }.to_h }
subject { described_class.represent(merge_requests, user_notes_count: user_notes_count).as_json }
it 'avoids N+1 database queries' do
control_count = ActiveRecord::QueryRecorder.new do
described_class.represent(merge_requests, user_notes_count: user_notes_count)
end.count
merge_requests << create(:merge_request, :unique_branches)
expect { subject }.not_to exceed_query_limit(control_count)
end
it 'uses counts from user_notes_count' do
expect(subject.map { |entity| entity[:commentCount] }).to match_array([1, 1, 1])
end
context 'when count is missing for some MRs' do
let(:user_notes_count) { [[merge_requests.last.id, 1]].to_h }
it 'uses 0 as default when count for the MR is not available' do
expect(subject.map { |entity| entity[:commentCount] }).to match_array([0, 0, 1])
end
end
end
end
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