Commit 54332bb7 authored by charlie ablett's avatar charlie ablett

Use existing group label when promoting

parent 922c9f44
...@@ -10,81 +10,79 @@ module Labels ...@@ -10,81 +10,79 @@ module Labels
label.is_a?(ProjectLabel) label.is_a?(ProjectLabel)
Label.transaction do Label.transaction do
new_label = clone_label_to_group_label(label) # use the existing group label if it exists
group_label = find_or_create_group_label(label)
label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids| label_ids_for_merge(group_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids|
update_old_label_relations(new_label, batched_ids) update_old_label_relations(group_label, batched_ids)
destroy_project_labels(batched_ids) destroy_project_labels(batched_ids)
end end
# We skipped validations during creation. Let's run them now, after deleting conflicting labels group_label
raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid?
new_label
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
private private
def update_old_label_relations(new_label, old_label_ids) def update_old_label_relations(group_label, old_label_ids)
update_issuables(new_label, old_label_ids) update_issuables(group_label, old_label_ids)
update_resource_label_events(new_label, old_label_ids) update_resource_label_events(group_label, old_label_ids)
update_issue_board_lists(new_label, old_label_ids) update_issue_board_lists(group_label, old_label_ids)
update_priorities(new_label, old_label_ids) update_priorities(group_label, old_label_ids)
subscribe_users(new_label, old_label_ids) subscribe_users(group_label, old_label_ids)
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def subscribe_users(new_label, label_ids) def subscribe_users(group_label, label_ids)
# users can be subscribed to multiple labels that will be merged into the group one # users can be subscribed to multiple labels that will be merged into the group one
# we want to keep only one subscription / user # we want to keep only one subscription / user
ids_to_update = Subscription.where(subscribable_id: label_ids, subscribable_type: 'Label') ids_to_update = Subscription.where(subscribable_id: label_ids, subscribable_type: 'Label')
.group(:user_id) .group(:user_id)
.pluck('MAX(id)') .pluck('MAX(id)')
Subscription.where(id: ids_to_update).update_all(subscribable_id: new_label.id) Subscription.where(id: ids_to_update).update_all(subscribable_id: group_label.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def label_ids_for_merge(new_label) def label_ids_for_merge(group_label)
LabelsFinder LabelsFinder
.new(current_user, title: new_label.title, group_id: project.group.id) .new(current_user, title: group_label.title, group_id: project.group.id)
.execute(skip_authorization: true) .execute(skip_authorization: true)
.where.not(id: new_label) .where.not(id: group_label)
.select(:id) # Can't use pluck() to avoid object-creation because of the batching .select(:id) # Can't use pluck() to avoid object-creation because of the batching
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def update_issuables(new_label, label_ids) def update_issuables(group_label, label_ids)
LabelLink LabelLink
.where(label: label_ids) .where(label: label_ids)
.update_all(label_id: new_label.id) .update_all(label_id: group_label.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def update_resource_label_events(new_label, label_ids) def update_resource_label_events(group_label, label_ids)
ResourceLabelEvent ResourceLabelEvent
.where(label: label_ids) .where(label: label_ids)
.update_all(label_id: new_label.id) .update_all(label_id: group_label.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def update_issue_board_lists(new_label, label_ids) def update_issue_board_lists(group_label, label_ids)
List List
.where(label: label_ids) .where(label: label_ids)
.update_all(label_id: new_label.id) .update_all(label_id: group_label.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def update_priorities(new_label, label_ids) def update_priorities(group_label, label_ids)
LabelPriority LabelPriority
.where(label: label_ids) .where(label: label_ids)
.update_all(label_id: new_label.id) .update_all(label_id: group_label.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -92,18 +90,12 @@ module Labels ...@@ -92,18 +90,12 @@ module Labels
def destroy_project_labels(label_ids) def destroy_project_labels(label_ids)
Label.where(id: label_ids).destroy_all # rubocop: disable Cop/DestroyAll Label.where(id: label_ids).destroy_all # rubocop: disable Cop/DestroyAll
end end
# rubocop: enable CodeReuse/ActiveRecord
def clone_label_to_group_label(label) def find_or_create_group_label(label)
params = label.attributes.slice('title', 'description', 'color') params = label.attributes.slice('title', 'description', 'color')
# Since the title of the new label has to be the same as the previous labels new_label = GroupLabel.create_with(params).find_or_initialize_by(group_id: project.group.id, title: label.title)
# and we're merging old labels in batches we'll skip validation to omit 2-step
# merge process and do it in one batch
# We'll be forcing validation at the end of the transaction to ensure everything
# was merged correctly
new_label = GroupLabel.new(params.merge(group: project.group))
new_label.save(validate: false)
new_label.save! unless new_label.persisted?
new_label new_label
end end
end end
......
---
title: Use existing group label when promoting project label
merge_request: 45122
author:
type: changed
...@@ -63,26 +63,12 @@ RSpec.describe Labels::PromoteService do ...@@ -63,26 +63,12 @@ RSpec.describe Labels::PromoteService do
expect(service.execute(group_label)).to be_falsey expect(service.execute(group_label)).to be_falsey
end end
shared_examples 'promoting a project label to a group label' do
it 'is truthy on success' do it 'is truthy on success' do
expect(service.execute(project_label_1_1)).to be_truthy expect(service.execute(project_label_1_1)).to be_truthy
end end
it 'recreates the label as a group label' do it 'removes all project labels with that title within the group' do
expect { service.execute(project_label_1_1) }
.to change(project_1.labels, :count).by(-1)
.and change(group_1.labels, :count).by(1)
expect(new_label).not_to be_nil
end
it 'copies title, description and color' do
service.execute(project_label_1_1)
expect(new_label.title).to eq(promoted_label_name)
expect(new_label.description).to eq(promoted_description)
expect(new_label.color).to eq(promoted_color)
end
it 'merges labels with the same name in group' do
expect { service.execute(project_label_1_1) }.to change(project_2.labels, :count).by(-1).and \ expect { service.execute(project_label_1_1) }.to change(project_2.labels, :count).by(-1).and \
change(project_3.labels, :count).by(-1) change(project_3.labels, :count).by(-1)
end end
...@@ -182,20 +168,44 @@ RSpec.describe Labels::PromoteService do ...@@ -182,20 +168,44 @@ RSpec.describe Labels::PromoteService do
expect(project_label_1_1.send(association.name).any?).to be_falsey expect(project_label_1_1.send(association.name).any?).to be_falsey
end end
end end
end
context 'with invalid group label' do context 'if there is an existing identical group label' do
before do let!(:existing_group_label) { create(:group_label, group: group_1, title: project_label_1_1.title ) }
allow(service).to receive(:clone_label_to_group_label).and_wrap_original do |m, *args|
label = m.call(*args)
allow(label).to receive(:valid?).and_return(false)
label it 'uses the existing group label' do
expect { service.execute(project_label_1_1) }
.to change(project_1.labels, :count).by(-1)
.and not_change(group_1.labels, :count)
expect(new_label).not_to be_nil
end end
it 'does not create a new group label clone' do
expect { service.execute(project_label_1_1) }.not_to change { GroupLabel.maximum(:id) }
end
it_behaves_like 'promoting a project label to a group label'
end end
it 'raises an exception' do context 'if there is no existing identical group label' do
expect { service.execute(project_label_1_1) }.to raise_error(ActiveRecord::RecordInvalid) let(:existing_group_label) { nil }
it 'recreates the label as a group label' do
expect { service.execute(project_label_1_1) }
.to change(project_1.labels, :count).by(-1)
.and change(group_1.labels, :count).by(1)
expect(new_label).not_to be_nil
end end
it 'copies title, description and color to cloned group label' do
service.execute(project_label_1_1)
expect(new_label.title).to eq(promoted_label_name)
expect(new_label.description).to eq(promoted_description)
expect(new_label.color).to eq(promoted_color)
end
it_behaves_like 'promoting a project label to a group label'
end 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