mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
import and revert custom fields
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# 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 JiraCustomFieldBuilder
|
||||
JIRA_TO_OP_FIELD_FORMAT = {
|
||||
"string" => "string",
|
||||
"text" => "text",
|
||||
"number" => "float",
|
||||
"date" => "date",
|
||||
"datetime" => "date", # TODO loss of precision
|
||||
"option" => "list",
|
||||
"any" => "string"
|
||||
}.freeze
|
||||
|
||||
attr_reader :jira_project, :jira_field, :values
|
||||
|
||||
def initialize(jira_field, jira_project, values)
|
||||
@jira_field = jira_field
|
||||
@jira_project = jira_project
|
||||
@values = values
|
||||
@import_name = jira_field.payload["name"]
|
||||
end
|
||||
|
||||
def find_existing_custom_field
|
||||
existing_cf = custom_field_by_name(@import_name) if %w[hierarchy list].exclude?(format)
|
||||
return existing_cf if existing_cf&.field_format == format
|
||||
|
||||
@import_name = unique_custom_field_name
|
||||
nil
|
||||
end
|
||||
|
||||
def custom_field_settings
|
||||
[@import_name, format]
|
||||
end
|
||||
|
||||
def custom_field_parameters
|
||||
params = {}
|
||||
if format == "list"
|
||||
params[:multi_value] = multi
|
||||
options = collect_list_options(values)
|
||||
params[:possible_values] = options unless options.empty?
|
||||
end
|
||||
params
|
||||
end
|
||||
|
||||
def convert_values(custom_field)
|
||||
@values
|
||||
end
|
||||
|
||||
def custom_field_post_processing(custom_field)
|
||||
populate_hierarchy_items(custom_field, values) if format == "hierarchy"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_field_by_name(name)
|
||||
WorkPackageCustomField.where("LOWER(name) = LOWER(?)", name).first
|
||||
end
|
||||
|
||||
def unique_custom_field_name
|
||||
unique_name = @import_name
|
||||
suffix = 2
|
||||
while custom_field_by_name(unique_name)
|
||||
unique_name = "#{@import_name} (#{suffix})"
|
||||
suffix += 1
|
||||
end
|
||||
unique_name
|
||||
end
|
||||
|
||||
def format
|
||||
@format ||= jira_to_op_field_format(jira_field)
|
||||
end
|
||||
|
||||
def jira_to_op_field_format(jira_field)
|
||||
schema = jira_field.payload["schema"] || {}
|
||||
type = schema["type"]
|
||||
items = schema["items"]
|
||||
custom = schema["custom"].to_s
|
||||
|
||||
if type == "array"
|
||||
items == "option" ? "list" : "string"
|
||||
elsif type == "option" && custom.include?("cascadingselect")
|
||||
EnterpriseToken.allows_to?(:custom_field_hierarchies) ? "hierarchy" : "string"
|
||||
else
|
||||
JIRA_TO_OP_FIELD_FORMAT.fetch(type, "string")
|
||||
end
|
||||
end
|
||||
|
||||
def collect_list_options(values)
|
||||
[] # TODO: collect_list_options
|
||||
end
|
||||
|
||||
def populate_hierarchy_items(custom_field, values)
|
||||
# TODO: populate_hierarchy_items
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -44,9 +44,10 @@ module Import
|
||||
@project_role = setup_project_role
|
||||
|
||||
Import::JiraProject.where(jira_id: @jira_id, jira_project_id: @jira_import.project_ids).find_each do |jira_project|
|
||||
project = import_project(jira_project)
|
||||
custom_field_list = import_custom_fields(jira_project)
|
||||
project = import_project(jira_project, custom_field_list)
|
||||
Import::JiraIssue.where(jira_id: @jira_id, jira_project_id: jira_project.id).find_each do |jira_issue|
|
||||
import_issue(jira_issue, project)
|
||||
import_issue(jira_issue, project, custom_field_list)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -71,7 +72,7 @@ module Import
|
||||
Role.find_by!(name: "JiraMember")
|
||||
end
|
||||
|
||||
def import_project(jira_project)
|
||||
def import_project(jira_project, _custom_field_list)
|
||||
identifier = jira_project.payload.fetch("key").downcase
|
||||
service_call = Projects::CreateService
|
||||
.new(user: @user, contract_class: EmptyContract)
|
||||
@@ -96,18 +97,18 @@ module Import
|
||||
taken_identifier = error.options[:value]
|
||||
project = Project.find_by!(identifier: taken_identifier)
|
||||
raise "You are trying to import a project with already used " \
|
||||
"identifier: #{taken_identifier}. Existing project: #{project}."
|
||||
"identifier: #{taken_identifier}. Existing project: #{project}."
|
||||
end
|
||||
|
||||
raise service_call.message
|
||||
end
|
||||
|
||||
def import_issue(jira_issue, project)
|
||||
def import_issue(jira_issue, project, custom_field_list)
|
||||
type = import_type(jira_issue, project)
|
||||
status = import_status(jira_issue)
|
||||
update_workflows(type)
|
||||
priority = import_priority(jira_issue)
|
||||
import_work_package(jira_issue, project, type, status, priority)
|
||||
import_work_package(jira_issue, project, type, status, priority, custom_field_list)
|
||||
end
|
||||
|
||||
def import_type(jira_issue, project)
|
||||
@@ -173,7 +174,7 @@ module Import
|
||||
raise call.message if call.failure?
|
||||
end
|
||||
|
||||
def import_work_package(jira_issue, project, type, status, priority)
|
||||
def import_work_package(jira_issue, project, type, status, priority, custom_field_list)
|
||||
# required because otherwise project.types does not include type and then wp creation fails.
|
||||
project.reload
|
||||
author_key = jira_issue.payload.dig("fields", "creator", "key")
|
||||
@@ -182,17 +183,27 @@ module Import
|
||||
assigned_to = find_user(assignee_key)
|
||||
|
||||
[author, assigned_to].uniq.compact.each { |member| import_member(project, member) }
|
||||
description = Import::JiraWikiMarkupConverter.new(jira_issue.payload["fields"]["description"] || "").convert
|
||||
|
||||
custom_field_attrs = custom_field_list.each_with_object({}) do |field, attrs|
|
||||
value = field[:values].select { |value| value[:issue_id] == jira_issue.id }
|
||||
next if value&.nil?
|
||||
|
||||
custom_field = field[:custom_field]
|
||||
attrs[custom_field.attribute_getter] = value.first[:value]
|
||||
end
|
||||
|
||||
service_call = WorkPackages::CreateService
|
||||
.new(user: author || User.system, contract_class: EmptyContract)
|
||||
.call(
|
||||
project:,
|
||||
subject: jira_issue.payload["fields"]["summary"],
|
||||
description: convert_rich_text(jira_issue.payload["fields"]["description"]),
|
||||
description:,
|
||||
type:,
|
||||
priority:,
|
||||
status:,
|
||||
assigned_to:
|
||||
assigned_to:,
|
||||
**custom_field_attrs
|
||||
)
|
||||
raise service_call.message unless service_call.success?
|
||||
|
||||
@@ -262,6 +273,57 @@ module Import
|
||||
raise service_call.message if service_call.errors.find { |error| error.type == :taken }.blank?
|
||||
end
|
||||
|
||||
def import_custom_fields(jira_project)
|
||||
usage = {}
|
||||
Import::JiraIssue.where(jira_id: @jira_id, jira_project_id: jira_project.id).find_each do |issue|
|
||||
issue.payload["fields"].each do |key, value|
|
||||
next unless key.start_with?("customfield_") && value.present?
|
||||
|
||||
usage[key] ||= { values: [] }
|
||||
usage[key][:values] << { value:, issue_id: issue.id }
|
||||
end
|
||||
end
|
||||
|
||||
custom_field_list = []
|
||||
usage.each do |jira_field_id, value|
|
||||
jira_field = Import::JiraField.find_by(jira_id: @jira_id, jira_field_id:)
|
||||
custom_field, values = import_custom_field(jira_field, jira_project, value[:values])
|
||||
custom_field_list << { custom_field:, values: }
|
||||
end
|
||||
custom_field_list
|
||||
end
|
||||
|
||||
def import_custom_field(jira_field, jira_project, values)
|
||||
jira_custom_field_builder = Import::JiraCustomFieldBuilder.new(jira_field, jira_project, values)
|
||||
existing_cf = jira_custom_field_builder.find_existing_custom_field
|
||||
if existing_cf
|
||||
unless Import::JiraOpenProjectReference.exists?(op_entity_id: existing_cf.id,
|
||||
op_entity_class: existing_cf.class.to_s,
|
||||
jira_id: @jira_id)
|
||||
create_reference!(op_leg: existing_cf, jira_leg: jira_field, jira_import:, uses_existing: true)
|
||||
end
|
||||
return [existing_cf, jira_custom_field_builder.convert_values(existing_cf)]
|
||||
end
|
||||
name, field_format = jira_custom_field_builder.custom_field_settings
|
||||
params = {
|
||||
type: "WorkPackageCustomField",
|
||||
name:,
|
||||
field_format:,
|
||||
is_required: false,
|
||||
is_for_all: false,
|
||||
**jira_custom_field_builder.custom_field_parameters
|
||||
}
|
||||
service_call = CustomFields::CreateService.new(user: @user).call(**params)
|
||||
if service_call.success?
|
||||
custom_field = service_call.result
|
||||
create_reference!(op_leg: custom_field, jira_leg: jira_field, jira_import: @jira_import, uses_existing: false)
|
||||
jira_custom_field_builder.custom_field_post_processing(custom_field)
|
||||
[custom_field, jira_custom_field_builder.convert_values(custom_field)]
|
||||
else
|
||||
raise "Failed to create custom field '#{jira_field['name']}': #{service_call.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def find_user(jira_user_key)
|
||||
return if jira_user_key.blank?
|
||||
|
||||
@@ -276,11 +338,6 @@ module Import
|
||||
end
|
||||
end
|
||||
|
||||
def convert_rich_text(description)
|
||||
return "" if description.blank?
|
||||
|
||||
Import::JiraWikiMarkupConverter.new(description).convert
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,6 +37,7 @@ module Import
|
||||
delete_users
|
||||
delete_groups
|
||||
delete_project_roles
|
||||
delete_custom_fields
|
||||
delete_references
|
||||
delete_jira_objects].freeze
|
||||
|
||||
@@ -132,6 +133,16 @@ module Import
|
||||
end
|
||||
end
|
||||
|
||||
def delete_custom_fields
|
||||
Import::JiraOpenProjectReference
|
||||
.where(jira_import_id: @jira_import.id, uses_existing: false)
|
||||
.where(op_entity_class: "WorkPackageCustomField")
|
||||
.find_each do |ref|
|
||||
op_leg = ref.op_leg
|
||||
op_leg.destroy!
|
||||
end
|
||||
end
|
||||
|
||||
def delete_references
|
||||
Import::JiraOpenProjectReference.where(jira_import_id: @jira_import.id).delete_all
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user