cm-admin 4.1.2 → 4.2.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/.github/workflows/linters.yml +4 -2
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +4 -2
- data/app/assets/javascripts/cm_admin/exports.js +48 -29
- data/app/assets/stylesheets/cm_admin/base/form.scss +7 -8
- data/app/assets/stylesheets/cm_admin/base/navbar.scss +9 -25
- data/app/assets/stylesheets/cm_admin/base/show.scss +1 -1
- data/app/assets/stylesheets/cm_admin/base/table.scss +1 -1
- data/app/assets/stylesheets/cm_admin/components/_buttons.scss +4 -0
- data/app/controllers/cm_admin/application_controller.rb +0 -8
- data/app/controllers/cm_admin/exports_controller.rb +14 -3
- data/app/controllers/cm_admin/resource_controller.rb +6 -8
- data/app/jobs/generate_export_file_job.rb +15 -0
- data/app/mailers/export_mailer.rb +22 -0
- data/app/models/concerns/exportable.rb +97 -0
- data/app/models/file_export.rb +17 -0
- data/app/views/cm_admin/main/_associated_table.html.slim +2 -0
- data/app/views/cm_admin/main/_card.html.slim +1 -1
- data/app/views/cm_admin/main/_kanban.html.slim +1 -1
- data/app/views/cm_admin/main/_table.html.slim +1 -1
- data/app/views/cm_admin/main/_top_navbar.html.slim +0 -10
- data/app/views/cm_admin/main/associated_index.html.slim +2 -4
- data/app/views/cm_admin/main/index.html.slim +7 -3
- data/app/views/export_mailer/export_email.html.slim +19 -0
- data/app/views/layouts/cm_admin.html.slim +12 -5
- data/app/views/layouts/mailer.html.slim +6 -0
- data/cm_admin.gemspec +5 -3
- data/config/routes.rb +1 -1
- data/lib/cm_admin/models/export.rb +2 -55
- data/lib/cm_admin/version.rb +1 -1
- data/lib/cm_admin/view_helpers.rb +55 -37
- data/lib/generators/cm_admin/install_generator.rb +1 -0
- metadata +26 -6
- data/.rubocop-https---raw-githubusercontent-com-commutatus-cm-linters-main-rubocop-yml +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 213f8a5059f6d3687a1e8c751deeec3ffd7c523206116f93927ed0ce0e98d888
|
4
|
+
data.tar.gz: d6845b50243ba50c3dcd3ac8dcab2c66cc108628bf9bb499e1dd8912e3ac161c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33dfb1f0668c377d8bf30790bb7647da357074c676de40370ed04ca904e99f4a3cab40da8cee2327c7bca477d976d28b2dfb9a6f07e7b8578028c2f5dbdda88e
|
7
|
+
data.tar.gz: 1d39c89c8a8245a212a5ae192de107ee10d8ec4505ee07fe0c08e00ae4130f7a4a6fe56b0ce6630a1b7f9ed874d33b0a8bcc4a8cb4401f37ee8b718df02a8ded
|
@@ -19,9 +19,11 @@ jobs:
|
|
19
19
|
rubocop_extensions: rubocop-rails:2.26.2
|
20
20
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
21
21
|
reporter: github-pr-check # Possible values are github-pr-check, github-pr-review
|
22
|
+
fail_level: none
|
23
|
+
fail_on_error: false
|
22
24
|
- uses: actions/checkout@v4
|
23
25
|
- name: stylelint
|
24
26
|
uses: reviewdog/action-stylelint@v1
|
25
27
|
with:
|
26
|
-
|
27
|
-
|
28
|
+
reporter: github-pr-review # Change reporter.
|
29
|
+
stylelint_input: "**/*.scss"
|
data/.rubocop.yml
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
inherit_from:
|
2
|
-
- https://raw.githubusercontent.com/commutatus/cm-linters/main/rubocop.yml
|
2
|
+
- https://raw.githubusercontent.com/commutatus/cm-linters/main/rubocop.yml
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cm-admin (4.
|
4
|
+
cm-admin (4.2.0)
|
5
5
|
caxlsx_rails
|
6
6
|
cocoon (~> 1.2.15)
|
7
|
-
csv
|
7
|
+
csv (>= 3.3.0)
|
8
|
+
csv-importer (>= 0.8.2)
|
8
9
|
importmap-rails
|
9
10
|
local_time (~> 3.0.2)
|
10
11
|
pagy (~> 4.11.0)
|
@@ -111,6 +112,7 @@ GEM
|
|
111
112
|
concurrent-ruby (1.3.4)
|
112
113
|
connection_pool (2.4.1)
|
113
114
|
crass (1.0.6)
|
115
|
+
csv (3.3.0)
|
114
116
|
csv-importer (0.8.2)
|
115
117
|
virtus
|
116
118
|
date (3.3.4)
|
@@ -1,33 +1,52 @@
|
|
1
|
-
$(document).on(
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
$(document).on("click", '[data-behaviour="export-select-all"]', function (e) {
|
2
|
+
if ($(this).is(":checked")) {
|
3
|
+
$('[data-behaviour="export-checkbox"]').prop("checked", true);
|
4
|
+
} else {
|
5
|
+
$('[data-behaviour="export-checkbox"]').prop("checked", false);
|
6
|
+
}
|
7
7
|
});
|
8
8
|
|
9
|
-
$(document).on(
|
10
|
-
"
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
}
|
9
|
+
$(document).on("click", '[data-behaviour="export-checkbox"]', function (e) {
|
10
|
+
const container = $(this).closest(".row");
|
11
|
+
if (
|
12
|
+
container.find('[data-behaviour="export-checkbox"]:checked').length ==
|
13
|
+
container.find('[data-behaviour="export-checkbox"]').length
|
14
|
+
) {
|
15
|
+
$('[data-behaviour="export-select-all"]').prop("checked", true);
|
16
|
+
} else {
|
17
|
+
$('[data-behaviour="export-select-all"]').prop("checked", false);
|
19
18
|
}
|
20
|
-
);
|
19
|
+
});
|
21
20
|
|
22
|
-
$(document).on(
|
23
|
-
"
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
);
|
21
|
+
$(document).on("click", '[data-behaviour="export-submit"]', function (e) {
|
22
|
+
$('[data-behaviour="export-form-submit"]').submit();
|
23
|
+
const exportFormBody = $("[data-behaviour='export-form-container']");
|
24
|
+
const exportProcessing = $("[data-behaviour='export-processing']");
|
25
|
+
const exportTitle = $("[data-behaviour='export-modal-title']");
|
26
|
+
const exportFooter = $("[data-behaviour='export-modal-footer']");
|
27
|
+
const exportContent = $("[data-behaviour='export-modal-content']");
|
28
|
+
exportTitle.html("Export Processing");
|
29
|
+
exportFormBody.addClass("hidden");
|
30
|
+
exportFooter.addClass("hidden");
|
31
|
+
exportProcessing.removeClass("hidden");
|
32
|
+
exportContent.removeClass("export-modal-content");
|
33
|
+
exportContent.addClass("modal-content");
|
34
|
+
});
|
35
|
+
|
36
|
+
document.addEventListener("turbo:load", function () {
|
37
|
+
document
|
38
|
+
.querySelector('[data-behaviour="export-modal"]')
|
39
|
+
.addEventListener("hidden.bs.modal", function () {
|
40
|
+
const exportFormBody = $("[data-behaviour='export-form-container']");
|
41
|
+
const exportProcessing = $("[data-behaviour='export-processing']");
|
42
|
+
const exportTitle = $("[data-behaviour='export-modal-title']");
|
43
|
+
const exportFooter = $("[data-behaviour='export-modal-footer']");
|
44
|
+
const exportContent = $("[data-behaviour='export-modal-content']");
|
45
|
+
exportTitle.html("Export Data");
|
46
|
+
exportProcessing.addClass("hidden");
|
47
|
+
exportFormBody.removeClass("hidden");
|
48
|
+
exportFooter.removeClass("hidden");
|
49
|
+
exportContent.addClass("export-modal-content");
|
50
|
+
exportContent.removeClass("modal-content");
|
51
|
+
});
|
52
|
+
});
|
@@ -50,10 +50,11 @@
|
|
50
50
|
|
51
51
|
.field-label {
|
52
52
|
@extend .form-label;
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
}
|
54
|
+
|
55
|
+
.required-label::after {
|
56
|
+
content: " *";
|
57
|
+
color: $red;
|
57
58
|
}
|
58
59
|
|
59
60
|
.field-control {
|
@@ -77,8 +78,6 @@
|
|
77
78
|
}
|
78
79
|
}
|
79
80
|
|
80
|
-
|
81
|
-
|
82
81
|
//Form card UI
|
83
82
|
.form-card {
|
84
83
|
@extend .card, .mt-3;
|
@@ -164,7 +163,7 @@
|
|
164
163
|
|
165
164
|
.bulk-action-button {
|
166
165
|
@extend .d-flex, .align-items-center, .gap-2;
|
167
|
-
|
166
|
+
|
168
167
|
&:hover {
|
169
168
|
text-decoration: none;
|
170
169
|
}
|
@@ -175,4 +174,4 @@
|
|
175
174
|
gap: 8px;
|
176
175
|
align-items: center;
|
177
176
|
}
|
178
|
-
}
|
177
|
+
}
|
@@ -7,30 +7,14 @@
|
|
7
7
|
line-height: 22px;
|
8
8
|
}
|
9
9
|
}
|
10
|
+
}
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
animation: fadeIn 0.2s ease-in-out;
|
20
|
-
.popup-option {
|
21
|
-
padding: 8px 16px;
|
22
|
-
@include font($size: $t4-text, $color: $primary-text-clr);
|
23
|
-
line-height: 22px;
|
24
|
-
transition: all 0.2s linear;
|
25
|
-
cursor: pointer;
|
26
|
-
&:hover {
|
27
|
-
background-color: $grey-lighter-clr;
|
28
|
-
}
|
29
|
-
span {
|
30
|
-
margin-right: 8px;
|
31
|
-
}
|
32
|
-
}
|
33
|
-
}
|
34
|
-
}
|
35
|
-
}
|
12
|
+
.export-container {
|
13
|
+
position: relative;
|
14
|
+
margin: 0 24px 0 auto;
|
15
|
+
}
|
16
|
+
|
17
|
+
.export-modal-content {
|
18
|
+
@extend .modal-content;
|
19
|
+
height: 80vh;
|
36
20
|
}
|
@@ -40,6 +40,10 @@
|
|
40
40
|
@extend .btn, .btn-primary, .btn-sm, .btn-icon-spacing, .disabled;
|
41
41
|
}
|
42
42
|
|
43
|
+
.cm-admin .btn-dark {
|
44
|
+
@extend .btn, .btn-outline-dark, .btn-sm;
|
45
|
+
}
|
46
|
+
|
43
47
|
// button mixin - We can use if needed
|
44
48
|
// @include button-variant($background: #6554e0, $border: #6554e0);
|
45
49
|
|
@@ -1,13 +1,5 @@
|
|
1
1
|
module CmAdmin
|
2
2
|
class ApplicationController < ::ActionController::Base
|
3
|
-
|
4
|
-
# before_action :check_cm_admin
|
5
3
|
layout 'cm_admin'
|
6
|
-
|
7
|
-
|
8
|
-
def check_cm_admin
|
9
|
-
redirect_to CmAdmin::Engine.mount_path + '/access-denied' unless defined?(::Current) && ::Current.cm_admin_user
|
10
|
-
end
|
11
|
-
|
12
4
|
end
|
13
5
|
end
|
@@ -6,9 +6,20 @@ require 'cm_admin/view_helpers'
|
|
6
6
|
|
7
7
|
module CmAdmin
|
8
8
|
class ExportsController < ApplicationController
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
before_action :set_current_user_permission
|
10
|
+
|
11
|
+
def send_export_email
|
12
|
+
export_params = params.permit(:select_all, :class_name, :associated_class_name, :parent_id, :child_records,
|
13
|
+
:action_name, :filters, columns: []).to_h
|
14
|
+
associated_model_name = params[:associated_class_name].presence || params[:class_name]
|
15
|
+
expires_at = DateTime.now + 1.day
|
16
|
+
FileExport.create!(associated_model_name:, exported_by: Current.user, expires_at:, params: export_params)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def set_current_user_permission
|
22
|
+
CmCurrent.user_permissions = Current.user.cm_role.cm_permissions if Current.user.cm_role.present?
|
12
23
|
end
|
13
24
|
end
|
14
25
|
end
|
@@ -8,11 +8,6 @@ module CmAdmin
|
|
8
8
|
skip_before_action :verify_authenticity_token, only: :reset_sort_columns
|
9
9
|
before_action :set_current_user_permission
|
10
10
|
|
11
|
-
|
12
|
-
def set_current_user_permission
|
13
|
-
CmCurrent.user_permissions = Current.user.cm_role.cm_permissions if Current.user.cm_role.present?
|
14
|
-
end
|
15
|
-
|
16
11
|
def cm_index(params)
|
17
12
|
@current_action = CmAdmin::Models::Action.find_by(@model, name: 'index')
|
18
13
|
# Based on the params the filter and pagination object to be set
|
@@ -138,7 +133,7 @@ module CmAdmin
|
|
138
133
|
|
139
134
|
if @action.execution_mode.to_sym == :individual
|
140
135
|
flash[:bulk_action_success] = "#{@bulk_action_processor.success_records.size} rows were successful for #{action_name}" if @bulk_action_processor.success_records.present?
|
141
|
-
flash[:alert] = "<b>#{@bulk_action_processor.invalid_records.size} rows were unsuccessful for #{action_name}</b>"
|
136
|
+
flash[:alert] = "<b>#{@bulk_action_processor.invalid_records.size} rows were unsuccessful for #{action_name}</b>"
|
142
137
|
flash[:bulk_action_error] = "<div>
|
143
138
|
<div>
|
144
139
|
<div><b>#{@bulk_action_processor.invalid_records.size} rows were unsuccessful for #{action_name}</b></div>
|
@@ -151,7 +146,7 @@ module CmAdmin
|
|
151
146
|
<div><b>#{action_name} encountered the following errors:</b></div>
|
152
147
|
<ul>#{error_messages}</ul>
|
153
148
|
</div>"
|
154
|
-
end
|
149
|
+
end
|
155
150
|
format.html { redirect_to request.referrer}
|
156
151
|
end
|
157
152
|
end
|
@@ -322,7 +317,6 @@ module CmAdmin
|
|
322
317
|
end
|
323
318
|
|
324
319
|
@associated_ar_object = if child_records.is_a? ActiveRecord::Relation
|
325
|
-
|
326
320
|
filter_by(params, child_records, parent_record: @ar_object, filter_params: @associated_model.filter_params(params))
|
327
321
|
else
|
328
322
|
child_records
|
@@ -444,6 +438,10 @@ module CmAdmin
|
|
444
438
|
|
445
439
|
private
|
446
440
|
|
441
|
+
def set_current_user_permission
|
442
|
+
CmCurrent.user_permissions = Current.user.cm_role.cm_permissions if Current.user.cm_role.present?
|
443
|
+
end
|
444
|
+
|
447
445
|
def attachment_fields(model_object)
|
448
446
|
model_object.reflect_on_all_associations.map do |reflection|
|
449
447
|
next if reflection.options[:polymorphic]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class GenerateExportFileJob < ApplicationJob
|
2
|
+
queue_as :default
|
3
|
+
|
4
|
+
def perform(file_export)
|
5
|
+
Current.user = file_export.exported_by
|
6
|
+
CmCurrent.user_permissions = Current.user.cm_role.cm_permissions if Current.user.cm_role.present?
|
7
|
+
|
8
|
+
file_export.attach_export_file
|
9
|
+
ExportMailer.export_email(file_export).deliver_now
|
10
|
+
file_export.success!
|
11
|
+
rescue StandardError => e
|
12
|
+
file_export&.failed!
|
13
|
+
raise e
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ExportMailer < ApplicationMailer
|
2
|
+
def export_email(file_export)
|
3
|
+
set_current_for_development
|
4
|
+
expires_in = (file_export.expires_at - Time.now).to_i
|
5
|
+
@model = file_export.associated_model_name&.pluralize
|
6
|
+
@link = file_export.export_file.url(expires_in:)
|
7
|
+
@expires_at = file_export.expires_at.utc
|
8
|
+
mail(to: file_export&.exported_by&.email, subject: "#{@model} Export is ready for download")
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def set_current_for_development
|
14
|
+
return unless Rails.env.development?
|
15
|
+
|
16
|
+
url = Rails.application.credentials[:be_url]
|
17
|
+
host = URI.parse(url).host
|
18
|
+
protocol = URI.parse(url).scheme
|
19
|
+
port = URI.parse(url).port
|
20
|
+
ActiveStorage::Current.url_options = { host:, protocol:, port: }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Exportable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def attach_export_file
|
5
|
+
file_path = generate_excel
|
6
|
+
filename = file_path&.split('/')&.last
|
7
|
+
export_file.attach(io: File.open(file_path), filename:)
|
8
|
+
rescue StandardError => e
|
9
|
+
failed!
|
10
|
+
raise e
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_excel
|
14
|
+
params = self.params.with_indifferent_access
|
15
|
+
klass_name = params[:class_name]
|
16
|
+
associated_klass_name = params[:associated_class_name]
|
17
|
+
action_name = params[:action_name] || 'index'
|
18
|
+
|
19
|
+
model = CmAdmin::Model.find_by({ name: klass_name })
|
20
|
+
klass = klass_name.constantize
|
21
|
+
current_action = CmAdmin::Models::Action.find_by(model, name: action_name)
|
22
|
+
|
23
|
+
if associated_klass_name.present? && params[:parent_id].present?
|
24
|
+
scoped_model = "CmAdmin::#{model.name}Policy::#{action_name.classify}Scope".constantize.new(Current.user, klass).resolve
|
25
|
+
parent_model = model
|
26
|
+
model = CmAdmin::Model.find_by({ name: associated_klass_name })
|
27
|
+
parent_record = fetch_ar_object(scoped_model, params[:parent_id])
|
28
|
+
child_records = parent_record.send(params[:child_records])
|
29
|
+
end
|
30
|
+
selected_column_names = params[:columns] || []
|
31
|
+
records = if associated_klass_name.present?
|
32
|
+
child_records
|
33
|
+
else
|
34
|
+
"CmAdmin::#{klass_name}Policy::Scope".constantize.new(Current.user, klass).resolve
|
35
|
+
end
|
36
|
+
records.includes(current_action.eager_load_associations) if current_action.eager_load_associations.present?
|
37
|
+
filter_params = JSON.parse(params[:filters] || '{}')
|
38
|
+
filtered_data = CmAdmin::Models::Filter.filtered_data(filter_params, records, model.filters)
|
39
|
+
formatted_data = format_records(filtered_data, model, selected_column_names, parent_model:, action_name:)
|
40
|
+
file_path = "#{Rails.root}/tmp/#{model.name}_data_#{DateTime.now.strftime('%Y-%m-%d_%H-%M-%S')}.xlsx"
|
41
|
+
create_workbook(model, formatted_data, file_path)
|
42
|
+
file_path
|
43
|
+
end
|
44
|
+
|
45
|
+
def exportable_columns(klass, action_name: :index)
|
46
|
+
klass.available_fields[action_name].select(&:exportable)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def fetch_ar_object(model_object, id)
|
52
|
+
return model_object.friendly.find(id) if model_object.respond_to?(:friendly)
|
53
|
+
|
54
|
+
model_object.find(id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_records(records, model, selected_column_names, parent_model:, action_name: :index)
|
58
|
+
deserialized_columns = CmAdmin::Utils.deserialize_csv_columns(selected_column_names, :as_json_params)
|
59
|
+
main_model = parent_model || model
|
60
|
+
available_fields = main_model.available_fields[action_name.to_sym]
|
61
|
+
# This includes isn't recursve, a full solution should be recursive
|
62
|
+
records_arr = []
|
63
|
+
records.includes(deserialized_columns[:include].keys).find_each do |record|
|
64
|
+
record_hash = record.as_json({ only: selected_column_names.map(&:to_sym) })
|
65
|
+
selected_column_names.each do |column_name|
|
66
|
+
break unless available_fields.map(&:field_name).include?(column_name.to_sym)
|
67
|
+
|
68
|
+
column = CmAdmin::Models::Column.find_by(model, :index, { name: column_name.to_sym })
|
69
|
+
record_hash[column.field_name] = if column.field_type == :custom
|
70
|
+
send(column.helper_method, record, column.field_name).to_s
|
71
|
+
else
|
72
|
+
record.send(column.field_name).to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
records_arr << record_hash
|
76
|
+
end
|
77
|
+
records_arr
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_workbook(cm_model, records, file_path)
|
81
|
+
flattened_records = records.map { |record| CmAdmin::Utils.flatten_hash(record) }
|
82
|
+
columns = exportable_columns(cm_model).select do |column|
|
83
|
+
flattened_records.first.keys.include?(column.field_name.to_s)
|
84
|
+
end
|
85
|
+
size_arr = []
|
86
|
+
columns.size.times { size_arr << 22 }
|
87
|
+
xl = Axlsx::Package.new
|
88
|
+
xl.workbook.add_worksheet do |sheet|
|
89
|
+
sheet.add_row columns&.map(&:header), b: true
|
90
|
+
flattened_records.each do |record|
|
91
|
+
sheet.add_row(columns.map { |column| record[column.field_name.to_s] })
|
92
|
+
end
|
93
|
+
sheet.column_widths(*size_arr)
|
94
|
+
end
|
95
|
+
xl.serialize(file_path)
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class FileExport < ApplicationRecord
|
2
|
+
include CmAdmin::CustomHelper
|
3
|
+
include Exportable
|
4
|
+
|
5
|
+
enum :status, { in_progress: 0, success: 1, failed: 2 }
|
6
|
+
|
7
|
+
has_one_attached :export_file
|
8
|
+
belongs_to :exported_by, polymorphic: true
|
9
|
+
|
10
|
+
after_create :generate_export_file
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def generate_export_file
|
15
|
+
GenerateExportFileJob.perform_later(self)
|
16
|
+
end
|
17
|
+
end
|
@@ -60,3 +60,5 @@
|
|
60
60
|
.pagination-bar
|
61
61
|
p.count-text.m-0 Showing #{number_with_delimiter(@associated_ar_object.pagy.from.to_i)} to #{number_with_delimiter(@associated_ar_object.pagy.to.to_i)} out of #{number_with_delimiter(@associated_ar_object.pagy.count.to_i)}
|
62
62
|
== render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @associated_ar_object.pagy }
|
63
|
+
|
64
|
+
= export_modal(@model, action_name: @action.name.to_sym, associated_klass: @associated_model)
|
@@ -55,5 +55,5 @@
|
|
55
55
|
p.count-text.m-0 Showing #{@ar_object.pagy.from} to #{@ar_object.pagy.to} out of #{@ar_object.pagy.count}
|
56
56
|
== render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @ar_object.pagy }
|
57
57
|
|
58
|
-
=
|
58
|
+
= export_modal(@model)
|
59
59
|
= manage_column_pop_up(@model)
|
@@ -53,5 +53,5 @@
|
|
53
53
|
p.count-text.m-0 Showing #{number_with_delimiter(@ar_object.pagy.from.to_i)} to #{number_with_delimiter(@ar_object.pagy.to.to_i)} out of #{number_with_delimiter(@ar_object.pagy.count.to_i)}
|
54
54
|
== render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @ar_object.pagy }
|
55
55
|
|
56
|
-
=
|
56
|
+
= export_modal(@model)
|
57
57
|
= manage_column_pop_up(@model)
|
@@ -6,16 +6,6 @@
|
|
6
6
|
h4 = action_title
|
7
7
|
.entity-header__actions
|
8
8
|
- if @model.current_action.name == 'index'
|
9
|
-
- if has_valid_policy(@ar_object, :export)
|
10
|
-
.export-container
|
11
|
-
.dropdown
|
12
|
-
button.btn-secondary.dropdown-toggle data-bs-toggle='dropdown'
|
13
|
-
i.fa.fa-arrow-down
|
14
|
-
| Export
|
15
|
-
ul.dropdown-menu.export-popup
|
16
|
-
li
|
17
|
-
.popup-option.pointer data-bs-toggle='modal' data-bs-target='#exportmodal'
|
18
|
-
span Export
|
19
9
|
- if @model.importer && has_valid_policy(@ar_object, :importable)
|
20
10
|
= link_to 'Import', cm_admin.send(:"cm_import_#{@model.name.underscore}_path"), class: 'btn-primary ms-2'
|
21
11
|
- new_action = @model.available_actions.select{|act| act if act.action_type.eql?(:default) && act.name.eql?('new')}
|
@@ -1,15 +1,19 @@
|
|
1
|
-
|
2
1
|
= hidden_field_tag :view_type, (@current_action.view_type || params[:view_type])
|
3
2
|
.cm-index-page.cm-page-container
|
4
3
|
.sticky-container.page-top-bar
|
5
4
|
== render 'cm_admin/main/top_navbar'
|
6
|
-
- if @model.filters.present? && @action.partial.nil?
|
5
|
+
- if (@model.filters.present? || has_valid_policy(@ar_object, :export)) && @action.partial.nil?
|
7
6
|
.cm-index-page__filters
|
8
7
|
== render partial: 'cm_admin/main/filters', locals: { filters: @model.filters }
|
8
|
+
- if has_valid_policy(@ar_object, :export)
|
9
|
+
.export-container
|
10
|
+
button.btn-dark data-bs-toggle="modal" data-bs-target="#exportModal"
|
11
|
+
i.fa-regular.fa-file-export.me-1
|
12
|
+
| Export
|
9
13
|
.cm-index-page__table-container
|
10
14
|
- if @action.partial
|
11
15
|
== render @action.partial
|
12
|
-
- elsif params[:view_type] == 'table' || @current_action.view_type == :table
|
16
|
+
- elsif params[:view_type] == 'table' || @current_action.view_type == :table
|
13
17
|
== render 'cm_admin/main/table'
|
14
18
|
- elsif params[:view_type] == 'card' || @current_action.view_type == :card
|
15
19
|
== render "cm_admin/#{@model.name.underscore}/card"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
table style="background: #F2F4F6; padding: 56px 0px; width: 100%; border-spacing: 24px;"
|
2
|
+
tr
|
3
|
+
td colspan="3" style="text-align: center; margin-bottom: 24px;"
|
4
|
+
img src="#{Rails.configuration.x.project_settings.logo_url}" alt="logo" style="height: auto; width: 50px;"
|
5
|
+
tr style="width: fit-content; style=width: 100px;"
|
6
|
+
td
|
7
|
+
td style="background: #fff; padding: 40px; padding-bottom: 56px;"
|
8
|
+
p style="color: #333333; font-weight: 700; font-size: 24px; line-height: 36px; margin: 0; margin-bottom: 24px;"
|
9
|
+
= "#{@model} Export is ready"
|
10
|
+
div style="margin-bottom: 40px;"
|
11
|
+
p
|
12
|
+
| The download link will expire at <strong>#{@expires_at.strftime("%H:%M:%S")} UTC</strong> on <strong>#{@expires_at.strftime("%d %B %Y")}.</strong>
|
13
|
+
p Make sure to save the file before the link expires.
|
14
|
+
a style="border: none; background: #6D0094; color: #fff; padding: 16px 32px; width: fit-content; border-radius: 5px; text-decoration: none;" href="#{@link}"
|
15
|
+
| Download Excel
|
16
|
+
td
|
17
|
+
tr
|
18
|
+
td colspan="3" style="text-align: center; color: #5F6166; font-size: 16px; margin-top: 24px;"
|
19
|
+
= "© #{Time.current.year} #{Rails.configuration.x.project_settings.name}"
|
@@ -26,12 +26,12 @@ html
|
|
26
26
|
script src="https://kit.fontawesome.com/9c93dde7a7.js" data-mutate-approach="sync"
|
27
27
|
= stylesheet_link_tag 'cm_admin/custom', media: 'all', 'data-turbo-track': 'reload'
|
28
28
|
link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /
|
29
|
-
|
29
|
+
|
30
30
|
script src="https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"
|
31
31
|
|
32
32
|
link[rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css"]
|
33
33
|
link[rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.rtl.min.css"]
|
34
|
-
|
34
|
+
|
35
35
|
body
|
36
36
|
- if CmAdmin.config.enable_tracking
|
37
37
|
/ Google Tag Manager (noscript)
|
@@ -54,13 +54,20 @@ html
|
|
54
54
|
== render 'cm_admin/main/top_navbar'
|
55
55
|
- if @ar_object.model_name
|
56
56
|
== render 'cm_admin/main/tabs', via_xhr: false
|
57
|
-
- if @associated_model && @associated_model.filters.present?
|
58
|
-
.
|
59
|
-
|
57
|
+
- if @associated_model && (@associated_model.filters.present? || has_valid_policy(@associated_ar_object, :export))
|
58
|
+
.cm-index-page__filters
|
59
|
+
.filters-bar
|
60
|
+
== render partial: 'cm_admin/main/filters', locals: { filters: @associated_model.filters }
|
61
|
+
- if has_valid_policy(@associated_ar_object, :export)
|
62
|
+
.export-container
|
63
|
+
button.btn-dark data-bs-toggle="modal" data-bs-target="#exportModal"
|
64
|
+
i.fa-regular.fa-file-export.me-1
|
65
|
+
| Export
|
60
66
|
= yield
|
61
67
|
- else
|
62
68
|
= yield
|
63
69
|
div data-behaviour="flash-container"
|
64
70
|
= render 'layouts/cm_flash_message'
|
71
|
+
div data-behaviour="export-modal-container"
|
65
72
|
- unless (@current_action&.view_type == :kanban || params[:view_type] == 'kanban')
|
66
73
|
= render 'layouts/custom_action_modals'
|
data/cm_admin.gemspec
CHANGED
@@ -10,13 +10,14 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = 'CmAdmin providing a streamlined and efficient solution for building customized admin panels within the context of Rails applications. Its robust features empower developers to effortlessly generate and manage administrative interfaces with precision and ease.'
|
11
11
|
spec.homepage = 'https://github.com/commutatus/cm-admin'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = '>=
|
13
|
+
spec.required_ruby_version = '>= 3.3.0'
|
14
14
|
|
15
15
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
16
16
|
|
17
17
|
spec.metadata = {
|
18
18
|
'homepage_uri' => spec.homepage,
|
19
|
-
'source_code_uri' => 'https://github.com/commutatus/cm-admin'
|
19
|
+
'source_code_uri' => 'https://github.com/commutatus/cm-admin',
|
20
|
+
'github_repo' => 'ssh://github.com/commutatus/cm-admin'
|
20
21
|
}
|
21
22
|
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
22
23
|
|
@@ -30,11 +31,12 @@ Gem::Specification.new do |spec|
|
|
30
31
|
spec.require_paths = ['lib']
|
31
32
|
spec.add_runtime_dependency 'caxlsx_rails'
|
32
33
|
spec.add_runtime_dependency 'cocoon', '~> 1.2.15'
|
33
|
-
spec.add_runtime_dependency 'csv-importer', '
|
34
|
+
spec.add_runtime_dependency 'csv-importer', '>= 0.8.2'
|
34
35
|
spec.add_runtime_dependency 'local_time', '~> 3.0.2'
|
35
36
|
spec.add_runtime_dependency 'pagy', '~> 4.11.0'
|
36
37
|
spec.add_runtime_dependency 'pundit', '~> 2.2.0'
|
37
38
|
spec.add_runtime_dependency 'rails', '>= 7.0'
|
38
39
|
spec.add_runtime_dependency 'slim', '>= 4.1.0'
|
40
|
+
spec.add_runtime_dependency 'csv', '>= 3.3.0'
|
39
41
|
spec.add_dependency 'importmap-rails'
|
40
42
|
end
|
data/config/routes.rb
CHANGED
@@ -2,62 +2,9 @@ module CmAdmin
|
|
2
2
|
module Models
|
3
3
|
class Export
|
4
4
|
class << self
|
5
|
-
def
|
6
|
-
klass
|
7
|
-
selected_column_names = params[:columns] || []
|
8
|
-
# filter_params = params[:filters]
|
9
|
-
model = CmAdmin::Model.find_by({name: klass_name})
|
10
|
-
# records = get_records(klass, model, columns, helpers)
|
11
|
-
records = "CmAdmin::#{klass_name}Policy::Scope".constantize.new(Current.user, klass).resolve
|
12
|
-
filtered_data = CmAdmin::Models::Filter.filtered_data(model.filter_params(params), records, model.filters)
|
13
|
-
formatted_data = format_records(filtered_data, model, selected_column_names, helpers)
|
14
|
-
file_path = "#{Rails.root}/tmp/#{klass}_data_#{DateTime.now.strftime("%Y-%m-%d_%H-%M-%S")}.xlsx"
|
15
|
-
create_workbook(model, formatted_data, selected_column_names, file_path)
|
16
|
-
return file_path
|
5
|
+
def exportable_columns(klass, action_name: :index)
|
6
|
+
klass.available_fields[action_name].select(&:exportable)
|
17
7
|
end
|
18
|
-
|
19
|
-
def format_records(records, model, selected_column_names, helpers)
|
20
|
-
deserialized_columns = CmAdmin::Utils.deserialize_csv_columns(selected_column_names, :as_json_params)
|
21
|
-
# This includes isn't recursve, a full solution should be recursive
|
22
|
-
records_arr = []
|
23
|
-
records.includes(deserialized_columns[:include].keys).find_each do |record|
|
24
|
-
record_hash = record.as_json({ only: selected_column_names.map(&:to_sym) })
|
25
|
-
selected_column_names.each do |column_name|
|
26
|
-
break unless model.available_fields[:index].map(&:field_name).include?(column_name.to_sym)
|
27
|
-
|
28
|
-
column = CmAdmin::Models::Column.find_by(model, :index, { name: column_name.to_sym })
|
29
|
-
if column.field_type == :custom
|
30
|
-
record_hash[column.field_name] = helpers.send(column.helper_method, record, column.field_name).to_s
|
31
|
-
else
|
32
|
-
record_hash[column.field_name] = record.send(column.field_name).to_s
|
33
|
-
end
|
34
|
-
end
|
35
|
-
records_arr << record_hash
|
36
|
-
end
|
37
|
-
records_arr
|
38
|
-
end
|
39
|
-
|
40
|
-
def create_workbook(cm_model, records, _class_name, file_path)
|
41
|
-
flattened_records = records.map { |record| CmAdmin::Utils.flatten_hash(record) }
|
42
|
-
# columns = flattened_records.map{|x| x.keys}.flatten.uniq.sort
|
43
|
-
columns = exportable_columns(cm_model).select { |column| flattened_records.first.keys.include?(column.field_name.to_s) }
|
44
|
-
size_arr = []
|
45
|
-
columns.size.times { size_arr << 22 }
|
46
|
-
xl = Axlsx::Package.new
|
47
|
-
xl.workbook.add_worksheet do |sheet|
|
48
|
-
sheet.add_row columns&.map(&:header), b: true
|
49
|
-
flattened_records.each do |record|
|
50
|
-
sheet.add_row(columns.map { |column| record[column.field_name.to_s] })
|
51
|
-
end
|
52
|
-
sheet.column_widths(*size_arr)
|
53
|
-
end
|
54
|
-
xl.serialize(file_path)
|
55
|
-
end
|
56
|
-
|
57
|
-
def exportable_columns(klass)
|
58
|
-
klass.available_fields[:index].select(&:exportable)
|
59
|
-
end
|
60
|
-
|
61
8
|
end
|
62
9
|
end
|
63
10
|
end
|
data/lib/cm_admin/version.rb
CHANGED
@@ -15,66 +15,84 @@ module CmAdmin
|
|
15
15
|
include ActionView::Helpers::TagHelper
|
16
16
|
include ActionView::Helpers::NumberHelper
|
17
17
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
tag.div class: 'modal-content' do
|
29
|
-
concat pop_ups(klass, required_filters)
|
18
|
+
def export_modal(klass, action_name: :index, associated_klass: nil, required_filters: nil)
|
19
|
+
has_default_email = Rails.configuration.x.project_settings.default_from_email.present?
|
20
|
+
tag.div class: 'modal fade form-modal', id: 'exportModal', role: 'dialog', data: { behaviour: 'export-modal' }, aria: { labelledby: 'exportModalLabel' } do
|
21
|
+
tag.div class: 'modal-dialog modal-dialog-centered modal-dialog-scrollable', role: 'document' do
|
22
|
+
tag.div class: "#{has_default_email ? 'export-modal-content' : 'modal-content'}", data: { behaviour: 'export-modal-content' } do
|
23
|
+
if has_default_email
|
24
|
+
modal_content(klass, action_name, associated_klass, required_filters)
|
25
|
+
else
|
26
|
+
modal_content_with_error
|
27
|
+
end
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
33
|
+
def modal_content(klass, action_name, associated_klass, required_filters)
|
34
|
+
concat modal_header
|
35
|
+
concat modal_body(klass, action_name, associated_klass)
|
36
|
+
concat modal_footer
|
40
37
|
end
|
41
38
|
|
42
|
-
def
|
39
|
+
def modal_header
|
43
40
|
tag.div class: 'modal-header' do
|
44
|
-
tag.
|
45
|
-
|
46
|
-
end
|
47
|
-
tag.h4 'Select columns to export', class: 'modal-title', id: 'exportModal'
|
41
|
+
concat tag.h5 'Export Data', class: 'modal-title', id: 'exportModalLabel', data: { behaviour: 'export-modal-title' }
|
42
|
+
concat tag.button type: 'button', class: 'btn-close', data: { bs_dismiss: 'modal' }, aria: { label: 'Close' }
|
48
43
|
end
|
49
44
|
end
|
50
45
|
|
51
|
-
def
|
52
|
-
tag.div class: 'modal-body' do
|
53
|
-
form_tag cm_admin.send('
|
54
|
-
concat
|
55
|
-
|
56
|
-
concat '
|
46
|
+
def modal_body(klass, action_name, associated_klass)
|
47
|
+
tag.div class: 'modal-body', data: { behaviour: 'export-modal-body-form' } do
|
48
|
+
form_tag cm_admin.send('send_export_email_path'), id: 'export-to-file-form', style: 'width: 100%;', class: 'cm-admin-csv-export-form', data: { turbo: false, behaviour: 'export-form-submit' } do
|
49
|
+
concat tag.p 'You’ll receive the exported file in your email soon.', class: 'hidden', data: { behaviour: 'export-processing' }
|
50
|
+
concat(content_tag(:div, class: 'export-form-container', data: { behaviour: 'export-form-container' }) do
|
51
|
+
concat tag.label 'Select columns to export', class: 'required-label mb-2'
|
52
|
+
concat(content_tag(:div, class: 'column export-select-container') do
|
53
|
+
concat check_box_tag('select_all', '1', false, class: 'form-check-input', data: { behaviour: 'export-select-all' })
|
54
|
+
concat 'All'
|
55
|
+
end)
|
56
|
+
concat hidden_field_tag 'class_name', klass.name.to_s
|
57
|
+
concat hidden_field_tag 'associated_class_name', associated_klass&.name.to_s.presence
|
58
|
+
concat hidden_field_tag 'parent_id', params[:id]
|
59
|
+
concat hidden_field_tag 'child_records', @current_action.child_records
|
60
|
+
concat hidden_field_tag 'action_name', @current_action.name
|
61
|
+
concat hidden_field_tag 'filters', params[:filters].to_json
|
62
|
+
concat checkbox_row(klass, action_name)
|
57
63
|
end)
|
58
|
-
concat hidden_field_tag 'class_name', klass.name.to_s, id: 'export-to-file-klass'
|
59
|
-
concat checkbox_row(klass)
|
60
|
-
concat tag.hr
|
61
|
-
# TODO: export-to-file-btn class is used for JS functionality, Have to remove
|
62
|
-
concat submit_tag 'Export', class: 'btn-primary export-to-file-btn'
|
63
64
|
end
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
67
|
-
def
|
68
|
+
def modal_footer
|
69
|
+
tag.div class: 'modal-footer', data: { behaviour: 'export-modal-footer' } do
|
70
|
+
concat tag.button 'Close', type: 'button', class: 'btn btn-secondary', data: { bs_dismiss: 'modal' }
|
71
|
+
concat tag.button 'Export', type: 'submit', class: 'btn btn-primary', data: { behaviour: 'export-submit' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def modal_content_with_error
|
76
|
+
concat(content_tag(:div, class: 'modal-header') do
|
77
|
+
concat tag.h5 'Export Data', class: 'modal-title', id: 'exportModalLabel', data: { behaviour: 'export-modal-title' }
|
78
|
+
concat tag.button type: 'button', class: 'btn-close', data: { bs_dismiss: 'modal' }, aria: { label: 'Close' }
|
79
|
+
end)
|
80
|
+
concat(content_tag(:div, class: 'modal-body') do
|
81
|
+
p "Your application does not have a default sender, so we can't send out any emails, please contact a developer."
|
82
|
+
end)
|
83
|
+
end
|
84
|
+
|
85
|
+
def checkbox_row(klass, action_name)
|
68
86
|
tag.div class: 'row' do
|
69
|
-
CmAdmin::Models::Export.exportable_columns(klass).each do |column|
|
87
|
+
CmAdmin::Models::Export.exportable_columns(klass, action_name:).each do |column|
|
70
88
|
concat create_checkbox(column)
|
71
89
|
end
|
72
90
|
end
|
73
91
|
end
|
74
92
|
|
75
93
|
def create_checkbox(column)
|
76
|
-
tag.div
|
77
|
-
concat check_box_tag('columns[]', column.field_name, false, id: column.field_name.to_s.gsub('/', '-'), data: { behaviour: 'export-checkbox' })
|
94
|
+
tag.div do
|
95
|
+
concat check_box_tag('columns[]', column.field_name, false, id: column.field_name.to_s.gsub('/', '-'), class: 'form-check-input', data: { behaviour: 'export-checkbox' })
|
78
96
|
concat " #{column.header.to_s.gsub('/', '_').humanize}"
|
79
97
|
end
|
80
98
|
end
|
@@ -14,6 +14,7 @@ module CmAdmin
|
|
14
14
|
copy_file 'application_policy.rb', 'app/policies/application_policy.rb'
|
15
15
|
route 'mount CmAdmin::Engine => "/admin"'
|
16
16
|
generate 'migration', 'CreateFileImport associated_model_name:string added_by:references{polymorphic} error_report:jsonb completed_at:datetime status:integer'
|
17
|
+
generate 'migration', 'CreateFileExport associated_model_name:string exported_by:references{polymorphic} expires_at:datetime status:integer params:jsonb'
|
17
18
|
rake 'db:migrate'
|
18
19
|
end
|
19
20
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cm-admin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: exe
|
16
16
|
cert_chain: []
|
17
|
-
date: 2024-12-
|
17
|
+
date: 2024-12-12 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: caxlsx_rails
|
@@ -48,14 +48,14 @@ dependencies:
|
|
48
48
|
name: csv-importer
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
|
-
- - "
|
51
|
+
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: 0.8.2
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
|
-
- - "
|
58
|
+
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: 0.8.2
|
61
61
|
- !ruby/object:Gem::Dependency
|
@@ -128,6 +128,20 @@ dependencies:
|
|
128
128
|
- - ">="
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: 4.1.0
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: csv
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 3.3.0
|
138
|
+
type: :runtime
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 3.3.0
|
131
145
|
- !ruby/object:Gem::Dependency
|
132
146
|
name: importmap-rails
|
133
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,7 +182,6 @@ files:
|
|
168
182
|
- ".github/workflows/test.yml"
|
169
183
|
- ".gitignore"
|
170
184
|
- ".rspec"
|
171
|
-
- ".rubocop-https---raw-githubusercontent-com-commutatus-cm-linters-main-rubocop-yml"
|
172
185
|
- ".rubocop.yml"
|
173
186
|
- ".stylelintrc.json"
|
174
187
|
- ".vscode/settings.json"
|
@@ -347,12 +360,16 @@ files:
|
|
347
360
|
- app/helpers/cm_admin/custom_helper.rb
|
348
361
|
- app/helpers/cm_admin/permission_helper.rb
|
349
362
|
- app/jobs/file_import_processor_job.rb
|
363
|
+
- app/jobs/generate_export_file_job.rb
|
364
|
+
- app/mailers/export_mailer.rb
|
350
365
|
- app/models/cm_current.rb
|
351
366
|
- app/models/cm_permission.rb
|
352
367
|
- app/models/cm_role.rb
|
353
368
|
- app/models/concerns/cm_admin/bulk_action_processor.rb
|
354
369
|
- app/models/concerns/cm_admin/cm_role.rb
|
355
370
|
- app/models/concerns/cm_admin/file_import.rb
|
371
|
+
- app/models/concerns/exportable.rb
|
372
|
+
- app/models/file_export.rb
|
356
373
|
- app/models/file_import.rb
|
357
374
|
- app/policies/cm_admin/file_import_policy.rb
|
358
375
|
- app/views/cm_admin/main/_actions_dropdown.html.slim
|
@@ -387,6 +404,7 @@ files:
|
|
387
404
|
- app/views/cm_admin/static/dashboard.html.slim
|
388
405
|
- app/views/cm_admin/static/error_401.html.slim
|
389
406
|
- app/views/cm_admin/static/error_403.html.slim
|
407
|
+
- app/views/export_mailer/export_email.html.slim
|
390
408
|
- app/views/layouts/_cm_flash_message.html.slim
|
391
409
|
- app/views/layouts/_custom_action_modal.html.slim
|
392
410
|
- app/views/layouts/_custom_action_modals.html.slim
|
@@ -395,6 +413,7 @@ files:
|
|
395
413
|
- app/views/layouts/_left_sidebar_nav.html.slim
|
396
414
|
- app/views/layouts/_quick_links.html.slim
|
397
415
|
- app/views/layouts/cm_admin.html.slim
|
416
|
+
- app/views/layouts/mailer.html.slim
|
398
417
|
- app/views/layouts/static.html.slim
|
399
418
|
- bin/console
|
400
419
|
- bin/importmap
|
@@ -485,6 +504,7 @@ licenses:
|
|
485
504
|
metadata:
|
486
505
|
homepage_uri: https://github.com/commutatus/cm-admin
|
487
506
|
source_code_uri: https://github.com/commutatus/cm-admin
|
507
|
+
github_repo: ssh://github.com/commutatus/cm-admin
|
488
508
|
post_install_message:
|
489
509
|
rdoc_options: []
|
490
510
|
require_paths:
|
@@ -493,7 +513,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
493
513
|
requirements:
|
494
514
|
- - ">="
|
495
515
|
- !ruby/object:Gem::Version
|
496
|
-
version:
|
516
|
+
version: 3.3.0
|
497
517
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
498
518
|
requirements:
|
499
519
|
- - ">="
|
@@ -1,20 +0,0 @@
|
|
1
|
-
AllCops:
|
2
|
-
DisabledByDefault: false
|
3
|
-
|
4
|
-
Style/FrozenStringLiteralComment:
|
5
|
-
Enabled: false
|
6
|
-
|
7
|
-
Style/Documentation:
|
8
|
-
Enabled: false
|
9
|
-
|
10
|
-
Layout/IndentationStyle:
|
11
|
-
Enabled: false
|
12
|
-
|
13
|
-
Layout/LineLength:
|
14
|
-
Enabled: false
|
15
|
-
|
16
|
-
Metrics/MethodLength:
|
17
|
-
Enabled: false
|
18
|
-
|
19
|
-
Metrics/AbcSize:
|
20
|
-
Enabled: false
|