Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/models/import/jira_open_project_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class LegNotFoundError < StandardError; end

belongs_to :jira_import, class_name: "Import::JiraImport"

def self.find_op_leg(jira_leg)
where(jira_entity_class: jira_leg.class.to_s, jira_entity_id: jira_leg.id).first&.op_leg
end

def self.find_jira_leg(op_leg)
where(op_entity_class: op_leg.class.to_s, op_entity_id: op_leg.id).first&.jira_leg
end

def op_leg
op_entity_class&.constantize&.find(op_entity_id)
rescue ActiveRecord::RecordNotFound
Expand Down
1 change: 1 addition & 0 deletions app/models/import/jira_project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ class JiraProject < ApplicationRecord

belongs_to :jira_import, class_name: "Import::JiraImport"
has_many :jira_issues, class_name: "Import::JiraIssue", dependent: :destroy
has_many :jira_versions, class_name: "Import::JiraVersion", dependent: :destroy
end
end
38 changes: 38 additions & 0 deletions app/models/import/jira_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module Import
class JiraVersion < ApplicationRecord
self.table_name = "jira_versions"

belongs_to :jira_import, class_name: "Import::JiraImport"
belongs_to :jira_project, class_name: "Import::JiraProject"
end
end
20 changes: 17 additions & 3 deletions app/workers/import/jira_create_project_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,31 @@
# Needed to avoid project.lft and project.rgt corruption due to race condition
# when multiple projects are created at the same time.
lock_key = "jira_import_#{jira_import_id}_create_project"
project = nil
OpenProject::Mutex.with_advisory_lock(@jira_import, lock_key) do
create_project(jira_project)
project = create_project(jira_project)
end
create_project_versions(project, jira_project)
end
end
end

private

# rubocop:disable Metrics/AbcSize
def create_project_versions(project, jira_project)
jira_project.jira_versions.each do |jira_version|
version = Version.find_or_create_by!(
project_id: project.id,
name: jira_version.payload.fetch("name")
)
Comment on lines +64 to +67
create_reference!(op_leg: version,
jira_leg: jira_version,
jira_import: @jira_import,
uses_existing: false)
end
end

# rubocop:disable-next Metrics/AbcSize
def create_project(jira_project)
project_key = jira_project.payload.fetch("key")
project_keys = jira_project.payload.fetch("projectKeys")
Expand Down Expand Up @@ -95,6 +110,5 @@

raise service_call.message
end
# rubocop:enable Metrics/AbcSize
end
end
26 changes: 16 additions & 10 deletions app/workers/import/jira_create_project_work_packages_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def percentage
end
end

# rubocop:disable Metrics/AbcSize
# rubocop:disable-next Metrics/AbcSize
def build_enumerator(jira_import_id, jira_project_id, cursor:)
@jira_import = Import::JiraImport.find(jira_import_id)
jira = @jira_import.jira
Expand All @@ -85,9 +85,8 @@ def build_enumerator(jira_import_id, jira_project_id, cursor:)
cursor: cursor
)
end
# rubocop:enable Metrics/AbcSize

# rubocop:disable Metrics/AbcSize
# rubocop:disable-next Metrics/AbcSize
def each_iteration(jira_issue, _jira_import_id, _jira_project_id)
jira_issue_key = jira_issue.payload["key"]
Rails.logger.tagged("jira_import_id:#{_jira_import_id}", "jira_project_id:#{_jira_project_id}",
Expand All @@ -110,7 +109,6 @@ def each_iteration(jira_issue, _jira_import_id, _jira_project_id)
end
end
end
# rubocop:enable Metrics/AbcSize

private

Expand Down Expand Up @@ -157,7 +155,7 @@ def update_custom_fields_in_project(project, jira_project, custom_field_registry
project.work_package_custom_fields << new_cfs if new_cfs.any?
end

# rubocop:disable Metrics/AbcSize
# rubocop:disable-next Metrics/AbcSize
def create_type(jira_issue, project)
issue_type = jira_issue.payload["fields"]["issuetype"]
type = Type.where("LOWER(name) = LOWER(?)", issue_type["name"]).first
Expand All @@ -178,7 +176,6 @@ def create_type(jira_issue, project)
create_reference!(op_leg: type, jira_leg: jira_issue_type, jira_import: @jira_import, uses_existing:)
type
end
# rubocop:enable Metrics/AbcSize

def enable_type(project, type)
service_call = Projects::Types::AddService
Expand Down Expand Up @@ -225,7 +222,7 @@ def update_workflows(type)
raise call.message if call.failure?
end

# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
# rubocop:disable-next Metrics/AbcSize, Metrics/PerceivedComplexity
def create_work_package(jira_issue, project, type, status, priority, custom_field_registry)
Rails.logger.debug "Creating work package"

Expand All @@ -243,6 +240,15 @@ def create_work_package(jira_issue, project, type, status, priority, custom_fiel
original_estimate_seconds = jira_issue.payload.dig("fields", "timetracking", "originalEstimateSeconds")
remaining_estimate_seconds = jira_issue.payload.dig("fields", "timetracking", "remainingEstimateSeconds")

target_versions_origin_ids = jira_issue.payload["fields"]["fixVersions"].map { |v| v.fetch("id") }
observed_in_versions_origin_ids = jira_issue.payload["fields"]["versions"].map { |v| v.fetch("id") }
target_versions = Import::JiraVersion
.where(jira_import: @jira_import, origin_id: target_versions_origin_ids)
.filter_map { |jira_version| Import::JiraOpenProjectReference.find_op_leg(jira_version) }
observed_in_versions = Import::JiraVersion
.where(jira_import: @jira_import, origin_id: observed_in_versions_origin_ids)
.filter_map { |jira_version| Import::JiraOpenProjectReference.find_op_leg(jira_version) }

service_call =
WorkPackages::CreateService
.new(user: author || @system_user, contract_class: EmptyContract)
Expand All @@ -254,6 +260,8 @@ def create_work_package(jira_issue, project, type, status, priority, custom_fiel
priority:,
status:,
assigned_to:,
target_versions:,
observed_in_versions:,
due_date: jira_issue.payload.dig("fields", "duedate"),
estimated_hours: (original_estimate_seconds / 3600.0 if original_estimate_seconds),
remaining_hours: (remaining_estimate_seconds / 3600.0 if remaining_estimate_seconds),
Expand Down Expand Up @@ -288,9 +296,8 @@ def create_work_package(jira_issue, project, type, status, priority, custom_fiel
create_work_package_history(work_package, jira_issue, project)
work_package
end
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity

# rubocop:disable Metrics/AbcSize
# rubocop:disable-next Metrics/AbcSize
def create_work_package_history(work_package, jira_issue, project)
journal_service = Import::JiraImportJournals.new(work_package:)

Expand All @@ -314,7 +321,6 @@ def create_work_package_history(work_package, jira_issue, project)

journal_service.call(updated_at: jira_issue.payload.dig("fields", "updated"))
end
# rubocop:enable Metrics/AbcSize

def create_member(project, member)
service_call = Members::CreateService
Expand Down
102 changes: 102 additions & 0 deletions app/workers/import/jira_fetch_project_versions_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module Import
class JiraFetchProjectVersionsJob < ProgressableJob
include JiraJobUtils

def text
jira_project_name = Import::JiraProject.find(arguments[1]).payload["name"]
"Fetch versions for project '#{jira_project_name}'"
end

def percentage
jira_import = Import::JiraImport.find(arguments[0])
cursor = jira_import.get_job_cursor(self)
if cursor.present?
cursor["start_at"] * 100 / cursor["total"]
else
0
end
end

# rubocop:disable Metrics/AbcSize
def build_enumerator(jira_import_id, jira_project_id, cursor:)
prepare_jira_import_ivars(jira_import_id)
jira_project = Import::JiraProject.find(jira_project_id)

cursor ||= @jira_import.get_job_cursor(self)
start_at = cursor&.dig("start_at") || 0

Enumerator.new do |yielder|
loop do
response = @jira_client.project_versions(
project_id_or_key: jira_project.origin_id,
start_at:,
max_results: 100
)

versions = response["values"]
total = response["total"]

break if versions.empty?

new_cursor = { "start_at" => start_at, "total" => total }
versions_and_total = { "versions" => versions, "total" => total }

@jira_import.set_job_cursor(self, new_cursor)

yielder.yield(
versions_and_total,
new_cursor
)

start_at += versions.size
end
end
end

def each_iteration(versions_and_total, jira_import_id, jira_project_id)
versions = versions_and_total["versions"]
versions_upsert_data = versions.map do |payload|
{
payload:,
jira_project_id:,
origin_id: payload.fetch("id"),
jira_import_id:,
created_at: @created_at,
updated_at: @updated_at
}
end
Import::JiraVersion.upsert_all(versions_upsert_data, unique_by: %i[jira_import_id origin_id])
end
# rubocop:enable Metrics/AbcSize
end
end
4 changes: 2 additions & 2 deletions app/workers/import/jira_staged_import_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

module Import
class JiraStagedImportJob < ApplicationJob
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
# rubocop:disable-next Metrics/AbcSize, Metrics/PerceivedComplexity
def perform(batch, _context)
jira_import = Import::JiraImport.find(batch.properties[:jira_import_id])

Expand All @@ -53,6 +53,7 @@ def perform(batch, _context)
Import::JiraProject.where(jira_import_id: jira_import.id,
origin_id: jira_import.project_ids).pluck(:id).each do |id|
Import::JiraFetchProjectIssuesJob.set(good_job_labels: ["stage_2"]).perform_later(jira_import.id, id)
Import::JiraFetchProjectVersionsJob.set(good_job_labels: ["stage_2"]).perform_later(jira_import.id, id)
end
end
elsif batch.properties[:stage] == 2
Expand Down Expand Up @@ -99,6 +100,5 @@ def perform(batch, _context)
jira_import.transition_to!(:import_error)
end
end
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
end
end
15 changes: 15 additions & 0 deletions db/migrate/20260917165643_create_jira_versions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class CreateJiraVersions < ActiveRecord::Migration[8.0]
def change
create_table :jira_versions do |t|
t.jsonb :payload
t.string :origin_id, null: false
t.references :jira_import, null: false, foreign_key: { on_delete: :cascade, on_update: :cascade }
t.references :jira_project, null: false, foreign_key: { on_delete: :cascade, on_update: :cascade }
t.index %i[jira_import_id origin_id], unique: true

t.timestamps
end
end
end
35 changes: 35 additions & 0 deletions spec/factories/import/jira_version_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

FactoryBot.define do
factory :jira_version, class: "Import::JiraVersion" do
sequence(:origin_id) { |n| (10000 + n).to_s }
end
end
Loading
Loading