Commit f8183776 authored by Felipe Artur's avatar Felipe Artur

Fix orphaned promoted issues

Fix promoted issues with missing promoted_to_epic_id field.
parent c5de0a40
# 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 AddTemporaryIndexToPromotionNotes < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :notes,
:note,
where: "noteable_type = 'Issue' AND system IS TRUE AND note LIKE 'promoted to epic%'",
name: 'tmp_idx_on_promoted_notes'
end
def down
# NO OP
end
end
# frozen_string_literal: true
class ScheduleFixOrphanPromotedIssues < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 100
BACKGROUND_MIGRATION = 'FixOrphanPromotedIssues'.freeze
disable_ddl_transaction!
class Note < ActiveRecord::Base
include EachBatch
self.table_name = 'notes'
scope :of_promotion, -> do
where(noteable_type: 'Issue')
.where('notes.system IS TRUE')
.where("notes.note LIKE 'promoted to epic%'")
end
end
def up
Note.of_promotion.each_batch(of: BATCH_SIZE) do |notes, index|
jobs = notes.map { |note| [BACKGROUND_MIGRATION, [note.id]] }
BackgroundMigrationWorker.bulk_perform_async(jobs)
end
end
def down
# NO OP
end
end
......@@ -2811,6 +2811,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.index ["id"], name: "epic_mentions_temp_index", where: "((note ~~ '%@%'::text) AND ((noteable_type)::text = 'Epic'::text))"
t.index ["line_code"], name: "index_notes_on_line_code"
t.index ["note"], name: "index_notes_on_note_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["note"], name: "tmp_idx_on_promoted_notes", where: "(((noteable_type)::text = 'Issue'::text) AND (system IS TRUE) AND (note ~~ 'promoted to epic%'::text))"
t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type"
t.index ["project_id", "id"], name: "index_notes_on_project_id_and_id_and_system_false", where: "(NOT system)"
t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type"
......
---
title: Fix orphan issues that were promoted to epics
merge_request: 23916
author:
type: fixed
# frozen_string_literal: true
module EE
module Gitlab
module BackgroundMigration
# This migration populates issues that were promoted to epics
# and have null promoted_to_epic_id.
# For more information please check https://gitlab.com/gitlab-org/gitlab/issues/194177
module FixOrphanPromotedIssues
extend ::Gitlab::Utils::Override
override :perform
def perform(note_id)
ActiveRecord::Base.connection.execute <<~SQL
WITH promotion_notes AS (
SELECT noteable_id, note as promotion_note, projects.namespace_id as epic_group_id FROM notes
INNER JOIN projects ON notes.project_id = projects.id
WHERE notes.noteable_type = 'Issue'
AND notes.system IS TRUE
AND notes.note like 'promoted to epic%'
AND notes.id = #{Integer(note_id)}
), promoted_epics AS (
SELECT epics.id as promoted_epic_id, promotion_notes.noteable_id as issue_id FROM epics
INNER JOIN promotion_notes on epics.group_id = promotion_notes.epic_group_id
WHERE concat('promoted to epic &', epics.iid) = promotion_notes.promotion_note
)
UPDATE issues
SET promoted_to_epic_id = promoted_epic_id
FROM promoted_epics
WHERE issues.id = promoted_epics.issue_id
AND issues.promoted_to_epic_id IS NULL
SQL
rescue ArgumentError
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::FixOrphanPromotedIssues, :migration, schema: 20200207185149 do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:issues) { table(:issues) }
let(:epics) { table(:epics) }
let(:notes) { table(:notes) }
let(:migration) { described_class.new }
let(:group1) { namespaces.create!(name: 'gitlab', path: 'gitlab') }
let(:project1) { projects.create!(namespace_id: group1.id) }
let(:group2) { namespaces.create!(name: 'other', path: 'other') }
let(:project2) { projects.create!(namespace_id: group2.id) }
let(:user) { users.create(name: 'any', email: 'user@example.com', projects_limit: 9) }
let!(:epic_from_issue_1) { epics.create(iid: 14532, title: 'from issue 1', group_id: group1.id, author_id: user.id, created_at: Time.now, updated_at: Time.now, title_html: 'any') }
let!(:epic_from_issue_2) { epics.create(iid: 209, title: 'from issue 2', group_id: group2.id, author_id: user.id, created_at: Time.now, updated_at: Time.now, title_html: 'any') }
let!(:promoted_orphan) { issues.create!(description: 'promoted 1', state_id: 1, project_id: project1.id) }
let!(:promoted) { issues.create!(description: 'promoted 3', state_id: 2, project_id: project2.id, promoted_to_epic_id: epic_from_issue_2.id) }
let!(:promotion_note_1) { notes.create!(project_id: project1.id, noteable_id: promoted_orphan.id, noteable_type: "Issue", note: "promoted to epic &14532", system: true) }
let!(:promotion_note_2) { notes.create!(project_id: project2.id, noteable_id: promoted.id, noteable_type: "Issue", note: "promoted to epic &209", system: true) }
context 'when promoted_to_epic_id is missing' do
it 'populates missing promoted_to_epic_id' do
expect do
described_class.new.perform(promotion_note_1.id)
promoted_orphan.reload
end.to change { promoted_orphan.promoted_to_epic_id }.from(nil).to(epic_from_issue_1.id)
end
end
context 'when promoted_to_epic_id is present' do
it 'does not change promoted_to_epic_id' do
expect do
described_class.new.perform(promotion_note_2.id)
promoted.reload
end.not_to change { promoted.promoted_to_epic_id }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200207185149_schedule_fix_orphan_promoted_issues.rb')
describe ScheduleFixOrphanPromotedIssues, :migration do
let(:projects) { table(:projects) }
let(:notes) { table(:notes) }
let(:project1) { projects.create!(namespace_id: 99) }
let!(:promote_orphan_1_note) { notes.create!(project_id: project1.id, noteable_id: 1, noteable_type: "Issue", note: "promoted to epic &14532", system: true) }
let!(:promote_orphan_2_note) { notes.create!(project_id: project1.id, noteable_id: 2, noteable_type: "Issue", note: "promoted to epic &209", system: true) }
let!(:promote_orphan_3_note) { notes.create!(project_id: project1.id, noteable_id: 4, noteable_type: "Issue", note: "promoted to epic &12", system: true) }
it 'correctly schedules background migrations' do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
Sidekiq::Testing.fake! do
Timecop.freeze do
migrate!
expect(described_class::BACKGROUND_MIGRATION).to be_scheduled_migration(promote_orphan_1_note.id)
expect(described_class::BACKGROUND_MIGRATION).to be_scheduled_migration(promote_orphan_2_note.id)
expect(described_class::BACKGROUND_MIGRATION).to be_scheduled_migration(promote_orphan_3_note.id)
expect(BackgroundMigrationWorker.jobs.size).to eq(3)
end
end
end
end
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# No OP for CE
class FixOrphanPromotedIssues
def perform(note_id)
end
end
end
end
Gitlab::BackgroundMigration::FixOrphanPromotedIssues.prepend_if_ee('EE::Gitlab::BackgroundMigration::FixOrphanPromotedIssues')
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