Commit 3b591ffe authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Create migration to remove existing cyclic epics

We have code to prevent this but there are existing
rows that needs to be cleaned up
parent 93483287
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveCyclicHierarchiesInEpics < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
return unless Gitlab::Database.postgresql?
epics_in_loops_sql = <<~SQL
WITH RECURSIVE base_and_descendants (id, path, cycle) AS (
SELECT epics.id, ARRAY[epics.id], false FROM epics WHERE epics.parent_id IS NOT NULL
UNION
SELECT epics.id, path || epics.id, epics.id = ANY(path)
FROM epics, base_and_descendants
WHERE epics.parent_id = base_and_descendants.id AND NOT cycle
)
SELECT id, array_to_string(path, ',') AS path FROM base_and_descendants WHERE cycle ORDER BY id
SQL
# Group by sorted path so we can group epics that belong to the same loop
epics_grouped_by_loop = select_all(epics_in_loops_sql).group_by { |r| r['path'].split(',').uniq.sort.join(',') }
# We only need to update the first epic of the loop to break the cycle
epic_ids_to_update = epics_grouped_by_loop.map { |path, epics| epics.first['id'] }
update_column_in_batches(:epics, :parent_id, nil) do |table, query|
query.where(
table[:id].in(epic_ids_to_update)
)
end
end
def down
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('ee', 'db', 'migrate', '20190327085945_remove_cyclic_hierarchies_in_epics.rb')
describe RemoveCyclicHierarchiesInEpics, :migration, :postgresql do
let(:epics) { table(:epics) }
let(:group) { table(:namespaces).create(name: 'Group 1', type: 'Group', path: 'group_1') }
let(:user) { table(:users).create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) }
def create_epic_with_defaults!(attributes = {})
@last_iid += 1
epics.create!({iid: @last_iid, group_id: group.id, author_id: user.id, title: "Epic", title_html: "Epic"}.merge(attributes))
end
let!(:epic_self_loop) { create_epic_with_defaults! }
let!(:epic_loop_1) { create_epic_with_defaults! }
let!(:epic_loop_2) { create_epic_with_defaults! }
let!(:epic_not_in_loop) { create_epic_with_defaults! }
before(:all) do
@last_iid = 0
end
before do
epic_self_loop.update(parent_id: epic_self_loop.id)
epic_loop_1_1 = create_epic_with_defaults!(parent_id: epic_loop_1.id)
epic_loop_1.update(parent_id: epic_loop_1_1.id)
epic_loop_2_1 = create_epic_with_defaults!(parent_id: epic_loop_2.id)
epic_loop_2_2 = create_epic_with_defaults!(parent_id: epic_loop_2_1.id)
epic_loop_2_3 = create_epic_with_defaults!(parent_id: epic_loop_2_2.id)
epic_loop_2.update(parent_id: epic_loop_2_3.id)
epic_parent = create_epic_with_defaults!
epic_not_in_loop.update(parent_id: epic_parent.id)
end
it 'clears parent_id of epic in loop' do
migrate!
expect(epic_self_loop.reload.parent_id).to be_nil
expect(epic_loop_1.reload.parent_id).to be_nil
expect(epic_loop_2.reload.parent_id).to be_nil
end
it 'does not clear parent_id for other epics' do
migrate!
expect(epic_not_in_loop.reload.parent_id).not_to be_nil
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