importo 3.0.24 → 3.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/Rakefile +3 -20
  4. data/app/controllers/importo/imports_controller.rb +13 -13
  5. data/app/importers/concerns/exportable.rb +2 -2
  6. data/app/importers/concerns/importable.rb +10 -10
  7. data/app/importers/concerns/original.rb +3 -3
  8. data/app/importers/concerns/result_feedback.rb +2 -2
  9. data/app/importers/importo/import_job_callback.rb +5 -4
  10. data/app/jobs/importo/application_job.rb +1 -0
  11. data/app/jobs/importo/import_job.rb +11 -23
  12. data/app/jobs/importo/import_scheduled_job.rb +2 -2
  13. data/app/jobs/importo/purge_import_job.rb +2 -3
  14. data/app/models/importo/import.rb +4 -7
  15. data/app/services/importo/import_context.rb +2 -2
  16. data/app/services/importo/import_service.rb +2 -2
  17. data/config/routes.rb +5 -5
  18. data/lib/importo/adapters/sidekiq_batch_adapter.rb +3 -5
  19. data/lib/importo/configuration.rb +18 -5
  20. data/lib/importo/engine.rb +0 -11
  21. data/lib/importo/test_helpers.rb +5 -6
  22. data/lib/importo/version.rb +1 -1
  23. data/lib/importo.rb +0 -1
  24. metadata +8 -24
  25. data/db/migrate/20251117132125_create_good_jobs.rb +0 -40
  26. data/db/migrate/20251117132126_create_good_job_settings.rb +0 -20
  27. data/db/migrate/20251117132127_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb +0 -19
  28. data/db/migrate/20251117132128_create_good_job_batches.rb +0 -35
  29. data/db/migrate/20251117132129_create_good_job_executions.rb +0 -33
  30. data/db/migrate/20251117132130_create_good_jobs_error_event.rb +0 -16
  31. data/db/migrate/20251117132131_recreate_good_job_cron_indexes_with_conditional.rb +0 -45
  32. data/db/migrate/20251117132132_create_good_job_labels.rb +0 -15
  33. data/db/migrate/20251117132133_create_good_job_labels_index.rb +0 -22
  34. data/db/migrate/20251117132134_remove_good_job_active_id_index.rb +0 -21
  35. data/db/migrate/20251117132135_create_index_good_job_jobs_for_candidate_lookup.rb +0 -19
  36. data/db/migrate/20251117132136_create_good_job_execution_error_backtrace.rb +0 -15
  37. data/db/migrate/20251117132137_create_good_job_process_lock_ids.rb +0 -18
  38. data/db/migrate/20251117132138_create_good_job_process_lock_indexes.rb +0 -38
  39. data/db/migrate/20251117132139_create_good_job_execution_duration.rb +0 -15
  40. data/lib/importo/import_helpers.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab14d9e5565f7313de5ff1453e203bbf3f64670eb12d0f1f76c0f7149f747ee7
4
- data.tar.gz: d9ab88a3625c778c7379def02f3f1680247c4228697e90fdd8d5f1f3599af954
3
+ metadata.gz: 58de2b19f5aa87cb2464fd8b9ca56d5d6e4e58ed2561ab9e33a8b31689ab1391
4
+ data.tar.gz: e55c5dfcaa8f59694b33fd00f8abd31bfd8873d2cf317f0133e828c8ea4c3d37
5
5
  SHA512:
6
- metadata.gz: 41719578949eb007165adfac5c64ce78ce301dc12283cf6b66b29cf075bc3cab2ccd53c57b25bee71b88df7795a4b96285e1ce168fc2fa93b15f52e02215e892
7
- data.tar.gz: 4c45ad49b70c9199c9ea9d7cc6766b1b937851fd730df733dc0985e8db00abbb89fcae86639022ecd7c69272de33ebb304870996b75831924a57adf230971127
6
+ metadata.gz: f711dee28330a128c7c1b17c89f5648008e86b207a0a08a7500ddbcd89d93b66cb0221bcbf1bcb9b44ecc9cd853289ea94535c2fdcdf9f34d661799b26689053
7
+ data.tar.gz: 667e0ef7154a7a9895b15d1bc22c6301c5e451dc0c780856d86e2bf64c257dd2b3654c48e86de2af9381a533aed23fd1dad777f3ad217a48c48b9d94e5bce248
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2017 Andre Meij
1
+ Copyright 2017 Andre Meij & Tom de Grunt
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,26 +1,8 @@
1
- # frozen_string_literal: true
2
-
3
- begin
4
- require "bundler/setup"
5
- rescue LoadError
6
- puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
- end
8
-
9
- require "rdoc/task"
10
-
11
- RDoc::Task.new(:rdoc) do |rdoc|
12
- rdoc.rdoc_dir = "rdoc"
13
- rdoc.title = "Importo"
14
- rdoc.options << "--line-numbers"
15
- rdoc.rdoc_files.include("README.md")
16
- rdoc.rdoc_files.include("lib/**/*.rb")
17
- end
1
+ require "bundler/setup"
18
2
 
19
3
  APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
20
4
  load "rails/tasks/engine.rake"
21
5
 
22
- load "rails/tasks/statistics.rake"
23
-
24
6
  require "bundler/gem_tasks"
25
7
 
26
8
  require "rake/testtask"
@@ -28,7 +10,8 @@ require "rake/testtask"
28
10
  Rake::TestTask.new(:test) do |t|
29
11
  t.libs << "test"
30
12
  t.pattern = "test/**/*_test.rb"
31
- t.verbose = false
13
+ t.verbose = true
14
+ t.warning = false
32
15
  end
33
16
 
34
17
  task default: :test
@@ -11,37 +11,37 @@ module Importo
11
11
  def preview
12
12
  @import = Import.find(params[:id])
13
13
  if @import&.original&.attachment.present?
14
- @original = Tempfile.new(['ActiveStorage', @import.original.filename.extension_with_delimiter])
14
+ @original = Tempfile.new(["ActiveStorage", @import.original.filename.extension_with_delimiter])
15
15
  @original.binmode
16
16
  @import.original.download { |block| @original.write(block) }
17
17
  @original.flush
18
18
  @original.rewind
19
- sheet = Roo::Excelx.new(@original.path)
19
+ sheet = Roo::Excelx.new(@original.path)
20
20
  @sheet_data = sheet.parse(headers: true)
21
- @check_header = @sheet_data.reject{ |h| h.keys == h.values }.map{|h| h.transform_values(&:to_s).compact_blank}.reduce({}, :merge).keys
21
+ @check_header = @sheet_data.reject { |h| h.keys == h.values }.map { |h| h.transform_values(&:to_s).compact_blank }.reduce({}, :merge).keys
22
22
  end
23
- if @sheet_data.nil? || @sheet_data.reject{ |h| h.keys == h.values }.blank?
24
- Signum.error(Current.user, text: t('.no_file'))
23
+ if @sheet_data.nil? || @sheet_data.reject { |h| h.keys == h.values }.blank?
24
+ Signum.error(Current.user, text: t(".no_file"))
25
25
  redirect_to action: :new
26
26
  end
27
27
  end
28
28
 
29
29
  def create
30
- unless import_params
31
- @import = Import.new(kind: params[:kind], locale: I18n.locale)
30
+ unless import_params["original"].present?
31
+ @import = Import.new(kind: import_params[:kind], locale: I18n.locale)
32
32
  Signum.error(Current.user, text: t(".flash.no_file"))
33
33
  render :new
34
34
  return
35
35
  end
36
36
  @import = Import.new(import_params.merge(locale: I18n.locale,
37
- importo_ownable: Importo.config.current_import_owner.call))
37
+ importo_ownable: Importo.config.current_import_owner.call))
38
38
  if params["commit"] == "Upload" && @import.valid? && @import.save!
39
39
  @import.confirm!
40
40
  @import.schedule!
41
- redirect_to importo.new_import_path(params[:kind] || @import.kind)
41
+ redirect_to importo.new_import_path(params[:kind] || @import.kind)
42
42
  elsif params["commit"] == "Preview" && @import.valid?
43
43
  @import.save!
44
- redirect_to action: :preview, id: @import.id, kind: @import.kind
44
+ redirect_to action: :preview, id: @import.id, kind: @import.kind
45
45
  else
46
46
  Signum.error(Current.user, text: t(".flash.error", error: @import.errors&.full_messages&.join(".")))
47
47
  render :new
@@ -51,7 +51,7 @@ module Importo
51
51
  def cancel
52
52
  @import = Import.find(params[:id])
53
53
  @import.original.purge if @import.concept?
54
- Signum.error(Current.user, text: t('.flash.cancel', id: @import.id))
54
+ Signum.error(Current.user, text: t(".flash.cancel", id: @import.id))
55
55
  @import.destroy!
56
56
  redirect_to action: :new, kind: @import.kind
57
57
  end
@@ -61,7 +61,7 @@ module Importo
61
61
  if @import.can_revert? && @import.revert
62
62
  redirect_to action: :index, notice: "Import reverted"
63
63
  else
64
- redirect_to action: :index, alert: 'Import could not be reverted'
64
+ redirect_to action: :index, alert: "Import could not be reverted"
65
65
  end
66
66
  end
67
67
 
@@ -72,7 +72,7 @@ module Importo
72
72
  if @import.valid? && @import.schedule!
73
73
  redirect_to action: :index
74
74
  else
75
- Signum.error(Current.user, text: t('.flash.error', id: @import.id))
75
+ Signum.error(Current.user, text: t(".flash.error", id: @import.id))
76
76
  render :new
77
77
  end
78
78
  end
@@ -81,9 +81,9 @@ module Exportable
81
81
  end
82
82
  styles = export_columns.map do |_, c|
83
83
  if c.options.dig(:export, :format) == "number" || (c.options.dig(:export, :format).nil? && c.options.dig(:export, :example).is_a?(Numeric))
84
- number = workbook.styles.add_style format_code: "#"
84
+ workbook.styles.add_style format_code: "0"
85
85
  elsif c.options.dig(:export, :format) == "text" || (c.options.dig(:export, :format).nil? && c.options.dig(:export, :example).is_a?(String))
86
- text = workbook.styles.add_style format_code: "@"
86
+ workbook.styles.add_style format_code: "@"
87
87
  elsif c.options.dig(:export, :format)
88
88
  workbook.styles.add_style format_code: c.options.dig(:export, :format).to_s
89
89
  else
@@ -21,7 +21,7 @@ module Importable
21
21
  columns.each do |k, col|
22
22
  next if col.proc.blank? || row[k].nil?
23
23
 
24
- attr = col.options[:attribute]
24
+ col.options[:attribute]
25
25
 
26
26
  row[k] = import.column_overrides[col.attribute] if import.column_overrides[col.attribute]
27
27
 
@@ -88,7 +88,7 @@ module Importable
88
88
  end
89
89
 
90
90
  #
91
- # Callbakcs
91
+ # Callbacks
92
92
  #
93
93
  def before_build(_record, _row)
94
94
  end
@@ -124,12 +124,12 @@ module Importable
124
124
  # Does the actual import
125
125
  #
126
126
  def import!(checked_columns)
127
- raise ArgumentError, 'Invalid data structure' unless structure_valid?
127
+ raise ArgumentError, "Invalid data structure" unless structure_valid?
128
128
 
129
- batch = Importo.config.batch_adapter.new
129
+ batch = Importo.config.batch_adapter_name.constantize.new
130
130
  batch.description = "#{import.original.filename} - #{import.kind}"
131
131
  batch.properties = {import_id: import.id}
132
- if Importo.config.batch_adapter == Importo::SidekiqBatchAdapter
132
+ if Importo.sidekiq?
133
133
  batch.on_success("Importo::ImportJobCallback")
134
134
  else
135
135
  batch.on_success = "Importo::ImportJobCallback"
@@ -144,14 +144,14 @@ module Importable
144
144
  v.delay.call(attributes[k])
145
145
  end
146
146
  end
147
- Importo::ImportJob.set(wait_until: (delay.max * index).seconds.from_now).perform_async(JSON.dump(attributes), index, import.id) if delay.present?
148
- Importo::ImportJob.perform_async(JSON.dump(attributes), index, import.id) unless delay.present?
147
+
148
+ method = Importo.sidekiq? ? "perform_async" : "perform_later"
149
+ Importo::ImportJob.set(wait_until: (delay.max * index).seconds.from_now).send(method, JSON.dump(attributes), index, import.id) if delay.present?
150
+ Importo::ImportJob.send(method, JSON.dump(attributes), index, import.id) unless delay.present?
149
151
  end
150
152
  end
151
153
 
152
- if defined?(GoodJob::Batch) && Importo.config.batch_adapter == GoodJob::Batch
153
- batch.enqueue
154
- end
154
+ batch.enqueue if Importo.good_job?
155
155
 
156
156
  true
157
157
  rescue => e
@@ -110,7 +110,7 @@ module Original
110
110
  duplicate(row_hash, id)
111
111
  end
112
112
 
113
- def loop_data_rows(checked_columns = nil)
113
+ def loop_data_rows(checked_columns = nil)
114
114
  (data_start_row..spreadsheet.last_row).map do |index|
115
115
  row = cells_from_row(index, false)
116
116
 
@@ -121,8 +121,8 @@ module Original
121
121
  end.to_h
122
122
  attributes.reject! { |k, _v| headers_added_by_import.include?(k) }
123
123
  if checked_columns&.dig(:checked_columns).present?
124
- selected_columns = checked_columns[:checked_columns].map{|i| col_for(i)&.first}
125
- attributes.reject!{|k, _v| selected_columns.exclude?(k) }
124
+ selected_columns = checked_columns[:checked_columns].map { |i| col_for(i)&.first }
125
+ attributes.reject! { |k, _v| selected_columns.exclude?(k) }
126
126
  end
127
127
  yield attributes, index
128
128
  end
@@ -35,7 +35,7 @@ module ResultFeedback
35
35
  attributes.map do |column, value|
36
36
  export_format = columns[column]&.options&.dig(:export, :format)
37
37
  format_code = if export_format == "number" || (export_format.nil? && value.is_a?(Numeric))
38
- "#"
38
+ "0"
39
39
  elsif export_format == "text" || (export_format.nil? && value.is_a?(String))
40
40
  "@"
41
41
  elsif export_format
@@ -66,7 +66,7 @@ module ResultFeedback
66
66
  base = friendly_name || model.class.name
67
67
  base = base.to_s unless base.is_a?(String)
68
68
  base = base.gsub(/[_\s-]/, "_").pluralize.downcase
69
- "#{base}#{suffix.present? ? "_#{suffix}" : ""}.xlsx"
69
+ "#{base}#{"_#{suffix}" if suffix.present?}.xlsx"
70
70
  end
71
71
 
72
72
  private
@@ -1,10 +1,11 @@
1
1
  module Importo
2
2
  class ImportJobCallback < ActiveJob::Base
3
- include Sidekiq::Batch::Callback
3
+ include Sidekiq::Batch::Callback if Importo.sidekiq?
4
4
  include Rails.application.routes.url_helpers
5
5
 
6
- def perform(batch, import_id)
7
- import = Import.find(import_id)
6
+ # This is for good_job
7
+ def perform(batch, context)
8
+ import = Import.find(batch.properties[:import_id])
8
9
  complete_import(import)
9
10
  end
10
11
 
@@ -29,11 +30,11 @@ module Importo
29
30
  end
30
31
  end
31
32
 
33
+ # This is for sidekiq
32
34
  def on_complete(status, options)
33
35
  options = options.deep_stringify_keys
34
36
  import = Import.find(options["import_id"])
35
37
  complete_import(import)
36
38
  end
37
-
38
39
  end
39
40
  end
@@ -1,4 +1,5 @@
1
1
  module Importo
2
2
  class ApplicationJob < ActiveJob::Base
3
+ include GoodJob::ActiveJobExtensions::Batches if Importo.good_job?
3
4
  end
4
5
  end
@@ -1,24 +1,9 @@
1
1
  module Importo
2
- base_class =
3
- if Importo.config.batch_adapter == Importo::SidekiqBatchAdapter
4
- Object
5
- else
6
- ApplicationJob
7
- end
8
-
9
- class ImportJob < base_class
10
-
2
+ class ImportJob < Importo.import_job_base_class_name.constantize
11
3
  # No options here, gets added from the adapter
12
4
 
13
5
  def perform(attributes, index, import_id)
14
- batch_id = if defined?(bid)
15
- bid
16
- else
17
- batch.id
18
- end
19
- batch
20
-
21
- self.class.execute_row(attributes, index, import_id, false, batch_id)
6
+ self.class.execute_row(attributes, index, import_id, false, defined?(bid) ? bid : batch.id)
22
7
  end
23
8
 
24
9
  def self.execute_row(attributes, index, import_id, last_attempt, bid)
@@ -27,17 +12,20 @@ module Importo
27
12
  import = Import.find(import_id)
28
13
  import.importer.process_data_row(attributes, index, last_attempt: last_attempt)
29
14
 
15
+ # This should not be needed:
16
+ # https://github.com/sidekiq/sidekiq/wiki/Batches#callbacks
17
+ #
30
18
  # Between sidekiq and good job, there's a big difference:
31
19
  # - Sidekiq calls on_complete callback when all jobs ran at least once.
32
20
  # - GoodJob calls on_complete callback when all jobs are done (including retries).
33
21
  # i.e. this logic is only needed for sidekiq
34
- if Importo.config.batch_adapter == Importo::SidekiqBatchAdapter
35
- batch = Importo::SidekiqBatchAdapter.find(bid)
22
+ # return unless Importo.sidekiq?
23
+
24
+ # batch = Importo::SidekiqBatchAdapter.find(bid)
36
25
 
37
- if !import.completed? && import.can_complete? && batch.finished?
38
- ImportJobCallback.perform_now(batch, import.id)
39
- end
40
- end
26
+ # if !import.completed? && import.can_complete? && batch.finished?
27
+ # ImportJobCallback.perform_now(batch, import.id)
28
+ # end
41
29
  end
42
30
  end
43
31
  end
@@ -1,6 +1,6 @@
1
1
  module Importo
2
- class ImportScheduledJob < ApplicationJob
3
- def perform()
2
+ class ImportScheduledJob < ActiveJob::Base
3
+ def perform
4
4
  imports = Import.where(state: "scheduled", created_at: ..30.minutes.ago)
5
5
 
6
6
  imports.each do |import|
@@ -1,7 +1,6 @@
1
1
  module Importo
2
- class PurgeImportJob < ApplicationJob
3
- def perform(owner, months,state = nil)
4
-
2
+ class PurgeImportJob < ActiveJob::Base
3
+ def perform(owner, months, state = nil)
5
4
  imports = Import.where(importo_ownable: owner, created_at: ..months.months.ago.beginning_of_day)
6
5
  imports = imports.where(state: state) if state
7
6
 
@@ -12,12 +12,9 @@ module Importo
12
12
  validates :kind, presence: true
13
13
  validates :original, presence: true
14
14
  validate :content_validator
15
- begin
16
- has_one_attached :original
17
- has_one_attached :result
18
- rescue NoMethodError
19
- # Weird loading sequence error, is fixed by the lib/importo/helpers
20
- end
15
+
16
+ has_one_attached :original
17
+ has_one_attached :result
21
18
 
22
19
  state_machine :state, initial: :concept do
23
20
  state :confirmed
@@ -115,7 +112,7 @@ module Importo
115
112
  private
116
113
 
117
114
  def schedule_import
118
- ImportService.perform_later(import: self, checked_columns: self.checked_columns)
115
+ ImportService.perform_later(import: self, checked_columns: checked_columns)
119
116
  end
120
117
 
121
118
  def schedule_revert
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Importo
4
4
  class ImportContext < ApplicationContext
5
- attribute :import, type: Import, typecaster: ->(value) { value.is_a?(Import) ? value : Import.find(value) }
6
- attribute :checked_columns, type: Array, typecaster: ->(value){ value.is_a?(Array) ? value : [] }
5
+ attribute :import, type: Import, typecaster: ->(value) { value.is_a?(Import) ? value : Import.find(value) }
6
+ attribute :checked_columns, type: Array, typecaster: ->(value) { value.is_a?(Array) ? value : [] }
7
7
  end
8
8
  end
@@ -4,8 +4,8 @@ module Importo
4
4
  class ImportService < ApplicationService
5
5
  def perform
6
6
  context.import.import!
7
- context.import.importer.import!(checked_columns: context.checked_columns )
8
- rescue StandardError
7
+ context.import.importer.import!(checked_columns: context.checked_columns)
8
+ rescue
9
9
  context.import.failure!
10
10
  context.fail!
11
11
  end
data/config/routes.rb CHANGED
@@ -8,9 +8,9 @@ Importo::Engine.routes.draw do
8
8
  post :cancel
9
9
  end
10
10
  end
11
- get ':kind/new', to: 'imports#new', as: :new_import
12
- get ':kind/sample', to: 'imports#sample', as: :sample_import
13
- get ':kind/export', to: 'imports#export', as: :export
14
- get ':kind/:id/preview', to: 'imports#preview', as: :preview
15
- root to: 'imports#index'
11
+ get ":kind/new", to: "imports#new", as: :new_import
12
+ get ":kind/sample", to: "imports#sample", as: :sample_import
13
+ get ":kind/export", to: "imports#export", as: :export
14
+ get ":kind/:id/preview", to: "imports#preview", as: :preview
15
+ root to: "imports#index"
16
16
  end
@@ -8,7 +8,6 @@ if !defined?(Sidekiq::Batch)
8
8
  end
9
9
  end
10
10
 
11
-
12
11
  module Importo
13
12
  class SidekiqBatchAdapter
14
13
  attr_reader :description
@@ -32,13 +31,13 @@ module Importo
32
31
  end
33
32
 
34
33
  def finished?
35
- status = Sidekiq::Batch::Status.new( @instance.bid)
34
+ status = Sidekiq::Batch::Status.new(@instance.bid)
36
35
  status.complete?
37
36
  end
38
37
 
39
38
  class << self
40
- def import_job_base_class
41
- Object
39
+ def import_job_base_class_name
40
+ "Object"
42
41
  end
43
42
 
44
43
  def find(id)
@@ -76,6 +75,5 @@ end
76
75
 
77
76
  require_relative "../../../app/jobs/importo/import_job"
78
77
 
79
-
80
78
  Importo::ImportJob.send(:include, Sidekiq::Job)
81
79
  Importo::ImportJob.send(:include, Importo::SidekiqBatchAdapter::ImportJobIncludes)
@@ -46,7 +46,7 @@ module Importo
46
46
  option :current_import_owner, default: lambda {}
47
47
  option :queue_name, default: :import
48
48
  # You can either use GoodJob::Batch or Importo::SidekiqBatchAdapter
49
- option :batch_adapter, default: lambda { GoodJob::Batch }, proc: true
49
+ option :batch_adapter_name, default: "Importo::SidekiqBatchAdapter"
50
50
 
51
51
  option :admin_visible_imports, default: lambda { Importo::Import.where(importo_ownable: Importo.config.current_import_owner) }
52
52
  option(:admin_can_destroy,
@@ -54,10 +54,6 @@ module Importo
54
54
  false
55
55
  end)
56
56
 
57
- # You can either use GoodJob::Batch or Importo::SidekiqBatchAdapter
58
- option :batch_adapter, default: lambda { Importo::SidekiqBatchAdapter }, proc: true
59
- option :import_job_base_class, default: "Object"
60
-
61
57
  # Extra links relevant for this import: { link_name: { icon: 'far fa-..', url: '...' } }
62
58
  option(:admin_extra_links,
63
59
  default: lambda do |import|
@@ -84,5 +80,22 @@ module Importo
84
80
  def reset_config!
85
81
  @config = Configuration.new
86
82
  end
83
+
84
+ def sidekiq?
85
+ config.batch_adapter_name == "Importo::SidekiqBatchAdapter"
86
+ end
87
+
88
+ def good_job?
89
+ config.batch_adapter_name == "GoodJob::Batch"
90
+ end
91
+
92
+ def import_job_base_class_name
93
+ if sidekiq?
94
+ "Object"
95
+ else
96
+ require_dependency "importo/application_job"
97
+ "::Importo::ApplicationJob"
98
+ end
99
+ end
87
100
  end
88
101
  end
@@ -13,16 +13,5 @@ require "with_advisory_lock"
13
13
  module Importo
14
14
  class Engine < ::Rails::Engine
15
15
  isolate_namespace Importo
16
-
17
- initializer "importo.active_storage.attached" do
18
- config.after_initialize do
19
- ActiveSupport.on_load(:active_record) do
20
- Importo::Import.include(ImportHelpers)
21
-
22
- # For now put this here to ensure compatibility
23
- require "importo/adapters/sidekiq_batch_adapter"
24
- end
25
- end
26
- end
27
16
  end
28
17
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true-
2
2
 
3
- require "axlsx"
4
- require "roo/excelx"
3
+ # require "axlsx"
4
+ # require "roo/excelx"
5
5
 
6
6
  module Importo
7
7
  module TestHelpers
@@ -30,11 +30,10 @@ module Importo
30
30
  import.save!
31
31
  import.confirm
32
32
  import.schedule
33
- ImportService.perform(import: import)
34
- if Importo.config.batch_adapter == Importo::SidekiqBatchAdapter
35
- ImportJobCallback.new.on_success(:success,{import_id: import.id})
33
+ if Importo.sidekiq?
34
+ ImportJobCallback.new.on_complete(:success, {import_id: import.id})
36
35
  end
37
- import
36
+ import.reload
38
37
  end
39
38
  end
40
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Importo
4
- VERSION = "3.0.24"
4
+ VERSION = "3.0.25"
5
5
  end
data/lib/importo.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require_relative "importo/engine"
4
4
  require_relative "importo/acts_as_import_owner"
5
5
  require_relative "importo/import_column"
6
- require_relative "importo/import_helpers"
7
6
  require_relative "importo/configuration"
8
7
 
9
8
  module Importo
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: importo
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.24
4
+ version: 3.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Meij
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-12-09 00:00:00.000000000 Z
12
+ date: 2026-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caxlsx
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 3.0.1
20
+ version: '4'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 3.0.1
27
+ version: '4'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: csv
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -59,28 +59,28 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '2.7'
62
+ version: '3'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '2.7'
69
+ version: '3'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: roo-xls
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: '1.1'
76
+ version: '2'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: '1.1'
83
+ version: '2'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: satis
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -345,21 +345,6 @@ files:
345
345
  - db/migrate/20190827093548_add_selected_fields_to_import.rb
346
346
  - db/migrate/20230510051447_remove_result_from_imports.rb
347
347
  - db/migrate/20230510083043_create_importo_results.rb
348
- - db/migrate/20251117132125_create_good_jobs.rb
349
- - db/migrate/20251117132126_create_good_job_settings.rb
350
- - db/migrate/20251117132127_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb
351
- - db/migrate/20251117132128_create_good_job_batches.rb
352
- - db/migrate/20251117132129_create_good_job_executions.rb
353
- - db/migrate/20251117132130_create_good_jobs_error_event.rb
354
- - db/migrate/20251117132131_recreate_good_job_cron_indexes_with_conditional.rb
355
- - db/migrate/20251117132132_create_good_job_labels.rb
356
- - db/migrate/20251117132133_create_good_job_labels_index.rb
357
- - db/migrate/20251117132134_remove_good_job_active_id_index.rb
358
- - db/migrate/20251117132135_create_index_good_job_jobs_for_candidate_lookup.rb
359
- - db/migrate/20251117132136_create_good_job_execution_error_backtrace.rb
360
- - db/migrate/20251117132137_create_good_job_process_lock_ids.rb
361
- - db/migrate/20251117132138_create_good_job_process_lock_indexes.rb
362
- - db/migrate/20251117132139_create_good_job_execution_duration.rb
363
348
  - lib/generators/importo/USAGE
364
349
  - lib/generators/importo/importer_generator.rb
365
350
  - lib/generators/importo/install_generator.rb
@@ -376,7 +361,6 @@ files:
376
361
  - lib/importo/configuration.rb
377
362
  - lib/importo/engine.rb
378
363
  - lib/importo/import_column.rb
379
- - lib/importo/import_helpers.rb
380
364
  - lib/importo/test_helpers.rb
381
365
  - lib/importo/version.rb
382
366
  homepage: https://github.com/entdec/importo
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobs < ActiveRecord::Migration[7.0]
4
- def change
5
- # Uncomment for Postgres v12 or earlier to enable gen_random_uuid() support
6
- # enable_extension 'pgcrypto'
7
-
8
- create_table :good_jobs, id: :uuid do |t|
9
- t.text :queue_name
10
- t.integer :priority
11
- t.jsonb :serialized_params
12
- t.datetime :scheduled_at
13
- t.datetime :performed_at
14
- t.datetime :finished_at
15
- t.text :error
16
-
17
- t.timestamps
18
-
19
- t.uuid :active_job_id
20
- t.text :concurrency_key
21
- t.text :cron_key
22
- t.uuid :retried_good_job_id
23
- t.datetime :cron_at
24
- end
25
-
26
- create_table :good_job_processes, id: :uuid do |t|
27
- t.timestamps
28
- t.jsonb :state
29
- end
30
-
31
- add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: :index_good_jobs_on_scheduled_at
32
- add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
33
- add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
34
- add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", name: :index_good_jobs_on_concurrency_key_when_unfinished
35
- add_index :good_jobs, [:cron_key, :created_at], name: :index_good_jobs_on_cron_key_and_created_at
36
- add_index :good_jobs, [:cron_key, :cron_at], name: :index_good_jobs_on_cron_key_and_cron_at, unique: true
37
- add_index :good_jobs, [:active_job_id], name: :index_good_jobs_on_active_job_id
38
- add_index :good_jobs, [:finished_at], where: "retried_good_job_id IS NULL AND finished_at IS NOT NULL", name: :index_good_jobs_jobs_on_finished_at
39
- end
40
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobSettings < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.table_exists?(:good_job_settings)
10
- end
11
- end
12
-
13
- create_table :good_job_settings, id: :uuid do |t|
14
- t.timestamps
15
- t.text :key
16
- t.jsonb :value
17
- t.index :key, unique: true
18
- end
19
- end
20
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateIndexGoodJobsJobsOnPriorityCreatedAtWhenUnfinished < ActiveRecord::Migration[7.0]
4
- disable_ddl_transaction!
5
-
6
- def change
7
- reversible do |dir|
8
- dir.up do
9
- # Ensure this incremental update migration is idempotent
10
- # with monolithic install migration.
11
- return if connection.index_name_exists?(:good_jobs, :index_good_jobs_jobs_on_priority_created_at_when_unfinished)
12
- end
13
- end
14
-
15
- add_index :good_jobs, [:priority, :created_at], order: { priority: "DESC NULLS LAST", created_at: :asc },
16
- where: "finished_at IS NULL", name: :index_good_jobs_jobs_on_priority_created_at_when_unfinished,
17
- algorithm: :concurrently
18
- end
19
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobBatches < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.table_exists?(:good_job_batches)
10
- end
11
- end
12
-
13
- create_table :good_job_batches, id: :uuid do |t|
14
- t.timestamps
15
- t.text :description
16
- t.jsonb :serialized_properties
17
- t.text :on_finish
18
- t.text :on_success
19
- t.text :on_discard
20
- t.text :callback_queue_name
21
- t.integer :callback_priority
22
- t.datetime :enqueued_at
23
- t.datetime :discarded_at
24
- t.datetime :finished_at
25
- end
26
-
27
- change_table :good_jobs do |t|
28
- t.uuid :batch_id
29
- t.uuid :batch_callback_id
30
-
31
- t.index :batch_id, where: "batch_id IS NOT NULL"
32
- t.index :batch_callback_id, where: "batch_callback_id IS NOT NULL"
33
- end
34
- end
35
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobExecutions < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.table_exists?(:good_job_executions)
10
- end
11
- end
12
-
13
- create_table :good_job_executions, id: :uuid do |t|
14
- t.timestamps
15
-
16
- t.uuid :active_job_id, null: false
17
- t.text :job_class
18
- t.text :queue_name
19
- t.jsonb :serialized_params
20
- t.datetime :scheduled_at
21
- t.datetime :finished_at
22
- t.text :error
23
-
24
- t.index [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
25
- end
26
-
27
- change_table :good_jobs do |t|
28
- t.boolean :is_discrete
29
- t.integer :executions_count
30
- t.text :job_class
31
- end
32
- end
33
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobsErrorEvent < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.column_exists?(:good_jobs, :error_event)
10
- end
11
- end
12
-
13
- add_column :good_jobs, :error_event, :integer, limit: 2
14
- add_column :good_job_executions, :error_event, :integer, limit: 2
15
- end
16
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RecreateGoodJobCronIndexesWithConditional < ActiveRecord::Migration[7.0]
4
- disable_ddl_transaction!
5
-
6
- def change
7
- reversible do |dir|
8
- dir.up do
9
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at_cond)
10
- add_index :good_jobs, [:cron_key, :created_at], where: "(cron_key IS NOT NULL)",
11
- name: :index_good_jobs_on_cron_key_and_created_at_cond, algorithm: :concurrently
12
- end
13
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at_cond)
14
- add_index :good_jobs, [:cron_key, :cron_at], where: "(cron_key IS NOT NULL)", unique: true,
15
- name: :index_good_jobs_on_cron_key_and_cron_at_cond, algorithm: :concurrently
16
- end
17
-
18
- if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at)
19
- remove_index :good_jobs, name: :index_good_jobs_on_cron_key_and_created_at
20
- end
21
- if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at)
22
- remove_index :good_jobs, name: :index_good_jobs_on_cron_key_and_cron_at
23
- end
24
- end
25
-
26
- dir.down do
27
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at)
28
- add_index :good_jobs, [:cron_key, :created_at],
29
- name: :index_good_jobs_on_cron_key_and_created_at, algorithm: :concurrently
30
- end
31
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at)
32
- add_index :good_jobs, [:cron_key, :cron_at], unique: true,
33
- name: :index_good_jobs_on_cron_key_and_cron_at, algorithm: :concurrently
34
- end
35
-
36
- if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at_cond)
37
- remove_index :good_jobs, name: :index_good_jobs_on_cron_key_and_created_at_cond
38
- end
39
- if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at_cond)
40
- remove_index :good_jobs, name: :index_good_jobs_on_cron_key_and_cron_at_cond
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobLabels < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.column_exists?(:good_jobs, :labels)
10
- end
11
- end
12
-
13
- add_column :good_jobs, :labels, :text, array: true
14
- end
15
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobLabelsIndex < ActiveRecord::Migration[7.0]
4
- disable_ddl_transaction!
5
-
6
- def change
7
- reversible do |dir|
8
- dir.up do
9
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_labels)
10
- add_index :good_jobs, :labels, using: :gin, where: "(labels IS NOT NULL)",
11
- name: :index_good_jobs_on_labels, algorithm: :concurrently
12
- end
13
- end
14
-
15
- dir.down do
16
- if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_labels)
17
- remove_index :good_jobs, name: :index_good_jobs_on_labels
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RemoveGoodJobActiveIdIndex < ActiveRecord::Migration[7.0]
4
- disable_ddl_transaction!
5
-
6
- def change
7
- reversible do |dir|
8
- dir.up do
9
- if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_active_job_id)
10
- remove_index :good_jobs, name: :index_good_jobs_on_active_job_id
11
- end
12
- end
13
-
14
- dir.down do
15
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_active_job_id)
16
- add_index :good_jobs, :active_job_id, name: :index_good_jobs_on_active_job_id
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateIndexGoodJobJobsForCandidateLookup < ActiveRecord::Migration[7.0]
4
- disable_ddl_transaction!
5
-
6
- def change
7
- reversible do |dir|
8
- dir.up do
9
- # Ensure this incremental update migration is idempotent
10
- # with monolithic install migration.
11
- return if connection.index_name_exists?(:good_jobs, :index_good_job_jobs_for_candidate_lookup)
12
- end
13
- end
14
-
15
- add_index :good_jobs, [:priority, :created_at], order: { priority: "ASC NULLS LAST", created_at: :asc },
16
- where: "finished_at IS NULL", name: :index_good_job_jobs_for_candidate_lookup,
17
- algorithm: :concurrently
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobExecutionErrorBacktrace < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.column_exists?(:good_job_executions, :error_backtrace)
10
- end
11
- end
12
-
13
- add_column :good_job_executions, :error_backtrace, :text, array: true
14
- end
15
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobProcessLockIds < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.column_exists?(:good_jobs, :locked_by_id)
10
- end
11
- end
12
-
13
- add_column :good_jobs, :locked_by_id, :uuid
14
- add_column :good_jobs, :locked_at, :datetime
15
- add_column :good_job_executions, :process_id, :uuid
16
- add_column :good_job_processes, :lock_type, :integer, limit: 2
17
- end
18
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobProcessLockIndexes < ActiveRecord::Migration[7.0]
4
- disable_ddl_transaction!
5
-
6
- def change
7
- reversible do |dir|
8
- dir.up do
9
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked)
10
- add_index :good_jobs, [:priority, :scheduled_at],
11
- order: { priority: "ASC NULLS LAST", scheduled_at: :asc },
12
- where: "finished_at IS NULL AND locked_by_id IS NULL",
13
- name: :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked,
14
- algorithm: :concurrently
15
- end
16
-
17
- unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_locked_by_id)
18
- add_index :good_jobs, :locked_by_id,
19
- where: "locked_by_id IS NOT NULL",
20
- name: :index_good_jobs_on_locked_by_id,
21
- algorithm: :concurrently
22
- end
23
-
24
- unless connection.index_name_exists?(:good_job_executions, :index_good_job_executions_on_process_id_and_created_at)
25
- add_index :good_job_executions, [:process_id, :created_at],
26
- name: :index_good_job_executions_on_process_id_and_created_at,
27
- algorithm: :concurrently
28
- end
29
- end
30
-
31
- dir.down do
32
- remove_index(:good_jobs, name: :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked) if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked)
33
- remove_index(:good_jobs, name: :index_good_jobs_on_locked_by_id) if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_locked_by_id)
34
- remove_index(:good_job_executions, name: :index_good_job_executions_on_process_id_and_created_at) if connection.index_name_exists?(:good_job_executions, :index_good_job_executions_on_process_id_and_created_at)
35
- end
36
- end
37
- end
38
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGoodJobExecutionDuration < ActiveRecord::Migration[7.0]
4
- def change
5
- reversible do |dir|
6
- dir.up do
7
- # Ensure this incremental update migration is idempotent
8
- # with monolithic install migration.
9
- return if connection.column_exists?(:good_job_executions, :duration)
10
- end
11
- end
12
-
13
- add_column :good_job_executions, :duration, :interval
14
- end
15
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Importo::ImportHelpers
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- has_one_attached :original
8
- has_one_attached :result
9
- end
10
- end