Commit c72abcef authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'fix-n-plus-one-queries-for-user-access' into 'master'

Fix N+1 queries for non-members in comment threads

See merge request !11827
parents 358662a9 7f73f440
...@@ -167,7 +167,7 @@ class ProjectTeam ...@@ -167,7 +167,7 @@ class ProjectTeam
access = RequestStore.store[key] access = RequestStore.store[key]
end end
# Lookup only the IDs we need # Look up only the IDs we need
user_ids = user_ids - access.keys user_ids = user_ids - access.keys
return access if user_ids.empty? return access if user_ids.empty?
...@@ -178,6 +178,13 @@ class ProjectTeam ...@@ -178,6 +178,13 @@ class ProjectTeam
maximum(:access_level) maximum(:access_level)
access.merge!(users_access) access.merge!(users_access)
missing_user_ids = user_ids - users_access.keys
missing_user_ids.each do |user_id|
access[user_id] = Gitlab::Access::NO_ACCESS
end
access access
end end
......
---
title: Fix N+1 queries for non-members in comment threads
merge_request:
author:
...@@ -327,69 +327,114 @@ describe ProjectTeam, models: true do ...@@ -327,69 +327,114 @@ describe ProjectTeam, models: true do
end end
end end
shared_examples_for "#max_member_access_for_users" do |enable_request_store| shared_examples 'max member access for users' do
describe "#max_member_access_for_users" do let(:project) { create(:project) }
before do let(:group) { create(:group) }
RequestStore.begin! if enable_request_store let(:second_group) { create(:group) }
end
after do let(:master) { create(:user) }
if enable_request_store let(:reporter) { create(:user) }
RequestStore.end! let(:guest) { create(:user) }
RequestStore.clear!
end let(:promoted_guest) { create(:user) }
let(:group_developer) { create(:user) }
let(:second_developer) { create(:user) }
let(:user_without_access) { create(:user) }
let(:second_user_without_access) { create(:user) }
let(:users) do
[master, reporter, promoted_guest, guest, group_developer, second_developer, user_without_access].map(&:id)
end end
it 'returns correct roles for different users' do let(:expected) do
master = create(:user) {
reporter = create(:user) master.id => Gitlab::Access::MASTER,
promoted_guest = create(:user) reporter.id => Gitlab::Access::REPORTER,
guest = create(:user) promoted_guest.id => Gitlab::Access::DEVELOPER,
project = create(:empty_project) guest.id => Gitlab::Access::GUEST,
group_developer.id => Gitlab::Access::DEVELOPER,
second_developer.id => Gitlab::Access::MASTER,
user_without_access.id => Gitlab::Access::NO_ACCESS
}
end
before do
project.add_master(master) project.add_master(master)
project.add_reporter(reporter) project.add_reporter(reporter)
project.add_guest(promoted_guest) project.add_guest(promoted_guest)
project.add_guest(guest) project.add_guest(guest)
group = create(:group)
group_developer = create(:user)
second_developer = create(:user)
project.project_group_links.create( project.project_group_links.create(
group: group, group: group,
group_access: Gitlab::Access::DEVELOPER) group_access: Gitlab::Access::DEVELOPER
)
group.add_master(promoted_guest) group.add_master(promoted_guest)
group.add_developer(group_developer) group.add_developer(group_developer)
group.add_developer(second_developer) group.add_developer(second_developer)
second_group = create(:group)
project.project_group_links.create( project.project_group_links.create(
group: second_group, group: second_group,
group_access: Gitlab::Access::MASTER) group_access: Gitlab::Access::MASTER
)
second_group.add_master(second_developer) second_group.add_master(second_developer)
end
users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id) it 'returns correct roles for different users' do
expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
end
end
expected = { describe '#max_member_access_for_user_ids' do
master.id => Gitlab::Access::MASTER, context 'with RequestStore enabled' do
reporter.id => Gitlab::Access::REPORTER, before do
promoted_guest.id => Gitlab::Access::DEVELOPER, RequestStore.begin!
guest.id => Gitlab::Access::GUEST, end
group_developer.id => Gitlab::Access::DEVELOPER,
second_developer.id => Gitlab::Access::MASTER
}
expect(project.team.max_member_access_for_user_ids(users)).to eq(expected) after do
RequestStore.end!
RequestStore.clear!
end end
include_examples 'max member access for users'
def access_levels(users)
project.team.max_member_access_for_user_ids(users)
end end
it 'does not perform extra queries when asked for users who have already been found' do
access_levels(users)
expect { access_levels(users) }.not_to exceed_query_limit(0)
expect(access_levels(users)).to eq(expected)
end end
describe '#max_member_access_for_users with RequestStore' do it 'only requests the extra users when uncached users are passed' do
it_behaves_like "#max_member_access_for_users", true new_user = create(:user)
second_new_user = create(:user)
all_users = users + [new_user.id, second_new_user.id]
expected_all = expected.merge(new_user.id => Gitlab::Access::NO_ACCESS,
second_new_user.id => Gitlab::Access::NO_ACCESS)
access_levels(users)
queries = ActiveRecord::QueryRecorder.new { access_levels(all_users) }
expect(queries.count).to eq(1)
expect(queries.log_message).to match(/\W#{new_user.id}\W/)
expect(queries.log_message).to match(/\W#{second_new_user.id}\W/)
expect(queries.log_message).not_to match(/\W#{promoted_guest.id}\W/)
expect(access_levels(all_users)).to eq(expected_all)
end
end end
describe '#max_member_access_for_users without RequestStore' do context 'with RequestStore disabled' do
it_behaves_like "#max_member_access_for_users", false include_examples 'max member access for users'
end
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