card-mod-csv_import 0.8

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.
@@ -0,0 +1,59 @@
1
+ event :import_csv, :integrate_with_delay, on: :update, when: :data_import? do
2
+ import_manager.import_rows selected_row_indices
3
+ end
4
+
5
+ event :flag_as_import, :prepare_to_validate, on: :update, when: :data_import? do
6
+ @empty_ok = true
7
+ end
8
+
9
+ event :prepare_import, :prepare_to_store, on: :update, when: :data_import? do
10
+ import_status_card.reset selected_row_count
11
+ end
12
+
13
+ def import_manager
14
+ @import_mananger =
15
+ ::ActImportManager.new self, csv_file, conflict_strategy, extra_data
16
+ end
17
+
18
+ def conflict_strategy
19
+ Env.params[:conflict_strategy]&.to_sym || :skip
20
+ end
21
+
22
+ def extra_data
23
+ @extra_data ||= normalize_extra_data
24
+ end
25
+
26
+ def normalize_extra_data
27
+ fetch_hash_from_params(:extra_data).deep_symbolize_keys
28
+ end
29
+
30
+ def fetch_hash_from_params key
31
+ case Env.params[key]
32
+ when Hash
33
+ Env.params[key]
34
+ when ActionController::Parameters
35
+ Env.params[key].to_unsafe_h
36
+ else
37
+ {}
38
+ end
39
+ end
40
+
41
+ def data_import?
42
+ Env.params[:import_rows].present?
43
+ end
44
+
45
+ def silent_change?
46
+ data_import? || super
47
+ end
48
+
49
+ def selected_row_count
50
+ selected_row_indices.size
51
+ end
52
+
53
+ def selected_row_indices
54
+ @selected_row_indices ||=
55
+ fetch_hash_from_params(:import_rows).each_with_object([]) do |(index, value), a|
56
+ next unless [true, "true"].include?(value)
57
+ a << index.to_i
58
+ end
59
+ end
@@ -0,0 +1,82 @@
1
+ format :html do
2
+ def humanized_attachment_name
3
+ "csv file"
4
+ end
5
+
6
+ view :import, cache: :never do
7
+ class_up "card-slot", "nodblclick"
8
+ frame_and_form :update do
9
+ [
10
+ hidden_import_tags,
11
+ render!(:additional_form_fields),
12
+ render!(:import_table_helper),
13
+ render!(:import_table),
14
+ render!(:import_button_formgroup)
15
+ ]
16
+ end
17
+ end
18
+
19
+ def hidden_import_tags
20
+ hidden_tags success: { name: card.import_status_card.name, view: :open }
21
+ end
22
+
23
+ view :import_button_formgroup do
24
+ button_formgroup { [import_button, cancel_button(href: path)] }
25
+ end
26
+
27
+ def import_button
28
+ button_tag "Import", class: "submit-button",
29
+ data: { disable_with: "Importing" }
30
+ end
31
+
32
+ view :import_table_helper do
33
+ wrap_with(:p, group_selection_checkboxes) +
34
+ wrap_with(:p, select_conflict_strategy)
35
+ end
36
+
37
+ view :additional_form_fields do
38
+ ""
39
+ end
40
+
41
+ def already_imported?
42
+ card.already_imported?
43
+ end
44
+
45
+ def group_selection_checkboxes
46
+ <<-HTML.html_safe
47
+ Select:
48
+ <span class="padding-20 background-grey">
49
+ #{check_box_tag '_check-all', '', false, class: 'checkbox-button'}
50
+ #{label_tag '_check-all', 'all'}
51
+ </span>
52
+ #{group_selection_checkbox('exact', 'exact matches', :success, true)}
53
+ #{group_selection_checkbox('alias', 'alias matches', :alias, true)}
54
+ #{group_selection_checkbox('partial', 'partial matches', :info, true)}
55
+ #{group_selection_checkbox('none', 'no matches', :warning)}
56
+ #{group_selection_checkbox('imported', 'already imported', :active) if already_imported?}
57
+ HTML
58
+ end
59
+
60
+ def group_selection_checkbox name, label, identifier, checked=false
61
+ wrap_with :span, class: "padding-20 table-#{identifier}" do
62
+ [
63
+ check_box_tag(
64
+ name, "", checked,
65
+ class: "checkbox-button _group-check",
66
+ data: { group: name }
67
+ ),
68
+ label_tag(name, label)
69
+ ]
70
+ end
71
+ end
72
+
73
+ def select_conflict_strategy
74
+ <<-HTML.html_safe
75
+ Conflicts with existing entries:
76
+ #{radio_button_tag 'conflict_strategy', 'skip', true}
77
+ #{label_tag 'conflict_strategy_skip', 'skip'}
78
+ #{radio_button_tag 'conflict_strategy', 'override', false}
79
+ #{label_tag 'conflict_strategy_override', 'override'}
80
+ HTML
81
+ end
82
+ end
@@ -0,0 +1,76 @@
1
+ format :html do
2
+ def import_table_row_class
3
+ Abstract::Import::TableRow
4
+ end
5
+
6
+ def column_keys
7
+ card.singleton_class::COLUMNS.keys
8
+ end
9
+
10
+ def column_titles
11
+ card.singleton_class::COLUMNS.values
12
+ end
13
+
14
+ def row_errors
15
+ return "" unless vm.errors?
16
+ alert(:danger, true) do
17
+ with_header "Invalid data", level: 4 do
18
+ list_group vm.error_list
19
+ end
20
+ end
21
+ end
22
+
23
+ def validation_manager
24
+ @vm ||= ValidationManager.new card.csv_file
25
+ end
26
+
27
+ alias_method :vm, :validation_manager
28
+ delegate :already_imported?, to: :card
29
+
30
+ def row_buckets
31
+ row_buckets = {
32
+ invalid: [],
33
+ valid: [],
34
+ imported: []
35
+ }
36
+ end
37
+
38
+ def bucket_key table_row
39
+ if already_imported? table_row.row_index
40
+ :imported
41
+ elsif table_row.status == :failed
42
+ :invalid
43
+ else
44
+ :valid
45
+ end
46
+ end
47
+
48
+ def table_rows
49
+ rb = row_buckets
50
+ vm.validate do |validated_csv_row|
51
+ table_row = import_table_row_class.new validated_csv_row, self
52
+ rb[bucket_key(table_row)] << table_row.render
53
+ end
54
+ rb.values.flatten
55
+ end
56
+
57
+ def table_with_errors *args
58
+ row_errors + table(*args)
59
+ end
60
+
61
+ view :import_table, cache: :never do
62
+ return alert(:warning) { "no import file attached" } if card.file.blank?
63
+ table_with_errors(table_rows, class: "_import-table import-table table-hover",
64
+ header: column_titles)
65
+ end
66
+
67
+ def extra_data_input_name index, *subfields
68
+ name = "extra_data[#{index}]"
69
+ name << subfields.map { |f| "[#{f}]" }.join("") if subfields.present?
70
+ name
71
+ end
72
+
73
+ def corrections_input_name index, key
74
+ extra_data_input_name index, :corrections, key
75
+ end
76
+ end
@@ -0,0 +1,8 @@
1
+ def import_act?
2
+ ActManager.act_card&.import_file?
3
+ end
4
+
5
+ # for override
6
+ def import_file?
7
+ false
8
+ end
@@ -0,0 +1,163 @@
1
+ def followable?
2
+ false
3
+ end
4
+
5
+ def history?
6
+ false
7
+ end
8
+
9
+ def status
10
+ @status ||= ImportManager::Status.new content
11
+ end
12
+
13
+ def import_counts
14
+ @import_counts ||= status[:counts]
15
+ end
16
+
17
+ def reset total
18
+ @status = ImportManager::Status.new act_id: ActManager.act&.id, counts: { total: total }
19
+ save_status
20
+ end
21
+
22
+ def step key
23
+ import_counts.step key
24
+ save_status
25
+ end
26
+
27
+ def save_status
28
+ update_attributes content: status.to_json
29
+ end
30
+
31
+ STATUS_HEADER = {
32
+ failed: "Failed",
33
+ imported: "Successfully created",
34
+ overridden: "Overridden",
35
+ skipped: "Skipped existing"
36
+ }.freeze
37
+
38
+ STATUS_CONTEXT = {
39
+ failed: :danger,
40
+ imported: :success,
41
+ overridden: :warning,
42
+ skipped: :info
43
+ }.freeze
44
+
45
+ format :html do
46
+ delegate :status, :import_counts, to: :card
47
+ delegate :percentage, :count, :step, to: :import_counts
48
+
49
+ def wrap_data _slot=true
50
+ super.merge "refresh-url" => path(view: @slot_view)
51
+ end
52
+
53
+ def wrap_classes slot
54
+ class_up "card-slot", "_refresh-timer", true if auto_refresh?
55
+ super
56
+ end
57
+
58
+ def auto_refresh?
59
+ @slot_view.in?([:open, :content, :titled]) && importing?
60
+ end
61
+
62
+ def importing?
63
+ STATUS_CONTEXT.keys.inject(0) do |sum, key|
64
+ sum + count(key)
65
+ end < count(:total)
66
+ end
67
+
68
+ # returns plural if there are more than one card of type `count_type`
69
+ def item_label count_type=nil
70
+ label = card.left&.try(:item_label) || "card"
71
+ count_type && count(count_type) > 1 ? label.pluralize : label
72
+ end
73
+
74
+ def item_count_label count_key
75
+ label = item_label count_key
76
+ "#{count(count_key)} #{label}"
77
+ end
78
+
79
+ def progress_header
80
+ if importing?
81
+ "Importing #{item_count_label :total} ..."
82
+ elsif count(:overridden).positive?
83
+ "#{item_count_label :imported} created and " \
84
+ "#{item_count_label :overridden} updated" \
85
+ else
86
+ "Imported #{item_count_label(:imported)}"
87
+ end
88
+ end
89
+
90
+ view :core, cache: :never do
91
+ with_header(progress_header, level: 4) do
92
+ _render_progress_bar
93
+ end + wrap_with(:p, undo_button) + wrap_with(:p, report)
94
+ end
95
+
96
+ view :progress_bar, cache: :never do
97
+ sections = %i[imported skipped overridden failed].map do |type|
98
+ progress_section type
99
+ end.compact
100
+ progress_bar(*sections)
101
+ end
102
+
103
+ view :compact, cache: :never, template: :haml do
104
+ end
105
+
106
+ def report
107
+ [:failed, :skipped, :overridden, :imported].map do |key|
108
+ next unless status[key].present?
109
+ generate_report_alert key
110
+ end.compact.join
111
+ end
112
+
113
+ def generate_report_alert type
114
+ alert STATUS_CONTEXT[type], false, false, href: "##{type}" do
115
+ with_header STATUS_HEADER[type], level: 5 do
116
+ list = []
117
+ status[type].each do |index, name|
118
+ list << report_line(index, name, type)
119
+ end
120
+ list_group list
121
+ end
122
+ end
123
+ end
124
+
125
+ def report_line index, name, type
126
+ label, status_key =
127
+ type == :failed ? [name, :errors] : [link_to_card(name), :reports]
128
+
129
+ text = "##{index + 1}: #{label}"
130
+ if status[status_key][index].present?
131
+ text += " - " if name.present?
132
+ text += status[status_key][index].join("; ")
133
+ end
134
+ text
135
+ end
136
+
137
+ def progress_section type
138
+ return if count(type).zero?
139
+ html_class = "bg-#{STATUS_CONTEXT[type]}"
140
+ html_class << " progress-bar-striped progress-bar-animated" if importing?
141
+ { value: percentage(type), label: "#{count(type)} #{type}", class: html_class }
142
+ end
143
+
144
+ def undo_button
145
+ return if importing?
146
+ return "" unless status[:act_id] && (act = Card::Act.find(status[:act_id]))
147
+ wrap_with :div, class: "d-flex flex-row-reverse" do
148
+ card.left.format(:html)
149
+ .revert_actions_link act, "Undo",
150
+ revert_to: :previous,
151
+ html_args: { class: "btn btn-danger",
152
+ "data-confirm" => undo_confirm_message }
153
+ end
154
+ end
155
+
156
+ def undo_confirm_message
157
+ text = "Do you really want to remove the imported #{item_label :imported}"
158
+ if count(:overridden).positive?
159
+ text += " and restore the overridden " + item_label(:overridden)
160
+ end
161
+ text << "?"
162
+ end
163
+ end
@@ -0,0 +1,20 @@
1
+ def followable?
2
+ false
3
+ end
4
+
5
+ def history?
6
+ false
7
+ end
8
+
9
+ def already_imported? index=nil
10
+ index ? imported_row_indices.include?(index) : imported_row_indices.present?
11
+ end
12
+
13
+ def mark_as_imported row_index
14
+ imported_rows_indices << row_index
15
+ update_attributes content: imported_row_indices.join(",")
16
+ end
17
+
18
+ def imported_row_indices
19
+ @imported_rows ||= ::Set.new content.split(",").map(&:to_i)
20
+ end
data/set/type/file.rb ADDED
@@ -0,0 +1,10 @@
1
+ def csv?
2
+ attachment.content_type.in? %w[text/csv
3
+ text/comma-separated-values
4
+ text/plain
5
+ text/x-csv
6
+ text/x-comma-separated-values
7
+ application/vnd.ms-excel
8
+ application/csv
9
+ application/x-csv]
10
+ end
@@ -0,0 +1,3 @@
1
+ = field_nest :description, view: :core
2
+
3
+ = tabpanel
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: card-mod-csv_import
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.8'
5
+ platform: ruby
6
+ authors:
7
+ - Philipp Kühl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: card
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: a card mod for importing data from csv files as cards into a deck
28
+ email:
29
+ - info@decko.org
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - config/initializers/import_logger.rb
35
+ - lib/csv_file.rb
36
+ - lib/csv_row.rb
37
+ - lib/csv_row/normalizer.rb
38
+ - lib/import_log.rb
39
+ - lib/import_manager.rb
40
+ - lib/import_manager/act_import_manager.rb
41
+ - lib/import_manager/conflicts.rb
42
+ - lib/import_manager/script_import_manager.rb
43
+ - lib/import_manager/status.rb
44
+ - lib/import_manager/status/counts.rb
45
+ - lib/import_manager/status_log.rb
46
+ - lib/import_manager/validation_manager.rb
47
+ - set/abstract/import.rb
48
+ - set/abstract/import/01_table_row.rb
49
+ - set/abstract/import/execute_import.rb
50
+ - set/abstract/import/import_page.rb
51
+ - set/abstract/import/table.rb
52
+ - set/all/import_act.rb
53
+ - set/right/import_status.rb
54
+ - set/right/imported_rows.rb
55
+ - set/type/file.rb
56
+ - template/self/import_tool.haml
57
+ homepage: http://decko.org
58
+ licenses:
59
+ - GPL-2.0
60
+ - GPL-3.0
61
+ metadata:
62
+ card-mod: csv_import
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.3.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.7.6
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: tool to import csv files
83
+ test_files: []