Commit 85e63b76 authored by Krasimir Angelov's avatar Krasimir Angelov

Merge branch 'fix_fetch_labels_query_on_project_transfer' into 'master'

Add a query optimisation for fetching group labels

See merge request gitlab-org/gitlab!73501
parents a8e34d60 6e0cd311
...@@ -50,21 +50,32 @@ module Labels ...@@ -50,21 +50,32 @@ module Labels
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_issues def group_labels_applied_to_issues
@group_labels_applied_to_issues ||= Label.joins(:issues) @labels_applied_to_issues ||= if use_optimized_group_labels_query?
.where( Label.joins(:issues)
issues: { project_id: project.id }, .joins("INNER JOIN namespaces on namespaces.id = labels.group_id AND namespaces.type = 'Group'" )
labels: { group_id: old_group.self_and_ancestors } .where(issues: { project_id: project.id }).reorder(nil)
) else
Label.joins(:issues).where(
issues: { project_id: project.id },
labels: { group_id: old_group.self_and_ancestors }
)
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_merge_requests def group_labels_applied_to_merge_requests
@group_labels_applied_to_merge_requests ||= Label.joins(:merge_requests) @labels_applied_to_mrs ||= if use_optimized_group_labels_query?
.where( Label.joins(:merge_requests)
merge_requests: { target_project_id: project.id }, .joins("INNER JOIN namespaces on namespaces.id = labels.group_id AND namespaces.type = 'Group'" )
labels: { group_id: old_group.self_and_ancestors } .where(merge_requests: { target_project_id: project.id }).reorder(nil)
) else
Label.joins(:merge_requests)
.where(
merge_requests: { target_project_id: project.id },
labels: { group_id: old_group.self_and_ancestors }
)
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -88,5 +99,9 @@ module Labels ...@@ -88,5 +99,9 @@ module Labels
.update_all(label_id: new_label_id) .update_all(label_id: new_label_id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def use_optimized_group_labels_query?
Feature.enabled?(:use_optimized_group_labels_query, project.root_namespace, default_enabled: :yaml)
end
end end
end end
---
name: use_optimized_group_labels_query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73501
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344957
milestone: '14.5'
type: development
group: group::workspace
default_enabled: false
...@@ -3,107 +3,121 @@ ...@@ -3,107 +3,121 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Labels::TransferService do RSpec.describe Labels::TransferService do
describe '#execute' do shared_examples 'transfer labels' do
let_it_be(:user) { create(:user) } describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:old_group_ancestor) { create(:group) } let_it_be(:old_group_ancestor) { create(:group) }
let_it_be(:old_group) { create(:group, parent: old_group_ancestor) } let_it_be(:old_group) { create(:group, parent: old_group_ancestor) }
let_it_be(:new_group) { create(:group) } let_it_be(:new_group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: new_group) } let_it_be(:project) { create(:project, :repository, group: new_group) }
subject(:service) { described_class.new(user, old_group, project) } subject(:service) { described_class.new(user, old_group, project) }
before do before do
old_group_ancestor.add_developer(user) old_group_ancestor.add_developer(user)
new_group.add_developer(user) new_group.add_developer(user)
end end
it 'recreates missing group labels at project level and assigns them to the issuables' do it 'recreates missing group labels at project level and assigns them to the issuables' do
old_group_label_1 = create(:group_label, group: old_group) old_group_label_1 = create(:group_label, group: old_group)
old_group_label_2 = create(:group_label, group: old_group) old_group_label_2 = create(:group_label, group: old_group)
labeled_issue = create(:labeled_issue, project: project, labels: [old_group_label_1]) labeled_issue = create(:labeled_issue, project: project, labels: [old_group_label_1])
labeled_merge_request = create(:labeled_merge_request, source_project: project, labels: [old_group_label_2]) labeled_merge_request = create(:labeled_merge_request, source_project: project, labels: [old_group_label_2])
expect { service.execute }.to change(project.labels, :count).by(2) expect { service.execute }.to change(project.labels, :count).by(2)
expect(labeled_issue.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_label_1.title)) expect(labeled_issue.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_label_1.title))
expect(labeled_merge_request.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_label_2.title)) expect(labeled_merge_request.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_label_2.title))
end end
it 'recreates missing ancestor group labels at project level and assigns them to the issuables' do it 'recreates missing ancestor group labels at project level and assigns them to the issuables' do
old_group_ancestor_label_1 = create(:group_label, group: old_group_ancestor) old_group_ancestor_label_1 = create(:group_label, group: old_group_ancestor)
old_group_ancestor_label_2 = create(:group_label, group: old_group_ancestor) old_group_ancestor_label_2 = create(:group_label, group: old_group_ancestor)
labeled_issue = create(:labeled_issue, project: project, labels: [old_group_ancestor_label_1]) labeled_issue = create(:labeled_issue, project: project, labels: [old_group_ancestor_label_1])
labeled_merge_request = create(:labeled_merge_request, source_project: project, labels: [old_group_ancestor_label_2]) labeled_merge_request = create(:labeled_merge_request, source_project: project, labels: [old_group_ancestor_label_2])
expect { service.execute }.to change(project.labels, :count).by(2) expect { service.execute }.to change(project.labels, :count).by(2)
expect(labeled_issue.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_ancestor_label_1.title)) expect(labeled_issue.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_ancestor_label_1.title))
expect(labeled_merge_request.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_ancestor_label_2.title)) expect(labeled_merge_request.reload.labels).to contain_exactly(project.labels.find_by_title(old_group_ancestor_label_2.title))
end end
it 'recreates label priorities related to the missing group labels' do it 'recreates label priorities related to the missing group labels' do
old_group_label = create(:group_label, group: old_group) old_group_label = create(:group_label, group: old_group)
create(:labeled_issue, project: project, labels: [old_group_label]) create(:labeled_issue, project: project, labels: [old_group_label])
create(:label_priority, project: project, label: old_group_label, priority: 1) create(:label_priority, project: project, label: old_group_label, priority: 1)
service.execute service.execute
new_project_label = project.labels.find_by(title: old_group_label.title) new_project_label = project.labels.find_by(title: old_group_label.title)
expect(new_project_label.id).not_to eq old_group_label.id expect(new_project_label.id).not_to eq old_group_label.id
expect(new_project_label.priorities).not_to be_empty expect(new_project_label.priorities).not_to be_empty
end end
it 'does not recreate missing group labels that are not applied to issues or merge requests' do it 'does not recreate missing group labels that are not applied to issues or merge requests' do
old_group_label = create(:group_label, group: old_group) old_group_label = create(:group_label, group: old_group)
service.execute service.execute
expect(project.labels.where(title: old_group_label.title)).to be_empty expect(project.labels.where(title: old_group_label.title)).to be_empty
end end
it 'does not recreate missing group labels that already exist in the project group' do it 'does not recreate missing group labels that already exist in the project group' do
old_group_label = create(:group_label, group: old_group) old_group_label = create(:group_label, group: old_group)
labeled_issue = create(:labeled_issue, project: project, labels: [old_group_label]) labeled_issue = create(:labeled_issue, project: project, labels: [old_group_label])
new_group_label = create(:group_label, group: new_group, title: old_group_label.title) new_group_label = create(:group_label, group: new_group, title: old_group_label.title)
service.execute service.execute
expect(project.labels.where(title: old_group_label.title)).to be_empty expect(project.labels.where(title: old_group_label.title)).to be_empty
expect(labeled_issue.reload.labels).to contain_exactly(new_group_label) expect(labeled_issue.reload.labels).to contain_exactly(new_group_label)
end end
it 'updates only label links in the given project' do it 'updates only label links in the given project' do
old_group_label = create(:group_label, group: old_group) old_group_label = create(:group_label, group: old_group)
other_project = create(:project, group: old_group) other_project = create(:project, group: old_group)
labeled_issue = create(:labeled_issue, project: project, labels: [old_group_label]) labeled_issue = create(:labeled_issue, project: project, labels: [old_group_label])
other_project_labeled_issue = create(:labeled_issue, project: other_project, labels: [old_group_label]) other_project_labeled_issue = create(:labeled_issue, project: other_project, labels: [old_group_label])
service.execute service.execute
expect(labeled_issue.reload.labels).not_to include(old_group_label) expect(labeled_issue.reload.labels).not_to include(old_group_label)
expect(other_project_labeled_issue.reload.labels).to contain_exactly(old_group_label) expect(other_project_labeled_issue.reload.labels).to contain_exactly(old_group_label)
end end
context 'when moving within the same ancestor group' do context 'when moving within the same ancestor group' do
let(:other_subgroup) { create(:group, parent: old_group_ancestor) } let(:other_subgroup) { create(:group, parent: old_group_ancestor) }
let(:project) { create(:project, :repository, group: other_subgroup) } let(:project) { create(:project, :repository, group: other_subgroup) }
it 'does not recreate ancestor group labels' do it 'does not recreate ancestor group labels' do
old_group_ancestor_label_1 = create(:group_label, group: old_group_ancestor) old_group_ancestor_label_1 = create(:group_label, group: old_group_ancestor)
old_group_ancestor_label_2 = create(:group_label, group: old_group_ancestor) old_group_ancestor_label_2 = create(:group_label, group: old_group_ancestor)
labeled_issue = create(:labeled_issue, project: project, labels: [old_group_ancestor_label_1]) labeled_issue = create(:labeled_issue, project: project, labels: [old_group_ancestor_label_1])
labeled_merge_request = create(:labeled_merge_request, source_project: project, labels: [old_group_ancestor_label_2]) labeled_merge_request = create(:labeled_merge_request, source_project: project, labels: [old_group_ancestor_label_2])
expect { service.execute }.not_to change(project.labels, :count) expect { service.execute }.not_to change(project.labels, :count)
expect(labeled_issue.reload.labels).to contain_exactly(old_group_ancestor_label_1) expect(labeled_issue.reload.labels).to contain_exactly(old_group_ancestor_label_1)
expect(labeled_merge_request.reload.labels).to contain_exactly(old_group_ancestor_label_2) expect(labeled_merge_request.reload.labels).to contain_exactly(old_group_ancestor_label_2)
end
end end
end end
end end
context 'with use_optimized_group_labels_query FF on' do
it_behaves_like 'transfer labels'
end
context 'with use_optimized_group_labels_query FF off' do
before do
stub_feature_flags(use_optimized_group_labels_query: false)
end
it_behaves_like 'transfer labels'
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