Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ See COPYRIGHT and LICENSE files for more details.
href: continue_admin_import_jira_run_path(jira_id: import_run.jira.id, id: import_run.id, step: step),
data: { turbo_stream: true }
) { I18n.t(:"admin.jira.run.wizard.button_retry") } %>
<%= import_run.error || I18n.t('js.error.internal') %>
<%= import_run.error&.html_safe || I18n.t('js.error.internal') %>
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
module Admin::Import::Jira::ImportRuns
class ErrorBannerComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include Redmine::I18n

attr_reader :import_run, :step

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 @@

belongs_to :jira, class_name: "Import::Jira"
belongs_to :jira_import, class_name: "Import::JiraImport"
has_many :jira_issues, class_name: "Import::JiraIssue"

Check notice on line 37 in app/models/import/jira_project.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] app/models/import/jira_project.rb#L37 <Rails/HasManyOrHasOneDependent>

Specify a `:dependent` option.
Raw output
app/models/import/jira_project.rb:37:5: C: Rails/HasManyOrHasOneDependent: Specify a `:dependent` option.
end
end
22 changes: 12 additions & 10 deletions app/models/work_package/semantic_identifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ class UnsupportedLookup < ArgumentError; end
.where("work_packages.identifier IS DISTINCT FROM projects.identifier || '-' || work_packages.sequence_number::text")
}

after_create :allocate_and_register_semantic_id, if: -> { Setting::WorkPackageIdentifier.semantic? }
attr_accessor :skip_semantic_id_allocation

after_create :allocate_and_register_semantic_id, if: -> { Setting::WorkPackageIdentifier.semantic? && !skip_semantic_id_allocation }

validate :semantic_identifier_fields_consistent
end
Expand Down Expand Up @@ -126,6 +128,15 @@ def allocate_and_register_semantic_id
end
end

# Builds alias rows for every identifier this project has ever used at the given sequence (including the current one).
# This also includes "ghost identifiers" -- i.e. those that weren't ever actually generated, but should work
# as a historical alias (e.g. OLDPROJ-42 should work even if WP #42 was created after rename to NEWPROJ)
def alias_rows_for_sequence_number(seq)
project.slugs
.pluck(:slug)
.map { |prefix| { identifier: "#{prefix}-#{seq}", work_package_id: id } }
end

private

# Ensures identifier and sequence_number are always written together.
Expand All @@ -135,13 +146,4 @@ def semantic_identifier_fields_consistent

errors.add(:identifier, :semantic_identifier_incomplete)
end

# Builds alias rows for every identifier this project has ever used at the given sequence (including the current one).
# This also includes "ghost identifiers" -- i.e. those that weren't ever actually generated, but should work
# as a historical alias (e.g. OLDPROJ-42 should work even if WP #42 was created after rename to NEWPROJ)
def alias_rows_for_sequence_number(seq)
project.slugs
.pluck(:slug)
.map { |prefix| { identifier: "#{prefix}-#{seq}", work_package_id: id } }
end
end
20 changes: 17 additions & 3 deletions app/views/admin/import/jira/instances/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ See COPYRIGHT and LICENSE files for more details.
label: t("admin.jira.configuration.title"),
scheme: :primary,
tag: :a,
href: new_admin_import_jira_path) {
t("admin.jira.configuration.title")
}
href: new_admin_import_jira_path,
disabled: !Setting::WorkPackageIdentifier.semantic?
) { t("admin.jira.configuration.title") }
end
%>

Expand All @@ -72,6 +72,20 @@ See COPYRIGHT and LICENSE files for more details.
})
}
end
unless Setting::WorkPackageIdentifier.semantic?
container.with_row do
render(Primer::Alpha::Banner.new(scheme: :danger, icon: :stop, mb: 3)) {
concat(render(Primer::Beta::Text.new(tag: :p, font_weight: :bold)) {
I18n.t("admin.jira.errors.semantic_identifiers_must_be_enabled.title")
})
concat(render(Primer::Beta::Text.new(tag: :p)) {
link_translate("admin.jira.errors.semantic_identifiers_must_be_enabled.description",
links: { link: admin_settings_work_packages_identifier_path },
external: true)
})
}
end
end
container.with_row(pb: 3) do
flex_layout do |flex|
flex.with_column(flex: 1, mr: 1) do
Expand Down
49 changes: 45 additions & 4 deletions app/workers/import/jira_import_projects_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
class JiraImportProjectsJob < ApplicationJob
include Import::JiraOpenProjectReferenceCreation
include JiraImportCustomFields
include Redmine::I18n

# rubocop:disable Metrics/AbcSize
def perform(jira_import_id)
Expand All @@ -41,6 +42,19 @@
@user = User.system
@jira_client = Import::JiraClient.new(url: jira.url, personal_access_token: jira.personal_access_token)

unless Setting::WorkPackageIdentifier.semantic?
view_context = ApplicationController.new.view_context
title = view_context.render(Primer::Beta::Text.new(tag: :p, font_weight: :bold).with_content(
I18n.t("admin.jira.errors.semantic_identifiers_must_be_enabled.title")
))
description = view_context.render(Primer::Beta::Text.new(tag: :p).with_content(
link_translate("admin.jira.errors.semantic_identifiers_must_be_enabled.description",
links: { link: OpenProject::StaticRouting::StaticUrlHelpers.new.admin_settings_work_packages_identifier_path },

Check notice on line 52 in app/workers/import/jira_import_projects_job.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] app/workers/import/jira_import_projects_job.rb#L52 <Layout/LineLength>

Line is too long. [170/130]
Raw output
app/workers/import/jira_import_projects_job.rb:52:168: C: Layout/LineLength: Line is too long. [170/130]
external: true)
))
raise title + description
end

ActiveRecord::Base.transaction do
@project_role = setup_project_role
custom_field_registry = build_custom_field_registry
Expand Down Expand Up @@ -76,12 +90,12 @@

def import_project(jira_project)
project_key = jira_project.payload.fetch("key")
identifier = Setting::WorkPackageIdentifier.semantic? ? project_key.upcase : project_key.downcase
project_keys = jira_project.payload.fetch("projectKeys")
service_call = Projects::CreateService
.new(user: @user, contract_class: EmptyContract)
.call(
name: jira_project.payload.fetch("name"),
identifier:,
identifier: project_key,
description: jira_project.payload.fetch("description"),
active: true,
public: false,
Expand All @@ -92,8 +106,12 @@
workspace_type: "project"
)
if service_call.success?
create_reference!(op_leg: service_call.result, jira_leg: jira_project, jira_import: @jira_import, uses_existing: false)
return service_call.result
project = service_call.result
project_keys.each do |key|
project.slugs.create(slug: key)
end
create_reference!(op_leg: project, jira_leg: jira_project, jira_import: @jira_import, uses_existing: false)
return project
end

if (error = service_call.errors.find { |e| e.attribute == :identifier && e.type == :taken }) && error.present?
Expand Down Expand Up @@ -246,11 +264,34 @@
priority:,
status:,
assigned_to:,
skip_semantic_id_allocation: true,
**custom_field_attrs
)
raise service_call.message unless service_call.success?

work_package = service_call.result

key = jira_issue.payload["key"]
_, sequence_number = key.split("-")
work_package.update_columns(sequence_number:, identifier: key)
work_package_id = work_package.id
aliases_from_history = jira_issue
.payload["changelog"]["histories"]
.flat_map { |i| i["items"] }
.select { |i| i["field"] == "Key" }
.flat_map do |i|
[
{ identifier: i["toString"], work_package_id: },
{ identifier: i["fromString"], work_package_id: }
]
end
aliases = work_package.alias_rows_for_sequence_number(sequence_number)
aliases.concat(aliases_from_history)
aliases.uniq!
work_package.semantic_aliases.upsert_all(aliases,
on_duplicate: :skip,
unique_by: :identifier)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to update project.wp_sequence_counter as well.

create_reference!(op_leg: work_package, jira_leg: jira_issue, jira_import: @jira_import, uses_existing: false)
import_work_package_history(work_package, jira_issue, project)
end
Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ en:
errors:
cannot_delete_with_imports: "Cannot delete Jira host with existing imports"
custom_field_creation_failed: "Failed to create custom field '%{name}': %{message}"
semantic_identifiers_must_be_enabled:
title: "Project-based semantic identifiers must be enabled."
description: "Jira uses work items identifiers consisting of project key and a sequence number (PRJ-123). OpenProject also supports it, but it needs to be enabled [here](link)."
blank:
title: "No Jira hosts configured yet"
description: "Configure a Jira host to start importing items from Jira to this OpenProject instance."
Expand Down
Loading