cm-admin 0.7.8 → 0.8.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +22 -5
  3. data/app/assets/images/image_not_available.png +0 -0
  4. data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +1 -0
  5. data/app/assets/stylesheets/cm_admin/pages/import_page.scss +125 -0
  6. data/app/controllers/cm_admin/resource_controller.rb +19 -0
  7. data/app/helpers/cm_admin/application_helper.rb +41 -0
  8. data/app/jobs/file_import_processor_job.rb +38 -0
  9. data/app/models/concerns/cm_admin/file_import.rb +40 -0
  10. data/app/models/file_import.rb +27 -0
  11. data/app/policies/cm_admin/file_import_policy.rb +11 -0
  12. data/app/views/cm_admin/main/_actions_dropdown.html.slim +3 -15
  13. data/app/views/cm_admin/main/_associated_table.html.slim +2 -0
  14. data/app/views/cm_admin/main/_member_custom_action_modal.html.slim +13 -0
  15. data/app/views/cm_admin/main/_table.html.slim +2 -0
  16. data/app/views/cm_admin/main/_top_navbar.html.slim +2 -0
  17. data/app/views/cm_admin/main/import_form.html.slim +35 -0
  18. data/cm_admin.gemspec +1 -0
  19. data/config/routes.rb +6 -0
  20. data/lib/cm_admin/model.rb +7 -1
  21. data/lib/cm_admin/models/column.rb +3 -1
  22. data/lib/cm_admin/models/field.rb +4 -2
  23. data/lib/cm_admin/models/form_field.rb +2 -1
  24. data/lib/cm_admin/models/importer.rb +17 -0
  25. data/lib/cm_admin/version.rb +1 -1
  26. data/lib/cm_admin/view_helpers/field_display_helper.rb +8 -0
  27. data/lib/cm_admin/view_helpers/form_field_helper.rb +2 -2
  28. data/lib/cm_admin.rb +2 -1
  29. data/lib/generators/cm_admin/install_generator.rb +2 -0
  30. data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +1 -1
  31. data/tmp/cache/webpacker/last-compilation-digest-development +1 -1
  32. metadata +28 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a03ed809475bd54b0365e020bb5d3ff1be6a2c16d2e2a2bf82b175af9ee26b8
4
- data.tar.gz: 454ea27724b9f6e8c6297a81af181ce9e365fba0bbd777c68c9c1fdb6d5f47d0
3
+ metadata.gz: 5fa47a489bd6b44db00d91cb7631c5491646fd00148ba756b87208f1218ee705
4
+ data.tar.gz: 5a61fd4d0b01637fbf16bd0a443b912929d12993863a936bd329682433ce54f6
5
5
  SHA512:
6
- metadata.gz: f6b7f5d92c8e26d67b3587fe3c1e0b674bf3bb49aca01d958495f1093b369ab1993383eda2ae744587dea723a8534a7bc3d4d51ef94cd0f6c9aa35bc2186525a
7
- data.tar.gz: 2ccc66f2c64f05e584a4a33da9b0117868272d039e984e8822a90c6957fa48c2654ca3f9ad8b1a2a631a87f50bcd2c761de5d84e4f50b30915b1ad24f2c291ec
6
+ metadata.gz: 708568975092346a45370f46c3b7119feb5a4c4bc43a33ec61a30a3966da7ed8ec13d1a9d91e3a2120f2d5a462f18cb5827a9deaed6fc84bc451b0740f80187d
7
+ data.tar.gz: fc2fba169f614e1d48a11cec08135a12db4e20eb0dcfb6eada41a810bbd7bbeb40b046512c472232542670da2e2c301db58b3263c37fe05de791c3e2b15d46a8
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cm-admin (0.7.7)
4
+ cm-admin (0.8.0)
5
5
  caxlsx_rails
6
6
  cocoon (~> 1.2.15)
7
+ csv-importer (~> 0.8.2)
7
8
  local_time (~> 2.1.0)
8
9
  pagy (~> 4.11.0)
9
10
  pundit (~> 2.2.0)
@@ -32,6 +33,10 @@ GEM
32
33
  minitest (>= 5.1)
33
34
  tzinfo (~> 2.0)
34
35
  ast (2.4.2)
36
+ axiom-types (0.1.1)
37
+ descendants_tracker (~> 0.0.4)
38
+ ice_nine (~> 0.11.0)
39
+ thread_safe (~> 0.3, >= 0.3.1)
35
40
  builder (3.2.4)
36
41
  caxlsx (3.2.0)
37
42
  htmlentities (~> 4.3, >= 4.3.4)
@@ -42,22 +47,29 @@ GEM
42
47
  actionpack (>= 3.1)
43
48
  caxlsx (>= 3.0)
44
49
  cocoon (1.2.15)
50
+ coercible (1.0.0)
51
+ descendants_tracker (~> 0.0.1)
45
52
  concurrent-ruby (1.1.10)
46
53
  crass (1.0.6)
54
+ csv-importer (0.8.2)
55
+ virtus
56
+ descendants_tracker (0.0.4)
57
+ thread_safe (~> 0.3, >= 0.3.1)
47
58
  diff-lcs (1.4.4)
48
- erubi (1.10.0)
59
+ erubi (1.11.0)
49
60
  htmlentities (4.3.4)
50
61
  i18n (1.11.0)
51
62
  concurrent-ruby (~> 1.0)
63
+ ice_nine (0.11.2)
52
64
  local_time (2.1.0)
53
- loofah (2.18.0)
65
+ loofah (2.19.0)
54
66
  crass (~> 1.0.2)
55
67
  nokogiri (>= 1.5.9)
56
68
  marcel (1.0.2)
57
69
  method_source (1.0.0)
58
70
  mini_portile2 (2.8.0)
59
71
  minitest (5.16.2)
60
- nokogiri (1.13.7)
72
+ nokogiri (1.13.8)
61
73
  mini_portile2 (~> 2.8.0)
62
74
  racc (~> 1.4)
63
75
  pagy (4.11.0)
@@ -68,7 +80,7 @@ GEM
68
80
  activesupport (>= 3.0.0)
69
81
  racc (1.6.0)
70
82
  rack (2.2.4)
71
- rack-proxy (0.7.2)
83
+ rack-proxy (0.7.4)
72
84
  rack
73
85
  rack-test (2.0.2)
74
86
  rack (>= 1.3)
@@ -120,10 +132,15 @@ GEM
120
132
  tilt (>= 2.0.6, < 2.1)
121
133
  temple (0.8.2)
122
134
  thor (1.2.1)
135
+ thread_safe (0.3.6)
123
136
  tilt (2.0.10)
124
137
  tzinfo (2.0.4)
125
138
  concurrent-ruby (~> 1.0)
126
139
  unicode-display_width (2.1.0)
140
+ virtus (2.0.0)
141
+ axiom-types (~> 0.1)
142
+ coercible (~> 1.0)
143
+ descendants_tracker (~> 0.0, >= 0.0.3)
127
144
  webpacker (5.4.3)
128
145
  activesupport (>= 5.2)
129
146
  rack-proxy (>= 0.6.1)
@@ -10,6 +10,7 @@
10
10
  * files in this directory. Styles in this file should be added after the last require_* statement.
11
11
  * It is generally better to create a new file per style scope.
12
12
  *
13
+ *= require 'cm_admin/pages/import_page'
13
14
  *= require 'cm_admin/base/table'
14
15
  *= require 'cm_admin/base/navbar'
15
16
  *= require 'cm_admin/base/sidebar'
@@ -0,0 +1,125 @@
1
+ @import "../helpers/index.scss";
2
+
3
+ @function size($size) {
4
+ @return map-get($font-size, $size);
5
+ }
6
+
7
+ @function weight($weight) {
8
+ @return map-get($font-weight, $weight);
9
+ }
10
+
11
+ .import-page {
12
+ &__header {
13
+ min-height: 110px;
14
+ padding: 24px;
15
+ background: $white;
16
+ .header-title {
17
+ @include font($size: size(24), $color: $primary-text-clr, $weight: bold);
18
+ font-family: $primary-font;
19
+ line-height: 32px;
20
+ margin-bottom: 8px;
21
+ }
22
+ .header-description {
23
+ @include font($size: size(14), $color: $primary-text-clr);
24
+ font-family: $primary-font;
25
+ line-height: 22px;
26
+ margin-bottom: 0;
27
+ }
28
+ }
29
+ &__body {
30
+ padding: 16px 24px;
31
+ .body-title {
32
+ @include font($size: size(16), $color: $primary-text-clr, $weight: bold);
33
+ font-family: $primary-font;
34
+ line-height: 24px;
35
+ margin-bottom: 16px;
36
+ }
37
+
38
+ .success-card {
39
+ max-width: 752px;
40
+ margin-top: 24px;
41
+ padding: 24px;
42
+ background: $green-lightest-clr;
43
+ border-radius: $radius-4;
44
+ .success-title {
45
+ @include font($size: size(12), $color: $green-regular-clr, $weight: bold);
46
+ font-family: $primary-font;
47
+ margin-bottom: 16px;
48
+ text-transform: uppercase;
49
+ }
50
+ .success-msg {
51
+ @include font($size: size(16), $weight: bold);
52
+ font-family: $primary-font;
53
+ line-height: 24px;
54
+ margin-bottom: 8px;
55
+ }
56
+ .success-msg-info {
57
+ @include font($size: size(14));
58
+ font-family: $primary-font;
59
+ line-height: 22px;
60
+ margin-bottom: 0;
61
+ }
62
+ }
63
+
64
+ .actions-wrapper {
65
+ margin-top: 32px;
66
+ button {
67
+ padding: 5px 10px;
68
+ }
69
+ }
70
+
71
+ //form UI
72
+ .csv-import-form {
73
+ .form-card {
74
+ max-width: 752px;
75
+ margin-bottom: 32px;
76
+ padding: 24px;
77
+ background: $white;
78
+ border: 1px solid $grey-light-clr;
79
+ border-radius: $radius-4;
80
+ }
81
+ .note-card {
82
+ width: 100%;
83
+ margin-top: 24px;
84
+ padding: 24px;
85
+ background: $blue-lightest-clr;
86
+ border-radius: $radius-4;
87
+ .note-title {
88
+ @include font($size: size(12), $color: $blue-regular-clr, $weight: bold);
89
+ font-family: $primary-font;
90
+ margin-bottom: 16px;
91
+ text-transform: uppercase;
92
+ }
93
+ .steps-wrapper {
94
+ .steps-title {
95
+ @include font($size: size(16), $weight: bold);
96
+ font-family: $primary-font;
97
+ line-height: 24px;
98
+ margin-bottom: 8px;
99
+ }
100
+ .steps-list {
101
+ margin-bottom: 0;
102
+ padding-inline-start: 16px;
103
+ li {
104
+ @include font($size: size(14));
105
+ font-family: $primary-font;
106
+ line-height: 22px;
107
+ margin-bottom: 8px;
108
+ &:nth-last-child(1) {
109
+ margin-bottom: 0;
110
+ }
111
+ a {
112
+ color: $brand-color;
113
+ text-decoration: underline !important;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ .import-btn {
120
+ padding: 5px 10px;
121
+ }
122
+ }
123
+
124
+ }
125
+ }
@@ -71,6 +71,25 @@ module CmAdmin
71
71
  end
72
72
  end
73
73
 
74
+ def import
75
+ @model = Model.find_by({name: controller_name.titleize})
76
+ allowed_params = params.permit(file_import: [:associated_model_name, :import_file]).to_h
77
+ file_import = ::FileImport.new(allowed_params[:file_import])
78
+ file_import.added_by = Current.user
79
+ respond_to do |format|
80
+ if file_import.save!
81
+ format.html { redirect_back fallback_location: cm_admin.send("#{@model.name.underscore}_index_path"), notice: "Your import is successfully queued." }
82
+ end
83
+ end
84
+ end
85
+
86
+ def import_form
87
+ @model = Model.find_by({name: controller_name.titleize})
88
+ respond_to do |format|
89
+ format.html { render '/cm_admin/main/import_form' }
90
+ end
91
+ end
92
+
74
93
  def cm_custom_method(params)
75
94
  scoped_model = "CmAdmin::#{@model.name}Policy::Scope".constantize.new(Current.user, @model.name.constantize).resolve
76
95
  resource_identifier
@@ -24,5 +24,46 @@ module CmAdmin
24
24
  return action_name.to_sym
25
25
  end
26
26
  end
27
+
28
+ def formatted_error_message(model_name, field_name)
29
+ invalid_rows = model_name.send(field_name)
30
+ if invalid_rows.present?
31
+ content_tag(:div) do
32
+ concat error_header
33
+ concat error_items(invalid_rows)
34
+ end
35
+ end
36
+ end
37
+
38
+ def error_header
39
+ content_tag :div, class: 'info-split' do
40
+ concat content_tag(:div, "Row number", class: 'info-split__lhs')
41
+ concat content_tag(:div, "Error")
42
+ end
43
+ end
44
+
45
+ def error_items(invalid_rows)
46
+ content_tag :div do
47
+ invalid_rows.each do |row_item|
48
+ concat format_error_item(row_item)
49
+ end
50
+ end
51
+ end
52
+
53
+ def format_error_item(row_item)
54
+ content_tag :div, class: 'info-split' do
55
+ concat content_tag(:div, row_item[0], class: 'info-split__lhs')
56
+ concat format_error(row_item[2])
57
+ end
58
+ end
59
+
60
+ def format_error(errors)
61
+ content_tag :div do
62
+ errors.each do |error|
63
+ message = error[1].instance_of?(Array) ? error[1].join(', ') : error[1]
64
+ concat content_tag(:div, error[0].titleize + '-' + message)
65
+ end
66
+ end
67
+ end
27
68
  end
28
69
  end
@@ -0,0 +1,38 @@
1
+ class FileImportProcessorJob < ApplicationJob
2
+ queue_as :default
3
+
4
+ def perform(file_import)
5
+ model = CmAdmin::Model.find_by({name: file_import.associated_model_name})
6
+ path = ActiveStorage::Blob.service.path_for(file_import.import_file.key)
7
+ importer = model.importer.class_name.classify.constantize.new(path: path)
8
+ case model.importer.importer_type.to_s
9
+ when 'csv_importer'
10
+ run_csv_importer(importer, file_import)
11
+ when 'custom_importer'
12
+ run_custom_importer(importer, file_import)
13
+ end
14
+ end
15
+
16
+ # All logic are from csv_importer gem
17
+ def run_csv_importer(importer, file_import)
18
+ if importer.valid_header?
19
+ importer.run!
20
+ if importer.report.success?
21
+ file_import.update(status: :success, completed_at: DateTime.now)
22
+ else
23
+ identifier = importer.config.identifiers.first
24
+ invalid_items_array = importer.report.invalid_rows.map { |row| [row.line_number, row.model.send(identifier), row.errors] }
25
+ file_import.update(status: :failed, completed_at: DateTime.now, invalid_row_items: invalid_items_array)
26
+ end
27
+ else
28
+ file_import.update(status: :failed, completed_at: DateTime.now, invalid_row_items: [[1, 'invalid_header', {invalid_header: importer.report.message}]])
29
+ end
30
+ end
31
+
32
+ def run_custom_importer(importer, file_import)
33
+ importer.run!
34
+ invalid_items_array = importer.invalid_rows.map { |row| [row.line_number, row.identifier, row.errors] }
35
+ status = importer.invalid_rows.empty? ? :success : :failed
36
+ file_import.update(status: status, completed_at: DateTime.now, invalid_row_items: invalid_items_array)
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ module CmAdmin::FileImport
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ cm_admin do
5
+ STATUS_TAG_COLOR = { in_progress: 'yellow-tag', success: 'success'}
6
+ actions only: [:index, :show]
7
+ set_icon 'fa fa-user'
8
+ cm_index do
9
+ page_title 'File Import'
10
+ page_description 'Manage all file import progress here'
11
+
12
+ column :id
13
+ column :imported_file_name, header: 'File'
14
+ column :created_at, header: 'Started At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
15
+ column :completed_at, header: 'Completed At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
16
+ column :associated_model_name, header: 'Model name'
17
+ column :added_by_name, header: 'Uploaded By'
18
+ column :status, field_type: :tag, tag_class: STATUS_TAG_COLOR
19
+ end
20
+
21
+ cm_show page_title: :id do
22
+ tab :profile, '' do
23
+ cm_show_section 'Import details' do
24
+ field :id, label: 'Import ID'
25
+ field :imported_file_name, label: 'File'
26
+ field :created_at, header: 'Started At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
27
+ field :completed_at, header: 'Completed At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
28
+ field :associated_model_name, header: 'Model name'
29
+ field :added_by_name, header: 'Uploaded By'
30
+ field :status, field_type: :tag, tag_class: STATUS_TAG_COLOR
31
+ end
32
+ cm_show_section 'Errors' do
33
+ field :invalid_row_items, field_type: :custom, helper_method: :formatted_error_message
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ class FileImport < ApplicationRecord
2
+ include CmAdmin::FileImport
3
+
4
+ belongs_to :added_by, polymorphic: true
5
+
6
+ enum status: { in_progress: 0, success: 1, failed: 2 }
7
+
8
+ has_one_attached :import_file
9
+
10
+ after_create_commit :process_uploaded_file
11
+
12
+ store_accessor :error_report, :invalid_row_items
13
+
14
+
15
+ def process_uploaded_file
16
+ FileImportProcessorJob.perform_later(self)
17
+ end
18
+
19
+ def imported_file_name
20
+ import_file.filename.to_s
21
+ end
22
+
23
+ def added_by_name
24
+ added_by.first_name
25
+ end
26
+
27
+ end
@@ -0,0 +1,11 @@
1
+ class CmAdmin::FileImportPolicy < ApplicationPolicy
2
+
3
+ def index?
4
+ true
5
+ end
6
+
7
+ def show?
8
+ true
9
+ end
10
+
11
+ end
@@ -1,8 +1,7 @@
1
1
  - edit_action = available_actions(cm_model, 'edit')
2
2
  - destroy_action = available_actions(cm_model, 'destroy')
3
3
  - custom_actions = available_actions(cm_model, 'custom_actions')
4
- - custom_actions_modals = custom_actions.select{ |act| act if act.display_type.eql?(:modal) }
5
-
4
+ - current_model = @associated_model || @model
6
5
  - if custom_actions.any? || edit_action.present? || destroy_action.present?
7
6
  td.row-action-cell
8
7
  .row-action-tool
@@ -13,13 +12,13 @@
13
12
  i.fa.fa-angle-down
14
13
  .popup-card.table-export-popup.hidden
15
14
  - if edit_action.present?
16
- = link_to "#{page_url('edit', ar_object)}" do
15
+ = link_to cm_admin.send("#{current_model.name.underscore}_edit_path", ar_object.id) do
17
16
  .popup-option
18
17
  span
19
18
  i.fa.fa-edit
20
19
  | Edit
21
20
  - if destroy_action.present?
22
- = link_to "#{page_url('destroy', ar_object)}", method: :delete do
21
+ = link_to cm_admin.send("#{current_model.name.underscore}_destroy_path", ar_object.id), method: :delete do
23
22
  .popup-option
24
23
  span
25
24
  i.fa.fa-trash
@@ -39,14 +38,3 @@
39
38
  span
40
39
  i class="#{custom_action.icon_name}"
41
40
  = custom_action.name.humanize
42
-
43
- - if custom_actions_modals.any?
44
- - custom_actions_modals.each do |custom_action|
45
- .modal.fade id="#{custom_action.name.classify}Modal-#{ar_object.id.to_s}" aria-hidden='true' aria-labelledby="#{custom_action.name.classify}ModalLabel" tabindex='1'
46
- .modal-dialog
47
- .modal-content
48
- .modal-header
49
- h5.modal-title id="#{custom_action.name.classify}ModalLabel" = custom_action.name.classify
50
- button.btn-close aria-label='Close' data-bs-dismiss='modal'
51
- .modal-body
52
- = render partial: custom_action.partial, locals: { ar_object: ar_object }
@@ -46,3 +46,5 @@
46
46
  .cm-pagination__lhs Showing #{@associated_ar_object.pagy.from} to #{@associated_ar_object.pagy.to} out of #{@associated_ar_object.pagy.count}
47
47
  .cm-pagination__rhs
48
48
  == render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @associated_ar_object.pagy }
49
+
50
+ = render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @associated_model, ar_collection: @associated_ar_object }
@@ -0,0 +1,13 @@
1
+ - custom_actions = available_actions(cm_model, 'custom_actions')
2
+ - custom_actions_modals = custom_actions.select{ |act| act if act.display_type.eql?(:modal) }
3
+ - ar_collection.data.each do |ar_object|
4
+ - if custom_actions_modals.any?
5
+ - custom_actions_modals.each do |custom_action|
6
+ .modal.fade id="#{custom_action.name.classify}Modal-#{ar_object.id.to_s}" aria-hidden='true' aria-labelledby="#{custom_action.name.classify}ModalLabel" tabindex='1'
7
+ .modal-dialog
8
+ .modal-content
9
+ .modal-header
10
+ h5.modal-title id="#{custom_action.name.classify}ModalLabel" = custom_action.name.classify
11
+ button.btn-close aria-label='Close' data-bs-dismiss='modal'
12
+ .modal-body
13
+ = render partial: custom_action.partial, locals: { ar_object: ar_object }
@@ -39,3 +39,5 @@
39
39
  .cm-pagination__lhs Showing #{@ar_object.pagy.from} to #{@ar_object.pagy.to} out of #{@ar_object.pagy.count}
40
40
  .cm-pagination__rhs
41
41
  == render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @ar_object.pagy }
42
+
43
+ = render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @model, ar_collection: @ar_object }
@@ -22,6 +22,8 @@
22
22
  li
23
23
  .popup-option.pointer data-bs-toggle='modal' data-bs-target='#exportmodal'
24
24
  span Export
25
+ - if @model.importer
26
+ = link_to 'Import', cm_admin.send(:"#{@model.name.underscore}_import_path"), class: 'primary-btn ml-2'
25
27
  - new_action = @model.available_actions.select{|act| act if act.action_type.eql?(:default) && act.name.eql?('new')}
26
28
  - if new_action.any? && policy([:cm_admin, @model.name.classify.constantize]).new?
27
29
  = link_to 'Add', cm_admin.send(:"#{@model.name.underscore}_new_path"), class: 'primary-btn ml-2'
@@ -0,0 +1,35 @@
1
+ .import-page
2
+ .import-page__header
3
+ h1.header-title #{@model.name} Import
4
+ p.header-description To import #{@model.name.underscore} data using the form below.
5
+ .import-page__body
6
+ h6.body-title Import data
7
+ - if flash[:notice]
8
+ .success-card
9
+ h6.success-title
10
+ i.fa-solid.fa-circle-check
11
+ | SUCCESS
12
+ p.success-msg Your file has been uploaded and it will be processed soon.
13
+ / p.success-msg-info An email will be sent once the process is completed. If there are any problems with the import, we'll let you know through an email.
14
+ .actions-wrapper
15
+ a.secondary-btn href="#{cm_admin.send(:"#{@model.name.underscore}_import_path")}" Import new data
16
+ / button.cta-btn.ml-2 Back to Page_Name
17
+ - else
18
+ = simple_form_for(FileImport.new, url: "/admin/#{@model.ar_model.table_name}/import", method: :post, html: { class: "csv-import-form" }) do |f|
19
+ .form-card
20
+ = f.input :associated_model_name, as: :hidden, placeholder: 'Enter product title', label: false, input_html: { value: @model.name }
21
+ = f.input :import_file, as: :file, placeholder: 'Enter product title', label: false
22
+ .note-card
23
+ h6.note-title
24
+ i.fa.fa-info-circle
25
+ | Note
26
+ .steps-wrapper
27
+ h6.steps-title Follow these steps to import your data
28
+ ul.steps-list
29
+ li
30
+ | Download this
31
+ a href="#" template file
32
+ li Add your data on the file without changing the format
33
+ li Save the file as a csv, xls or xlsx
34
+ li Upload the file in the field above
35
+ = f.button :submit, class: "cta-btn import-btn", value: 'Import data'
data/cm_admin.gemspec CHANGED
@@ -33,4 +33,5 @@ Gem::Specification.new do |spec|
33
33
  spec.add_runtime_dependency 'pundit', '~> 2.2.0'
34
34
  spec.add_runtime_dependency 'slim', '~> 4.1.0'
35
35
  spec.add_runtime_dependency 'webpacker', '~> 5.4.3'
36
+ spec.add_runtime_dependency 'csv-importer', '~> 0.8.2'
36
37
  end
data/config/routes.rb CHANGED
@@ -10,6 +10,12 @@ CmAdmin::Engine.routes.draw do
10
10
 
11
11
  # Defining action routes for each model
12
12
  CmAdmin.config.cm_admin_models.each do |model|
13
+ if model.importer
14
+ scope model.name.tableize do
15
+ send(:get, 'import', to: "#{model.name.underscore}#import_form", as: "#{model.name.underscore}_import_form")
16
+ send(:post, 'import', to: "#{model.name.underscore}#import", as: "#{model.name.underscore}_import")
17
+ end
18
+ end
13
19
  model.available_actions.sort_by {|act| act.name}.each do |act|
14
20
  scope model.name.tableize do
15
21
  send(act.verb, act.path.present? ? act.path : act.name, to: "#{model.name.underscore}##{act.name}", as: "#{model.name.underscore}_#{act.name}")
@@ -1,5 +1,6 @@
1
1
  require_relative 'constants'
2
2
  require_relative 'models/action'
3
+ require_relative 'models/importer'
3
4
  require_relative 'models/custom_action'
4
5
  require_relative 'models/field'
5
6
  require_relative 'models/form_field'
@@ -15,6 +16,7 @@ require 'axlsx'
15
16
  require 'cocoon'
16
17
  require 'pundit'
17
18
  require 'local_time'
19
+ require 'csv_importer'
18
20
 
19
21
  module CmAdmin
20
22
  class Model
@@ -23,7 +25,7 @@ module CmAdmin
23
25
  include Models::DslMethod
24
26
  attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields,
25
27
  :current_action, :params, :filters, :available_tabs, :icon_name
26
- attr_reader :name, :ar_model, :is_visible_on_sidebar
28
+ attr_reader :name, :ar_model, :is_visible_on_sidebar, :importer
27
29
 
28
30
  def initialize(entity, &block)
29
31
  @name = entity.name
@@ -80,6 +82,10 @@ module CmAdmin
80
82
  @actions_set = true
81
83
  end
82
84
 
85
+ def importable(class_name:, importer_type:)
86
+ @importer = CmAdmin::Models::Importer.new(class_name, importer_type)
87
+ end
88
+
83
89
  def visible_on_sidebar(visible_option)
84
90
  @is_visible_on_sidebar = visible_option
85
91
  end
@@ -1,7 +1,7 @@
1
1
  module CmAdmin
2
2
  module Models
3
3
  class Column
4
- attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round,
4
+ attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round, :height, :width,
5
5
  :cm_css_class, :link, :url, :custom_method, :helper_method, :managable, :lockable, :drawer_partial, :tag_class
6
6
 
7
7
  def initialize(field_name, attributes = {})
@@ -13,6 +13,8 @@ module CmAdmin
13
13
 
14
14
  #formatting header (either field_name or value present in header attribute)
15
15
  self.send("header=", format_header)
16
+ self.height = 50 if self.field_type == :image && self.height.nil?
17
+ self.width = 50 if self.field_type == :image && self.width.nil?
16
18
  end
17
19
 
18
20
  #returns a string value as a header (either field_name or value present in header attribute)
@@ -2,8 +2,8 @@ module CmAdmin
2
2
  module Models
3
3
  class Field
4
4
 
5
- attr_accessor :field_name, :label, :header, :field_type, :format, :precision,
6
- :helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
5
+ attr_accessor :field_name, :label, :header, :field_type, :format, :precision, :height,
6
+ :width, :helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
7
7
 
8
8
  def initialize(field_name, attributes = {})
9
9
  @field_name = field_name
@@ -11,6 +11,8 @@ module CmAdmin
11
11
  attributes.each do |key, value|
12
12
  self.send("#{key.to_s}=", value)
13
13
  end
14
+ self.height = 50 if self.field_type == :image && self.height.nil?
15
+ self.width = 50 if self.field_type == :image && self.width.nil?
14
16
  end
15
17
 
16
18
  def set_default_values
@@ -5,17 +5,18 @@ module CmAdmin
5
5
  VALID_INPUT_TYPES = [:integer, :decimal, :string, :single_select, :multi_select, :date, :date_time, :text, :single_file_upload, :multi_file_upload, :hidden, :rich_text].freeze
6
6
 
7
7
  def initialize(field_name, input_type, attributes = {})
8
- raise ArgumentError, "Kindly select a valid filter type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{input_type} for column #{field_name}" unless VALID_INPUT_TYPES.include?(input_type.to_sym)
9
8
  @field_name = field_name
10
9
  set_default_values
11
10
  attributes.each do |key, value|
12
11
  self.send("#{key.to_s}=", value)
13
12
  end
13
+ raise ArgumentError, "Kindly select a valid input type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{self.input_type} for form field #{field_name}" unless VALID_INPUT_TYPES.include?(self.input_type.to_sym)
14
14
  end
15
15
 
16
16
  def set_default_values
17
17
  self.disabled = false
18
18
  self.label = self.field_name.to_s.titleize
19
+ self.input_type = :string
19
20
  end
20
21
  end
21
22
  end
@@ -0,0 +1,17 @@
1
+ module CmAdmin
2
+ module Models
3
+ class Importer
4
+
5
+ attr_accessor :class_name, :importer_type
6
+
7
+ VALID_IMPORTER_TYPES = [:csv_importer, :custom_importer]
8
+
9
+ def initialize(class_name, importer_type=:csv_importer)
10
+ raise ArgumentError, "Kindly select a valid importer type like #{VALID_IMPORTER_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{importer_type}" unless VALID_IMPORTER_TYPES.include?(importer_type.to_sym)
11
+ @class_name = class_name
12
+ @importer_type = importer_type
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module CmAdmin
2
- VERSION = "0.7.8"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -61,6 +61,14 @@ module CmAdmin
61
61
  concat content_tag(:div, ar_object.send(field.field_name).to_s, class: 'text-ellipsis')
62
62
  concat content_tag(:div, 'View', class: 'drawer-btn')
63
63
  end
64
+ when :image
65
+ content_tag(:div, class: 'd-flex') do
66
+ if ar_object.send(field.field_name).attached?
67
+ image_tag(ar_object.send(field.field_name).url, height: field.height, width: field.height)
68
+ else
69
+ image_tag('/assets/image_not_available', height: 50, width: 50)
70
+ end
71
+ end
64
72
  end
65
73
  end
66
74
 
@@ -13,9 +13,9 @@ module CmAdmin
13
13
  when :string
14
14
  return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value, placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}"
15
15
  when :single_select
16
- return f.select field.field_name, options_for_select(select_collection_value(field), value), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled
16
+ return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled
17
17
  when :multi_select
18
- return f.select field.field_name, options_for_select(select_collection_value(field), value), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled, multiple: true
18
+ return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled, multiple: true
19
19
  when :date
20
20
  return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value&.strftime('%d-%m-%Y'), placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}", data: { behaviour: 'date-only' }
21
21
  when :date_time
data/lib/cm_admin.rb CHANGED
@@ -35,7 +35,8 @@ module CmAdmin
35
35
  def initialize_model(entity, &block)
36
36
  if entity.is_a?(Class)
37
37
  return if CmAdmin::Model.find_by({name: entity.name})
38
- config.cm_admin_models << CmAdmin::Model.new(entity, &block)
38
+ cm_model = CmAdmin::Model.new(entity, &block)
39
+ config.cm_admin_models << cm_model
39
40
  end
40
41
  end
41
42
  end
@@ -14,6 +14,8 @@ module CmAdmin
14
14
  remove_file 'app/assets/stylesheets/actiontext.scss'
15
15
  copy_file 'application_policy.rb', 'app/policies/application_policy.rb'
16
16
  route 'mount CmAdmin::Engine => "/admin"'
17
+ generate 'migration', 'CreateFileImport associated_model_name:string added_by:references{polymorphic} error_report:jsonb completed_at:datetime status:integer'
18
+ rake 'db:migrate'
17
19
  end
18
20
  end
19
21
  end
@@ -2,5 +2,5 @@ CmAdmin.configure do |config|
2
2
  # Sets the default layout to be used for admin
3
3
  config.layout = 'admin'
4
4
  # config.authorized_roles = [:super_admin?]
5
- # config.included_models = [Admin]
5
+ config.included_models = [FileImport]
6
6
  end
@@ -1 +1 @@
1
- 00ac6841e4fa3607e5f705b375dada0dcaf2c84d
1
+ 31533361827c4faa12bf892403c629479a81246a
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cm-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.8
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sajinmp
8
8
  - anbublacky
9
9
  - AdityaTiwari2102
10
- autorequire:
10
+ autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2022-09-15 00:00:00.000000000 Z
13
+ date: 2022-10-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: caxlsx_rails
@@ -110,6 +110,20 @@ dependencies:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
112
  version: 5.4.3
113
+ - !ruby/object:Gem::Dependency
114
+ name: csv-importer
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: 0.8.2
120
+ type: :runtime
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: 0.8.2
113
127
  description: This is an admin panel gem
114
128
  email:
115
129
  - sajinprasadkm@gmail.com
@@ -128,6 +142,7 @@ files:
128
142
  - LICENSE.txt
129
143
  - README.md
130
144
  - Rakefile
145
+ - app/assets/images/image_not_available.png
131
146
  - app/assets/images/logo.png
132
147
  - app/assets/stylesheets/cm_admin/base/auth.scss
133
148
  - app/assets/stylesheets/cm_admin/base/common.scss
@@ -155,6 +170,7 @@ files:
155
170
  - app/assets/stylesheets/cm_admin/helpers/_mixins.scss
156
171
  - app/assets/stylesheets/cm_admin/helpers/_variable.scss
157
172
  - app/assets/stylesheets/cm_admin/helpers/index.scss
173
+ - app/assets/stylesheets/cm_admin/pages/import_page.scss
158
174
  - app/assets/stylesheets/cm_admin/scaffold.scss
159
175
  - app/controllers/cm_admin/application_controller.rb
160
176
  - app/controllers/cm_admin/exports_controller.rb
@@ -169,11 +185,16 @@ files:
169
185
  - app/javascript/packs/cm_admin/quick_search.js
170
186
  - app/javascript/packs/cm_admin/scaffolds.js
171
187
  - app/javascript/stylesheets/cm_admin/application.scss
188
+ - app/jobs/file_import_processor_job.rb
189
+ - app/models/concerns/cm_admin/file_import.rb
190
+ - app/models/file_import.rb
191
+ - app/policies/cm_admin/file_import_policy.rb
172
192
  - app/views/cm_admin/main/_actions_dropdown.html.slim
173
193
  - app/views/cm_admin/main/_associated_table.html.slim
174
194
  - app/views/cm_admin/main/_cm_pagy_nav.html.slim
175
195
  - app/views/cm_admin/main/_drawer.html.slim
176
196
  - app/views/cm_admin/main/_filters.html.slim
197
+ - app/views/cm_admin/main/_member_custom_action_modal.html.slim
177
198
  - app/views/cm_admin/main/_nested_fields.html.slim
178
199
  - app/views/cm_admin/main/_nested_table_form.html.slim
179
200
  - app/views/cm_admin/main/_table.html.slim
@@ -183,6 +204,7 @@ files:
183
204
  - app/views/cm_admin/main/associated_show.html.slim
184
205
  - app/views/cm_admin/main/dashboard.html.slim
185
206
  - app/views/cm_admin/main/edit.html.slim
207
+ - app/views/cm_admin/main/import_form.html.slim
186
208
  - app/views/cm_admin/main/index.html.slim
187
209
  - app/views/cm_admin/main/new.html.slim
188
210
  - app/views/cm_admin/main/show.html.slim
@@ -225,6 +247,7 @@ files:
225
247
  - lib/cm_admin/models/field.rb
226
248
  - lib/cm_admin/models/filter.rb
227
249
  - lib/cm_admin/models/form_field.rb
250
+ - lib/cm_admin/models/importer.rb
228
251
  - lib/cm_admin/models/tab.rb
229
252
  - lib/cm_admin/utils.rb
230
253
  - lib/cm_admin/version.rb
@@ -278,7 +301,7 @@ licenses:
278
301
  metadata:
279
302
  homepage_uri: https://github.com/commutatus/cm-admin
280
303
  source_code_uri: https://github.com/commutatus/cm-admin
281
- post_install_message:
304
+ post_install_message:
282
305
  rdoc_options: []
283
306
  require_paths:
284
307
  - lib
@@ -294,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
294
317
  version: '0'
295
318
  requirements: []
296
319
  rubygems_version: 3.2.3
297
- signing_key:
320
+ signing_key:
298
321
  specification_version: 4
299
322
  summary: This is an admin panel gem
300
323
  test_files: []