ixport 0.3.2 → 0.4.0
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/README.md +42 -0
- data/app/controllers/ixport/application_controller.rb +4 -3
- data/app/controllers/ixport/export_templates_controller.rb +14 -5
- data/app/controllers/ixport/imports_controller.rb +14 -2
- data/app/controllers/ixport/previews_controller.rb +1 -0
- data/app/controllers/ixport/runs_controller.rb +17 -4
- data/app/models/ixport/export_template.rb +2 -0
- data/app/models/ixport/import.rb +42 -10
- data/app/models/ixport/wallflower_task_runner.rb +2 -2
- data/app/views/ixport/export_templates/edit.html.erb +1 -1
- data/app/views/ixport/export_templates/new.html.erb +1 -1
- data/app/views/ixport/imports/new.html.erb +8 -0
- data/app/views/ixport/imports/show.html.erb +7 -2
- data/app/views/ixport/previews/show.html.erb +9 -2
- data/lib/generators/ixport/install/install_generator.rb +1 -0
- data/lib/generators/ixport/install/templates/add_target_to_ixport_imports.rb.erb +5 -0
- data/lib/generators/ixport/update/update_generator.rb +4 -0
- data/lib/ixport/importer.rb +3 -2
- data/lib/ixport/registrations.rb +8 -5
- data/lib/ixport/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 317e30ac3c1fe5057cfe6ce21402e6e8e73cb4076966fee0b030f67eed212da8
|
|
4
|
+
data.tar.gz: 41f70d3512416fb810524d1defe824d7b1d550918542abda43b437a0a5ee93c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dca598f882beb292b8159f43bac75cd00d29cff9ad7d85ef15fbb5d090fde8048def9377cc2e25880de9ac0ab7d7ff20d09e029198752ba0556dc5ea0026aac3
|
|
7
|
+
data.tar.gz: 816ee37b957e2adfc0734aab39a35a48da427a614ec923a5a248034af8f3b77d95b17e2bb170c94a5171e1efa0929daeaf46b130617dd9586267387165c5a047
|
data/README.md
CHANGED
|
@@ -77,6 +77,39 @@ Each field's key is the record's attribute name. ixport looks up every match val
|
|
|
77
77
|
|
|
78
78
|
To match on several fields, name them all, for example `match_on :happened_on, :amount, :name`.
|
|
79
79
|
|
|
80
|
+
## Importing into a chosen record
|
|
81
|
+
|
|
82
|
+
An import can add its rows to one record the app chooses, such as questions uploaded into one quiz. The app names that record in the link to the upload page, as its Global ID:
|
|
83
|
+
|
|
84
|
+
```erb
|
|
85
|
+
<%= link_to "Import questions", ixport.new_import_path(key: :quiz_questions, target: @quiz.to_global_id) %>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The import keeps that record through upload, mapping, preview and run, and its importer reaches it as `target`:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
class QuizQuestionImporter < Ixport::Importer
|
|
92
|
+
field :prompt, label: "Question"
|
|
93
|
+
match_on :prompt
|
|
94
|
+
|
|
95
|
+
def records
|
|
96
|
+
target.questions
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A link naming a record that does not exist answers not found. Anyone can edit the link, so the registration's `allow:` rule decides which records a person may import into, as described under [Who may import or export](#who-may-import-or-export). An import opened with no record has a `target` of `nil` and works as before. Run `bin/rails generate ixport:update` after updating, for the migration that lets an import keep its record.
|
|
102
|
+
|
|
103
|
+
## Refusing a whole file
|
|
104
|
+
|
|
105
|
+
An import can be registered to save nothing from a file in which any row is refused, so a partial import never leaves half of a quiz's questions added:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Ixport.register_import(:quiz_questions, title: "Quiz questions", importer: "QuizQuestionImporter", refuse_whole_file: true)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
While any row is refused, its preview offers no Run import button and says to fix the refused rows in the file and upload it again. A run of such a file, inside the request or in the background, saves no row, records each refused row and counts no row added or updated. When every row is valid, every row is saved. If the run stops on an error, every row it saved is undone and it counts no row added or updated. Imports registered without `refuse_whole_file` work as before.
|
|
112
|
+
|
|
80
113
|
## Uploading and mapping a file
|
|
81
114
|
|
|
82
115
|
From the engine's root page, a person picks a registered import and uploads a CSV for it. The upload is refused, with a message on the upload page, when the file is not UTF-8 text, when its quoting is broken (the message names the line where reading stopped), when it has no header row, or, in an app with no background runner, when it has more rows than `config.inline_row_limit`. A byte order mark at the start of the file is ignored, so the first column keeps its name.
|
|
@@ -164,6 +197,15 @@ Ixport.register_import :products, title: "Products", importer: "ProductImporter"
|
|
|
164
197
|
allow: ->(person, account) { ProductImportPolicy.new(person, account).create? }
|
|
165
198
|
```
|
|
166
199
|
|
|
200
|
+
A rule that takes a third argument also receives the import's chosen record, or `nil` when there is none, and returns false to refuse it:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
Ixport.register_import :quiz_questions, title: "Quiz questions", importer: "QuizQuestionImporter",
|
|
204
|
+
allow: ->(person, account, quiz) { quiz.nil? || quiz.account == account }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
A link or upload naming a record the rule refuses answers not found, and so does every page of an import whose record it refuses. A rule that takes two arguments is called as before.
|
|
208
|
+
|
|
167
209
|
A person the rule refuses does not see that import or export listed, nor their past imports of it, and every one of its pages and downloads answers not found. A registration with no rule is open to every signed-in person.
|
|
168
210
|
|
|
169
211
|
## Claude Code agents
|
|
@@ -7,13 +7,14 @@ module Ixport
|
|
|
7
7
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
|
-
def allowed?(registration)
|
|
11
|
-
registration.allows?(ixport_person, ixport_account)
|
|
10
|
+
def allowed?(registration, target = nil)
|
|
11
|
+
registration.allows?(ixport_person, ixport_account, target)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def find_import
|
|
15
15
|
@import = Import.find_by(id: params[:import_id] || params[:id], person: ixport_person, account: ixport_account)
|
|
16
|
-
|
|
16
|
+
registration = @import && Ixport.registrations[@import.key.to_sym]
|
|
17
|
+
head :not_found unless registration && allowed?(registration, @import.target)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def ixport_person
|
|
@@ -10,8 +10,12 @@ module Ixport
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def create
|
|
13
|
-
ExportTemplate.
|
|
14
|
-
redirect_to exports_path
|
|
13
|
+
template = ExportTemplate.new(key: @registration.key, name: params[:name], columns: known_columns(@registration), person: ixport_person, account: ixport_account)
|
|
14
|
+
return redirect_to exports_path if template.save
|
|
15
|
+
|
|
16
|
+
@columns = @registration.exporter.constantize.columns
|
|
17
|
+
@name_error = template.errors[:name].first
|
|
18
|
+
render :new, status: :unprocessable_entity
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def show
|
|
@@ -24,8 +28,12 @@ module Ixport
|
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def update
|
|
27
|
-
@template.update
|
|
28
|
-
|
|
31
|
+
return redirect_to exports_path if @template.update(name: params[:name], columns: known_columns(@template.registration))
|
|
32
|
+
|
|
33
|
+
@columns = @template.registration.exporter.constantize.columns
|
|
34
|
+
@name_error = @template.errors[:name].first
|
|
35
|
+
@template.restore_attributes
|
|
36
|
+
render :edit, status: :unprocessable_entity
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
def destroy
|
|
@@ -37,7 +45,8 @@ module Ixport
|
|
|
37
45
|
|
|
38
46
|
def find_template
|
|
39
47
|
@template = ExportTemplate.find_by(id: params[:id], account: ixport_account)
|
|
40
|
-
|
|
48
|
+
registration = @template && Ixport.export_registrations[@template.key.to_sym]
|
|
49
|
+
head :not_found unless registration && allowed?(registration)
|
|
41
50
|
end
|
|
42
51
|
|
|
43
52
|
def find_chosen_export
|
|
@@ -4,6 +4,7 @@ module Ixport
|
|
|
4
4
|
class ImportsController < ApplicationController
|
|
5
5
|
before_action :find_registration, only: %i[new create]
|
|
6
6
|
before_action :find_import, only: :show
|
|
7
|
+
before_action :find_fields, only: %i[new create]
|
|
7
8
|
|
|
8
9
|
def index
|
|
9
10
|
@registrations = Ixport.registrations.values.select { |registration| allowed?(registration) }
|
|
@@ -18,7 +19,7 @@ module Ixport
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def create
|
|
21
|
-
import = Import.new(key: @registration.key, person: ixport_person, account: ixport_account, file: params[:file])
|
|
22
|
+
import = Import.new(key: @registration.key, person: ixport_person, account: ixport_account, target: @target, file: params[:file])
|
|
22
23
|
if import.save
|
|
23
24
|
redirect_to import_mapping_path(import)
|
|
24
25
|
else
|
|
@@ -31,7 +32,18 @@ module Ixport
|
|
|
31
32
|
|
|
32
33
|
def find_registration
|
|
33
34
|
@registration = Ixport.registrations[params[:key].to_s.to_sym]
|
|
34
|
-
|
|
35
|
+
@target = chosen_target
|
|
36
|
+
head :not_found unless @registration && (@target || params[:target].blank?) && allowed?(@registration, @target)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_fields
|
|
40
|
+
@fields = @registration.importer.constantize.fields
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def chosen_target
|
|
44
|
+
GlobalID::Locator.locate(params[:target]) if params[:target].present?
|
|
45
|
+
rescue ActiveRecord::RecordNotFound, NameError
|
|
46
|
+
nil
|
|
35
47
|
end
|
|
36
48
|
end
|
|
37
49
|
end
|
|
@@ -11,6 +11,7 @@ module Ixport
|
|
|
11
11
|
@counts = { add: 0, update: 0, refused: 0 }.merge(rows.group_by(&:outcome).transform_values(&:size))
|
|
12
12
|
@update_rows = rows.select { |row| row.outcome == :update }
|
|
13
13
|
@refused_rows = rows.select { |row| row.outcome == :refused }
|
|
14
|
+
@whole_file_refused = @import.whole_file_refused?(rows)
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
@@ -7,15 +7,28 @@ module Ixport
|
|
|
7
7
|
def create
|
|
8
8
|
runner = Ixport.background_runner
|
|
9
9
|
return run_in_request unless runner
|
|
10
|
-
return redirect_to import_path(@import) unless
|
|
10
|
+
return redirect_to import_path(@import) unless claim_pending_import
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
runner.enqueue(@import)
|
|
14
|
-
redirect_to @import.wallflower_task_id ? wallflower.task_path(@import.wallflower_task_id) : import_path(@import)
|
|
12
|
+
hand_over(runner)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
private
|
|
18
16
|
|
|
17
|
+
def claim_pending_import
|
|
18
|
+
claimed = Import.where(id: @import.id, status: "pending").update_all(status: "running", updated_at: Time.current) == 1
|
|
19
|
+
@import.reload if claimed
|
|
20
|
+
claimed
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def hand_over(runner)
|
|
24
|
+
runner.enqueue(@import)
|
|
25
|
+
redirect_to @import.wallflower_task_id ? wallflower.task_path(@import.wallflower_task_id) : import_path(@import)
|
|
26
|
+
rescue StandardError => error
|
|
27
|
+
Rails.error.report(error)
|
|
28
|
+
@import.update!(status: "pending")
|
|
29
|
+
redirect_to import_preview_path(@import), flash: { ixport_not_started: true }
|
|
30
|
+
end
|
|
31
|
+
|
|
19
32
|
def run_in_request
|
|
20
33
|
@import.run!
|
|
21
34
|
redirect_to import_path(@import)
|
data/app/models/ixport/import.rb
CHANGED
|
@@ -8,15 +8,20 @@ module Ixport
|
|
|
8
8
|
|
|
9
9
|
belongs_to :person, polymorphic: true
|
|
10
10
|
belongs_to :account, polymorphic: true, optional: true
|
|
11
|
+
belongs_to :target, polymorphic: true, optional: true
|
|
11
12
|
|
|
12
13
|
has_one_attached :file
|
|
13
14
|
has_many :refusals, -> { order(:row_number) }, class_name: "Ixport::ImportRefusal", dependent: :destroy
|
|
14
15
|
|
|
16
|
+
STALLED_AFTER = 1.hour
|
|
17
|
+
|
|
15
18
|
validate :incoming_file_is_usable, on: :create
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
attr_reader :run_error
|
|
21
|
+
|
|
22
|
+
def run!(reporter: NullReporter.new, report_failure: true)
|
|
18
23
|
with_lock do
|
|
19
|
-
walk_and_save(reporter) if status.in?(%w[pending running])
|
|
24
|
+
walk_and_save(reporter, report_failure) if status.in?(%w[pending running])
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
|
|
@@ -28,6 +33,10 @@ module Ixport
|
|
|
28
33
|
status == "running"
|
|
29
34
|
end
|
|
30
35
|
|
|
36
|
+
def stalled?
|
|
37
|
+
running? && updated_at < STALLED_AFTER.ago
|
|
38
|
+
end
|
|
39
|
+
|
|
31
40
|
def failed?
|
|
32
41
|
status == "failed"
|
|
33
42
|
end
|
|
@@ -46,8 +55,12 @@ module Ixport
|
|
|
46
55
|
RowWalk.new(self, importer).rows
|
|
47
56
|
end
|
|
48
57
|
|
|
58
|
+
def whole_file_refused?(rows = self.rows)
|
|
59
|
+
Ixport.registration(key).refuse_whole_file && rows.any? { |row| row.outcome == :refused }
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
def importer
|
|
50
|
-
importer_class.new(person: person, account: account)
|
|
63
|
+
importer_class.new(person: person, account: account, target: target)
|
|
51
64
|
end
|
|
52
65
|
|
|
53
66
|
def importer_class
|
|
@@ -72,21 +85,40 @@ module Ixport
|
|
|
72
85
|
|
|
73
86
|
private
|
|
74
87
|
|
|
75
|
-
def walk_and_save(reporter)
|
|
88
|
+
def walk_and_save(reporter, report_failure)
|
|
76
89
|
counts = { add: 0, update: 0 }
|
|
77
|
-
rows =
|
|
90
|
+
rows = rows_to_walk
|
|
78
91
|
reporter.set_total(rows.size)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
saving_together(counts) do
|
|
93
|
+
rows.each do |row|
|
|
94
|
+
save_row(row, reporter)
|
|
95
|
+
counts[row.outcome] += 1 if counts.key?(row.outcome)
|
|
96
|
+
reporter.advance
|
|
97
|
+
end
|
|
83
98
|
end
|
|
84
99
|
update!(status: "finished", added_count: counts[:add], updated_count: counts[:update])
|
|
85
100
|
rescue StandardError => error
|
|
86
|
-
|
|
101
|
+
@run_error = error
|
|
102
|
+
Rails.error.report(error) if report_failure
|
|
87
103
|
update!(status: "failed", error_message: error.message, added_count: counts[:add], updated_count: counts[:update])
|
|
88
104
|
end
|
|
89
105
|
|
|
106
|
+
def saving_together(counts, &block)
|
|
107
|
+
return yield unless Ixport.registration(key).refuse_whole_file
|
|
108
|
+
|
|
109
|
+
begin
|
|
110
|
+
self.class.transaction(requires_new: true, &block)
|
|
111
|
+
rescue StandardError
|
|
112
|
+
counts.transform_values! { 0 }
|
|
113
|
+
raise
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def rows_to_walk
|
|
118
|
+
rows = self.rows
|
|
119
|
+
whole_file_refused?(rows) ? rows.select { |row| row.outcome == :refused } : rows
|
|
120
|
+
end
|
|
121
|
+
|
|
90
122
|
def save_row(row, reporter)
|
|
91
123
|
if row.outcome == :refused
|
|
92
124
|
refusals.create!(row_number: row.number, reason: row.reason)
|
|
@@ -6,9 +6,9 @@ module Ixport
|
|
|
6
6
|
|
|
7
7
|
def call(task)
|
|
8
8
|
import = Import.find(task.params["import_id"])
|
|
9
|
-
|
|
9
|
+
import.run!(reporter: task, report_failure: false)
|
|
10
10
|
task.link_result(ixport.import_path(import))
|
|
11
|
-
raise ImportFailed
|
|
11
|
+
raise(import.run_error || ImportFailed.new(import.error_message)) if import.failed?
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%= ui_form_page(title: "Edit #{@template.name}", subtitle: @template.registration.title, back_url: exports_path) %>
|
|
2
2
|
<%= ui_page(max_width: :md) do %>
|
|
3
3
|
<%= ui_form(action: export_template_path(@template), method: :patch) do %>
|
|
4
|
-
<%= ui_form_field(attribute: "name", label: "Template name", required: true, value: @template.name) %>
|
|
4
|
+
<%= ui_form_field(attribute: "name", label: "Template name", required: true, value: @template.name, errors: @name_error.present? ? [tag.span(@name_error, data: { name_error: true })] : []) %>
|
|
5
5
|
<% column_options = @columns.map { |column| [column.label, column.key] } %>
|
|
6
6
|
<% @columns.each_index do |index| %>
|
|
7
7
|
<%= ui_form_field(attribute: "columns[]", label: "Column #{index + 1}", type: :select, options: column_options, include_blank: "None", value: @template.columns[index]) %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%= ui_form_page(title: "New #{@registration.title} template", back_url: exports_path) %>
|
|
2
2
|
<%= ui_page(max_width: :md) do %>
|
|
3
3
|
<%= ui_form(action: export_templates_path(@registration.key)) do %>
|
|
4
|
-
<%= ui_form_field(attribute: "name", label: "Template name", required: true) %>
|
|
4
|
+
<%= ui_form_field(attribute: "name", label: "Template name", required: true, errors: @name_error.present? ? [tag.span(@name_error, data: { name_error: true })] : []) %>
|
|
5
5
|
<% column_options = @columns.map { |column| [column.label, column.key] } %>
|
|
6
6
|
<% @columns.each_index do |index| %>
|
|
7
7
|
<%= ui_form_field(attribute: "columns[]", label: "Column #{index + 1}", type: :select, options: column_options, include_blank: "None") %>
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
<% if @error.present? %>
|
|
4
4
|
<%= ui_alert(type: :error, message: tag.span(@error, data: { upload_error: true })) %>
|
|
5
5
|
<% end %>
|
|
6
|
+
<%= ui_section(title: "Fields this import expects") do %>
|
|
7
|
+
<ul>
|
|
8
|
+
<% @fields.each do |field| %>
|
|
9
|
+
<%= tag.li(field.label, data: { expected_field: true }) %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</ul>
|
|
12
|
+
<% end %>
|
|
6
13
|
<%= ui_form(action: imports_path, multipart: true) do %>
|
|
7
14
|
<%= hidden_field_tag "key", @registration.key %>
|
|
15
|
+
<%= hidden_field_tag "target", @target.to_global_id if @target %>
|
|
8
16
|
<%= ui_file_upload(name: "file", label: "CSV file", accept: ".csv") %>
|
|
9
17
|
<%= ui_button(label: "Upload") %>
|
|
10
18
|
<% end %>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
<% heading = if @import.
|
|
1
|
+
<% heading = if @import.stalled?
|
|
2
|
+
"Import stalled"
|
|
3
|
+
elsif @import.running?
|
|
2
4
|
"Import running"
|
|
3
5
|
elsif @import.failed?
|
|
4
6
|
"Import failed"
|
|
@@ -8,7 +10,10 @@
|
|
|
8
10
|
<%= ui_show_page(title: heading, back_url: root_path) %>
|
|
9
11
|
<%= ui_page(max_width: :md) do %>
|
|
10
12
|
<%= ui_page_header(title: heading) %>
|
|
11
|
-
<% if @import.
|
|
13
|
+
<% if @import.stalled? %>
|
|
14
|
+
<%= ui_alert(type: :error, message: tag.span("This import has stopped making progress.", data: { import_stalled: true })) %>
|
|
15
|
+
<p>Rows saved before it stopped stay saved.</p>
|
|
16
|
+
<% elsif @import.running? %>
|
|
12
17
|
<%= ui_alert(type: :info, message: tag.span("This import is running.", data: { import_running: true })) %>
|
|
13
18
|
<% else %>
|
|
14
19
|
<% if @import.failed? %>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<%= ui_show_page(title: "Preview", back_url: import_mapping_path(@import)) %>
|
|
2
2
|
<%= ui_page(max_width: :md) do %>
|
|
3
3
|
<%= ui_page_header(title: "Preview") %>
|
|
4
|
+
<% if flash[:ixport_not_started].present? %>
|
|
5
|
+
<%= ui_alert(type: :error, message: tag.span("This import did not start. Run it again.", data: { import_not_started: true })) %>
|
|
6
|
+
<% end %>
|
|
4
7
|
<%= ui_grid(cols: { default: 1, sm: 3 }) do %>
|
|
5
8
|
<%= ui_stat_card(label: "Will add", value: tag.span(@counts[:add], data: { count: "add" })) %>
|
|
6
9
|
<%= ui_stat_card(label: "Will update", value: tag.span(@counts[:update], data: { count: "update" })) %>
|
|
@@ -29,7 +32,11 @@
|
|
|
29
32
|
</ul>
|
|
30
33
|
<% end %>
|
|
31
34
|
<% end %>
|
|
32
|
-
|
|
33
|
-
<%=
|
|
35
|
+
<% if @whole_file_refused %>
|
|
36
|
+
<%= ui_alert(type: :error, message: tag.span("This import saves no row while any row is refused. Fix the refused rows in the file and upload it again.", data: { whole_file_refused: true })) %>
|
|
37
|
+
<% else %>
|
|
38
|
+
<%= ui_form(action: import_run_path(@import)) do %>
|
|
39
|
+
<%= ui_button(label: "Run import") %>
|
|
40
|
+
<% end %>
|
|
34
41
|
<% end %>
|
|
35
42
|
<% end %>
|
|
@@ -18,6 +18,7 @@ module Ixport
|
|
|
18
18
|
migration_template "create_ixport_export_templates.rb.erb", "db/migrate/create_ixport_export_templates.rb"
|
|
19
19
|
migration_template "add_wallflower_task_to_ixport_imports.rb.erb", "db/migrate/add_wallflower_task_to_ixport_imports.rb"
|
|
20
20
|
migration_template "add_error_message_to_ixport_imports.rb.erb", "db/migrate/add_error_message_to_ixport_imports.rb"
|
|
21
|
+
migration_template "add_target_to_ixport_imports.rb.erb", "db/migrate/add_target_to_ixport_imports.rb"
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
end
|
|
@@ -23,6 +23,10 @@ module Ixport
|
|
|
23
23
|
def copy_error_message_migration
|
|
24
24
|
migration_template "add_error_message_to_ixport_imports.rb.erb", "db/migrate/add_error_message_to_ixport_imports.rb"
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
def copy_target_migration
|
|
28
|
+
migration_template "add_target_to_ixport_imports.rb.erb", "db/migrate/add_target_to_ixport_imports.rb"
|
|
29
|
+
end
|
|
26
30
|
end
|
|
27
31
|
end
|
|
28
32
|
end
|
data/lib/ixport/importer.rb
CHANGED
|
@@ -4,11 +4,12 @@ module Ixport
|
|
|
4
4
|
class Importer
|
|
5
5
|
Field = Struct.new(:key, :label, keyword_init: true)
|
|
6
6
|
|
|
7
|
-
attr_reader :person, :account
|
|
7
|
+
attr_reader :person, :account, :target
|
|
8
8
|
|
|
9
|
-
def initialize(person:, account:)
|
|
9
|
+
def initialize(person:, account:, target: nil)
|
|
10
10
|
@person = person
|
|
11
11
|
@account = account
|
|
12
|
+
@target = target
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def self.field(key, label:)
|
data/lib/ixport/registrations.rb
CHANGED
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module Ixport
|
|
4
4
|
module AccessRule
|
|
5
|
-
def allows?(person, account)
|
|
6
|
-
allow.nil?
|
|
5
|
+
def allows?(person, account, target = nil)
|
|
6
|
+
return true if allow.nil?
|
|
7
|
+
return allow.call(person, account) if allow.arity == 2
|
|
8
|
+
|
|
9
|
+
allow.call(person, account, target)
|
|
7
10
|
end
|
|
8
11
|
end
|
|
9
12
|
|
|
10
|
-
Registration = Struct.new(:key, :title, :importer, :allow, keyword_init: true) { include AccessRule }
|
|
13
|
+
Registration = Struct.new(:key, :title, :importer, :allow, :refuse_whole_file, keyword_init: true) { include AccessRule }
|
|
11
14
|
ExportRegistration = Struct.new(:key, :title, :exporter, :chosen, :allow, keyword_init: true) { include AccessRule }
|
|
12
15
|
|
|
13
16
|
class MissingImporter < StandardError; end
|
|
14
17
|
class MissingExporter < StandardError; end
|
|
15
18
|
|
|
16
|
-
def self.register_import(key, title:, importer:, allow: nil)
|
|
17
|
-
registrations[key.to_sym] = Registration.new(key: key.to_sym, title: title, importer: importer.to_s, allow: allow)
|
|
19
|
+
def self.register_import(key, title:, importer:, allow: nil, refuse_whole_file: false)
|
|
20
|
+
registrations[key.to_sym] = Registration.new(key: key.to_sym, title: title, importer: importer.to_s, allow: allow, refuse_whole_file: refuse_whole_file)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def self.registration(key)
|
data/lib/ixport/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ixport
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tyler Schneider
|
|
@@ -118,6 +118,7 @@ files:
|
|
|
118
118
|
- config/routes.rb
|
|
119
119
|
- lib/generators/ixport/install/install_generator.rb
|
|
120
120
|
- lib/generators/ixport/install/templates/add_error_message_to_ixport_imports.rb.erb
|
|
121
|
+
- lib/generators/ixport/install/templates/add_target_to_ixport_imports.rb.erb
|
|
121
122
|
- lib/generators/ixport/install/templates/add_wallflower_task_to_ixport_imports.rb.erb
|
|
122
123
|
- lib/generators/ixport/install/templates/create_ixport_export_templates.rb.erb
|
|
123
124
|
- lib/generators/ixport/install/templates/create_ixport_import_refusals.rb.erb
|