importo 3.0.26 → 3.0.28
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.
- checksums.yaml +4 -4
- data/app/controllers/importo/imports_controller.rb +11 -6
- data/app/importers/importo/base_importer.rb +0 -5
- data/app/importers/importo/import_job_callback.rb +27 -17
- data/app/jobs/importo/export_job.rb +22 -0
- data/app/jobs/importo/import_job.rb +5 -5
- data/app/models/importo/import.rb +25 -4
- data/app/tables/importo/imports_table.rb +1 -1
- data/app/tables/importo/mensa_imports_table.rb +1 -1
- data/config/locales/en.yml +2 -2
- data/config/locales/nl.yml +1 -1
- data/lib/importo/adapters/sidekiq_batch_adapter.rb +5 -0
- data/lib/importo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 013fc113e0d5a9cd16cd888462a9d5437ee21d329cbe680ed5a905b6fd53dd23
|
|
4
|
+
data.tar.gz: 2d7b1fadf7a61b4527d02bfc3a206096f0acaa413b26a3cf61ebb43f15a45ef5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 967c9f7fc07adadaab9707cd6e6b87098a9672026871992b86d985d73e270bb5269cbd2ae244e0c30e85d89f8bbae4e28bc469e07c8bf3013f0820d8a11f6060
|
|
7
|
+
data.tar.gz: 2c6f5a588b252208692070db52fb65e3a5aa74a42040f9e30acdb8d085725672db888e143b76e28a02ba7b3de3ba8f1b20c33c45af15a52c4b4ea15884ac62a0
|
|
@@ -86,15 +86,13 @@ module Importo
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def sample
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename: import.importer.file_name("sample")
|
|
89
|
+
create_export(:sample)
|
|
90
|
+
redirect_back(fallback_location: importo.new_import_path(params[:kind]))
|
|
92
91
|
end
|
|
93
92
|
|
|
94
93
|
def export
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename: import.importer.file_name("export")
|
|
94
|
+
create_export(:export)
|
|
95
|
+
redirect_back(fallback_location: importo.new_import_path(params[:kind]))
|
|
98
96
|
end
|
|
99
97
|
|
|
100
98
|
def index
|
|
@@ -103,6 +101,13 @@ module Importo
|
|
|
103
101
|
|
|
104
102
|
private
|
|
105
103
|
|
|
104
|
+
def create_export(kind)
|
|
105
|
+
import = Import.new(kind: params[:kind], locale: I18n.locale,
|
|
106
|
+
importo_ownable: Importo.config.current_import_owner.call)
|
|
107
|
+
import.save!(validate: false)
|
|
108
|
+
ExportJob.perform_later(import.id, kind)
|
|
109
|
+
end
|
|
110
|
+
|
|
106
111
|
def import_params
|
|
107
112
|
params.require(:import).permit(:original, :kind, :column_overrides,
|
|
108
113
|
column_overrides: params.dig(:import, :column_overrides)&.keys)
|
|
@@ -28,11 +28,6 @@ module Importo
|
|
|
28
28
|
I18n.locale = import.locale if import&.locale # Should we do this?? here??
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def state_changed(_import, transition)
|
|
32
|
-
run_callbacks(transition.to_name) do
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
31
|
class << self
|
|
37
32
|
def t(key, options = {})
|
|
38
33
|
if I18n.exists? :"importers.#{name.underscore}#{key}"
|
|
@@ -10,23 +10,29 @@ module Importo
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def complete_import(import)
|
|
13
|
-
if import.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
13
|
+
return if import.blank?
|
|
14
|
+
|
|
15
|
+
# Sidekiq's batch :complete event can fire as soon as every row has been
|
|
16
|
+
# attempted once, even though some rows failed and are still awaiting a
|
|
17
|
+
# retry. Bail out here so we don't finalize (and report a wrong count)
|
|
18
|
+
# before every row has actually finished processing.
|
|
19
|
+
return unless import.results.count == import.importer.send(:row_count)
|
|
20
|
+
|
|
21
|
+
results_file = import.importer.results_file
|
|
22
|
+
results_file = results_file.is_a?(StringIO) ? results_file : File.open(results_file)
|
|
23
|
+
|
|
24
|
+
import.result.attach(io: results_file, filename: import.importer.file_name("results"),
|
|
25
|
+
content_type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
|
26
|
+
|
|
27
|
+
ActiveRecord::Base.uncached do
|
|
28
|
+
import.result_message = I18n.t("importo.importers.result_message",
|
|
29
|
+
nr: import.results.where("details @> ?", {state: "success"}.to_json).count, of: import.importer.send(:row_count))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if import.can_complete?
|
|
33
|
+
import.complete!
|
|
34
|
+
else
|
|
35
|
+
import.save!
|
|
30
36
|
end
|
|
31
37
|
end
|
|
32
38
|
|
|
@@ -36,5 +42,9 @@ module Importo
|
|
|
36
42
|
import = Import.find(options["import_id"])
|
|
37
43
|
complete_import(import)
|
|
38
44
|
end
|
|
45
|
+
|
|
46
|
+
# Sidekiq's batch :success event only fires once every job (including
|
|
47
|
+
# retries) has truly succeeded, so it is the reliable place to finalize.
|
|
48
|
+
alias_method :on_success, :on_complete
|
|
39
49
|
end
|
|
40
50
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Importo
|
|
2
|
+
class ExportJob < ApplicationJob
|
|
3
|
+
CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
4
|
+
|
|
5
|
+
def perform(import_id, kind)
|
|
6
|
+
import = Import.find(import_id)
|
|
7
|
+
import.start_export!
|
|
8
|
+
|
|
9
|
+
importer = import.importer
|
|
10
|
+
file_kind = kind.to_sym
|
|
11
|
+
file = importer.public_send(:"#{file_kind}_file")
|
|
12
|
+
|
|
13
|
+
filename = importer.file_name(file_kind)
|
|
14
|
+
import.result.attach(io: file, filename: filename, content_type: CONTENT_TYPE)
|
|
15
|
+
import.update!(result_message: filename)
|
|
16
|
+
import.complete_export!
|
|
17
|
+
rescue => error
|
|
18
|
+
import&.update!(result_message: "Exception: #{error.message}")
|
|
19
|
+
import&.failure!
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -16,13 +16,13 @@ module Importo
|
|
|
16
16
|
# - Sidekiq calls on_complete callback when all jobs ran at least once.
|
|
17
17
|
# - GoodJob calls on_complete callback when all jobs are done (including retries).
|
|
18
18
|
# i.e. this logic is only needed for sidekiq
|
|
19
|
-
return unless Importo.sidekiq?
|
|
19
|
+
# return unless Importo.sidekiq?
|
|
20
20
|
|
|
21
|
-
batch = Importo::SidekiqBatchAdapter.find(bid)
|
|
21
|
+
# batch = Importo::SidekiqBatchAdapter.find(bid)
|
|
22
22
|
|
|
23
|
-
if !import.completed? && import.can_complete? && batch.finished?
|
|
24
|
-
|
|
25
|
-
end
|
|
23
|
+
# if !import.completed? && import.can_complete? && batch.finished?
|
|
24
|
+
# ImportJobCallback.perform_now(batch, import.id)
|
|
25
|
+
# end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -10,7 +10,7 @@ module Importo
|
|
|
10
10
|
has_many :results, class_name: "Importo::Result", dependent: :delete_all
|
|
11
11
|
|
|
12
12
|
validates :kind, presence: true
|
|
13
|
-
validates :original, presence: true
|
|
13
|
+
validates :original, presence: true, unless: :export_state?
|
|
14
14
|
validate :content_validator
|
|
15
15
|
|
|
16
16
|
has_one_attached :original
|
|
@@ -23,10 +23,11 @@ module Importo
|
|
|
23
23
|
state :completed
|
|
24
24
|
state :failed
|
|
25
25
|
state :reverted
|
|
26
|
+
state :exporting
|
|
27
|
+
state :exported
|
|
28
|
+
state :reverting
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
imprt.importer.state_changed(imprt, transition)
|
|
29
|
-
end
|
|
30
|
+
around_transition :run_transition_callbacks
|
|
30
31
|
|
|
31
32
|
after_transition any => :scheduled, :do => :schedule_import
|
|
32
33
|
after_transition any => :reverting, :do => :schedule_revert
|
|
@@ -53,6 +54,14 @@ module Importo
|
|
|
53
54
|
transition any => :failed
|
|
54
55
|
end
|
|
55
56
|
|
|
57
|
+
event :start_export do
|
|
58
|
+
transition concept: :exporting
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
event :complete_export do
|
|
62
|
+
transition exporting: :exported
|
|
63
|
+
end
|
|
64
|
+
|
|
56
65
|
event :revert do
|
|
57
66
|
transition completed: :reverting
|
|
58
67
|
end
|
|
@@ -71,6 +80,8 @@ module Importo
|
|
|
71
80
|
end
|
|
72
81
|
|
|
73
82
|
def content_validator
|
|
83
|
+
return if export_state?
|
|
84
|
+
|
|
74
85
|
unless importer.structure_valid?
|
|
75
86
|
errors.add(:original,
|
|
76
87
|
I18n.t("importo.errors.structure_invalid",
|
|
@@ -109,6 +120,16 @@ module Importo
|
|
|
109
120
|
results.where("details @> ?", '{"state":"processing"}').none?
|
|
110
121
|
end
|
|
111
122
|
|
|
123
|
+
def export_state?
|
|
124
|
+
%w[exporting exported failed].include?(state) && original.blank?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def run_transition_callbacks(transition)
|
|
128
|
+
importer.run_callbacks(transition.to_name) do
|
|
129
|
+
yield
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
112
133
|
private
|
|
113
134
|
|
|
114
135
|
def schedule_import
|
|
@@ -44,7 +44,7 @@ if defined? Mensa
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
def scope
|
|
47
|
-
@scope = Importo.config.admin_visible_imports.call
|
|
47
|
+
@scope = Importo.config.admin_visible_imports.call.where.not(state: %w[exporting exported])
|
|
48
48
|
@scope = @scope.joins("LEFT JOIN users on importo_imports.importo_ownable_type = 'User' and importo_imports.importo_ownable_id = users.id ")
|
|
49
49
|
end
|
|
50
50
|
|
data/config/locales/en.yml
CHANGED
|
@@ -27,8 +27,8 @@ en:
|
|
|
27
27
|
new:
|
|
28
28
|
submit: "Import"
|
|
29
29
|
title: Import
|
|
30
|
-
explanation_html: A CSV or Excel file can be used to import records. The first row should be the column names.<br>If an <b>id</b> is supplied it will update the matching record instead of creating a new one.<br>Download a <a href='%{sample_path}'
|
|
31
|
-
export_html: You can download the currently stored records.<br>Download the <a href='%{export_path}'
|
|
30
|
+
explanation_html: A CSV or Excel file can be used to import records. The first row should be the column names.<br>If an <b>id</b> is supplied it will update the matching record instead of creating a new one.<br>Download a <a href='%{sample_path}'>sample template</a> with all supported column names and their explanation.
|
|
31
|
+
export_html: You can download the currently stored records.<br>Download the <a href='%{export_path}'>current data</a> with all supported columns
|
|
32
32
|
error_explanation: "The following problems prohibited this import from completing:"
|
|
33
33
|
import: Import %{kind}
|
|
34
34
|
import_button: Import
|
data/config/locales/nl.yml
CHANGED
|
@@ -18,7 +18,7 @@ nl:
|
|
|
18
18
|
new:
|
|
19
19
|
import:
|
|
20
20
|
title: Importeer
|
|
21
|
-
explanation_html: Een CSV of Excel bestand kan gebruikt worden om records te importeren. De eerste rij moet de kolom namen bevatten.<br>Als een <b>id</b> is ingevuld, zal het bestaande record worden geupdate in plaats van een nieuwe aan te maken.<br>Download een <a href='%{sample_path}'
|
|
21
|
+
explanation_html: Een CSV of Excel bestand kan gebruikt worden om records te importeren. De eerste rij moet de kolom namen bevatten.<br>Als een <b>id</b> is ingevuld, zal het bestaande record worden geupdate in plaats van een nieuwe aan te maken.<br>Download een <a href='%{sample_path}'>voorbeeld file</a> met alle ondersteunde kolomnamen en de bijbehorende uitleg.
|
|
22
22
|
error_explanation: "De import is mislukt door de volgende problemen:"
|
|
23
23
|
import: Import %{kind}
|
|
24
24
|
import_button: Importeer
|
|
@@ -21,7 +21,12 @@ module Importo
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def on_success(job)
|
|
24
|
+
# Register for both events: :complete can fire prematurely while rows are
|
|
25
|
+
# still awaiting a Sidekiq retry, :success only fires once every row has
|
|
26
|
+
# truly succeeded. ImportJobCallback itself guards against finalizing
|
|
27
|
+
# before every row has actually finished processing.
|
|
24
28
|
@instance.on(:complete, job.constantize, properties)
|
|
29
|
+
@instance.on(:success, job.constantize, properties)
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
def add
|
data/lib/importo/version.rb
CHANGED
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.
|
|
4
|
+
version: 3.0.28
|
|
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: 2026-
|
|
12
|
+
date: 2026-08-27 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: caxlsx
|
|
@@ -318,6 +318,7 @@ files:
|
|
|
318
318
|
- app/importers/importo/base_importer.rb
|
|
319
319
|
- app/importers/importo/import_job_callback.rb
|
|
320
320
|
- app/jobs/importo/application_job.rb
|
|
321
|
+
- app/jobs/importo/export_job.rb
|
|
321
322
|
- app/jobs/importo/import_job.rb
|
|
322
323
|
- app/jobs/importo/import_revert_job.rb
|
|
323
324
|
- app/jobs/importo/import_schedule_job.rb
|