importo 2.0.4
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +99 -0
- data/Rakefile +39 -0
- data/app/assets/config/importo_manifest.js +2 -0
- data/app/assets/javascripts/importo/application.js +13 -0
- data/app/assets/stylesheets/importo/application.css +15 -0
- data/app/controllers/concerns/maintenance_standards.rb +24 -0
- data/app/controllers/importo/application_controller.rb +8 -0
- data/app/controllers/importo/imports_controller.rb +65 -0
- data/app/helpers/importo/application_helper.rb +17 -0
- data/app/importers/concerns/exportable.rb +128 -0
- data/app/importers/concerns/importable.rb +168 -0
- data/app/importers/concerns/importer_dsl.rb +122 -0
- data/app/importers/concerns/original.rb +150 -0
- data/app/importers/concerns/result_feedback.rb +69 -0
- data/app/importers/concerns/revertable.rb +41 -0
- data/app/importers/importo/base_importer.rb +32 -0
- data/app/mailers/importo/application_mailer.rb +8 -0
- data/app/models/importo/application_record.rb +7 -0
- data/app/models/importo/import.rb +93 -0
- data/app/services/importo/application_context.rb +6 -0
- data/app/services/importo/application_service.rb +9 -0
- data/app/services/importo/callback_service.rb +14 -0
- data/app/services/importo/import_context.rb +9 -0
- data/app/services/importo/import_service.rb +15 -0
- data/app/services/importo/revert_service.rb +14 -0
- data/app/tables/importo/imports_table.rb +39 -0
- data/app/views/importo/imports/index.html.slim +2 -0
- data/app/views/importo/imports/new.html.slim +24 -0
- data/config/locales/en.yml +33 -0
- data/config/locales/nl.yml +28 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20180409151031_create_importo_import.rb +21 -0
- data/db/migrate/20180628175535_add_locale_importo_import.rb +7 -0
- data/db/migrate/20190827093548_add_selected_fields_to_import.rb +5 -0
- data/lib/generators/importo/USAGE +8 -0
- data/lib/generators/importo/importer_generator.rb +10 -0
- data/lib/generators/importo/install_generator.rb +27 -0
- data/lib/generators/templates/README +14 -0
- data/lib/generators/templates/application_importer.rb +4 -0
- data/lib/generators/templates/importer.rb +24 -0
- data/lib/generators/templates/importo.rb +21 -0
- data/lib/importo/acts_as_import_owner.rb +11 -0
- data/lib/importo/configuration.rb +68 -0
- data/lib/importo/engine.rb +23 -0
- data/lib/importo/import_column.rb +55 -0
- data/lib/importo/import_helpers.rb +10 -0
- data/lib/importo/version.rb +5 -0
- data/lib/importo.rb +29 -0
- metadata +332 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
en:
|
2
|
+
helpers:
|
3
|
+
submit:
|
4
|
+
importo/import:
|
5
|
+
create: "Import"
|
6
|
+
importo:
|
7
|
+
sheet:
|
8
|
+
results:
|
9
|
+
name: Results
|
10
|
+
explanation:
|
11
|
+
name: Explanation
|
12
|
+
column: Column
|
13
|
+
explanation: Explanation
|
14
|
+
imports:
|
15
|
+
index:
|
16
|
+
title: Import results
|
17
|
+
new:
|
18
|
+
submit: 'Import'
|
19
|
+
title: Import
|
20
|
+
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}' target='_blank'>sample template</a> with all supported column names and their explanation.
|
21
|
+
export_html: You can download the currently stored records.<br>Download the <a href='%{export_path}' target='_blank'>current data</a> with all supported columns
|
22
|
+
error_explanation: 'The following problems prohibited this import from completing:'
|
23
|
+
import: Import %{kind}
|
24
|
+
import_button: Import
|
25
|
+
create:
|
26
|
+
flash:
|
27
|
+
no_file: Import failed, please upload a file.
|
28
|
+
error: Import failed, there were problems.
|
29
|
+
success: 'Import scheduled with id %{id}, you will get an email with the results.'
|
30
|
+
errors:
|
31
|
+
structure_invalid: 'The structure is invalid, these are the invalid headers: %{invalid_headers}'
|
32
|
+
importers:
|
33
|
+
result_message: "Successfully imported %{nr} of %{of} rows"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
nl:
|
2
|
+
importo:
|
3
|
+
sheet:
|
4
|
+
results:
|
5
|
+
name: Resultaten
|
6
|
+
explanation:
|
7
|
+
name: Uitleg
|
8
|
+
column: Kolom
|
9
|
+
explanation: Uitleg
|
10
|
+
imports:
|
11
|
+
index:
|
12
|
+
title: Import resultaten
|
13
|
+
new:
|
14
|
+
import:
|
15
|
+
title: Importeer
|
16
|
+
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}' target='_blank'>voorbeeld file</a> met alle ondersteunde kolomnamen en de bijbehorende uitleg.
|
17
|
+
error_explanation: 'De import is mislukt door de volgende problemen:'
|
18
|
+
import: Import %{kind}
|
19
|
+
import_button: Importeer
|
20
|
+
create:
|
21
|
+
flash:
|
22
|
+
no_file: Import mislukt, upload een bestand.
|
23
|
+
error: Import mislukt, er waren problemen, voor details zie hieronder.
|
24
|
+
success: 'Import geplanned met id %{id}, je krijgt een email met de resultaten.'
|
25
|
+
errors:
|
26
|
+
structure_invalid: 'The structuur is ongeldig, dit zijn de ongeldige headers: %{invalid_headers}'
|
27
|
+
importers:
|
28
|
+
result_message: "%{nr} van %{of} rijen succesvol geimporteerd"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Importo::Engine.routes.draw do
|
4
|
+
resources :imports, except: %i[new] do
|
5
|
+
member do
|
6
|
+
post :undo
|
7
|
+
end
|
8
|
+
end
|
9
|
+
get ':kind/new', to: 'imports#new', as: :new_import
|
10
|
+
get ':kind/sample', to: 'imports#sample', as: :sample_import
|
11
|
+
get ':kind/export', to: 'imports#export', as: :export
|
12
|
+
root to: 'imports#index'
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateImportoImport < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
enable_extension 'uuid-ossp'
|
4
|
+
enable_extension 'pgcrypto'
|
5
|
+
|
6
|
+
return if table_exists?(:importo_imports)
|
7
|
+
|
8
|
+
create_table :importo_imports, id: :uuid do |t|
|
9
|
+
t.string :importo_ownable_type, null: false
|
10
|
+
t.uuid :importo_ownable_id, null: false
|
11
|
+
|
12
|
+
t.string :kind
|
13
|
+
t.string :state
|
14
|
+
t.string :file_name
|
15
|
+
t.string :result_message
|
16
|
+
t.jsonb :results
|
17
|
+
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Importo
|
2
|
+
class ImporterGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def copy_initializer_file
|
6
|
+
template "application_importer.rb", "app/importers/application_importer.rb"
|
7
|
+
template "importer.rb", "app/importers/#{file_name}_importer.rb"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
module Importo
|
6
|
+
module Generators
|
7
|
+
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path('../templates', __dir__)
|
10
|
+
|
11
|
+
desc 'Creates a Importo initializer and copy locale files to your application.'
|
12
|
+
|
13
|
+
def copy_initializer
|
14
|
+
template 'importo.rb', 'config/initializers/importo.rb'
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_locale
|
18
|
+
copy_file '../../../config/locales/en.yml', 'config/locales/importo.en.yml'
|
19
|
+
copy_file '../../../config/locales/nl.yml', 'config/locales/importo.nl.yml'
|
20
|
+
end
|
21
|
+
|
22
|
+
def show_readme
|
23
|
+
readme 'README' if behavior == :invoke
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
===============================================================================
|
2
|
+
|
3
|
+
Some setup you must do manually if you haven't yet:
|
4
|
+
|
5
|
+
1. Check the initializer (in config/initializers/importo.rb) for additional
|
6
|
+
configuration options. Take not of coupon and provider sections.
|
7
|
+
|
8
|
+
2.
|
9
|
+
|
10
|
+
3.
|
11
|
+
|
12
|
+
4.
|
13
|
+
|
14
|
+
===============================================================================
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%=name%>Importer < ApplicationImporter
|
4
|
+
# Whether the excel sheet should contain a header
|
5
|
+
includes_header true
|
6
|
+
# Whether to allow importing of duplicates
|
7
|
+
allow_duplicates false
|
8
|
+
# Whether to ignore the given header and use our internal mapping
|
9
|
+
ignore_header false
|
10
|
+
|
11
|
+
field 'id', 'record ID of the <%=name%> (only if you want to update)'
|
12
|
+
field 'name', 'name of the <%=name%>'
|
13
|
+
|
14
|
+
# Here you will build the record based on the row
|
15
|
+
def build(row)
|
16
|
+
record = <%=name%>.find_or_initialize_by(id: row['id'])
|
17
|
+
record.name = row['name']
|
18
|
+
record
|
19
|
+
end
|
20
|
+
|
21
|
+
# Uncomment if you need to do something before saving the record
|
22
|
+
# def before_save(record, _row)
|
23
|
+
# end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Importo.setup do |config|
|
4
|
+
config.base_controller = '::ApplicationController'
|
5
|
+
config.admin_authentication_module = 'Authenticated'
|
6
|
+
|
7
|
+
# Current import owner
|
8
|
+
config.current_import_owner = -> { User.current }
|
9
|
+
|
10
|
+
# Set callbacks for the import states. You can configure callbacks to work with different states
|
11
|
+
config.import_callbacks = {
|
12
|
+
importing: lambda do |import|
|
13
|
+
end,
|
14
|
+
completed: lambda do |import|
|
15
|
+
end,
|
16
|
+
failed: lambda do |import|
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
config.queue_name = :import
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importo
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :admin_authentication_module
|
6
|
+
attr_accessor :base_controller
|
7
|
+
attr_accessor :base_service
|
8
|
+
attr_accessor :base_service_context
|
9
|
+
attr_accessor :queue_name
|
10
|
+
|
11
|
+
attr_writer :logger
|
12
|
+
attr_writer :current_import_owner
|
13
|
+
attr_writer :import_callbacks
|
14
|
+
attr_writer :admin_visible_imports
|
15
|
+
attr_writer :admin_can_destroy
|
16
|
+
attr_writer :admin_extra_links
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@logger = Logger.new(STDOUT)
|
20
|
+
@logger.level = Logger::INFO
|
21
|
+
@base_controller = '::ApplicationController'
|
22
|
+
@base_service = '::ApplicationService'
|
23
|
+
@base_service_context = '::ApplicationContext'
|
24
|
+
@current_import_owner = -> {}
|
25
|
+
@import_callbacks = {
|
26
|
+
importing: lambda do |_import|
|
27
|
+
end,
|
28
|
+
completed: lambda do |_import|
|
29
|
+
end,
|
30
|
+
failed: lambda do |_import|
|
31
|
+
end
|
32
|
+
}
|
33
|
+
@queue_name = :import
|
34
|
+
|
35
|
+
@admin_visible_imports = -> { Importo::Import.where(importo_ownable: current_import_owner) }
|
36
|
+
@admin_can_destroy = ->(_import) { false }
|
37
|
+
|
38
|
+
# Extra links relevant for this import: { link_name: { icon: 'far fa-..', url: '...' } }
|
39
|
+
@admin_extra_links = ->(_import) { }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Config: logger [Object].
|
43
|
+
def logger
|
44
|
+
@logger.is_a?(Proc) ? instance_exec(&@logger) : @logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_import_owner
|
48
|
+
raise 'current_import_owner should be a Proc' unless @current_import_owner.is_a? Proc
|
49
|
+
instance_exec(&@current_import_owner)
|
50
|
+
end
|
51
|
+
|
52
|
+
def import_callback(import, state)
|
53
|
+
instance_exec(import, &@import_callbacks[state]) if @import_callbacks[state]
|
54
|
+
end
|
55
|
+
|
56
|
+
def admin_visible_imports
|
57
|
+
instance_exec(&@admin_visible_imports) if @admin_visible_imports
|
58
|
+
end
|
59
|
+
|
60
|
+
def admin_can_destroy(import)
|
61
|
+
instance_exec(import, &@admin_can_destroy) if @admin_can_destroy
|
62
|
+
end
|
63
|
+
|
64
|
+
def admin_extra_links(import)
|
65
|
+
instance_exec(import, &@admin_extra_links) if @admin_extra_links
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importo
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace Importo
|
6
|
+
|
7
|
+
initializer 'importo.active_storage.attached' do
|
8
|
+
config.after_initialize do
|
9
|
+
ActiveSupport.on_load(:active_record) do
|
10
|
+
Importo::Import.include(ImportHelpers)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer 'importo.append_migrations' do |app|
|
16
|
+
unless app.root.to_s.match?(root.to_s)
|
17
|
+
config.paths['db/migrate'].expanded.each do |expanded_path|
|
18
|
+
app.config.paths['db/migrate'] << expanded_path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Importo
|
4
|
+
class ImportColumn
|
5
|
+
attr_accessor :proc, :options
|
6
|
+
attr_writer :name, :hint, :explanation
|
7
|
+
|
8
|
+
def initialize(name, hint, explanation, options, &block)
|
9
|
+
@name = name
|
10
|
+
@hint = hint
|
11
|
+
@explanation = explanation
|
12
|
+
@options = options || {}
|
13
|
+
@proc = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute
|
17
|
+
options[:attribute] || @name
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
name = options[:attribute] || @name
|
22
|
+
I18n.t(".column.#{name}", scope: [:importers, options[:scope]], default: name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def allowed_names
|
26
|
+
return @allowed_names if @allowed_names.present?
|
27
|
+
|
28
|
+
name = options[:attribute] || @name
|
29
|
+
|
30
|
+
@allowed_names = I18n.available_locales.map do |locale|
|
31
|
+
I18n.t(".column.#{name}", scope: [:importers, options[:scope]], locale: locale, default: name)
|
32
|
+
end.compact.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def hint
|
36
|
+
I18n.t(".hint.#{options[:attribute]}", scope: [:importers, options[:scope]], default: '') if options[:attribute]
|
37
|
+
end
|
38
|
+
|
39
|
+
def explanation
|
40
|
+
I18n.t(".explanation.#{options[:attribute]}", scope: [:importers, options[:scope]], default: '') if options[:attribute]
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# If set this allows the user to set a value during upload that overrides the uploaded values.
|
45
|
+
def overridable?
|
46
|
+
options[:overridable]
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Collection of values (name, id) that are valid for this field, if a name is entered it will be replaced by the id during pre-processing
|
51
|
+
def collection
|
52
|
+
options[:collection]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/importo.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'axlsx'
|
4
|
+
require 'roo'
|
5
|
+
require 'roo-xls'
|
6
|
+
require 'slim'
|
7
|
+
require 'state_machines-activerecord'
|
8
|
+
# require 'active_storage/downloading'
|
9
|
+
|
10
|
+
require_relative 'importo/engine'
|
11
|
+
require_relative 'importo/acts_as_import_owner'
|
12
|
+
require_relative 'importo/import_column'
|
13
|
+
require_relative 'importo/import_helpers'
|
14
|
+
require_relative 'importo/configuration'
|
15
|
+
|
16
|
+
module Importo
|
17
|
+
class Error < StandardError; end
|
18
|
+
|
19
|
+
class DuplicateRowError < Error; end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_reader :config
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@config = Configuration.new
|
26
|
+
yield config
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|