Commit eca3c4cf authored by Alexandru Croitor's avatar Alexandru Croitor

Implements bare stages skeleton for jira importer

Jira importer will eventually import a lot of various data:
issues, comments, attachments, labels, custom fields, etc. We
can organize this data to be imported in stages thus parallelizing
the process. This change adds the initial structure of the
import process.
parent 250c86ea
...@@ -42,11 +42,13 @@ module Projects ...@@ -42,11 +42,13 @@ module Projects
def schedule_import(params) def schedule_import(params)
import_data = @project.create_or_update_import_data(data: {}).becomes(JiraImportData) import_data = @project.create_or_update_import_data(data: {}).becomes(JiraImportData)
import_data << JiraImportData::JiraProjectDetails.new( jira_project_details = JiraImportData::JiraProjectDetails.new(
params[:jira_project_key], params[:jira_project_key],
Time.now.strftime('%Y-%m-%d %H:%M:%S'), Time.now.strftime('%Y-%m-%d %H:%M:%S'),
{ user_id: current_user.id, name: current_user.name } { user_id: current_user.id, name: current_user.name }
) )
import_data << jira_project_details
import_data.force_import!
@project.import_type = 'jira' @project.import_type = 'jira'
@project.import_state.schedule if @project.save @project.import_state.schedule if @project.save
......
...@@ -3,17 +3,40 @@ ...@@ -3,17 +3,40 @@
class JiraImportData < ProjectImportData class JiraImportData < ProjectImportData
JiraProjectDetails = Struct.new(:key, :scheduled_at, :scheduled_by) JiraProjectDetails = Struct.new(:key, :scheduled_at, :scheduled_by)
FORCE_IMPORT_KEY = 'force-import'
def projects def projects
return [] unless data return [] unless data
projects = data.dig('jira', 'projects').map do |p| projects = data.dig('jira', 'projects')&.map do |p|
JiraProjectDetails.new(p['key'], p['scheduled_at'], p['scheduled_by']) JiraProjectDetails.new(p['key'], p['scheduled_at'], p['scheduled_by'])
end end
projects.sort_by { |jp| jp.scheduled_at }
projects&.sort_by { |jp| jp.scheduled_at } || []
end end
def <<(project) def <<(project)
self.data ||= { jira: { projects: [] } } self.data ||= { 'jira' => { 'projects' => [] } }
self.data['jira']['projects'] << project.to_h.deep_stringify_keys! self.data['jira'] ||= { 'projects' => [] }
self.data['jira']['projects'] = [] if data['jira']['projects'].blank? || !data['jira']['projects'].is_a?(Array)
self.data['jira']['projects'] << project.to_h
self.data.deep_stringify_keys!
end
def force_import!
self.data ||= {}
self.data.deep_merge!({ 'jira' => { FORCE_IMPORT_KEY => true } })
self.data.deep_stringify_keys!
end
def force_import?
!!data&.dig('jira', FORCE_IMPORT_KEY) && !projects.blank?
end
def finish_import!
return if data&.dig('jira', FORCE_IMPORT_KEY).nil?
data['jira'].delete(FORCE_IMPORT_KEY)
end end
end end
...@@ -868,6 +868,8 @@ class Project < ApplicationRecord ...@@ -868,6 +868,8 @@ class Project < ApplicationRecord
elsif gitlab_project_import? elsif gitlab_project_import?
# Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-foss/issues/26189 is solved. # Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-foss/issues/26189 is solved.
RepositoryImportWorker.set(retry: false).perform_async(self.id) RepositoryImportWorker.set(retry: false).perform_async(self.id)
elsif jira_import?
Gitlab::JiraImport::Stage::StartImportWorker.perform_async(self.id)
else else
RepositoryImportWorker.perform_async(self.id) RepositoryImportWorker.perform_async(self.id)
end end
...@@ -900,7 +902,7 @@ class Project < ApplicationRecord ...@@ -900,7 +902,7 @@ class Project < ApplicationRecord
# This method is overridden in EE::Project model # This method is overridden in EE::Project model
def remove_import_data def remove_import_data
import_data&.destroy import_data&.destroy unless jira_import?
end end
def ci_config_path=(value) def ci_config_path=(value)
...@@ -947,7 +949,7 @@ class Project < ApplicationRecord ...@@ -947,7 +949,7 @@ class Project < ApplicationRecord
end end
def import? def import?
external_import? || forked? || gitlab_project_import? || bare_repository_import? external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import?
end end
def external_import? def external_import?
...@@ -962,6 +964,14 @@ class Project < ApplicationRecord ...@@ -962,6 +964,14 @@ class Project < ApplicationRecord
import_type == 'bare_repository' import_type == 'bare_repository'
end end
def jira_import?
import_type == 'jira' && Feature.enabled?(:jira_issue_import, self)
end
def jira_force_import?
jira_import? && import_data&.becomes(JiraImportData)&.force_import?
end
def gitlab_project_import? def gitlab_project_import?
import_type == 'gitlab_project' import_type == 'gitlab_project'
end end
......
...@@ -528,6 +528,55 @@ ...@@ -528,6 +528,55 @@
:resource_boundary: :unknown :resource_boundary: :unknown
:weight: 2 :weight: 2
:idempotent: :idempotent:
- :name: jira_importer:jira_import_advance_stage
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_finish_import
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_attachments
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_issues
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_labels
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_notes
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_start_import
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: mail_scheduler:mail_scheduler_issue_due - :name: mail_scheduler:mail_scheduler_issue_due
:feature_category: :issue_tracking :feature_category: :issue_tracking
:has_external_dependencies: :has_external_dependencies:
......
# frozen_string_literal: true
module Gitlab
module JiraImport
module ImportWorker
extend ActiveSupport::Concern
included do
include ApplicationWorker
include Gitlab::JiraImport::QueueOptions
end
def perform(project_id)
project = Project.find_by(id: project_id) # rubocop: disable CodeReuse/ActiveRecord
return unless can_import?(project)
import(project)
end
private
def import(project)
raise NotImplementedError
end
def can_import?(project)
return false unless project
return false if Feature.disabled?(:jira_issue_import, project)
project.import_state.started?
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module QueueOptions
extend ActiveSupport::Concern
included do
queue_namespace :jira_importer
feature_category :importers
sidekiq_options retry: 5
end
end
end
end
...@@ -13,8 +13,6 @@ module Gitlab ...@@ -13,8 +13,6 @@ module Gitlab
sidekiq_options dead: false sidekiq_options dead: false
feature_category :importers feature_category :importers
private
# The known importer stages and their corresponding Sidekiq workers. # The known importer stages and their corresponding Sidekiq workers.
STAGES = { STAGES = {
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker, issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
...@@ -23,6 +21,8 @@ module Gitlab ...@@ -23,6 +21,8 @@ module Gitlab
finish: Stage::FinishImportWorker finish: Stage::FinishImportWorker
}.freeze }.freeze
private
def next_stage_worker(next_stage) def next_stage_worker(next_stage)
STAGES.fetch(next_stage.to_sym) STAGES.fetch(next_stage.to_sym)
end end
......
# frozen_string_literal: true
module Gitlab
module JiraImport
class AdvanceStageWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include QueueOptions
include ::Gitlab::Import::AdvanceStage
# The known importer stages and their corresponding Sidekiq workers.
STAGES = {
labels: Gitlab::JiraImport::Stage::ImportLabelsWorker,
issues: Gitlab::JiraImport::Stage::ImportIssuesWorker,
attachments: Gitlab::JiraImport::Stage::ImportAttachmentsWorker,
notes: Gitlab::JiraImport::Stage::ImportNotesWorker,
finish: Gitlab::JiraImport::Stage::FinishImportWorker
}.freeze
private
def next_stage_worker(next_stage)
STAGES.fetch(next_stage.to_sym)
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class FinishImportWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
project.after_import
ensure
project.import_data.becomes(JiraImportData).finish_import!
project.import_data.save!
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake a attahcments import workers for now.
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
fake_waiter = JobWaiter.new
project.import_state.refresh_jid_expiration
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { fake_waiter.key => fake_waiter.jobs_remaining }, :notes)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportIssuesWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake issues import workers for now
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
jobs_waiter = JobWaiter.new
project.import_state.refresh_jid_expiration
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { jobs_waiter.key => jobs_waiter.jobs_remaining }, :attachments)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportLabelsWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake labels import workers for now
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
fake_waiter = JobWaiter.new
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { fake_waiter.key => fake_waiter.jobs_remaining }, :issues)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportNotesWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake notes import workers for now
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
jobs_waiter = JobWaiter.new
project.import_state.refresh_jid_expiration
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { jobs_waiter.key => jobs_waiter.jobs_remaining }, :finish)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class StartImportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ProjectStartImport
include ProjectImportOptions
include Gitlab::JiraImport::QueueOptions
attr_reader :project
def perform(project_id)
@project = Project.find_by(id: project_id) # rubocop: disable CodeReuse/ActiveRecord
return unless start_import
Gitlab::Import::SetAsyncJid.set_jid(project)
Gitlab::JiraImport::Stage::ImportLabelsWorker.perform_async(project.id)
end
private
def start_import
return false unless project
return false if Feature.disabled?(:jira_issue_import, project)
return true if start(project.import_state)
Gitlab::Import::Logger.info(
{
project_id: project.id,
project_path: project.full_path,
state: project&.import_status,
message: 'inconsistent state while importing'
}
)
false
end
end
end
end
end
...@@ -128,6 +128,8 @@ ...@@ -128,6 +128,8 @@
- 1 - 1
- - jira_connect - - jira_connect
- 1 - 1
- - jira_importer
- 1
- - ldap_group_sync - - ldap_group_sync
- 2 - 2
- - mail_scheduler - - mail_scheduler
......
...@@ -346,9 +346,19 @@ module EE ...@@ -346,9 +346,19 @@ module EE
def add_import_job def add_import_job
return if gitlab_custom_project_template_import? return if gitlab_custom_project_template_import?
if import? && !repository_exists? # Historically this was intended ensure `super` is only called
super # when a project is imported(usually on project creation only) so `repository_exists?`
elsif mirror? # check was added so that it does not stop mirroring if later on mirroring option is added to the project.
#
# With jira importer we need to allow to run the import multiple times on same project,
# which can conflict with scheduled mirroring(if that project had or will have mirroring enabled),
# so we are checking if its a jira reimport then we trigger that and skip mirroring even if mirroring
# should have been started. When we run into race condition with mirroring on a jira imported project
# the mirroring would still be picked up 1 minute later, based on `Gitlab::Mirror::SCHEDULER_CRON` and
# `ProjectUpdateState#mirror_update_due?``
return super if jira_force_import? || import? && !repository_exists?
if mirror?
::Gitlab::Metrics.add_event(:mirrors_scheduled) ::Gitlab::Metrics.add_event(:mirrors_scheduled)
job_id = RepositoryUpdateMirrorWorker.perform_async(self.id) job_id = RepositoryUpdateMirrorWorker.perform_async(self.id)
......
...@@ -1763,13 +1763,54 @@ describe Project do ...@@ -1763,13 +1763,54 @@ describe Project do
describe '#add_import_job' do describe '#add_import_job' do
let(:project) { create(:project) } let(:project) { create(:project) }
context 'when import_type is gitlab_custom_project_template_import' do before do
stub_licensed_features(custom_project_templates: true)
end
context 'when import_type is gitlab_custom_project_template' do
it 'does not create import job' do it 'does not create import job' do
project.import_type = 'gitlab_custom_project_template_import' project.import_type = 'gitlab_custom_project_template'
expect(project.gitlab_custom_project_template_import?).to be true
expect(project.add_import_job).to be_nil expect(project.add_import_job).to be_nil
end end
end end
context 'when mirror true on a jira imported project' do
let(:user) { create(:user) }
let(:symbol_keys_project) do
{ key: 'AA', scheduled_at: 2.days.ago.strftime('%Y-%m-%d %H:%M:%S'), scheduled_by: { 'user_id' => 1, 'name' => 'tester1' } }
end
let(:project) { create(:project, :repository, import_type: 'jira', mirror: true, import_url: 'http://some_url.com', mirror_user_id: user.id, import_data: import_data) }
context 'when jira import is forced' do
let(:import_data) { JiraImportData.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => true } }) }
it 'does not trigger mirror update' do
expect(RepositoryUpdateMirrorWorker).not_to receive(:perform_async)
expect(Gitlab::JiraImport::Stage::StartImportWorker).to receive(:perform_async)
expect(project.mirror).to be true
expect(project.jira_import?).to be true
expect(project.jira_force_import?).to be true
project.add_import_job
end
end
context 'when jira import is not forced' do
let(:import_data) { JiraImportData.new(data: { jira: { projects: [symbol_keys_project] } }) }
it 'does trigger mirror update' do
expect(RepositoryUpdateMirrorWorker).to receive(:perform_async)
expect(Gitlab::JiraImport::Stage::StartImportWorker).not_to receive(:perform_async)
expect(project.mirror).to be true
expect(project.jira_import?).to be true
expect(project.jira_force_import?).to be false
project.add_import_job
end
end
end
end end
describe '#gitlab_custom_project_template_import?' do describe '#gitlab_custom_project_template_import?' do
...@@ -2588,4 +2629,19 @@ describe Project do ...@@ -2588,4 +2629,19 @@ describe Project do
end end
end end
end end
describe '#remove_import_data' do
let(:import_data) { ProjectImportData.new(data: { 'test' => 'some data' }) }
context 'when mirror' do
let(:user) { create(:user) }
let!(:project) { create(:project, mirror: true, import_url: 'http://some_url.com', mirror_user_id: user.id, import_data: import_data) }
it 'does not remove import data' do
expect(project.mirror?).to be true
expect(project.jira_import?).to be false
expect { project.remove_import_data }.not_to change { ProjectImportData.count }
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe JiraImportData do
let(:symbol_keys_project) do
{ key: 'AA', scheduled_at: 2.days.ago.strftime('%Y-%m-%d %H:%M:%S'), scheduled_by: { 'user_id' => 1, 'name' => 'tester1' } }
end
let(:string_keys_project) do
{ 'key': 'BB', 'scheduled_at': 1.hour.ago.strftime('%Y-%m-%d %H:%M:%S'), 'scheduled_by': { 'user_id': 2, 'name': 'tester2' } }
end
let(:jira_project_details) do
JiraImportData::JiraProjectDetails.new('CC', 1.day.ago.strftime('%Y-%m-%d %H:%M:%S'), { user_id: 3, name: 'tester3' })
end
describe '#projects' do
it 'returns empty array if no data' do
expect(described_class.new.projects).to eq([])
end
it 'returns empty array if no projects' do
import_data = described_class.new(data: { 'some-key' => 10 })
expect(import_data.projects).to eq([])
end
it 'returns JiraProjectDetails sorted by scheduled_at time' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project, string_keys_project, jira_project_details] } })
expect(import_data.projects.size).to eq 3
expect(import_data.projects.map(&:key)).to eq(%w(AA CC BB))
expect(import_data.projects.map(&:scheduled_by).map {|e| e['name']}).to eq %w(tester1 tester3 tester2)
expect(import_data.projects.map(&:scheduled_by).map {|e| e['user_id']}).to eq [1, 3, 2]
end
end
describe 'add projects' do
it 'adds project when data is nil' do
import_data = described_class.new
expect(import_data.data).to be nil
import_data << string_keys_project
expect(import_data.data).to eq({ 'jira' => { 'projects' => [string_keys_project] } })
end
it 'adds project when data has some random info' do
import_data = described_class.new(data: { 'one-key': 10 })
expect(import_data.data).to eq({ 'one-key' => 10 })
import_data << string_keys_project
expect(import_data.data).to eq({ 'one-key' => 10, 'jira' => { 'projects' => [string_keys_project] } })
end
it 'adds project when data already has some jira projects' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project] } })
expect(import_data.projects.map(&:to_h)).to eq [symbol_keys_project]
import_data << string_keys_project
expect(import_data.data['jira']['projects'].size).to eq 2
expect(import_data.projects.map(&:key)).to eq(%w(AA BB))
expect(import_data.projects.map(&:scheduled_by).map {|e| e['name']}).to eq %w(tester1 tester2)
expect(import_data.projects.map(&:scheduled_by).map {|e| e['user_id']}).to eq [1, 2]
end
end
describe '#force_import!' do
it 'sets force import when data is nil' do
import_data = described_class.new
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.force_import?).to be false
end
it 'sets force import when data is present but no jira key' do
import_data = described_class.new(data: { 'some-key': 'some-data' })
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.data).to eq({ 'some-key' => 'some-data', 'jira' => { JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be false
end
it 'sets force import when data and jira keys exist' do
import_data = described_class.new(data: { 'some-key': 'some-data', 'jira': {} })
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.data).to eq({ 'some-key' => 'some-data', 'jira' => { JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be false
end
it 'sets force import when data and jira project data exist' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => false }, 'some-key': 'some-data' })
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.data).to eq({ 'some-key' => 'some-data', 'jira' => { 'projects' => [symbol_keys_project.deep_stringify_keys!], JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be true
end
end
describe '#force_import?' do
it 'returns false when data blank' do
expect(described_class.new.force_import?).to be false
end
it 'returns false if there is no project data present' do
import_data = described_class.new(data: { jira: { JiraImportData::FORCE_IMPORT_KEY => true }, 'one-key': 10 })
expect(import_data.force_import?).to be false
end
it 'returns false when force import set to false' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => false }, 'one-key': 10 })
expect(import_data.force_import?).to be false
end
it 'returns true when force import set to true' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be true
end
end
end
...@@ -2353,6 +2353,63 @@ describe Project do ...@@ -2353,6 +2353,63 @@ describe Project do
expect(project.add_import_job).to eq(import_jid) expect(project.add_import_job).to eq(import_jid)
end end
end end
context 'jira import' do
it 'schedules a jira import job' do
project = create(:project, import_type: 'jira')
expect(Gitlab::JiraImport::Stage::StartImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
expect(project.add_import_job).to eq(import_jid)
end
end
end
describe '#jira_import?' do
subject(:project) { build(:project, import_type: 'jira') }
it { expect(project.jira_import?).to be true }
it { expect(project.import?).to be true }
end
describe '#jira_force_import?' do
let(:imported_jira_project) do
JiraImportData::JiraProjectDetails.new('xx', Time.now.strftime('%Y-%m-%d %H:%M:%S'), { user_id: 1, name: 'root' })
end
let(:jira_import_data) do
data = JiraImportData.new
data << imported_jira_project
data.force_import!
data
end
subject(:project) { build(:project, import_type: 'jira', import_data: jira_import_data) }
it { expect(project.jira_force_import?).to be true }
end
describe '#remove_import_data' do
let(:import_data) { ProjectImportData.new(data: { 'test' => 'some data' }) }
context 'when jira import' do
let!(:project) { create(:project, import_type: 'jira', import_data: import_data) }
it 'does not remove import data' do
expect(project.mirror?).to be false
expect(project.jira_import?).to be true
expect { project.remove_import_data }.not_to change { ProjectImportData.count }
end
end
context 'when not mirror neither jira import' do
let(:user) { create(:user) }
let!(:project) { create(:project, import_type: 'github', import_data: import_data) }
it 'removes import data' do
expect(project.mirror?).to be false
expect(project.jira_import?).to be false
expect { project.remove_import_data }.to change { ProjectImportData.count }.by(-1)
end
end
end end
describe '#gitlab_project_import?' do describe '#gitlab_project_import?' do
......
# frozen_string_literal: true
shared_examples 'include import workers modules' do
it { expect(described_class).to include_module(ApplicationWorker) }
it { expect(described_class).to include_module(Gitlab::JiraImport::QueueOptions) }
if described_class == Gitlab::JiraImport::Stage::StartImportWorker
it { expect(described_class).to include_module(ProjectStartImport) }
it { expect(described_class).to include_module(ProjectImportOptions) }
else
it { expect(described_class).to include_module(Gitlab::JiraImport::ImportWorker) }
end
end
shared_examples 'exit import not started' do
it 'does nothing, and exits' do
expect(Gitlab::JiraImport::AdvanceStageWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
shared_examples 'advance to next stage' do |next_stage|
let(:job_waiter) { Gitlab::JobWaiter.new(2, 'some-job-key') }
it "advances to #{next_stage} stage" do
expect(Gitlab::JobWaiter).to receive(:new).and_return(job_waiter)
expect(Gitlab::JiraImport::AdvanceStageWorker).to receive(:perform_async).with(project.id, { job_waiter.key => job_waiter.jobs_remaining }, next_stage.to_sym)
worker.perform(project.id)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::FinishImportWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let(:imported_jira_project) do
JiraImportData::JiraProjectDetails.new('xx', Time.now.strftime('%Y-%m-%d %H:%M:%S'), { user_id: 1, name: 'root' })
end
let(:jira_import_data) do
data = JiraImportData.new
data << imported_jira_project
data.force_import!
data
end
let(:import_state) { create(:import_state, status: :started) }
let(:project) { create(:project, import_type: 'jira', import_data: jira_import_data, import_state: import_state) }
it 'changes import state to finished' do
worker.perform(project.id)
expect(project.reload.import_state.status).to eq "finished"
end
it 'removes force-import flag' do
expect(project.reload.import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
worker.perform(project.id)
expect(project.reload.import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be nil
expect(project.reload.import_data.data['jira']).not_to be nil
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportAttachmentsWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :notes
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportIssuesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :attachments
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportLabelsWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :issues
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportNotesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :finish
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::StartImportWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
let(:jid) { '12345678' }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag not enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it 'exits because import not allowed' do
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when feature flag not enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import is not scheudled' do
let!(:import_state) { create(:import_state, project: project, status: :none, jid: jid) }
it 'exits because import not started' do
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when import is scheduled' do
let!(:import_state) { create(:import_state, project: project, status: :scheduled, jid: jid) }
it 'advances to importing labels' do
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when import is started' do
let!(:import_state) { create(:import_state, project: project, status: :started, jid: jid) }
context 'when this is the same worker that stated import' do
it 'advances to importing labels' do
allow(worker).to receive(:jid).and_return(jid)
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when this is a different worker that stated import' do
it 'advances to importing labels' do
allow(worker).to receive(:jid).and_return('87654321')
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
end
context 'when import is finished' do
let!(:import_state) { create(:import_state, project: project, status: :finished, jid: jid) }
it 'advances to importing labels' do
allow(worker).to receive(:jid).and_return(jid)
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
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