ixport 0.1.0 → 0.3.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -6
  3. data/app/controllers/ixport/application_controller.rb +6 -2
  4. data/app/controllers/ixport/export_templates_controller.rb +52 -0
  5. data/app/controllers/ixport/exports_controller.rb +18 -0
  6. data/app/controllers/ixport/imports_controller.rb +4 -3
  7. data/app/controllers/ixport/previews_controller.rb +2 -2
  8. data/app/controllers/ixport/runs_controller.rb +13 -1
  9. data/app/helpers/ixport/imports_helper.rb +13 -0
  10. data/app/models/ixport/csv_writer.rb +19 -0
  11. data/app/models/ixport/export_template.rb +19 -0
  12. data/app/models/ixport/import.rb +71 -19
  13. data/app/models/ixport/null_reporter.rb +11 -0
  14. data/app/models/ixport/row_walk.rb +15 -6
  15. data/app/models/ixport/wallflower_runner.rb +10 -0
  16. data/app/models/ixport/wallflower_task_runner.rb +14 -0
  17. data/app/views/ixport/export_templates/edit.html.erb +14 -0
  18. data/app/views/ixport/export_templates/new.html.erb +11 -0
  19. data/app/views/ixport/exports/index.html.erb +23 -0
  20. data/app/views/ixport/imports/index.html.erb +17 -0
  21. data/app/views/ixport/imports/show.html.erb +30 -15
  22. data/config/routes.rb +4 -0
  23. data/lib/generators/ixport/install/install_generator.rb +3 -0
  24. data/lib/generators/ixport/install/templates/add_error_message_to_ixport_imports.rb.erb +5 -0
  25. data/lib/generators/ixport/install/templates/add_wallflower_task_to_ixport_imports.rb.erb +6 -0
  26. data/lib/generators/ixport/install/templates/create_ixport_export_templates.rb.erb +12 -0
  27. data/lib/generators/ixport/update/update_generator.rb +28 -0
  28. data/lib/ixport/background_runner.rb +23 -0
  29. data/lib/ixport/configuration.rb +3 -1
  30. data/lib/ixport/engine.rb +4 -1
  31. data/lib/ixport/exporter.rb +27 -0
  32. data/lib/ixport/importer.rb +7 -3
  33. data/lib/ixport/registrations.rb +29 -3
  34. data/lib/ixport/version.rb +1 -1
  35. data/lib/ixport.rb +2 -0
  36. data/the_local/agents/ixport-develop.md +235 -37
  37. data/the_local/agents/ixport-info.md +61 -26
  38. data/the_local/agents/ixport-install.md +66 -38
  39. data/the_local/interface.yml +37 -1
  40. metadata +18 -1
@@ -7,4 +7,21 @@
7
7
  <div data-import><%= link_to registration.title, new_import_path(key: registration.key) %></div>
8
8
  <% end %>
9
9
  <% end %>
10
+ <% if @imports.any? %>
11
+ <%= ui_section(title: "Your imports") do %>
12
+ <% @imports.each do |import| %>
13
+ <div data-past-import>
14
+ <%= link_to import_step_path(import), data: { step_link: true } do %><span data-import-title><%= import.title %></span><% end %>
15
+ <span data-file-name><%= import.file.filename.to_s %></span>
16
+ <span data-status><%= import.status.capitalize %></span>
17
+ <span data-made-on><%= import.created_at.strftime("%b %-d, %Y") %></span>
18
+ <% if import.finished? %>
19
+ <span>Added <span data-added><%= import.added_count %></span></span>
20
+ <span>Updated <span data-updated><%= import.updated_count %></span></span>
21
+ <span>Refused <span data-refused><%= import.refusals.size %></span></span>
22
+ <% end %>
23
+ </div>
24
+ <% end %>
25
+ <% end %>
26
+ <% end %>
10
27
  <% end %>
@@ -1,20 +1,35 @@
1
- <%= ui_show_page(title: "Import finished", back_url: root_path) %>
1
+ <% heading = if @import.running?
2
+ "Import running"
3
+ elsif @import.failed?
4
+ "Import failed"
5
+ else
6
+ "Import finished"
7
+ end %>
8
+ <%= ui_show_page(title: heading, back_url: root_path) %>
2
9
  <%= ui_page(max_width: :md) do %>
3
- <%= ui_page_header(title: "Import finished") %>
4
- <%= ui_grid(cols: { default: 1, sm: 2 }) do %>
5
- <%= ui_stat_card(label: "Added", value: tag.span(@import.added_count, data: { result_count: "added" })) %>
6
- <%= ui_stat_card(label: "Updated", value: tag.span(@import.updated_count, data: { result_count: "updated" })) %>
7
- <% end %>
8
- <% if @import.refusals.any? %>
9
- <%= ui_section(title: "Refused") do %>
10
- <ul>
11
- <% @import.refusals.each do |refusal| %>
12
- <%= tag.li(data: { refused_row: true }) do %>
13
- Row <%= tag.span(refusal.row_number, data: { row_number: true }) %>
14
- <%= tag.span(refusal.reason, data: { reason: true }) %>
10
+ <%= ui_page_header(title: heading) %>
11
+ <% if @import.running? %>
12
+ <%= ui_alert(type: :info, message: tag.span("This import is running.", data: { import_running: true })) %>
13
+ <% else %>
14
+ <% if @import.failed? %>
15
+ <%= ui_alert(type: :error, message: tag.span(@import.error_message, data: { import_error: true })) %>
16
+ <p>Rows saved before it stopped stay saved.</p>
17
+ <% end %>
18
+ <%= ui_grid(cols: { default: 1, sm: 2 }) do %>
19
+ <%= ui_stat_card(label: "Added", value: tag.span(@import.added_count, data: { result_count: "added" })) %>
20
+ <%= ui_stat_card(label: "Updated", value: tag.span(@import.updated_count, data: { result_count: "updated" })) %>
21
+ <% end %>
22
+ <% if @import.refusals.any? %>
23
+ <%= ui_section(title: "Refused") do %>
24
+ <ul>
25
+ <% @import.refusals.each do |refusal| %>
26
+ <%= tag.li(data: { refused_row: true }) do %>
27
+ Row <%= tag.span(refusal.row_number, data: { row_number: true }) %>
28
+ <%= tag.span(refusal.reason, data: { reason: true }) %>
29
+ <% end %>
15
30
  <% end %>
16
- <% end %>
17
- </ul>
31
+ </ul>
32
+ <% end %>
18
33
  <% end %>
19
34
  <% end %>
20
35
  <% end %>
data/config/routes.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  Ixport::Engine.routes.draw do
2
2
  root "imports#index"
3
+ resources :exports, only: %i[index show] do
4
+ resources :templates, only: %i[new create], controller: "export_templates"
5
+ end
6
+ resources :export_templates, only: %i[show edit update destroy]
3
7
  resources :imports, only: %i[new create show] do
4
8
  resource :mapping, only: %i[show update]
5
9
  resource :preview, only: :show
@@ -15,6 +15,9 @@ module Ixport
15
15
  def copy_migrations
16
16
  migration_template "create_ixport_imports.rb.erb", "db/migrate/create_ixport_imports.rb"
17
17
  migration_template "create_ixport_import_refusals.rb.erb", "db/migrate/create_ixport_import_refusals.rb"
18
+ migration_template "create_ixport_export_templates.rb.erb", "db/migrate/create_ixport_export_templates.rb"
19
+ migration_template "add_wallflower_task_to_ixport_imports.rb.erb", "db/migrate/add_wallflower_task_to_ixport_imports.rb"
20
+ migration_template "add_error_message_to_ixport_imports.rb.erb", "db/migrate/add_error_message_to_ixport_imports.rb"
18
21
  end
19
22
  end
20
23
  end
@@ -0,0 +1,5 @@
1
+ class AddErrorMessageToIxportImports < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ add_column :ixport_imports, :error_message, :text
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddWallflowerTaskToIxportImports < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ add_column :ixport_imports, :wallflower_task_id, :bigint
4
+ add_index :ixport_imports, :wallflower_task_id
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ class CreateIxportExportTemplates < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :ixport_export_templates do |t|
4
+ t.string :key, null: false
5
+ t.string :name, null: false
6
+ t.json :columns, null: false
7
+ t.references :person, polymorphic: true, null: false
8
+ t.references :account, polymorphic: true, null: true
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Ixport
7
+ module Generators
8
+ class UpdateGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path("../install/templates", __dir__)
12
+
13
+ desc "Updates Ixport: copies the migrations added since it was installed."
14
+
15
+ def copy_export_templates_migration
16
+ migration_template "create_ixport_export_templates.rb.erb", "db/migrate/create_ixport_export_templates.rb"
17
+ end
18
+
19
+ def copy_wallflower_task_migration
20
+ migration_template "add_wallflower_task_to_ixport_imports.rb.erb", "db/migrate/add_wallflower_task_to_ixport_imports.rb"
21
+ end
22
+
23
+ def copy_error_message_migration
24
+ migration_template "add_error_message_to_ixport_imports.rb.erb", "db/migrate/add_error_message_to_ixport_imports.rb"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ixport
4
+ class ImportFailed < StandardError; end
5
+
6
+ def self.background_runner
7
+ case configuration.background_runner
8
+ when nil then nil
9
+ when :wallflower then WallflowerRunner
10
+ else configuration.background_runner.to_s.constantize
11
+ end
12
+ end
13
+
14
+ def self.run_import(import, reporter: NullReporter.new)
15
+ import.run!(reporter: reporter)
16
+ end
17
+
18
+ def self.install_background_runner!
19
+ return unless configuration.background_runner == :wallflower
20
+
21
+ Wallflower.register_kind(:ixport_import, title: "Import", runner: "Ixport::WallflowerTaskRunner")
22
+ end
23
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Ixport
4
4
  class Configuration
5
- attr_accessor :authentication_method, :current_person_method, :current_account_method, :layout, :inline_row_limit
5
+ attr_accessor :authentication_method, :current_person_method, :current_account_method, :layout, :inline_row_limit, :background_runner, :base_controller
6
6
 
7
7
  def initialize
8
8
  @authentication_method = :authenticate_user!
@@ -10,6 +10,8 @@ module Ixport
10
10
  @current_account_method = :current_account
11
11
  @layout = "application"
12
12
  @inline_row_limit = 1_000
13
+ @background_runner = nil
14
+ @base_controller = "::ApplicationController"
13
15
  end
14
16
  end
15
17
 
data/lib/ixport/engine.rb CHANGED
@@ -4,6 +4,9 @@ module Ixport
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Ixport
6
6
 
7
- config.after_initialize { Ixport.check_registrations! }
7
+ config.after_initialize do
8
+ Ixport.check_registrations!
9
+ Ixport.install_background_runner!
10
+ end
8
11
  end
9
12
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ixport
4
+ class Exporter
5
+ Column = Struct.new(:key, :label, :through, keyword_init: true)
6
+
7
+ attr_reader :person, :account
8
+
9
+ def initialize(person:, account:)
10
+ @person = person
11
+ @account = account
12
+ end
13
+
14
+ def self.column(key, label:, through: nil)
15
+ columns << Column.new(key: key.to_sym, label: label, through: through)
16
+ end
17
+
18
+ def self.columns
19
+ @columns ||= []
20
+ end
21
+
22
+ def value(record, column)
23
+ source = column.through ? record.public_send(column.through) : record
24
+ source&.public_send(column.key)
25
+ end
26
+ end
27
+ end
@@ -19,12 +19,16 @@ module Ixport
19
19
  @fields ||= []
20
20
  end
21
21
 
22
- def self.match_on(key)
23
- @match_field = key.to_sym
22
+ def self.match_on(*keys)
23
+ @match_fields = keys.map(&:to_sym)
24
+ end
25
+
26
+ def self.match_fields
27
+ @match_fields || []
24
28
  end
25
29
 
26
30
  def self.match_field
27
- @match_field
31
+ match_fields.first
28
32
  end
29
33
  end
30
34
  end
@@ -1,12 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ixport
4
- Registration = Struct.new(:key, :title, :importer, keyword_init: true)
4
+ module AccessRule
5
+ def allows?(person, account)
6
+ allow.nil? || allow.call(person, account)
7
+ end
8
+ end
9
+
10
+ Registration = Struct.new(:key, :title, :importer, :allow, keyword_init: true) { include AccessRule }
11
+ ExportRegistration = Struct.new(:key, :title, :exporter, :chosen, :allow, keyword_init: true) { include AccessRule }
5
12
 
6
13
  class MissingImporter < StandardError; end
14
+ class MissingExporter < StandardError; end
7
15
 
8
- def self.register_import(key, title:, importer:)
9
- registrations[key.to_sym] = Registration.new(key: key.to_sym, title: title, importer: importer.to_s)
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)
10
18
  end
11
19
 
12
20
  def self.registration(key)
@@ -23,9 +31,27 @@ module Ixport
23
31
 
24
32
  raise MissingImporter, "Ixport import #{registration.key} names the importer #{registration.importer}, which does not exist."
25
33
  end
34
+ export_registrations.each_value do |registration|
35
+ next if registration.exporter.safe_constantize
36
+
37
+ raise MissingExporter, "Ixport export #{registration.key} names the exporter #{registration.exporter}, which does not exist."
38
+ end
39
+ end
40
+
41
+ def self.register_export(key, title:, exporter:, chosen: false, allow: nil)
42
+ export_registrations[key.to_sym] = ExportRegistration.new(key: key.to_sym, title: title, exporter: exporter.to_s, chosen: chosen, allow: allow)
43
+ end
44
+
45
+ def self.export_registration(key)
46
+ export_registrations.fetch(key.to_sym)
47
+ end
48
+
49
+ def self.export_registrations
50
+ @export_registrations ||= {}
26
51
  end
27
52
 
28
53
  def self.reset_registrations!
29
54
  @registrations = {}
55
+ @export_registrations = {}
30
56
  end
31
57
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ixport
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/ixport.rb CHANGED
@@ -3,6 +3,8 @@ require "ixport/version"
3
3
  require "ixport/configuration"
4
4
  require "ixport/registrations"
5
5
  require "ixport/importer"
6
+ require "ixport/exporter"
7
+ require "ixport/background_runner"
6
8
  require "ixport/engine"
7
9
 
8
10
  module Ixport