mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Use state_machine to manage jira_import state.
This commit is contained in:
@@ -167,6 +167,9 @@ gem "meta-tags", "~> 2.22.2"
|
||||
|
||||
gem "paper_trail", "~> 17.0.0"
|
||||
|
||||
# State machine with audit trail
|
||||
gem "statesman", "~> 13.1.0"
|
||||
|
||||
gem "op-clamav-client", "~> 3.4", require: "clamav"
|
||||
|
||||
# Global ID for polymorphic associations
|
||||
|
||||
@@ -1405,6 +1405,7 @@ GEM
|
||||
sprockets (>= 3.0.0)
|
||||
ssrf_filter (1.0.8)
|
||||
stackprof (0.2.27)
|
||||
statesman (13.1.0)
|
||||
store_attribute (2.0.1)
|
||||
activerecord (>= 6.1)
|
||||
stringex (2.8.6)
|
||||
@@ -1727,6 +1728,7 @@ DEPENDENCIES
|
||||
sprockets (~> 3.7.2)
|
||||
sprockets-rails (~> 3.5.1)
|
||||
stackprof
|
||||
statesman (~> 13.1.0)
|
||||
store_attribute (~> 2.0)
|
||||
stringex (~> 2.8.5)
|
||||
structured_warnings (~> 0.5.0)
|
||||
@@ -2226,6 +2228,7 @@ CHECKSUMS
|
||||
sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e
|
||||
ssrf_filter (1.0.8) sha256=03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db
|
||||
stackprof (0.2.27) sha256=aff6d28656c852e74cf632cc2046f849033dc1dedffe7cb8c030d61b5745e80c
|
||||
statesman (13.1.0) sha256=3ecb78466dfd2682e433f335a7722aa0e5b8c6853d72d83e460151b8af17a84e
|
||||
store_attribute (2.0.1) sha256=643655e4800655b58379e8b01bd524f5586093a9d88698483ac8762cda25b5ab
|
||||
stringex (2.8.6) sha256=c7b382d2b2a47a1e1646f256df201c48d487d6296fbb289d76802f67f5e929c4
|
||||
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
|
||||
|
||||
@@ -42,7 +42,7 @@ module Admin::Import::Jira::ImportRuns
|
||||
end
|
||||
|
||||
def status
|
||||
render(Admin::Import::Jira::ImportRuns::StatusBadgeComponent.new(model.status))
|
||||
render(Admin::Import::Jira::ImportRuns::StatusBadgeComponent.new(model.current_state))
|
||||
end
|
||||
|
||||
def last_changed
|
||||
|
||||
@@ -41,13 +41,18 @@ module Admin::Import::Jira::ImportRuns
|
||||
|
||||
def status_color_scheme(status)
|
||||
case status
|
||||
when JiraImport::IMPORT_ERROR, JiraImport::REVERT_ERROR,
|
||||
JiraImport::INSTANCE_META_ERROR, JiraImport::PROJECTS_META_ERROR
|
||||
when JiraImportStateMachine::IMPORT_ERROR,
|
||||
JiraImportStateMachine::REVERT_ERROR,
|
||||
JiraImportStateMachine::INSTANCE_META_ERROR,
|
||||
JiraImportStateMachine::PROJECTS_META_ERROR
|
||||
:danger
|
||||
when JiraImport::COMPLETED, JiraImport::REVERTED
|
||||
when JiraImportStateMachine::COMPLETED,
|
||||
JiraImportStateMachine::REVERTED
|
||||
:success
|
||||
when JiraImport::INSTANCE_META_FETCHING, JiraImport::PROJECTS_META_FETCHING,
|
||||
JiraImport::IMPORTING, JiraImport::REVERTING
|
||||
when JiraImportStateMachine::INSTANCE_META_FETCHING,
|
||||
JiraImportStateMachine::PROJECTS_META_FETCHING,
|
||||
JiraImportStateMachine::IMPORTING,
|
||||
JiraImportStateMachine::REVERTING
|
||||
:accent
|
||||
else
|
||||
:attention
|
||||
|
||||
+6
-6
@@ -32,17 +32,17 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.title")
|
||||
})
|
||||
if model.status_before?(JiraImport::PROJECTS_META_DONE)
|
||||
if model.status_before?(JiraImportStateMachine::PROJECTS_META_DONE)
|
||||
concat(render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.caption")
|
||||
})
|
||||
elsif model.status_equal_or_after?(JiraImport::IMPORTED)
|
||||
elsif model.status_equal_or_after?(JiraImportStateMachine::IMPORTED)
|
||||
concat(render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.caption_done")
|
||||
})
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::PROJECTS_META_DONE, JiraImport::IMPORT_ERROR)
|
||||
if model.in_state?(JiraImportStateMachine::PROJECTS_META_DONE, JiraImportStateMachine::IMPORT_ERROR)
|
||||
box.with_row(mb: 3) do
|
||||
render(Primer::Beta::Text.new) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.description")
|
||||
@@ -54,7 +54,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
list: import_selection
|
||||
))
|
||||
end
|
||||
if model.status?(JiraImport::PROJECTS_META_DONE)
|
||||
if model.in_state?(JiraImportStateMachine::PROJECTS_META_DONE)
|
||||
box.with_row do
|
||||
render(Primer::Beta::Button.new(
|
||||
scheme: :primary,
|
||||
@@ -66,12 +66,12 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.button_start")
|
||||
}
|
||||
end
|
||||
elsif model.status?(JiraImport::IMPORT_ERROR)
|
||||
elsif model.in_state?(JiraImportStateMachine::IMPORT_ERROR)
|
||||
box.with_row do
|
||||
render(Admin::Import::Jira::ImportRuns::ErrorBannerComponent.new(model, 'import'))
|
||||
end
|
||||
end
|
||||
elsif model.status?(JiraImport::IMPORTING)
|
||||
elsif model.in_state?(JiraImportStateMachine::IMPORTING)
|
||||
box.with_row(mt: 3, mb: 3) do
|
||||
render(Admin::Import::Jira::ImportRuns::ProgressBoxComponent.new(
|
||||
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.label_progress")
|
||||
|
||||
+5
-5
@@ -34,13 +34,13 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.fetch_data.title")
|
||||
})
|
||||
if model.status_equal_or_after?(JiraImport::INSTANCE_META_DONE)
|
||||
if model.status_equal_or_after?(JiraImportStateMachine::INSTANCE_META_DONE)
|
||||
concat(render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.fetch_data.caption_done")
|
||||
})
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::INSTANCE_META_DONE, JiraImport::CONFIGURING)
|
||||
if model.in_state?(:instance_meta_done, :configuring)
|
||||
row.with_column do
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
@@ -56,7 +56,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
end
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::INITIAL)
|
||||
if model.in_state?(JiraImportStateMachine::INITIAL)
|
||||
box.with_row(mb: 3) do
|
||||
render(Primer::Beta::Text.new) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.fetch_data.description")
|
||||
@@ -73,11 +73,11 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
I18n.t(:"admin.jira.run.wizard.sections.fetch_data.button_fetch")
|
||||
end
|
||||
end
|
||||
elsif model.status?(JiraImport::INSTANCE_META_ERROR)
|
||||
elsif model.in_state?(:instance_meta_error)
|
||||
box.with_row(mt: 3) do
|
||||
render(Admin::Import::Jira::ImportRuns::ErrorBannerComponent.new(model, 'fetch'))
|
||||
end
|
||||
elsif model.status?(JiraImport::INSTANCE_META_FETCHING)
|
||||
elsif model.in_state?(:instance_meta_fetching)
|
||||
box.with_row(mt: 3) do
|
||||
render(Admin::Import::Jira::ImportRuns::ProgressBoxComponent.new(
|
||||
I18n.t(:"admin.jira.run.wizard.sections.fetch_data.label_progress")
|
||||
|
||||
+7
-7
@@ -32,17 +32,17 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.title")
|
||||
})
|
||||
if model.status_before?(JiraImport::INSTANCE_META_DONE)
|
||||
if model.status_before?(JiraImportStateMachine::INSTANCE_META_DONE)
|
||||
concat(render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.caption")
|
||||
})
|
||||
elsif model.status_equal_or_after?(JiraImport::PROJECTS_META_DONE)
|
||||
elsif model.status_equal_or_after?(JiraImportStateMachine::PROJECTS_META_DONE)
|
||||
concat(render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.caption_done")
|
||||
})
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::INSTANCE_META_DONE)
|
||||
if model.in_state?(JiraImportStateMachine::INSTANCE_META_DONE)
|
||||
box.with_row(mb: 3, mt: 3) do
|
||||
render(Primer::Alpha::Banner.new(scheme: :warning, icon: :alert)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_info")
|
||||
@@ -82,7 +82,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.button_continue")
|
||||
}
|
||||
end
|
||||
elsif model.status?(JiraImport::CONFIGURING, JiraImport::PROJECTS_META_ERROR)
|
||||
elsif model.in_state?(JiraImportStateMachine::CONFIGURING, JiraImportStateMachine::PROJECTS_META_ERROR)
|
||||
box.with_row(mb: 3, mt: 3) do
|
||||
render(Primer::Beta::Text.new) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_import")
|
||||
@@ -99,7 +99,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.button_select")
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::CONFIGURING)
|
||||
if model.in_state?(JiraImportStateMachine::CONFIGURING)
|
||||
box.with_row do
|
||||
render(Primer::Beta::Button.new(
|
||||
scheme: :primary,
|
||||
@@ -112,12 +112,12 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.button_continue")
|
||||
}
|
||||
end
|
||||
elsif model.status?(JiraImport::PROJECTS_META_ERROR)
|
||||
elsif model.in_state?(JiraImportStateMachine::PROJECTS_META_ERROR)
|
||||
box.with_row do
|
||||
render(Admin::Import::Jira::ImportRuns::ErrorBannerComponent.new(model, 'stats'))
|
||||
end
|
||||
end
|
||||
elsif model.status?(JiraImport::PROJECTS_META_FETCHING)
|
||||
elsif model.in_state?(JiraImportStateMachine::PROJECTS_META_FETCHING)
|
||||
box.with_row(mt: 3) do
|
||||
render(Admin::Import::Jira::ImportRuns::ProgressBoxComponent.new(
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_progress")
|
||||
|
||||
@@ -28,14 +28,14 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
++#%>
|
||||
|
||||
<%= flex_layout do |box|
|
||||
if model.status?(JiraImport::REVERTING, JiraImport::REVERTED, JiraImport::REVERT_ERROR)
|
||||
if model.in_state?(JiraImportStateMachine::REVERTING, JiraImportStateMachine::REVERTED, JiraImportStateMachine::REVERT_ERROR)
|
||||
box.with_row do
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_result.label_revert")
|
||||
})
|
||||
end
|
||||
case model.status
|
||||
when JiraImport::REVERTING
|
||||
case model.current_state
|
||||
when JiraImportStateMachine::REVERTING
|
||||
box.with_row(mt: 3) do
|
||||
render(Primer::Box.new(border: true, border_color: :accent, border_radius: 2, bg: :accent, p: 3)) do
|
||||
concat render(Primer::Beta::Spinner.new(size: :small))
|
||||
@@ -44,13 +44,13 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
}
|
||||
end
|
||||
end
|
||||
when JiraImport::REVERTED
|
||||
when JiraImportStateMachine::REVERTED
|
||||
box.with_row(mt: 3) do
|
||||
render(Primer::Alpha::Banner.new(scheme: :success, icon: :"check-circle")) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_result.label_reverted")
|
||||
}
|
||||
end
|
||||
when JiraImport::REVERT_ERROR
|
||||
when JiraImportStateMachine::REVERT_ERROR
|
||||
box.with_row(mt: 3) do
|
||||
render(Admin::Import::Jira::ImportRuns::ErrorBannerComponent.new(model, 'revert'))
|
||||
end
|
||||
@@ -60,20 +60,20 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_result.title")
|
||||
})
|
||||
if model.status_before?(JiraImport::IMPORTED)
|
||||
if model.status_before?(JiraImportStateMachine::IMPORTED)
|
||||
concat(render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_result.caption")
|
||||
})
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::IMPORTED)
|
||||
if model.in_state?(JiraImportStateMachine::IMPORTED)
|
||||
box.with_row(mt: 3) do
|
||||
render(Primer::Alpha::Banner.new(scheme: :success, icon: :"check-circle")) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_result.info")
|
||||
}
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::IMPORTED, JiraImport::COMPLETED)
|
||||
if model.in_state?(JiraImportStateMachine::IMPORTED, JiraImportStateMachine::COMPLETED)
|
||||
box.with_row(mt: 3, mb: 3) do
|
||||
render(Admin::Import::Jira::ImportRuns::InfoListBoxComponent.new(
|
||||
title: I18n.t(:"admin.jira.run.wizard.sections.import_result.label_results"),
|
||||
@@ -81,7 +81,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
))
|
||||
end
|
||||
end
|
||||
if model.status?(JiraImport::IMPORTED)
|
||||
if model.in_state?(JiraImportStateMachine::IMPORTED)
|
||||
box.with_row(mb: 3) do
|
||||
render(Primer::Alpha::Banner.new(scheme: :default, icon: :info)) {
|
||||
I18n.t(:"admin.jira.run.wizard.sections.import_result.preview_description")
|
||||
|
||||
@@ -48,13 +48,13 @@ module Admin::Import::Jira
|
||||
menu_item :jira_import
|
||||
|
||||
before_action :require_admin
|
||||
before_action :find_jira_and_jira_import, only: %i[show continue remove revert_modal]
|
||||
before_action :find_jira_and_jira_import, only: %i[show continue remove revert_modal history]
|
||||
|
||||
def show; end
|
||||
|
||||
def new
|
||||
jira = Jira.find(params[:jira_id])
|
||||
jira_import = JiraImport.create!(author_id: current_user.id, jira_id: jira.id, status: JiraImport::INITIAL)
|
||||
jira_import = JiraImport.create!(author_id: current_user.id, jira_id: jira.id)
|
||||
redirect_to(admin_import_jira_run_path(jira_id: jira.id, id: jira_import.id))
|
||||
end
|
||||
|
||||
@@ -76,6 +76,10 @@ module Admin::Import::Jira
|
||||
redirect_to admin_import_jira_path(@jira), status: :see_other
|
||||
end
|
||||
|
||||
def history
|
||||
@history = @jira_import.history
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def change_step(step)
|
||||
@@ -90,7 +94,7 @@ module Admin::Import::Jira
|
||||
def handle_error(error)
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
render_error_flash_message_via_turbo_stream(message: error.message)
|
||||
render_error_flash_message_via_turbo_stream(message: "#{error.message}\n#{error.backtrace}")
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
format.html do
|
||||
@@ -101,49 +105,31 @@ module Admin::Import::Jira
|
||||
end
|
||||
|
||||
def init
|
||||
return unless @jira_import.status_equal_or_before?(JiraImport::CONFIGURING)
|
||||
|
||||
@jira_import.update!(status: JiraImport::INITIAL)
|
||||
@jira_import.transition_to!(:initial)
|
||||
end
|
||||
|
||||
def fetch_instance_meta
|
||||
return unless @jira_import.status_equal_or_before?(JiraImport::CONFIGURING)
|
||||
|
||||
job = JiraInstanceMetaDataJob.perform_later(@jira_import.id)
|
||||
@jira_import.update!(status: JiraImport::INSTANCE_META_FETCHING, job_id: job.job_id)
|
||||
@jira_import.transition_to!(:instance_meta_fetching, job_id: "JOOOOOOOO")
|
||||
end
|
||||
|
||||
def fetch_projects_meta
|
||||
return unless @jira_import.status?(JiraImport::CONFIGURING, JiraImport::PROJECTS_META_ERROR)
|
||||
|
||||
job = JiraProjectsMetaDataJob.perform_later(@jira_import.id)
|
||||
@jira_import.update!(status: JiraImport::PROJECTS_META_FETCHING, job_id: job.job_id)
|
||||
@jira_import.transition_to!(:projects_meta_fetching)
|
||||
end
|
||||
|
||||
def import
|
||||
return unless @jira_import.status?(JiraImport::IMPORT_ERROR, JiraImport::PROJECTS_META_DONE)
|
||||
|
||||
job = JiraFetchAndImportProjectsJob.perform_later(@jira_import.id)
|
||||
@jira_import.update!(status: JiraImport::IMPORTING, job_id: job.job_id)
|
||||
@jira_import.transition_to!(:importing)
|
||||
end
|
||||
|
||||
def configure
|
||||
return unless @jira_import.status?(JiraImport::INSTANCE_META_DONE)
|
||||
|
||||
@jira_import.update!(status: JiraImport::CONFIGURING)
|
||||
@jira_import.transition_to!(:configuring)
|
||||
end
|
||||
|
||||
def revert
|
||||
return unless @jira_import.status?(JiraImport::REVERT_ERROR, JiraImport::IMPORTED)
|
||||
|
||||
job = JiraRevertJiraImportJob.perform_later(@jira_import.id)
|
||||
@jira_import.update!(status: JiraImport::REVERTING, job_id: job.job_id)
|
||||
@jira_import.transition_to!(:reverting)
|
||||
end
|
||||
|
||||
def finalize
|
||||
return unless @jira_import.status?(JiraImport::IMPORTED)
|
||||
|
||||
@jira_import.update!(status: JiraImport::COMPLETED)
|
||||
@jira_import.transition_to!(:completed)
|
||||
end
|
||||
|
||||
def find_jira_and_jira_import
|
||||
|
||||
@@ -43,7 +43,7 @@ module Admin::Import::Jira::ImportRuns
|
||||
method: "morph"
|
||||
)
|
||||
update_via_turbo_stream(
|
||||
component: ::Admin::Import::Jira::ImportRuns::StreamableStatusBadgeComponent.new(@jira_import.status),
|
||||
component: ::Admin::Import::Jira::ImportRuns::StreamableStatusBadgeComponent.new(@jira_import.current_state),
|
||||
method: "morph"
|
||||
)
|
||||
render turbo_stream: turbo_streams
|
||||
|
||||
+22
-63
@@ -32,72 +32,31 @@ class JiraImport < ApplicationRecord
|
||||
belongs_to :jira
|
||||
belongs_to :author, class_name: "User"
|
||||
|
||||
INITIAL = "initial"
|
||||
INSTANCE_META_FETCHING = "instance-meta-fetching"
|
||||
INSTANCE_META_ERROR = "instance-meta-error"
|
||||
INSTANCE_META_DONE = "instance-meta-done"
|
||||
CONFIGURING = "configuring"
|
||||
PROJECTS_META_FETCHING = "projects-meta-fetching"
|
||||
PROJECTS_META_ERROR = "projects-meta-error"
|
||||
PROJECTS_META_DONE = "projects-meta-done"
|
||||
IMPORTING = "importing"
|
||||
IMPORT_ERROR = "import-error"
|
||||
IMPORTED = "imported"
|
||||
COMPLETED = "completed"
|
||||
REVERTING = "reverting"
|
||||
REVERT_ERROR = "revert-error"
|
||||
REVERTED = "reverted"
|
||||
has_many :transitions, class_name: "JiraImportTransition", autosave: false
|
||||
|
||||
STATUSES = [
|
||||
INITIAL,
|
||||
INSTANCE_META_FETCHING,
|
||||
INSTANCE_META_ERROR,
|
||||
INSTANCE_META_DONE,
|
||||
CONFIGURING,
|
||||
PROJECTS_META_FETCHING,
|
||||
PROJECTS_META_ERROR,
|
||||
PROJECTS_META_DONE,
|
||||
IMPORTING,
|
||||
IMPORT_ERROR,
|
||||
IMPORTED,
|
||||
COMPLETED,
|
||||
REVERTING,
|
||||
REVERT_ERROR,
|
||||
REVERTED
|
||||
].freeze
|
||||
|
||||
def status_equal_or_after?(check_status)
|
||||
STATUSES.index(status) >= STATUSES.index(check_status)
|
||||
def state_machine
|
||||
@state_machine ||= JiraImportStateMachine.new(
|
||||
self,
|
||||
transition_class: JiraImportTransition,
|
||||
association_name: :transitions
|
||||
)
|
||||
end
|
||||
|
||||
def status_equal_or_before?(check_status)
|
||||
STATUSES.index(status) <= STATUSES.index(check_status)
|
||||
end
|
||||
|
||||
def status_before?(check_status)
|
||||
STATUSES.index(status) < STATUSES.index(check_status)
|
||||
end
|
||||
|
||||
def status_after?(check_status)
|
||||
STATUSES.index(status) > STATUSES.index(check_status)
|
||||
end
|
||||
|
||||
def status?(*check_statuses)
|
||||
check_statuses.include?(status)
|
||||
end
|
||||
|
||||
def deletable?
|
||||
!status_running? && !status?(IMPORTED, IMPORT_ERROR, REVERT_ERROR)
|
||||
end
|
||||
|
||||
def status_running?
|
||||
[
|
||||
INSTANCE_META_FETCHING,
|
||||
PROJECTS_META_FETCHING,
|
||||
IMPORTING,
|
||||
REVERTING
|
||||
].include?(status)
|
||||
end
|
||||
delegate :can_transition_to?,
|
||||
:current_state,
|
||||
:history,
|
||||
:last_transition,
|
||||
:last_transition_to,
|
||||
:transition_to!,
|
||||
:transition_to,
|
||||
:in_state?,
|
||||
:status_running?,
|
||||
:status_equal_or_after?,
|
||||
:status_equal_or_before?,
|
||||
:status_after?,
|
||||
:status_before?,
|
||||
:deletable?,
|
||||
to: :state_machine
|
||||
|
||||
def project_ids
|
||||
(projects || []).pluck("id")
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
class JiraImportStateMachine
|
||||
include Statesman::Machine
|
||||
|
||||
state :initial, initial: true
|
||||
state :instance_meta_fetching
|
||||
state :instance_meta_error
|
||||
state :instance_meta_done
|
||||
state :configuring
|
||||
state :projects_meta_fetching
|
||||
state :projects_meta_error
|
||||
state :projects_meta_done
|
||||
state :importing
|
||||
state :import_error
|
||||
state :imported
|
||||
state :completed
|
||||
state :reverting
|
||||
state :revert_error
|
||||
state :reverted
|
||||
|
||||
STATES_ORDER = [
|
||||
INITIAL,
|
||||
INSTANCE_META_FETCHING,
|
||||
INSTANCE_META_ERROR,
|
||||
INSTANCE_META_DONE,
|
||||
CONFIGURING,
|
||||
PROJECTS_META_FETCHING,
|
||||
PROJECTS_META_ERROR,
|
||||
PROJECTS_META_DONE,
|
||||
IMPORTING,
|
||||
IMPORT_ERROR,
|
||||
IMPORTED,
|
||||
COMPLETED,
|
||||
REVERTING,
|
||||
REVERT_ERROR,
|
||||
REVERTED
|
||||
].freeze
|
||||
|
||||
transition from: INITIAL, to: [INSTANCE_META_FETCHING]
|
||||
transition from: INSTANCE_META_FETCHING, to: [INSTANCE_META_DONE, INSTANCE_META_ERROR]
|
||||
transition from: INSTANCE_META_ERROR, to: [INSTANCE_META_FETCHING]
|
||||
transition from: INSTANCE_META_DONE, to: [CONFIGURING]
|
||||
transition from: CONFIGURING, to: [PROJECTS_META_FETCHING]
|
||||
transition from: PROJECTS_META_FETCHING, to: [PROJECTS_META_DONE, PROJECTS_META_ERROR]
|
||||
transition from: PROJECTS_META_ERROR, to: [PROJECTS_META_FETCHING]
|
||||
transition from: PROJECTS_META_DONE, to: [IMPORTING]
|
||||
transition from: IMPORTING, to: [IMPORTED, IMPORT_ERROR]
|
||||
transition from: IMPORT_ERROR, to: [IMPORTING]
|
||||
transition from: IMPORTED, to: [COMPLETED, REVERTING]
|
||||
transition from: REVERTING, to: [REVERTED, REVERT_ERROR]
|
||||
|
||||
after_transition(to: :instance_meta_fetching) do |jira_import, transition|
|
||||
JiraInstanceMetaDataJob.perform_later(jira_import.id)
|
||||
end
|
||||
|
||||
after_transition(to: :projects_meta_fetching) do |jira_import, transition|
|
||||
JiraProjectsMetaDataJob.perform_later(jira_import.id)
|
||||
end
|
||||
|
||||
after_transition(to: :importing) do |jira_import, transition|
|
||||
JiraFetchAndImportProjectsJob.perform_later(jira_import.id)
|
||||
end
|
||||
|
||||
after_transition(to: :reverting) do |jira_import, transition|
|
||||
JiraRevertJiraImportJob.perform_later(jira_import.id)
|
||||
end
|
||||
|
||||
def status_running?
|
||||
[
|
||||
INSTANCE_META_FETCHING,
|
||||
PROJECTS_META_FETCHING,
|
||||
IMPORTING,
|
||||
REVERTING
|
||||
].include?(current_state)
|
||||
end
|
||||
|
||||
def status_equal_or_after?(check_status)
|
||||
STATES_ORDER.index(current_state) >= STATES_ORDER.index(check_status)
|
||||
end
|
||||
|
||||
def status_equal_or_before?(check_status)
|
||||
STATES_ORDER.index(current_state) <= STATES_ORDER.index(check_status)
|
||||
end
|
||||
|
||||
def status_before?(check_status)
|
||||
STATES_ORDER.index(current_state) < STATES_ORDER.index(check_status)
|
||||
end
|
||||
|
||||
def status_after?(check_status)
|
||||
STATES_ORDER.index(current_state) > STATES_ORDER.index(check_status)
|
||||
end
|
||||
|
||||
def deletable?
|
||||
!status_running? && !in_state?(IMPORTED, IMPORT_ERROR, REVERT_ERROR)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class JiraImportTransition < ApplicationRecord
|
||||
belongs_to :jira_import, inverse_of: :transitions
|
||||
|
||||
after_destroy :update_most_recent, if: :most_recent?
|
||||
|
||||
private
|
||||
|
||||
def update_most_recent
|
||||
last_transition = jira_import.jira_import_transitions.order(:sort_key).last
|
||||
return unless last_transition.present?
|
||||
last_transition.update_column(:most_recent, true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,101 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<% html_title t(:label_administration), JiraImport.model_name, @jira_import.id %>
|
||||
<%=
|
||||
render(Primer::OpenProject::PageHeader.new) do |header|
|
||||
header.with_title do
|
||||
concat(render(Primer::Beta::Text.new(mr: 2)) {
|
||||
"#{I18n.t(:"admin.jira.run.title")} #{@jira_import.id.to_s}"
|
||||
})
|
||||
concat(render(Admin::Import::Jira::ImportRuns::StreamableStatusBadgeComponent.new(@jira_import.current_state)))
|
||||
end
|
||||
header.with_breadcrumbs(
|
||||
[
|
||||
{ href: admin_index_path, text: t(:label_administration) },
|
||||
{ href: admin_import_path, text: t(:"admin.import.title") },
|
||||
{ href: admin_import_jira_index_path, text: I18n.t(:"admin.jira.title") },
|
||||
{ href: admin_import_jira_path(@jira), text: @jira.name || @jira.url },
|
||||
{ href: admin_import_jira_run_path(id: @jira_import.id), text: "Run: #{@jira_import.id}" },
|
||||
I18n.t(:"admin.jira.run.history"),
|
||||
]
|
||||
)
|
||||
end
|
||||
%>
|
||||
<%=
|
||||
table = Class.new(OpPrimer::BorderBoxTableComponent) do
|
||||
columns :from_state, :to_state, :metadata
|
||||
|
||||
def self.name
|
||||
"HistoryTable"
|
||||
end
|
||||
|
||||
def mobile_title
|
||||
"History Table"
|
||||
end
|
||||
|
||||
def row_class
|
||||
@row_class ||= Class.new(OpPrimer::BorderBoxRowComponent) do
|
||||
def self.name
|
||||
"HistoryRow"
|
||||
end
|
||||
|
||||
def button_links
|
||||
[]
|
||||
end
|
||||
|
||||
def from_state
|
||||
model.from_state
|
||||
end
|
||||
|
||||
def to_state
|
||||
model.to_state
|
||||
end
|
||||
|
||||
def metadata
|
||||
model.metadata
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def has_actions?
|
||||
false
|
||||
end
|
||||
|
||||
def headers
|
||||
[
|
||||
[:from_state, { caption: "From state" }],
|
||||
[:to_state, { caption: "To state" }],
|
||||
[:metadata, { caption: "Metadata" }]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
render(table.new(rows: @history))
|
||||
%>
|
||||
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
concat(render(Primer::Beta::Text.new(mr: 2)) {
|
||||
"#{I18n.t(:"admin.jira.run.title")} #{@jira_import.id.to_s}"
|
||||
})
|
||||
concat(render(Admin::Import::Jira::ImportRuns::StreamableStatusBadgeComponent.new(@jira_import.status)))
|
||||
concat(render(Admin::Import::Jira::ImportRuns::StreamableStatusBadgeComponent.new(@jira_import.current_state)))
|
||||
end
|
||||
header.with_breadcrumbs(
|
||||
[
|
||||
|
||||
@@ -6,8 +6,9 @@ class JiraFetchAndImportProjectsJob < ApplicationJob
|
||||
JiraFetchProjectsJob.perform_now(jira_import_id)
|
||||
JiraImportProjectsJob.perform_now(jira_import_id)
|
||||
|
||||
jira_import.update!(status: JiraImport::IMPORTED, job_id: nil)
|
||||
jira_import.transition_to!(:imported)
|
||||
rescue StandardError => e
|
||||
jira_import.update!(status: JiraImport::IMPORT_ERROR, job_id: nil, error: e.message)
|
||||
jira_import.transition_to!(:import_error, error: e.message)
|
||||
jira_import.update!(job_id: nil, error: e.message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,9 +47,11 @@ class JiraInstanceMetaDataJob < ApplicationJob
|
||||
jira = jira_import.jira
|
||||
client = JiraClient.new(url: jira.url, personal_access_token: jira.personal_access_token)
|
||||
available = collect_metadata(client)
|
||||
jira_import.update!(status: JiraImport::INSTANCE_META_DONE, job_id: nil, available:, error: nil)
|
||||
jira_import.update!(job_id: nil, available:, error: nil)
|
||||
jira_import.transition_to!(:instance_meta_done)
|
||||
rescue StandardError => e
|
||||
jira_import.update!(status: JiraImport::INSTANCE_META_ERROR, job_id: nil, error: e.message)
|
||||
jira_import.transition_to!(:instance_meta_error, error: e.message)
|
||||
jira_import.update!(job_id: nil, error: e.message)
|
||||
end
|
||||
|
||||
def collect_metadata(client)
|
||||
|
||||
@@ -41,15 +41,17 @@ class JiraProjectsMetaDataJob < ApplicationJob
|
||||
def perform(jira_import_id)
|
||||
jira_import = JiraImport.find(jira_import_id)
|
||||
get_meta(jira_import)
|
||||
rescue StandardError => e
|
||||
jira_import.transition_to!(:projects_meta_error, error: e.message)
|
||||
jira_import.update!(job_id: nil, error: e.message)
|
||||
end
|
||||
|
||||
def get_meta(jira_import)
|
||||
jira = jira_import.jira
|
||||
client = JiraClient.new(url: jira.url, personal_access_token: jira.personal_access_token)
|
||||
selected = collect_metadata(client, jira_import.project_ids)
|
||||
jira_import.update!(status: JiraImport::PROJECTS_META_DONE, job_id: nil, selected:, error: nil)
|
||||
rescue StandardError => e
|
||||
jira_import.update!(status: JiraImport::PROJECTS_META_ERROR, job_id: nil, error: e.message)
|
||||
jira_import.transition_to!(:projects_meta_done, selected:)
|
||||
jira_import.update!(job_id: nil, selected:, error: nil)
|
||||
end
|
||||
|
||||
def collect_metadata(client, project_ids)
|
||||
|
||||
@@ -65,9 +65,9 @@ class JiraRevertJiraImportJob < ApplicationJob
|
||||
|
||||
OpenProjectJiraReference.where(jira_import_id: jira_import.id).delete_all
|
||||
|
||||
jira_import.update!(status: JiraImport::REVERTED, job_id: nil)
|
||||
jira_import.transition_to!(:reverted)
|
||||
end
|
||||
rescue StandardError => e
|
||||
jira_import.update!(status: JiraImport::REVERT_ERROR, job_id: nil, error: e.message)
|
||||
jira_import.transition_to!(:revert_error, job_id: nil, error: e.message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# config/initializers/statesman.rb
|
||||
Statesman.configure do
|
||||
storage_adapter(Statesman::Adapters::ActiveRecord)
|
||||
end
|
||||
@@ -164,6 +164,7 @@ en:
|
||||
label_ago: "%{amount} ago"
|
||||
run:
|
||||
title: "Import run"
|
||||
history: "History"
|
||||
remove_error: "A Jira import cannot be removed while it is running"
|
||||
blank:
|
||||
title: "No import runs set up yet"
|
||||
|
||||
@@ -742,6 +742,7 @@ Rails.application.routes.draw do
|
||||
delete :remove
|
||||
|
||||
get :revert_modal
|
||||
get :history
|
||||
end
|
||||
|
||||
resource :select_projects,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateJiraImportTransitions < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :jira_import_transitions do |t|
|
||||
t.string :from_state, null: false
|
||||
t.string :to_state, null: false
|
||||
t.jsonb :metadata, default: {}
|
||||
t.integer :sort_key, null: false
|
||||
t.integer :jira_import_id, null: false
|
||||
t.boolean :most_recent, null: false
|
||||
|
||||
# If you decide not to include an updated timestamp column in your transition
|
||||
# table, you'll need to configure the `updated_timestamp_column` setting in your
|
||||
# migration class.
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
# Foreign keys are optional, but highly recommended
|
||||
add_foreign_key :jira_import_transitions, :jira_imports
|
||||
|
||||
add_index(:jira_import_transitions,
|
||||
%i(jira_import_id sort_key),
|
||||
unique: true,
|
||||
name: "index_jira_import_transitions_parent_sort")
|
||||
add_index(:jira_import_transitions,
|
||||
%i(jira_import_id most_recent),
|
||||
unique: true,
|
||||
where: "most_recent",
|
||||
name: "index_jira_import_transitions_parent_most_recent")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoveJiraImportStatus < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
remove_column :jira_imports, :status, :string
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user